improoooved

This commit is contained in:
33333-33333 2026-05-30 03:25:19 +09:00
commit 0990b98436
5 changed files with 358 additions and 2795 deletions

View file

@ -3,6 +3,7 @@ import { generateMap } from "./mapPipeline.js";
import { generateTerrainRect } from "./mapTerrain.js";
import { LANDUSE } from "./landuseCodes.js";
import { reconcileMunicipalMetadata, refreshPrefectureRegionsMetadata } from "./mapMunicipalCoherence.js";
import { createPatchContext } from "./mapPatchContext.js";
export const PATCH_MIN_WIDTH = 48;
export const PATCH_MIN_HEIGHT = 48;
@ -1358,8 +1359,8 @@ function repairDiscreteSeamOwnership(world, rects, oldFields, seed = 0) {
return { adminSeamCellsResolved, prefectureSeamCellsResolved };
}
function copyFullPipelineFields(world, candidate, rects, seed, sourceMap = null, seaLevel = 0.30) {
const window = sourceWindowForRects(rects);
function copyFullPipelineFields(world, candidate, rects, seed, sourceMap = null, seaLevel = 0.30, patchContext = null) {
const window = patchContext?.candidateWindow || sourceWindowForRects(rects);
const elevationAdjustment = computeElevationCandidateAdjustment(world, candidate, rects, window, seed);
const oldSea = world.fields.sea ? new Uint8Array(world.fields.sea) : null;
const oldLanduse = world.fields.landuse ? new world.fields.landuse.constructor(world.fields.landuse) : null;
@ -1381,6 +1382,7 @@ function copyFullPipelineFields(world, candidate, rects, seed, sourceMap = null,
let adminCellsReassigned = 0;
let landUseCellsUpdated = 0;
const fieldEntries = [];
for (const [name, source] of Object.entries(candidate || {})) {
if (SKIP_CELL_FIELDS.has(name) || !isCandidateCellField(candidate, source, window)) continue;
const dest = ensureWorldField(world, name, source);
@ -1388,7 +1390,11 @@ function copyFullPipelineFields(world, candidate, rects, seed, sourceMap = null,
const isFloat = source.constructor === Float32Array || source.constructor === Float64Array;
const isDiscrete = DISCRETE_FIELD_NAMES.has(name) || !isFloat;
const idOffset = fieldIdOffset(name, seed);
fieldEntries.push({ name, source, dest, isDiscrete, idOffset });
}
const cells = patchContext?.writeCells?.length ? patchContext.writeCells : (() => {
const out = [];
for (let y = rects.writeRect.y0; y < rects.writeRect.y1; y++) {
for (let x = rects.writeRect.x0; x < rects.writeRect.x1; x++) {
const wi = worldIndex(world, x, y);
@ -1396,33 +1402,39 @@ function copyFullPipelineFields(world, candidate, rects, seed, sourceMap = null,
const si = sourceIndexForWorld(rects, window, x, y);
if (si < 0) continue;
const alpha = patchAlpha(x, y, rects, seed);
if (alpha <= 0.005) continue;
if (alpha > 0.005) out.push({ x, y, wi, si, alpha });
}
}
return out;
})();
if (isDiscrete) {
const threshold = continuityReplaceThreshold(name, x, y, rects, seed);
if (alpha >= threshold) {
const raw = source[si];
const mapped = remapAdminCandidateValue(name, raw, adminIdMapping);
const value = (name === "adminId" || name === "municipalityId" || name === "prefectureRegionId")
? mapped
: idOffset && raw >= 0 ? raw + idOffset : raw;
if (name === "sea" && oldSea && dest[wi] !== value) coastCellsChanged++;
if ((name === "naturalCompartmentId" || name === "watershedId" || name === "regionId") && dest[wi] !== value) naturalRegionsUpdated++;
if ((name === "adminId" || name === "municipalityId") && dest[wi] !== value) adminCellsReassigned++;
if (name === "landuse" && dest[wi] !== value) landUseCellsUpdated++;
dest[wi] = value;
}
} else {
const before = dest[wi] || 0;
let candidateValue = source[si] || 0;
if (name === "elevation") candidateValue = adjustedCandidateElevation(candidateValue, x, y, elevationAdjustment);
dest[wi] = lerp(before, candidateValue, alpha);
}
for (const cell of cells) {
const { x, y, wi, si, alpha } = cell;
for (const entry of fieldEntries) {
const { name, source, dest, isDiscrete, idOffset } = entry;
if (isDiscrete) {
const threshold = continuityReplaceThreshold(name, x, y, rects, seed);
if (alpha < threshold) continue;
const raw = source[si];
const mapped = remapAdminCandidateValue(name, raw, adminIdMapping);
const value = (name === "adminId" || name === "municipalityId" || name === "prefectureRegionId")
? mapped
: idOffset && raw >= 0 ? raw + idOffset : raw;
if (name === "sea" && oldSea && dest[wi] !== value) coastCellsChanged++;
if ((name === "naturalCompartmentId" || name === "watershedId" || name === "regionId") && dest[wi] !== value) naturalRegionsUpdated++;
if ((name === "adminId" || name === "municipalityId") && dest[wi] !== value) adminCellsReassigned++;
if (name === "landuse" && dest[wi] !== value) landUseCellsUpdated++;
dest[wi] = value;
} else {
const before = dest[wi] || 0;
let candidateValue = source[si] || 0;
if (name === "elevation") candidateValue = adjustedCandidateElevation(candidateValue, x, y, elevationAdjustment);
dest[wi] = lerp(before, candidateValue, alpha);
}
if (name === "elevation") {
updatedCells++;
if (alpha > 0.94) terrainCellsFullyReplaced++;
}
if (name === "elevation") {
updatedCells++;
if (alpha > 0.94) terrainCellsFullyReplaced++;
}
}
}
@ -3212,7 +3224,6 @@ function reconnectTransportGraph(world, sourceMap, rects, seed, mode, graphRect,
const rejectedPairs = new Set();
let attemptsRemaining = options.maxAttempts ?? (mode === "rail" ? 4 : 8);
for (let pass = 0; pass < maxAdds && attemptsRemaining > 0; pass++) {
snapshot = buildTransportGraphSnapshot(world, sourceMap, mode, graphRect, rects, seed);
const dirtyCount = snapshot.comps.filter(eligible).length;
if (dirtyCount <= 1) break;
const comps = snapshot.comps.filter(contextual);
@ -3285,13 +3296,13 @@ function reconnectTransportGraph(world, sourceMap, rects, seed, mode, graphRect,
rejectedPairs.add(transportPairSignature(b, a, mode));
continue;
}
snapshot = checkSnapshot;
debug[`${mode}GraphConnectorsAdded`] += writeResult.wrote;
accepted = true;
break;
}
if (!accepted) break;
}
snapshot = buildTransportGraphSnapshot(world, sourceMap, mode, graphRect, rects, seed);
debug[`${mode}GraphAfterComponents`] = snapshot.comps.filter(eligible).length;
return debug;
}
@ -3438,7 +3449,6 @@ function mergePathLayers(world, sourceMap, candidate, rects, window, seed) {
// repair is intentionally allowed to operate in a much broader neighborhood,
// because clipping graph repairs to the selected polygon leaves implausible
// dangling regional networks just outside the patch.
const connectorPatchOptions = null;
const externalRoadAnchors = collectExternalNetworkAnchors(world, sourceMap, ["nationalRoads", "minorRoads", "premodernRoads", "externalRoads"], rects.writeRect, transportRect, "road");
const externalRailAnchors = collectExternalNetworkAnchors(world, sourceMap, ["railways", "branchRailways", "externalRailways"], rects.writeRect, transportRect, "rail");
roadAnchors = roadAnchors.concat(externalRoadAnchors);
@ -3447,9 +3457,6 @@ function mergePathLayers(world, sourceMap, candidate, rects, window, seed) {
// visible fragments that did not reduce disconnected components. Keep the
// anchor collection for diagnostics, but route all actual repair through the
// graph reconnection pass below, which rolls back failed candidates.
const roadConn = { connectors: 0, disconnected: roadAnchors.length, skippedConnectorAnchors: 0, connectorAttempts: 0 };
const railConn = { connectors: 0, disconnected: railAnchors.length, skippedConnectorAnchors: 0, connectorAttempts: 0 };
const settlementRoadConnectors = { connectors: 0, skippedServedSettlements: 0, checkedSettlementCoverage: 0 };
const graphRect = strictMask
? transportRect
: transportRect;
@ -3459,14 +3466,14 @@ function mergePathLayers(world, sourceMap, candidate, rects, window, seed) {
roadsClipped,
railsClipped,
regeneratedPaths,
roadConnectorsCreated: roadConn.connectors + settlementRoadConnectors.connectors + (roadGraph.roadGraphConnectorsAdded || 0),
railwayConnectorsCreated: railConn.connectors + (railGraph.railGraphConnectorsAdded || 0),
disconnectedRoadComponents: roadConn.disconnected,
disconnectedRailComponents: railConn.disconnected,
skippedConnectorAnchors: (roadConn.skippedConnectorAnchors || 0) + (railConn.skippedConnectorAnchors || 0),
connectorAttempts: (roadConn.connectorAttempts || 0) + (railConn.connectorAttempts || 0),
skippedServedSettlements: settlementRoadConnectors.skippedServedSettlements || 0,
checkedSettlementCoverage: settlementRoadConnectors.checkedSettlementCoverage || 0,
roadConnectorsCreated: roadGraph.roadGraphConnectorsAdded || 0,
railwayConnectorsCreated: railGraph.railGraphConnectorsAdded || 0,
disconnectedRoadComponents: roadAnchors.length,
disconnectedRailComponents: railAnchors.length,
skippedConnectorAnchors: 0,
connectorAttempts: (roadGraph.roadGraphCandidatesConsidered || 0) + (railGraph.railGraphCandidatesConsidered || 0),
skippedServedSettlements: 0,
checkedSettlementCoverage: 0,
externalRoadAnchors: externalRoadAnchors.length,
externalRailAnchors: externalRailAnchors.length,
...roadGraph,
@ -4208,6 +4215,82 @@ function addInvalidatedRect(world, rect) {
if (!list.some((r) => rectKey(r) === key)) list.push({ ...normalized });
}
function repairPatchTerrain(world, rects, seed, seaLevel) {
const terrainSeamDebug = featherTerrainSeam(world, rects, seed);
const elevationCliffDebug = smoothExtremeElevationSeams(world, rects, seed, seaLevel);
const waterDebug = smoothWaterTopology(world, rects.writeRect, seaLevel, rects, seed);
const waterComponentDebug = repairWaterComponentTopology(world, rects, seaLevel, seed);
const residualSeaDebug = fillTinyResidualSeas(world, rects, seaLevel, seed, { aggressive: true });
const waterElevationDebug = smoothPatchedWaterElevation(world, rects, seaLevel, seed);
const maskDebug = repairDisplayMasks(world, rects, seed);
recomputeSlopeAndWaterDependentFields(world, rects.repairRect, seaLevel);
return {
terrainSeamDebug,
elevationCliffDebug,
waterDebug,
waterComponentDebug,
residualSeaDebug,
waterElevationDebug,
maskDebug,
};
}
function repairPatchAdministration(world, sourceMap, rects, seed, adminIdMapping, strictFieldSnapshot) {
const landDebug = repairLanduseAndPopulation(world, rects);
const finalAdminCoverageDebug = repairAdminCoverage(world, sourceMap, rects, adminIdMapping, seed);
const adminTopologyDebug = repairPatchAdministrativeTopology(world, rects);
const strictMaskDebugPreCoherence = restoreOutsideStrictSelectionFields(world, rects, strictFieldSnapshot, seed);
const sourceAdminMetadataUpdated = updateSourceAdminMetadata(sourceMap, adminIdMapping);
const municipalCoherence = reconcileMunicipalMetadata({
adminId: world.fields.adminId,
municipalityId: world.fields.municipalityId,
prefectureRegionId: world.fields.prefectureRegionId,
sea: world.fields.sea,
adminCenters: sourceMap.adminCenters || [],
municipalityToPrefectureId: sourceMap.municipalityToPrefectureId,
fields: world.fields,
width: world.width,
height: world.height,
pointOffsetX: world.originX || 0,
pointOffsetY: world.originY || 0,
seed,
});
sourceMap.adminCenters = municipalCoherence.adminCenters;
sourceMap.municipalityToPrefectureId = municipalCoherence.municipalityToPrefectureId;
const prefectureCoherence = refreshPrefectureRegionsMetadata({
prefectureRegionId: world.fields.prefectureRegionId,
sea: world.fields.sea,
existing: sourceMap.prefectureRegions || [],
adminCenters: sourceMap.adminCenters || [],
fields: world.fields,
width: world.width,
height: world.height,
pointOffsetX: world.originX || 0,
pointOffsetY: world.originY || 0,
});
sourceMap.prefectureRegions = prefectureCoherence.prefectureRegions;
const strictMaskDebugPostCoherence = restoreOutsideStrictSelectionFields(world, rects, strictFieldSnapshot, seed);
sourceMap.adminDebug = {
...(sourceMap.adminDebug || {}),
municipalCoherence: municipalCoherence.debug,
prefectureMetadataCoherence: prefectureCoherence.debug,
};
return {
landDebug,
finalAdminCoverageDebug,
adminTopologyDebug,
strictMaskDebugPreCoherence,
strictMaskDebugPostCoherence,
sourceAdminMetadataUpdated,
municipalCoherence,
prefectureCoherence,
};
}
function repairPatchTransport(world, sourceMap, candidate, rects, window, seed) {
return mergePathLayers(world, sourceMap, candidate, rects, window, seed);
}
export function generatePatch(world, userRectInput, options = {}) {
const validation = validatePatchRect(userRectInput, world);
if (!validation.ok) return { ok: false, ...validation };
@ -4258,85 +4341,80 @@ export function generatePatch(world, userRectInput, options = {}) {
: (candidateWindow.variable ? "Variable candidate generation" : "Full candidate generation"));
getPatchAlphaCache(rects, seed);
getPatchSourceIndexCache(rects, candidateWindow);
const patchContext = createPatchContext({
world,
rects,
candidateWindow,
seed,
patchAlpha,
sourceIndexForWorld,
worldIndex,
});
const sourceMap = world.sourceMap || (world.sourceMap = {});
const strictMetadataSnapshot = captureStrictMetadataSnapshot(world, sourceMap, rects, seed);
const logisticsLabelsMigrated = sanitizeExistingLogistics(sourceMap);
const seaLevel = candidate.seaLevel || world.sourceMap?.seaLevel || 0.30;
const fieldDebug = copyFullPipelineFields(world, candidate, rects, seed, sourceMap, seaLevel);
const fieldDebug = copyFullPipelineFields(world, candidate, rects, seed, sourceMap, seaLevel, patchContext);
patchTimer.mark("fields", "Field copy and alpha blend");
const terrainSeamDebug = featherTerrainSeam(world, rects, seed);
const elevationCliffDebug = smoothExtremeElevationSeams(world, rects, seed, seaLevel);
const waterDebug = smoothWaterTopology(world, rects.writeRect, seaLevel, rects, seed);
const waterComponentDebug = repairWaterComponentTopology(world, rects, seaLevel, seed);
const residualSeaDebug = fillTinyResidualSeas(world, rects, seaLevel, seed, { aggressive: true });
const waterElevationDebug = smoothPatchedWaterElevation(world, rects, seaLevel, seed);
const maskDebug = repairDisplayMasks(world, rects, seed);
recomputeSlopeAndWaterDependentFields(world, rects.repairRect, seaLevel);
const terrainDebug = repairPatchTerrain(world, rects, seed, seaLevel);
patchTimer.mark("terrainRepair", "Water, masks, and terrain repair");
const pointDebug = mergePointLayers(world, sourceMap, candidate, rects, fieldDebug.window, seed, fieldDebug.adminIdMapping);
patchTimer.mark("points", "Point merge");
const pathDebug = mergePathLayers(world, sourceMap, candidate, rects, fieldDebug.window, seed);
const pathDebug = repairPatchTransport(world, sourceMap, candidate, rects, fieldDebug.window, seed);
patchTimer.mark("paths", "Path merge and connector repair");
const influenceDebug = refreshPatchInfluenceFields(world, sourceMap, rects);
patchTimer.mark("influence", "Influence refresh");
const landDebug = repairLanduseAndPopulation(world, rects);
const finalAdminCoverageDebug = repairAdminCoverage(world, sourceMap, rects, fieldDebug.adminIdMapping, seed);
const adminTopologyDebug = repairPatchAdministrativeTopology(world, rects);
const strictMaskDebugPreCoherence = restoreOutsideStrictSelectionFields(world, rects, strictFieldSnapshot, seed);
const sourceAdminMetadataUpdated = updateSourceAdminMetadata(sourceMap, fieldDebug.adminIdMapping);
const municipalCoherence = reconcileMunicipalMetadata({
adminId: world.fields.adminId,
municipalityId: world.fields.municipalityId,
prefectureRegionId: world.fields.prefectureRegionId,
sea: world.fields.sea,
adminCenters: sourceMap.adminCenters || [],
municipalityToPrefectureId: sourceMap.municipalityToPrefectureId,
fields: world.fields,
width: world.width,
height: world.height,
pointOffsetX: world.originX || 0,
pointOffsetY: world.originY || 0,
seed,
});
sourceMap.adminCenters = municipalCoherence.adminCenters;
sourceMap.municipalityToPrefectureId = municipalCoherence.municipalityToPrefectureId;
const prefectureCoherence = refreshPrefectureRegionsMetadata({
prefectureRegionId: world.fields.prefectureRegionId,
sea: world.fields.sea,
existing: sourceMap.prefectureRegions || [],
adminCenters: sourceMap.adminCenters || [],
fields: world.fields,
width: world.width,
height: world.height,
pointOffsetX: world.originX || 0,
pointOffsetY: world.originY || 0,
});
sourceMap.prefectureRegions = prefectureCoherence.prefectureRegions;
const strictMaskDebugPostCoherence = restoreOutsideStrictSelectionFields(world, rects, strictFieldSnapshot, seed);
const adminDebug = repairPatchAdministration(world, sourceMap, rects, seed, fieldDebug.adminIdMapping, strictFieldSnapshot);
const residualSeaStrictDebug = fillTinyResidualSeas(world, rects, seaLevel, seed, { aggressive: true, preservePockets: true });
if ((residualSeaStrictDebug.residualSeaCellsFilled || 0) > 0) {
repairDisplayMasks(world, rects, seed);
recomputeSlopeAndWaterDependentFields(world, rects.repairRect, seaLevel);
}
const strictMetadataDebug = restoreOutsideStrictMetadata(world, sourceMap, rects, strictMetadataSnapshot, seed);
sourceMap.adminDebug = {
...(sourceMap.adminDebug || {}),
municipalCoherence: municipalCoherence.debug,
prefectureMetadataCoherence: prefectureCoherence.debug,
};
const segmentDebug = mergeSegmentLayers(world, sourceMap, rects, seed, candidate, fieldDebug.window);
const candidateCompartmentSegmentsAdded = mergeCandidateCompartmentDebugSegments(world, sourceMap, candidate, rects, fieldDebug.window, seed);
patchTimer.mark("segments", "Boundary and debug segment merge");
sanitizeExistingLogistics(sourceMap);
patchTimer.mark("cleanup", "Land-use, admin, and label cleanup");
const patchTimings = patchTimer.timings;
const {
terrainSeamDebug,
elevationCliffDebug,
waterDebug,
waterComponentDebug,
residualSeaDebug,
waterElevationDebug,
maskDebug,
} = terrainDebug;
const {
landDebug,
finalAdminCoverageDebug,
adminTopologyDebug,
strictMaskDebugPreCoherence,
strictMaskDebugPostCoherence,
sourceAdminMetadataUpdated,
municipalCoherence,
prefectureCoherence,
} = adminDebug;
const seaStats = countSea(world, rects.coreRect, rects, seed);
const label = terrainLabel(candidate, terrainType);
const id = terrainId(candidate, terrainType);
const patchStageDebug = {
candidate: { cacheHit, cacheSize, patchGenerationMode, candidateAreaRatio: candidateWindow.areaRatio ?? 1 },
fields: fieldDebug,
terrain: terrainDebug,
points: pointDebug,
transport: pathDebug,
influence: influenceDebug,
admin: adminDebug,
segments: { ...segmentDebug, candidateCompartmentSegmentsAdded },
cleanup: { logisticsLabelsMigrated, strictMetadataDebug, residualSeaStrictDebug },
};
const humanGeography = {
ok: true,
patchStages: patchStageDebug,
modernCities: (sourceMap.modernCities || []).filter((p) => insideRect(pointWorldX(world, p), pointWorldY(world, p), rects.writeRect)).length,
ports: (sourceMap.ports || []).filter((p) => insideRect(pointWorldX(world, p), pointWorldY(world, p), rects.writeRect)).length,
villages: (sourceMap.villages || []).filter((p) => insideRect(pointWorldX(world, p), pointWorldY(world, p), rects.writeRect)).length,