1619 lines
108 KiB
JavaScript
1619 lines
108 KiB
JavaScript
import { generateMap, MAP_W, MAP_H, indexOf } from "../src/mapPipeline.js";
|
|
import { createWorldMap } from "../src/worldMap.js";
|
|
import { PATCH_MIN_HEIGHT, generatePatch } from "../src/mapPatch.js";
|
|
import { applyCommittedMirrorDelta, buildCommittedMirrorDelta, runPatchCandidateSearch } from "../src/mapPatchWorker.js";
|
|
import {
|
|
CUSTOM_NAME_LIST,
|
|
NAME_KANJI_POOLS,
|
|
NAME_PROBABILITIES,
|
|
NAME_TEMPLATES,
|
|
NAME_TEMPLATE_WEIGHTS,
|
|
generateEntityName,
|
|
validateGeneratedName,
|
|
} from "../src/names.js";
|
|
|
|
const IS_BROWSER = typeof document !== "undefined";
|
|
const result = IS_BROWSER ? document.getElementById("result") : { className: "", textContent: "" };
|
|
const logLines = [];
|
|
let failed = 0;
|
|
|
|
const TEST_SUITE = (() => {
|
|
if (IS_BROWSER) return new URLSearchParams(location.search).get("suite") || "core";
|
|
const arg = process.argv.find((value) => value.startsWith("--suite="));
|
|
return arg ? arg.slice("--suite=".length) : "core";
|
|
})();
|
|
const STATIC_TEST_SUITES = new Set(["all", "core", "terrain", "terrain-name", "admin", "patch", "patch-large", "determinism"]);
|
|
if (!STATIC_TEST_SUITES.has(TEST_SUITE) && !/^determinism-(?:0|[1-9]\d*)$/.test(TEST_SUITE)) {
|
|
throw new Error(`Unknown test suite: ${TEST_SUITE}`);
|
|
}
|
|
const TEST_STARTED_AT = typeof performance !== "undefined" && performance.now ? performance.now() : Date.now();
|
|
const DETERMINISM_SEED = (() => {
|
|
if (IS_BROWSER) return Number(new URLSearchParams(location.search).get("seed")) || 114514;
|
|
const arg = process.argv.find((value) => value.startsWith("--seed="));
|
|
return arg ? Number(arg.slice("--seed=".length)) : 114514;
|
|
})();
|
|
let fullMapGenerations = 0;
|
|
function suiteEnabled(name) {
|
|
return TEST_SUITE === "all" || TEST_SUITE === name;
|
|
}
|
|
function generateTestMap(seed) {
|
|
fullMapGenerations++;
|
|
return generateMap(seed);
|
|
}
|
|
|
|
async function readLocalText(path) {
|
|
if (IS_BROWSER) return fetch(path).then((response) => response.text());
|
|
const { readFile } = await import("node:fs/promises");
|
|
return readFile(new URL(path, import.meta.url), "utf8");
|
|
}
|
|
|
|
let namesSource = "";
|
|
let mapGeneratorSource = "";
|
|
let mapOutputSource = "";
|
|
let mapTerrainSource = "";
|
|
let rendererSource = "";
|
|
let appSource = "";
|
|
let mapPipelineSource = "";
|
|
let mapAdminStageSource = "";
|
|
let mapPatchSource = "";
|
|
let mapPatchWorkerSource = "";
|
|
let committedWorldDeltaSource = "";
|
|
let worldMapSource = "";
|
|
let municipalSource = "";
|
|
if (suiteEnabled("core")) {
|
|
[namesSource, mapOutputSource, mapTerrainSource, rendererSource, appSource, mapPipelineSource, mapAdminStageSource, mapPatchSource, mapPatchWorkerSource, committedWorldDeltaSource, worldMapSource, municipalSource] = await Promise.all([
|
|
readLocalText("../src/names.js"),
|
|
readLocalText("../src/mapOutput.js"),
|
|
readLocalText("../src/mapTerrain.js"),
|
|
readLocalText("../src/renderer.js"),
|
|
readLocalText("../src/app.js"),
|
|
readLocalText("../src/mapPipeline.js"),
|
|
readLocalText("../src/mapAdminStage.js"),
|
|
readLocalText("../src/mapPatch.js"),
|
|
readLocalText("../src/mapPatchWorker.js"),
|
|
readLocalText("../src/committedWorldDelta.js"),
|
|
readLocalText("../src/worldMap.js"),
|
|
readLocalText("../src/mapMunicipalCoherence.js"),
|
|
]);
|
|
mapGeneratorSource = mapPipelineSource;
|
|
}
|
|
const derivePatchSeedStart = appSource.indexOf("function derivePatchSeed");
|
|
const derivePatchSeedEnd = derivePatchSeedStart >= 0 ? appSource.indexOf("\n}", derivePatchSeedStart) : -1;
|
|
const derivePatchSeedSource = derivePatchSeedStart >= 0 && derivePatchSeedEnd > derivePatchSeedStart
|
|
? appSource.slice(derivePatchSeedStart, derivePatchSeedEnd + 2)
|
|
: "";
|
|
|
|
function assert(condition, message) {
|
|
if (condition) logLines.push(`OK: ${message}`);
|
|
else {
|
|
failed += 1;
|
|
logLines.push(`NG: ${message}`);
|
|
}
|
|
}
|
|
|
|
function arraysEqual(a, b) {
|
|
if (!a || !b || a.length !== b.length) return false;
|
|
for (let index = 0; index < a.length; index++) {
|
|
if (a[index] !== b[index] && !(Number.isNaN(a[index]) && Number.isNaN(b[index]))) return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
function terrainBoundaryTargetForMetrics(map, i) {
|
|
const lu = map.landuse[i];
|
|
const urbanPenalty = Math.min(1, (lu === 3 ? 1.45 : lu === 2 ? 1.12 : lu === 4 ? 0.95 : lu === 7 ? 0.90 : lu === 8 ? 0.64 : lu === 5 || lu === 6 ? 0.48 : 0) + map.populationDensity[i] * 1.35);
|
|
const majorRiver = Math.min(1, Math.max(map.river[i] - 0.32, 0) * 1.9 + Math.max(map.flowAccum[i] - 0.38, 0) * 0.75);
|
|
const minorStream = Math.min(1, map.river[i] * 0.34 + map.flowAccum[i] * 0.18);
|
|
const ridgeDivide = Math.min(1, map.ridgeField[i] * 1.55 + Math.max(0, map.elevation[i] - 0.54) * map.ridgeField[i] * 0.95);
|
|
const slopeBreak = Math.min(1, map.slope[i] * 0.58 + Math.max(0, map.slope[i] - 0.32) * 0.68);
|
|
const highGround = Math.max(0, map.elevation[i] - 0.56) * 0.22;
|
|
const valleyFloorPenalty = map.valleyField[i] * (majorRiver > 0.34 ? -0.10 : -0.62);
|
|
return Math.max(0, Math.min(1, ridgeDivide + majorRiver * 0.88 + minorStream * 0.22 + slopeBreak + highGround + valleyFloorPenalty - urbanPenalty * 0.72));
|
|
}
|
|
|
|
function humanRegionMaskForMetrics(map) {
|
|
if (map.humanRegionMask?.length === MAP_W * MAP_H) return map.humanRegionMask;
|
|
const mask = new Uint8Array(MAP_W * MAP_H);
|
|
for (let i = 0; i < mask.length; i++) mask[i] = !map.sea[i] && (map.prefectureRegionId?.[i] ?? -1) >= 0 ? 1 : 0;
|
|
return mask;
|
|
}
|
|
|
|
function deterministicTransportDebug(debug) {
|
|
if (!debug) return debug;
|
|
const copy = typeof structuredClone === "function" ? structuredClone(debug) : JSON.parse(JSON.stringify(debug));
|
|
delete copy.featureTimings;
|
|
if (copy.layers) delete copy.layers.featureTimings;
|
|
return copy;
|
|
}
|
|
|
|
function adminBoundaryMetrics(map) {
|
|
const humanMask = humanRegionMaskForMetrics(map);
|
|
let borderEdges = 0;
|
|
let targetSum = 0;
|
|
let denseUrbanEdges = 0;
|
|
let rightAngleRuns = 0;
|
|
let voronoiLikeEdges = 0;
|
|
let lowScoreFlatEdges = 0;
|
|
for (let y = 1; y < MAP_H - 1; y++) {
|
|
for (let x = 1; x < MAP_W - 1; x++) {
|
|
const i = indexOf(x, y);
|
|
if (!humanMask[i] || map.sea[i] || map.adminId[i] < 0) continue;
|
|
for (const [dx, dy] of [[1, 0], [0, 1]]) {
|
|
const ni = indexOf(x + dx, y + dy);
|
|
if (!humanMask[ni] || map.sea[ni] || map.adminId[ni] < 0 || map.adminId[ni] === map.adminId[i]) continue;
|
|
borderEdges++;
|
|
const edgeTarget = (terrainBoundaryTargetForMetrics(map, i) + terrainBoundaryTargetForMetrics(map, ni)) * 0.5;
|
|
targetSum += edgeTarget;
|
|
const urban = Math.max(map.populationDensity[i], map.populationDensity[ni]) > 0.58 || [2, 3, 4, 7, 8].includes(map.landuse[i]) || [2, 3, 4, 7, 8].includes(map.landuse[ni]);
|
|
if (urban) denseUrbanEdges++;
|
|
const ca = map.adminCenters[map.adminId[i]];
|
|
const cb = map.adminCenters[map.adminId[ni]];
|
|
if (ca && cb) {
|
|
const mx = x + dx * 0.5;
|
|
const my = y + dy * 0.5;
|
|
const dA = Math.hypot(mx - ca.x, my - ca.y);
|
|
const dB = Math.hypot(mx - cb.x, my - cb.y);
|
|
if (Math.abs(dA - dB) < 4.2 && edgeTarget < 0.40) voronoiLikeEdges++;
|
|
}
|
|
if (edgeTarget < 0.16 && Math.max(map.slope[i], map.slope[ni]) < 0.24 && Math.max(map.ridgeField[i], map.ridgeField[ni]) < 0.28 && Math.max(map.river[i], map.river[ni]) < 0.26) {
|
|
lowScoreFlatEdges++;
|
|
}
|
|
const sideA = indexOf(x + (dy ? 1 : 0), y + (dx ? 1 : 0));
|
|
const sideB = indexOf(x - (dy ? 1 : 0), y - (dx ? 1 : 0));
|
|
if (humanMask[sideA] && humanMask[sideB] && !map.sea[sideA] && !map.sea[sideB]) {
|
|
const turnA = map.adminId[sideA] !== map.adminId[i] && map.adminId[sideA] !== map.adminId[ni];
|
|
const turnB = map.adminId[sideB] !== map.adminId[i] && map.adminId[sideB] !== map.adminId[ni];
|
|
if ((turnA || turnB) && terrainBoundaryTargetForMetrics(map, i) < 0.46) rightAngleRuns++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
const ids = new Set([...map.adminId].filter((id, i) => id >= 0 && humanMask[i] && !map.sea[i]));
|
|
const areaById = new Map();
|
|
for (let i = 0; i < map.adminId.length; i++) {
|
|
if (humanMask[i] && !map.sea[i] && map.adminId[i] >= 0) areaById.set(map.adminId[i], (areaById.get(map.adminId[i]) || 0) + 1);
|
|
}
|
|
const areas = [...areaById.values()].sort((a, b) => a - b);
|
|
const medianArea = areas.length ? areas[Math.floor(areas.length / 2)] : 1;
|
|
const maxArea = areas.length ? areas[areas.length - 1] : 1;
|
|
let disconnectedMunicipalities = 0;
|
|
let maxComponents = 0;
|
|
let maxLandmassComponents = 0;
|
|
const landmassId = new Int32Array(MAP_W * MAP_H);
|
|
landmassId.fill(-1);
|
|
let landmass = 0;
|
|
for (let i = 0; i < landmassId.length; i++) {
|
|
if (!humanMask[i] || map.sea[i] || landmassId[i] >= 0) continue;
|
|
const queue = [i];
|
|
landmassId[i] = landmass;
|
|
for (let q = 0; q < queue.length; q++) {
|
|
const cur = queue[q];
|
|
const x = cur % MAP_W, y = Math.floor(cur / MAP_W);
|
|
for (const [dx, dy] of [[1,0],[-1,0],[0,1],[0,-1]]) {
|
|
const nx = x + dx, ny = y + dy;
|
|
if (nx < 0 || ny < 0 || nx >= MAP_W || ny >= MAP_H) continue;
|
|
const ni = indexOf(nx, ny);
|
|
if (!humanMask[ni] || map.sea[ni] || landmassId[ni] >= 0) continue;
|
|
landmassId[ni] = landmass;
|
|
queue.push(ni);
|
|
}
|
|
}
|
|
landmass++;
|
|
}
|
|
const seen = new Uint8Array(MAP_W * MAP_H);
|
|
for (const id of ids) {
|
|
let comps = 0;
|
|
const componentsByLandmass = new Map();
|
|
seen.fill(0);
|
|
for (let i = 0; i < map.adminId.length; i++) {
|
|
if (seen[i] || map.adminId[i] !== id || !humanMask[i] || map.sea[i]) continue;
|
|
comps++;
|
|
const mass = landmassId[i];
|
|
componentsByLandmass.set(mass, (componentsByLandmass.get(mass) || 0) + 1);
|
|
const queue = [i];
|
|
seen[i] = 1;
|
|
for (let q = 0; q < queue.length; q++) {
|
|
const cur = queue[q];
|
|
const x = cur % MAP_W;
|
|
const y = Math.floor(cur / MAP_W);
|
|
for (const [dx, dy] of [[1, 0], [-1, 0], [0, 1], [0, -1]]) {
|
|
const nx = x + dx;
|
|
const ny = y + dy;
|
|
if (nx < 0 || ny < 0 || nx >= MAP_W || ny >= MAP_H) continue;
|
|
const ni = indexOf(nx, ny);
|
|
if (seen[ni] || map.adminId[ni] !== id || !humanMask[ni] || map.sea[ni]) continue;
|
|
seen[ni] = 1;
|
|
queue.push(ni);
|
|
}
|
|
}
|
|
}
|
|
if (comps > 1) disconnectedMunicipalities++;
|
|
maxComponents = Math.max(maxComponents, comps);
|
|
maxLandmassComponents = Math.max(maxLandmassComponents, ...componentsByLandmass.values(), 0);
|
|
}
|
|
|
|
const centerValidCount = map.adminCenters.filter((center) => {
|
|
const i = indexOf(center.x, center.y);
|
|
const id = center.adminId ?? center.municipalityId ?? center.adminNumericId;
|
|
return humanMask[i] && !map.sea[i] && Number.isFinite(id) && map.adminId[i] === id;
|
|
}).length;
|
|
|
|
return {
|
|
borderEdges,
|
|
avgTarget: borderEdges ? targetSum / borderEdges : 0,
|
|
denseUrbanRate: borderEdges ? denseUrbanEdges / borderEdges : 0,
|
|
rightAngleRate: borderEdges ? rightAngleRuns / borderEdges : 0,
|
|
voronoiLikeRate: borderEdges ? voronoiLikeEdges / borderEdges : 0,
|
|
lowScoreFlatRate: borderEdges ? lowScoreFlatEdges / borderEdges : 0,
|
|
areaDiversity: maxArea / Math.max(1, medianArea),
|
|
municipalityCount: ids.size,
|
|
disconnectedMunicipalities,
|
|
maxComponents,
|
|
maxLandmassComponents,
|
|
centerValidRatio: map.adminCenters.length ? centerValidCount / map.adminCenters.length : 1,
|
|
};
|
|
}
|
|
|
|
function majorCityCoreIntegrity(map) {
|
|
const majorCities = map.modernCities.filter((city) => (city.population || 0) >= 180000);
|
|
if (majorCities.length === 0) return 1;
|
|
let sum = 0;
|
|
let checked = 0;
|
|
for (const city of majorCities) {
|
|
const counts = new Map();
|
|
const r = Math.ceil(Math.max(3, city.coreRadius || 4));
|
|
for (let dy = -r; dy <= r; dy++) {
|
|
for (let dx = -r; dx <= r; dx++) {
|
|
const x = city.x + dx;
|
|
const y = city.y + dy;
|
|
if (x < 0 || y < 0 || x >= MAP_W || y >= MAP_H || Math.hypot(dx, dy) > r) continue;
|
|
const i = indexOf(x, y);
|
|
if (!map.prefectureMask[i] || map.sea[i]) continue;
|
|
if (map.landuse[i] !== 3 && map.populationDensity[i] < 0.38) continue;
|
|
const id = map.adminId[i];
|
|
if (id >= 0) counts.set(id, (counts.get(id) || 0) + 1);
|
|
}
|
|
}
|
|
const total = [...counts.values()].reduce((a, b) => a + b, 0);
|
|
if (total === 0) continue;
|
|
sum += Math.max(...counts.values()) / total;
|
|
checked++;
|
|
}
|
|
return checked ? sum / checked : 1;
|
|
}
|
|
|
|
function satelliteMunicipalityMetrics(map) {
|
|
const areaById = new Map();
|
|
for (let i = 0; i < map.adminId.length; i++) {
|
|
if (map.prefectureMask[i] && !map.sea[i] && map.adminId[i] >= 0) areaById.set(map.adminId[i], (areaById.get(map.adminId[i]) || 0) + 1);
|
|
}
|
|
const rows = (map.satelliteCities || [])
|
|
.filter((sat) => map.prefectureMask[indexOf(sat.x, sat.y)] && !map.sea[indexOf(sat.x, sat.y)])
|
|
.map((sat) => {
|
|
const admin = map.adminId[indexOf(sat.x, sat.y)];
|
|
return { sat, admin, area: areaById.get(admin) || 0 };
|
|
});
|
|
const independent = rows.filter((row) => row.sat.municipalityClass === "independentSatelliteMunicipality");
|
|
const small = independent.filter((row) => row.area < 80);
|
|
const largeTooSmall = rows.filter((row) => (row.sat.population || 0) >= 60000 && row.sat.municipalityClass === "independentSatelliteMunicipality" && row.area < 120);
|
|
const average = independent.length ? independent.reduce((sum, row) => sum + row.area, 0) / independent.length : 0;
|
|
return { rows, independent, small, largeTooSmall, average };
|
|
}
|
|
|
|
function regionalComponentMetrics(map) {
|
|
const ids = new Set([...map.prefectureRegionId].filter((id, i) => id >= 0 && !map.sea[i]));
|
|
const landmassId = new Int32Array(MAP_W * MAP_H);
|
|
landmassId.fill(-1);
|
|
let landmass = 0;
|
|
for (let i = 0; i < landmassId.length; i++) {
|
|
if (map.sea[i] || landmassId[i] >= 0) continue;
|
|
const queue = [i];
|
|
landmassId[i] = landmass;
|
|
for (let q = 0; q < queue.length; q++) {
|
|
const cur = queue[q];
|
|
const x = cur % MAP_W, y = Math.floor(cur / MAP_W);
|
|
for (const [dx, dy] of [[1,0],[-1,0],[0,1],[0,-1]]) {
|
|
const nx = x + dx, ny = y + dy;
|
|
if (nx < 0 || ny < 0 || nx >= MAP_W || ny >= MAP_H) continue;
|
|
const ni = indexOf(nx, ny);
|
|
if (map.sea[ni] || landmassId[ni] >= 0) continue;
|
|
landmassId[ni] = landmass;
|
|
queue.push(ni);
|
|
}
|
|
}
|
|
landmass++;
|
|
}
|
|
const seen = new Uint8Array(MAP_W * MAP_H);
|
|
let maxComponents = 0;
|
|
let maxLandmassComponents = 0;
|
|
const areas = [];
|
|
for (const id of ids) {
|
|
seen.fill(0);
|
|
let comps = 0;
|
|
let area = 0;
|
|
const componentsByLandmass = new Map();
|
|
for (let i = 0; i < map.prefectureRegionId.length; i++) {
|
|
if (!map.sea[i] && map.prefectureRegionId[i] === id) area++;
|
|
if (seen[i] || map.sea[i] || map.prefectureRegionId[i] !== id) continue;
|
|
comps++;
|
|
const mass = landmassId[i];
|
|
componentsByLandmass.set(mass, (componentsByLandmass.get(mass) || 0) + 1);
|
|
const queue = [i];
|
|
seen[i] = 1;
|
|
for (let q = 0; q < queue.length; q++) {
|
|
const cur = queue[q];
|
|
const x = cur % MAP_W;
|
|
const y = Math.floor(cur / MAP_W);
|
|
for (const [dx, dy] of [[1, 0], [-1, 0], [0, 1], [0, -1]]) {
|
|
const nx = x + dx;
|
|
const ny = y + dy;
|
|
if (nx < 0 || ny < 0 || nx >= MAP_W || ny >= MAP_H) continue;
|
|
const ni = indexOf(nx, ny);
|
|
if (seen[ni] || map.sea[ni] || map.prefectureRegionId[ni] !== id) continue;
|
|
seen[ni] = 1;
|
|
queue.push(ni);
|
|
}
|
|
}
|
|
}
|
|
maxComponents = Math.max(maxComponents, comps);
|
|
maxLandmassComponents = Math.max(maxLandmassComponents, ...componentsByLandmass.values(), 0);
|
|
areas.push(area);
|
|
}
|
|
areas.sort((a, b) => a - b);
|
|
const medianArea = areas.length ? areas[Math.floor(areas.length / 2)] : 0;
|
|
const minArea = areas.length ? areas[0] : 0;
|
|
const tinyCount = areas.filter((area) => area < 520).length;
|
|
return { regionCount: ids.size, maxComponents, maxLandmassComponents, minArea, medianArea, tinyCount, areas };
|
|
}
|
|
|
|
function regionalBorderMetrics(map) {
|
|
let invalidSame = 0;
|
|
let expected = 0;
|
|
const ids = map.prefectureRegionId;
|
|
for (let y = 0; y < MAP_H; y++) {
|
|
for (let x = 0; x < MAP_W; x++) {
|
|
const i = indexOf(x, y);
|
|
if (map.sea[i] || ids[i] < 0) continue;
|
|
if (x + 1 < MAP_W) {
|
|
const ni = indexOf(x + 1, y);
|
|
if (!map.sea[ni] && ids[ni] >= 0 && ids[ni] !== ids[i]) expected++;
|
|
}
|
|
if (y + 1 < MAP_H) {
|
|
const ni = indexOf(x, y + 1);
|
|
if (!map.sea[ni] && ids[ni] >= 0 && ids[ni] !== ids[i]) expected++;
|
|
}
|
|
}
|
|
}
|
|
for (const segment of map.regionalPrefectureBorders || []) {
|
|
const [[x1, y1], [x2, y2]] = segment;
|
|
let a = -1, b = -1;
|
|
if (x1 === x2) {
|
|
const x = x1;
|
|
const y = Math.min(y1, y2);
|
|
if (x > 0 && x < MAP_W && y >= 0 && y < MAP_H) {
|
|
a = ids[indexOf(x - 1, y)];
|
|
b = ids[indexOf(x, y)];
|
|
}
|
|
} else if (y1 === y2) {
|
|
const x = Math.min(x1, x2);
|
|
const y = y1;
|
|
if (y > 0 && y < MAP_H && x >= 0 && x < MAP_W) {
|
|
a = ids[indexOf(x, y - 1)];
|
|
b = ids[indexOf(x, y)];
|
|
}
|
|
}
|
|
if (a < 0 || b < 0 || a === b) invalidSame++;
|
|
}
|
|
return { invalidSame, expected, actual: (map.regionalPrefectureBorders || []).length };
|
|
}
|
|
|
|
function municipalBorderVectorMetrics(map) {
|
|
let invalid = 0;
|
|
let expected = 0;
|
|
const admin = map.adminId;
|
|
const pref = map.prefectureRegionId;
|
|
for (let y = 0; y < MAP_H; y++) {
|
|
for (let x = 0; x < MAP_W; x++) {
|
|
const i = indexOf(x, y);
|
|
if (map.sea[i] || admin[i] < 0 || pref[i] < 0) continue;
|
|
if (x + 1 < MAP_W) {
|
|
const j = indexOf(x + 1, y);
|
|
if (!map.sea[j] && admin[j] >= 0 && pref[j] === pref[i] && admin[j] !== admin[i]) expected++;
|
|
}
|
|
if (y + 1 < MAP_H) {
|
|
const j = indexOf(x, y + 1);
|
|
if (!map.sea[j] && admin[j] >= 0 && pref[j] === pref[i] && admin[j] !== admin[i]) expected++;
|
|
}
|
|
}
|
|
}
|
|
for (const segment of map.adminBorders || []) {
|
|
const [[x1, y1], [x2, y2]] = segment;
|
|
let ai = -1;
|
|
let bi = -1;
|
|
if (x1 === x2) {
|
|
const x = x1;
|
|
const y = Math.min(y1, y2);
|
|
if (x > 0 && x < MAP_W && y >= 0 && y < MAP_H) {
|
|
ai = indexOf(x - 1, y);
|
|
bi = indexOf(x, y);
|
|
}
|
|
} else if (y1 === y2) {
|
|
const x = Math.min(x1, x2);
|
|
const y = y1;
|
|
if (y > 0 && y < MAP_H && x >= 0 && x < MAP_W) {
|
|
ai = indexOf(x, y - 1);
|
|
bi = indexOf(x, y);
|
|
}
|
|
}
|
|
if (ai < 0 || bi < 0 || map.sea[ai] || map.sea[bi] || admin[ai] < 0 || admin[bi] < 0 || admin[ai] === admin[bi] || pref[ai] < 0 || pref[ai] !== pref[bi]) invalid++;
|
|
}
|
|
return { invalid, expected, actual: (map.adminBorders || []).length };
|
|
}
|
|
|
|
function borderHierarchyViolations(map) {
|
|
let prefectureCutsMunicipality = 0;
|
|
let municipalityCutsCompartment = 0;
|
|
const compId = map.naturalCompartmentId;
|
|
for (let y = 0; y < MAP_H; y++) {
|
|
for (let x = 0; x < MAP_W; x++) {
|
|
const i = indexOf(x, y);
|
|
if (map.sea[i]) continue;
|
|
for (const [nx, ny] of [[x + 1, y], [x, y + 1]]) {
|
|
if (nx >= MAP_W || ny >= MAP_H) continue;
|
|
const ni = indexOf(nx, ny);
|
|
if (map.sea[ni]) continue;
|
|
if (map.prefectureRegionId[i] !== map.prefectureRegionId[ni] && map.adminId[i] === map.adminId[ni]) prefectureCutsMunicipality++;
|
|
if (map.adminId[i] !== map.adminId[ni] && compId && compId[i] === compId[ni]) municipalityCutsCompartment++;
|
|
}
|
|
}
|
|
}
|
|
return { prefectureCutsMunicipality, municipalityCutsCompartment };
|
|
}
|
|
|
|
function longStraightLowBarrierSegments(map, segments, minRun = 18) {
|
|
const runs = new Map();
|
|
for (const seg of segments || []) {
|
|
const [[x1, y1], [x2, y2]] = seg;
|
|
const vertical = x1 === x2;
|
|
const key = vertical ? `v:${x1}` : `h:${y1}`;
|
|
const pos = vertical ? Math.min(y1, y2) : Math.min(x1, x2);
|
|
if (!runs.has(key)) runs.set(key, []);
|
|
runs.get(key).push({ pos, seg });
|
|
}
|
|
let bad = 0;
|
|
for (const rows of runs.values()) {
|
|
rows.sort((a, b) => a.pos - b.pos);
|
|
let start = 0;
|
|
for (let k = 1; k <= rows.length; k++) {
|
|
if (k < rows.length && rows[k].pos <= rows[k - 1].pos + 1.01) continue;
|
|
const run = rows.slice(start, k);
|
|
if (run.length >= minRun) {
|
|
const natural = run.reduce((sum, row) => {
|
|
const [[x1, y1], [x2, y2]] = row.seg;
|
|
const sx = Math.min(Math.max(0, Math.floor((x1 + x2) / 2)), MAP_W - 1);
|
|
const sy = Math.min(Math.max(0, Math.floor((y1 + y2) / 2)), MAP_H - 1);
|
|
return sum + (map.naturalBarrierScore?.[indexOf(sx, sy)] || 0);
|
|
}, 0) / run.length;
|
|
if (natural < 0.18) bad++;
|
|
}
|
|
start = k;
|
|
}
|
|
}
|
|
return bad;
|
|
}
|
|
|
|
function regionalEnclaveCount(map) {
|
|
const ids = map.prefectureRegionId;
|
|
const regionIds = [...new Set([...ids].filter((id, i) => id >= 0 && !map.sea[i]))];
|
|
let enclaves = 0;
|
|
for (const id of regionIds) {
|
|
const seen = new Uint8Array(MAP_W * MAP_H);
|
|
for (let i = 0; i < ids.length; i++) {
|
|
if (seen[i] || map.sea[i] || ids[i] !== id) continue;
|
|
const queue = [i];
|
|
const cells = [];
|
|
let touchesOutside = false;
|
|
const neighbors = new Set();
|
|
seen[i] = 1;
|
|
for (let q = 0; q < queue.length; q++) {
|
|
const cur = queue[q];
|
|
cells.push(cur);
|
|
const x = cur % MAP_W;
|
|
const y = Math.floor(cur / MAP_W);
|
|
if (x === 0 || y === 0 || x === MAP_W - 1 || y === MAP_H - 1) touchesOutside = true;
|
|
for (const [dx, dy] of [[1, 0], [-1, 0], [0, 1], [0, -1]]) {
|
|
const nx = x + dx;
|
|
const ny = y + dy;
|
|
if (nx < 0 || ny < 0 || nx >= MAP_W || ny >= MAP_H) continue;
|
|
const ni = indexOf(nx, ny);
|
|
if (map.sea[ni]) {
|
|
touchesOutside = true;
|
|
continue;
|
|
}
|
|
if (ids[ni] !== id && ids[ni] >= 0) neighbors.add(ids[ni]);
|
|
if (seen[ni] || ids[ni] !== id) continue;
|
|
seen[ni] = 1;
|
|
queue.push(ni);
|
|
}
|
|
}
|
|
if (cells.length && !touchesOutside && neighbors.size === 1) enclaves++;
|
|
}
|
|
}
|
|
return enclaves;
|
|
}
|
|
|
|
function cityMunicipalityAreaMetrics(map) {
|
|
const areaById = new Map();
|
|
for (let i = 0; i < map.adminId.length; i++) {
|
|
const id = map.adminId[i];
|
|
if (id >= 0 && !map.sea[i]) areaById.set(id, (areaById.get(id) || 0) + 1);
|
|
}
|
|
const rows = (map.modernCities || [])
|
|
.filter((city) => (city.population || 0) >= 95000 && city.insidePrefecture && !map.sea[indexOf(city.x, city.y)])
|
|
.map((city) => {
|
|
const admin = map.adminId[indexOf(city.x, city.y)];
|
|
const minArea = Math.min((city.population || 0) >= 450000 ? 780 : 520, Math.max(130, 95 + Math.sqrt(city.population || 0) * 0.72 + (city.urbanFootprintCells || 0) * 0.42));
|
|
return { city, admin, area: areaById.get(admin) || 0, minArea };
|
|
});
|
|
return { rows, tooSmall: rows.filter((row) => row.area + 1e-6 < row.minArea * 0.82) };
|
|
}
|
|
|
|
function prefectureNameForTest(map, i) {
|
|
const id = map.prefectureRegionId?.[i] ?? -1;
|
|
return (map.prefectureRegions || []).find((region) => region.id === id)?.name || "";
|
|
}
|
|
|
|
function meanField(map, fieldName, predicate) {
|
|
let sum = 0;
|
|
let count = 0;
|
|
const field = map[fieldName];
|
|
for (let i = 0; i < field.length; i++) {
|
|
if (!predicate(i)) continue;
|
|
sum += field[i];
|
|
count++;
|
|
}
|
|
return count ? sum / count : 0;
|
|
}
|
|
|
|
function ridgeSinuosityMetric(map) {
|
|
const centers = [];
|
|
for (let y = 1; y < MAP_H - 1; y++) {
|
|
let sum = 0;
|
|
let weight = 0;
|
|
for (let x = 1; x < MAP_W - 1; x++) {
|
|
const i = indexOf(x, y);
|
|
if (map.sea[i]) continue;
|
|
const r = Math.max(0, map.ridgeField[i] - 0.36);
|
|
sum += x * r;
|
|
weight += r;
|
|
}
|
|
if (weight > 1.2) centers.push(sum / weight);
|
|
}
|
|
if (centers.length < 8) return 0;
|
|
let turn = 0;
|
|
let total = 0;
|
|
for (let i = 2; i < centers.length; i++) {
|
|
const a = centers[i - 1] - centers[i - 2];
|
|
const b = centers[i] - centers[i - 1];
|
|
turn += Math.abs(b - a);
|
|
total += Math.abs(b) + Math.abs(a) + 0.01;
|
|
}
|
|
return turn / total;
|
|
}
|
|
|
|
function terrainCoreMetrics(map) {
|
|
const land = [...map.elevation].map((_, i) => i).filter((i) => !map.sea[i]);
|
|
const mountainCells = land.filter((i) => map.elevation[i] > 0.58 || map.ridgeField[i] > 0.42).length;
|
|
const lowlandCells = land.filter((i) => map.plain[i] > 0.38 || map.depositionalLowland?.[i] > 0.24).length;
|
|
const ridgeValues = land.map((i) => map.ridgeField[i]);
|
|
const ridgeMean = ridgeValues.reduce((sum, value) => sum + value, 0) / Math.max(1, ridgeValues.length);
|
|
const ridgeVariance = ridgeValues.reduce((sum, value) => sum + (value - ridgeMean) ** 2, 0) / Math.max(1, ridgeValues.length);
|
|
const depositionTargetMean = meanField(map, "depositionField", (i) => !map.sea[i] && (map.coastalLowland[i] > 0.18 || map.basinField[i] > 0.22 || map.river[i] > 0.18 || map.flowAccum[i] > 0.24));
|
|
const depositionOtherMean = meanField(map, "depositionField", (i) => !map.sea[i] && map.coastalLowland[i] < 0.08 && map.basinField[i] < 0.12 && map.river[i] < 0.06 && map.flowAccum[i] < 0.12 && map.ridgeField[i] < 0.28);
|
|
const riverValleyMean = meanField(map, "valleyField", (i) => !map.sea[i] && map.river[i] > 0.20);
|
|
const nonRiverValleyMean = meanField(map, "valleyField", (i) => !map.sea[i] && map.river[i] <= 0.02);
|
|
return {
|
|
landCount: land.length,
|
|
mountainRatio: mountainCells / Math.max(1, land.length),
|
|
lowlandRatio: lowlandCells / Math.max(1, land.length),
|
|
ridgeVariance,
|
|
ridgeSinuosity: ridgeSinuosityMetric(map),
|
|
depositionTargetMean,
|
|
depositionOtherMean,
|
|
riverValleyMean,
|
|
nonRiverValleyMean,
|
|
depositionSum: [...map.depositionField].reduce((sum, value) => sum + value, 0),
|
|
alluvialMax: Math.max(...(map.alluvialFanField || [0])),
|
|
deltaMax: Math.max(...(map.deltaField || [0])),
|
|
};
|
|
}
|
|
|
|
function requiredTransportNodes(map) {
|
|
const nodes = [];
|
|
const seen = new Set();
|
|
function add(p, reason) {
|
|
if (!p) return;
|
|
const key = `${p.x},${p.y}`;
|
|
if (seen.has(key)) return;
|
|
seen.add(key);
|
|
nodes.push({ ...p, requiredTransportReason: reason });
|
|
}
|
|
add(map.prefecturalCapital, "capital");
|
|
for (const gate of map.externalGateways || []) add(gate, "externalGateway");
|
|
for (const city of map.modernCities || []) if ((city.population || 0) >= 120000 || city.isPrefecturalCapital) add(city, "majorCity");
|
|
for (const port of map.ports || []) if (port.portClass === "major") add(port, "majorPort");
|
|
return nodes;
|
|
}
|
|
|
|
function transportConnectivityMetrics(map) {
|
|
const paths = [
|
|
...map.railways,
|
|
...map.branchRailways,
|
|
...map.externalRailways,
|
|
...map.nationalRoads,
|
|
...(map.ringRoads || []),
|
|
...map.expressways,
|
|
...map.externalRoads,
|
|
...map.externalExpressways,
|
|
];
|
|
const pathCells = new Set();
|
|
for (const path of paths) for (const [x, y] of path) pathCells.add(`${x},${y}`);
|
|
const nodes = requiredTransportNodes(map);
|
|
function nearestCell(node) {
|
|
let best = null;
|
|
let bestD = Infinity;
|
|
for (const key of pathCells) {
|
|
const [x, y] = key.split(",").map(Number);
|
|
const d = Math.hypot(node.x - x, node.y - y);
|
|
if (d < bestD) {
|
|
bestD = d;
|
|
best = key;
|
|
}
|
|
}
|
|
return { key: best, distance: bestD };
|
|
}
|
|
const seen = new Set();
|
|
const components = [];
|
|
for (const key of pathCells) {
|
|
if (seen.has(key)) continue;
|
|
const queue = [key];
|
|
const component = new Set([key]);
|
|
seen.add(key);
|
|
for (let q = 0; q < queue.length; q++) {
|
|
const [x, y] = queue[q].split(",").map(Number);
|
|
for (const [dx, dy] of [[1,0],[-1,0],[0,1],[0,-1],[1,1],[-1,1],[1,-1],[-1,-1]]) {
|
|
const nk = `${x + dx},${y + dy}`;
|
|
if (!pathCells.has(nk) || seen.has(nk)) continue;
|
|
seen.add(nk);
|
|
component.add(nk);
|
|
queue.push(nk);
|
|
}
|
|
}
|
|
components.push(component);
|
|
}
|
|
const mapped = nodes.map((node) => ({
|
|
node,
|
|
nearest: nearestCell(node),
|
|
components: components
|
|
.map((component, componentIndex) => ({
|
|
componentIndex,
|
|
near: [...component].some((key) => {
|
|
const [x, y] = key.split(",").map(Number);
|
|
return Math.hypot(node.x - x, node.y - y) <= 7;
|
|
}),
|
|
}))
|
|
.filter((item) => item.near)
|
|
.map((item) => item.componentIndex),
|
|
}));
|
|
const reachable = mapped.filter((item) => item.components.length > 0);
|
|
let largestRequiredComponent = 0;
|
|
for (let componentIndex = 0; componentIndex < components.length; componentIndex++) {
|
|
largestRequiredComponent = Math.max(largestRequiredComponent, reachable.filter((item) => item.components.includes(componentIndex)).length);
|
|
}
|
|
return {
|
|
requiredCount: nodes.length,
|
|
reachableCount: reachable.length,
|
|
largestRequiredComponent,
|
|
isolatedExternalGateways: mapped.filter((item) => item.node.requiredTransportReason === "externalGateway" && item.components.length === 0).length,
|
|
isolatedMajorCities: mapped.filter((item) => item.node.requiredTransportReason === "majorCity" && item.components.length === 0).length,
|
|
};
|
|
}
|
|
|
|
try {
|
|
const size = MAP_W * MAP_H;
|
|
let terrainSeedSummaries = [];
|
|
if (suiteEnabled("core")) {
|
|
const map = generateTestMap(12345);
|
|
const urbanCellCount = [...map.landuse].filter((value) => value >= 2 && value <= 8).length;
|
|
const cityPopulations = map.modernCities.map((city) => city.population || 0);
|
|
const maxPopulation = Math.max(...cityPopulations);
|
|
const minPopulation = Math.min(...cityPopulations);
|
|
const landElevations = [...map.elevation].filter((_, i) => !map.sea[i]);
|
|
const meanElevation = landElevations.reduce((sum, value) => sum + value, 0) / landElevations.length;
|
|
const elevationStdDev = Math.sqrt(landElevations.reduce((sum, value) => sum + (value - meanElevation) ** 2, 0) / landElevations.length);
|
|
const modernPaths = [
|
|
...map.railways,
|
|
...map.branchRailways,
|
|
...map.externalRailways,
|
|
...(map.ringRailways || []),
|
|
...map.nationalRoads,
|
|
...(map.ringRoads || []),
|
|
...map.expressways,
|
|
...(map.ringExpressways || []),
|
|
...map.externalRoads,
|
|
...map.externalExpressways,
|
|
];
|
|
const endpointDegree = new Map();
|
|
for (const path of modernPaths) {
|
|
if (path.length < 2) continue;
|
|
for (const point of [path[0], path[path.length - 1]]) {
|
|
const key = point.join(",");
|
|
endpointDegree.set(key, (endpointDegree.get(key) || 0) + 1);
|
|
}
|
|
}
|
|
const maxModernEndpointDegree = Math.max(0, ...endpointDegree.values());
|
|
let maxCoastalElevationStep = 0;
|
|
for (let y = 1; y < MAP_H - 1; y++) {
|
|
for (let x = 1; x < MAP_W - 1; x++) {
|
|
const i = indexOf(x, y);
|
|
if (map.sea[i]) continue;
|
|
for (const [dx, dy] of [[1, 0], [-1, 0], [0, 1], [0, -1]]) {
|
|
const ni = indexOf(x + dx, y + dy);
|
|
if (map.sea[ni]) maxCoastalElevationStep = Math.max(maxCoastalElevationStep, Math.abs(map.elevation[i] - map.elevation[ni]));
|
|
}
|
|
}
|
|
}
|
|
let railExpressHighMountainCells = 0;
|
|
for (const path of [...map.railways, ...map.branchRailways, ...(map.ringRailways || []), ...map.externalRailways, ...map.expressways, ...(map.ringExpressways || []), ...map.externalExpressways]) {
|
|
for (const [x, y] of path) {
|
|
const i = indexOf(x, y);
|
|
if (map.elevation[i] > 0.82) railExpressHighMountainCells += 1;
|
|
}
|
|
}
|
|
let trunkHighElevationCells = 0;
|
|
for (const path of modernPaths) {
|
|
for (const [x, y] of path) {
|
|
if (map.elevation[indexOf(x, y)] > 0.72) trunkHighElevationCells += 1;
|
|
}
|
|
}
|
|
const flatPlainCells = [...map.plain].filter((value, i) => !map.sea[i] && value > 0.72 && map.slope[i] < 0.12).length;
|
|
const largeMountainCities = map.modernCities.filter((city) => {
|
|
const i = indexOf(city.x, city.y);
|
|
return (city.population || 0) >= 250000 && (map.elevation[i] > 0.66 || map.plain[i] < 0.18 || map.slope[i] > 0.88);
|
|
});
|
|
const capitalInside = map.prefecturalCapital && map.prefectureMask[indexOf(map.prefecturalCapital.x, map.prefecturalCapital.y)];
|
|
const allNameable = map.entitiesForNames || [];
|
|
const uniqueNames = new Set(allNameable.map((item) => item.name));
|
|
const duplicateNameRatio = allNameable.length ? 1 - uniqueNames.size / allNameable.length : 0;
|
|
const activePoolChars = new Set(Object.values(NAME_KANJI_POOLS).flat().flatMap((part) => Array.from(String(part))));
|
|
const namedEntityCount = [
|
|
...map.villages,
|
|
...map.ports,
|
|
...map.crossings,
|
|
...map.passes,
|
|
...map.markets,
|
|
...map.castles,
|
|
...map.castleTowns,
|
|
...map.modernCities,
|
|
...map.stations,
|
|
...map.industrialZones,
|
|
...map.interchanges,
|
|
...map.logisticsParks,
|
|
...map.satelliteCities,
|
|
...map.newTowns,
|
|
...map.castleRuins,
|
|
...map.externalGateways,
|
|
...map.adminCenters,
|
|
].filter((item) => item?.id && item?.name).length;
|
|
const villageClusterMean = map.villages.length
|
|
? map.villages.reduce((sum, p) => sum + (map.settlementCluster?.[indexOf(p.x, p.y)] || 0), 0) / map.villages.length
|
|
: 0;
|
|
const meaningfulTransportNodes = [
|
|
...map.modernCities,
|
|
...map.ports,
|
|
...map.markets,
|
|
...map.externalGateways,
|
|
...map.interchanges,
|
|
...map.industrialZones,
|
|
...map.logisticsParks,
|
|
];
|
|
const endpointPaths = [
|
|
...map.railways,
|
|
...map.branchRailways,
|
|
...map.externalRailways,
|
|
...map.nationalRoads,
|
|
...map.expressways,
|
|
...map.externalRoads,
|
|
...map.externalExpressways,
|
|
...(map.icAccessRoads || []),
|
|
];
|
|
const endpointDistances = endpointPaths.flatMap((path) => path.length >= 2 ? [path[0], path[path.length - 1]] : [])
|
|
.map(([x, y]) => Math.min(...meaningfulTransportNodes.map((p) => Math.hypot(p.x - x, p.y - y))));
|
|
const saneEndpointRatio = endpointDistances.length
|
|
? endpointDistances.filter((d) => d <= 10).length / endpointDistances.length
|
|
: 1;
|
|
const adminMetrics = adminBoundaryMetrics(map);
|
|
const cityCoreIntegrity = majorCityCoreIntegrity(map);
|
|
const satelliteMetrics = satelliteMunicipalityMetrics(map);
|
|
const regionalMetrics = regionalComponentMetrics(map);
|
|
const terrainMetrics = terrainCoreMetrics(map);
|
|
|
|
assert(NAME_KANJI_POOLS && Array.isArray(NAME_KANJI_POOLS.modifiers), "NAME_KANJI_POOLS exists");
|
|
assert(NAME_TEMPLATES && NAME_TEMPLATES.modifierTerrain?.slots?.length === 2, "NAME_TEMPLATES exists");
|
|
assert(NAME_TEMPLATE_WEIGHTS && NAME_TEMPLATE_WEIGHTS.generic?.modifierTerrain > 0, "NAME_TEMPLATE_WEIGHTS exists");
|
|
assert(NAME_PROBABILITIES && NAME_PROBABILITIES.contextCategoryWeights?.generic, "NAME_PROBABILITIES exists");
|
|
const removedContextModule = "placeName" + "Context.js";
|
|
assert(!namesSource.includes(removedContextModule) && !mapGeneratorSource.includes(removedContextModule), "removed name-context import is absent from production modules");
|
|
assert(Object.keys(NAME_KANJI_POOLS).every((key) => Array.isArray(NAME_KANJI_POOLS[key])), "name category pools are centralized arrays");
|
|
assert(Object.values(NAME_KANJI_POOLS).every((pool) => pool.every((part) => typeof part === "string" && !part.includes("\uFFFD"))), "configured name category pools contain valid strings");
|
|
const removedContextSuffixConst = "CONTEXT" + "_SUFFIXES";
|
|
const removedContextSuffixKey = "context" + "Suffixes";
|
|
assert(!namesSource.includes(removedContextSuffixConst) && !namesSource.includes(removedContextSuffixKey), "hidden context suffix arrays are absent");
|
|
assert(!/export\s+const\s+NAME_PROBABILITIES[\s\S]*export\s+const\s+NAME_PROBABILITIES/.test(namesSource), "NAME_PROBABILITIES has one source");
|
|
assert(mapPatchSource.includes("splitWorldPathByPatch") && mapPatchSource.includes("patchAffected"), "patch path merging is alpha-aware for lasso selections");
|
|
assert(!mapPatchSource.includes("patchAlpha(x, y, rects, 0)"), "patch admin repair uses the active patch seed");
|
|
assert(mapPatchSource.includes("refreshPatchInfluenceFields") && mapPatchSource.includes("roadCellsPainted"), "patch generation refreshes derived transport influence fields after path merges");
|
|
assert(mapPatchSource.includes("createTransportPathSpatialIndex") && mapPatchSource.includes("TRANSPORT_SPATIAL_BUCKET_SIZE"), "patch transport queries use a bucketed spatial index instead of repeatedly scanning every path");
|
|
assert(mapPatchSource.includes("pathfindScratch") && mapPatchSource.includes("Float32Array") && mapPatchSource.includes("buildHierarchicalPathCorridor"), "patch connector pathfinding reuses typed scratch storage and has a coarse-to-fine hierarchy");
|
|
assert(mapPatchSource.includes("TransportUnionFind") && mapPatchSource.includes("GraphIncrementalUnions") && mapPatchSource.includes("GraphFullRebuilds"), "transport graph repair tracks connector merges incrementally and reserves full rebuilds for audit");
|
|
assert(mapPatchSource.includes("getRadialInfluenceKernel") && mapPatchSource.includes("paintInfluenceDisk"), "regional influence refresh reuses exact radial kernels instead of recalculating distance powers per painted cell");
|
|
assert(mapPatchSource.includes("regionalUrbanScratch") && mapPatchSource.includes("population.subarray"), "regional urban recalculation reuses compact scratch buffers and row copies");
|
|
assert(mapPatchSource.includes("ensurePatchSelectionDistanceCache") && mapPatchSource.includes("_selectionDistanceCache") && mapPatchSource.includes("selectionDistanceCacheReused"), "lasso regional transport and urban recalculation share one lazy selection-distance raster");
|
|
assert(mapPatchWorkerSource.includes("admissible-terrain-scout-branch-and-bound-two-lane-v2") && mapPatchWorkerSource.includes("draftCandidateUpperBound") && mapPatchWorkerSource.includes("branchBoundPrunedCount") && mapPatchWorkerSource.includes("precomputeRawTerrainScoutBatch") && mapPatchWorkerSource.includes("fullProductionFromTerrainOnly") && !mapPatchWorkerSource.includes("draftNearTieFullGap"), "candidate ranking uses admissible Branch-and-Bound with resident two-lane terrain scouts; simplified human/transport drafts are never reused for publication");
|
|
assert(worldMapSource.includes("INITIAL_QUALITY_TRANSPORT_CLASSES") && worldMapSource.includes("initial-production-quality-v2") && mapPatchSource.includes("transportHierarchyPass"), "initial quality oracle records national, expressway, and rail-trunk production density and enforces hierarchy-aware final quality");
|
|
assert(mapPatchSource.includes("finalizeRegionalTrunkTransport") && mapPatchSource.includes("patchRegionalTrunkFinalizer") && mapPatchSource.includes("productionTransportParity"), "full additional-generation finalists restore production trunk transport and fill missing national, expressway, and trunk-rail demand");
|
|
assert(mapPatchSource.includes("connectorLayerForEndpoints") && mapPatchSource.includes("transportHierarchyAtPoint"), "transport seam repair preserves expressway, national-road, and trunk-rail hierarchy instead of demoting every connector");
|
|
assert(appSource.includes("PATCH_SEARCH_BATCH_SIZE = 3") && appSource.includes("PATCH_SEARCH_DEFAULT_LIMIT = 12") && appSource.includes("contentBatchExhausted") && appSource.includes("allCandidatePlan"), "quality-rejected candidates advance automatically in three-candidate batches up to twelve without publishing drafts");
|
|
assert(!appSource.includes("BEST AVAILABLE") && appSource.includes("acceptBestAvailableQuality: false"), "top-level preview publication has no best-available quality bypass");
|
|
assert(mapPatchSource.includes("normalizeGeneratedPointIds"), "patch generation normalizes generated point admin and prefecture ids");
|
|
assert(mapPatchSource.includes("generateUnifiedWorldNativePatchCandidate") && mapPatchSource.includes("generateMap(seed") && mapPatchSource.includes("unified-world-native-patch"), "patch modes execute the complete production generation pipeline");
|
|
assert(!mapPatchSource.includes("generateVariablePatchCandidate") && !mapPatchSource.includes("PATCH_VARIABLE_CANDIDATE_ENABLED"), "retired variable rectangle candidate implementation is removed");
|
|
assert(mapPatchSource.includes("resolvePatchMode") && mapPatchSource.includes("PATCH_MODE_EXPANSION") && mapPatchSource.includes("PATCH_MODE_REGENERATION"), "patch generation separates expansion and regeneration modes");
|
|
assert(mapPatchSource.includes("resolveWorldSeaLevel") && mapPatchSource.includes("buildTerrainBoundaryContract") && mapPatchSource.includes("applyTerrainBoundaryContract"), "patch terrain uses a shared world sea level and an explicit boundary contract");
|
|
assert(mapPatchSource.includes("candidateOriginX") && mapPatchSource.includes("world?.originX") && mapPatchSource.includes("canonicalWorldGrid"), "patch candidates use padding-invariant world coordinates and canonical tile windows");
|
|
assert(appSource.includes("activePatchOperation") && appSource.includes("selectionRevision") && appSource.includes("isPatchOperationCurrent"), "patch preview publication is guarded by immutable operation and selection generations");
|
|
assert(appSource.includes("fullGenerationBusy") && appSource.includes("state.patchBusy || state.fullGenerationBusy") && appSource.includes("cancelPatchGeneration"), "full and patch generation share one explicit busy/cancellation domain");
|
|
assert(derivePatchSeedSource.includes("function derivePatchSeed(world, terrainType, variant") && !derivePatchSeedSource.includes("rect.x") && !derivePatchSeedSource.includes("rect.y"), "UI patch seed is independent of selection bounds and backing-world padding");
|
|
assert(!appSource.includes("qualityWorkerRetries: 1") && !appSource.includes("attemptVariant = (attemptVariant + 3)"), "UI does not run the obsolete hidden whole-patch retry wrapper");
|
|
assert(mapPatchSource.includes("generateTiledRegenerationPatch") && mapPatchSource.includes("patch-candidate-coverage-incomplete"), "large Regeneration is tiled and rejects uncovered active cells instead of silently skipping them");
|
|
assert(mapPatchSource.includes("initial-quality-oracle-admin-transport-coherence-v4") && mapPatchSource.includes("const requestedAttempts = 1"), "each search candidate runs exactly one explicit complete production variant");
|
|
assert(mapPatchWorkerSource.includes("runPatchCandidateSearch") && mapPatchWorkerSource.includes("candidatePlan") && mapPatchWorkerSource.includes("patch-search-exhausted"), "worker owns a bounded multi-candidate search controller");
|
|
assert(mapPatchWorkerSource.includes("persistentCommittedMirror") && appSource.includes("reuseCommittedMirror") && appSource.includes("committedRevision"), "warm Alternative searches reuse a revision-checked committed Worker mirror");
|
|
assert(mapPatchWorkerSource.includes("patch-apply-ack") && appSource.includes("acknowledgePatchApply"), "Apply advances the persistent mirror through a revision-checked transactional ACK");
|
|
assert(appSource.includes("stagePreviewRenderBundle") && appSource.includes("publishPreviewRenderBundle"), "preview rendering is staged offscreen before atomic state and canvas publication");
|
|
assert(mapPatchSource.includes("auditRepairAndReauditPatchSeam") && mapPatchSource.includes("PATCH_SEAM_INVARIANT_REASONS"), "seam repair is audit-driven and keeps invariant failures outside the repair path");
|
|
assert(mapPatchSource.includes("large-regeneration-final-quality") && mapPatchSource.includes("whole-selection-post-merge"), "tiled Regeneration applies one authoritative whole-selection quality audit");
|
|
assert(mapPatchSource.includes("minFinalAdminCenters") && mapPatchSource.includes("transportRequired")
|
|
&& mapPatchSource.includes("roadPaths > 0"), "final quality rejects Regeneration candidates that lose required administration or transport");
|
|
assert(mapPatchWorkerSource.includes("computePreviewDelta") && appSource.includes("previewPatchDelta(baseWorld, job.world"),
|
|
"preview raster and feature change auditing remains available for both immutable-reference and transactional-delta paths");
|
|
assert(appSource.includes("buildPatchCandidatePlan") && appSource.includes("consumedCandidateIds") && appSource.includes("nextVariant"), "UI continues Alternative batches without repeating content-rejected candidates");
|
|
const controllerBase = { fields: { marker: new Uint8Array([1]) } };
|
|
const controllerCalls = [];
|
|
const controllerProgress = [];
|
|
const controllerResult = runPatchCandidateSearch({
|
|
id: 1,
|
|
world: controllerBase,
|
|
rect: { x0: 0, y0: 0, x1: 1, y1: 1 },
|
|
options: {},
|
|
search: {
|
|
searchId: "controller-regression",
|
|
workerEpoch: 1,
|
|
committedRevision: 1,
|
|
candidatePlan: [{ variant: 5, seed: 105 }, { variant: 6, seed: 106 }, { variant: 7, seed: 107 }],
|
|
},
|
|
}, {
|
|
cloneWorld: (value) => structuredClone(value),
|
|
onProgress: (message) => controllerProgress.push(message.progress),
|
|
generateCandidate: (candidateWorld, rect, options) => {
|
|
controllerCalls.push({ variant: options.variant, baseline: candidateWorld.fields.marker[0] });
|
|
candidateWorld.fields.marker[0] = options.variant;
|
|
if (options.variant === 5) return { ok: false, code: "patch-quality-gate-failed", reason: "forced content rejection" };
|
|
return { ok: true, variant: options.variant, seed: options.seed, seamDiagnostics: { hardPass: true } };
|
|
},
|
|
});
|
|
assert(controllerResult.result?.ok === true && controllerResult.result?.actualVariant === 6, "content rejection automatically advances to the next complete candidate");
|
|
assert(controllerCalls.length === 2 && controllerCalls.every((call) => call.baseline === 1) && controllerBase.fields.marker[0] === 1, "each candidate starts from an isolated immutable committed baseline");
|
|
assert(controllerResult.result?.searchAttempts?.map((attempt) => attempt.status).join(",") === "rejected,success", "candidate search preserves an ordered rejection and success audit trail");
|
|
assert(controllerProgress.length > 0 && controllerProgress.every((event) => Number.isFinite(event.eventSeq) && event.phase && event.workUnitId), "worker progress carries ordered operation, phase, and work-unit identity");
|
|
assert(controllerProgress.filter((event) => event.boundedWork).every((event) => event.completed >= 0 && event.completed <= event.total), "bounded worker progress never exceeds its declared finite work total");
|
|
const invalidProgressResult = runPatchCandidateSearch({
|
|
id: 4,
|
|
world: controllerBase,
|
|
rect: { x0: 0, y0: 0, x1: 1, y1: 1 },
|
|
options: {},
|
|
search: { searchId: "controller-progress-invariant", candidatePlan: [{ variant: 12, seed: 112 }] },
|
|
}, {
|
|
cloneWorld: (value) => structuredClone(value),
|
|
generateCandidate: (candidateWorld, rect, options) => {
|
|
options.onProgress({ status: "step", key: "invalid-bounds", completed: 2, total: 1 });
|
|
return { ok: true, seamDiagnostics: { hardPass: true } };
|
|
},
|
|
});
|
|
assert(invalidProgressResult.ok === false && invalidProgressResult.code === "worker-progress-invariant", "invalid or runaway bounded progress stops the worker search as an invariant failure");
|
|
const invariantCalls = [];
|
|
const invariantResult = runPatchCandidateSearch({
|
|
id: 2,
|
|
world: controllerBase,
|
|
rect: { x0: 0, y0: 0, x1: 1, y1: 1 },
|
|
options: {},
|
|
search: {
|
|
searchId: "controller-invariant",
|
|
workerEpoch: 1,
|
|
committedRevision: 1,
|
|
candidatePlan: [{ variant: 8, seed: 108 }, { variant: 9, seed: 109 }],
|
|
},
|
|
}, {
|
|
cloneWorld: (value) => structuredClone(value),
|
|
generateCandidate: (candidateWorld, rect, options) => {
|
|
invariantCalls.push(options.variant);
|
|
return {
|
|
ok: false,
|
|
code: "patch-seam-gate-failed",
|
|
reason: "forced write escape",
|
|
seamDiagnostics: { hardPass: false, gateReasons: ["generated-footprint-write-escape"] },
|
|
};
|
|
},
|
|
});
|
|
assert(invariantResult.result?.searchStatus === "invariant-breach" && invariantCalls.length === 1, "write invariant failures stop the search without consuming another variant");
|
|
const cloneFailure = runPatchCandidateSearch({
|
|
id: 3,
|
|
world: controllerBase,
|
|
rect: { x0: 0, y0: 0, x1: 1, y1: 1 },
|
|
options: {},
|
|
search: { searchId: "controller-clone", candidatePlan: [{ variant: 10, seed: 110 }, { variant: 11, seed: 111 }] },
|
|
}, {
|
|
cloneWorld: () => { throw new Error("forced clone failure"); },
|
|
generateCandidate: () => { throw new Error("candidate must not start after clone failure"); },
|
|
});
|
|
assert(cloneFailure.result?.searchStatus === "infrastructure-error" && cloneFailure.result?.nextVariant === 10, "candidate clone failure preserves the current variant for infrastructure retry");
|
|
const deltaBase = {
|
|
width: 4, height: 2,
|
|
fields: { elevation: new Float32Array([0, 1, 2, 3, 4, 5, 6, 7]), adminId: new Int32Array(8) },
|
|
generatedMask: new Uint8Array(8), sourceMap: { villages: [{ x: 1, y: 1 }] }, patchGenerationSerial: 1,
|
|
};
|
|
const deltaTarget = structuredClone(deltaBase);
|
|
deltaTarget.fields.elevation[2] = 20;
|
|
deltaTarget.fields.adminId[7] = 9;
|
|
deltaTarget.generatedMask[6] = 1;
|
|
deltaTarget.sourceMap.villages.push({ x: 2, y: 2 });
|
|
deltaTarget.patchGenerationSerial = 2;
|
|
const committedDelta = buildCommittedMirrorDelta(deltaBase, deltaTarget);
|
|
const deltaApplied = applyCommittedMirrorDelta(structuredClone(deltaBase), committedDelta);
|
|
assert(
|
|
arraysEqual(deltaApplied.fields.elevation, deltaTarget.fields.elevation)
|
|
&& arraysEqual(deltaApplied.fields.adminId, deltaTarget.fields.adminId)
|
|
&& arraysEqual(deltaApplied.generatedMask, deltaTarget.generatedMask)
|
|
&& JSON.stringify(deltaApplied.sourceMap) === JSON.stringify(deltaTarget.sourceMap)
|
|
&& deltaApplied.patchGenerationSerial === 2,
|
|
"transactional Apply delta reproduces the accepted world fields, mask, metadata, and serial"
|
|
);
|
|
assert(mapPatchWorkerSource.includes("sourceMapDelta") && mapPatchWorkerSource.includes("metaDelta")
|
|
&& !mapPatchWorkerSource.includes("sourceMap: structuredClone(nextWorld.sourceMap"), "Apply ACK retains only changed source/world metadata instead of duplicating the complete map metadata");
|
|
assert(mapPatchWorkerSource.includes("buildExactArraySplice")
|
|
&& mapPatchWorkerSource.includes("arraySplices")
|
|
&& mapPatchWorkerSource.includes("isDensePlainArray")
|
|
&& committedWorldDeltaSource.includes("applyCommittedWorldDelta"),
|
|
"changed dense feature/path/history layers use exact splice deltas instead of transferring the complete layer");
|
|
assert(appSource.includes("consumeMetadata: true")
|
|
&& mapPatchWorkerSource.includes("pending.delta, { consumeMetadata: true }"),
|
|
"main preview publication and Worker Apply ACK consume their isolated metadata delta without cloning it a second time");
|
|
assert(mapPatchWorkerSource.includes("buildMainThreadTransferDelta")
|
|
&& !mapPatchWorkerSource.includes("const mainThreadDelta = structuredClone(delta)")
|
|
&& mapPatchWorkerSource.includes("{ fields: payload.worldDelta.fields, generatedMask: payload.worldDelta.generatedMask }"),
|
|
"Worker main-transfer preparation copies detachable raster rows without cloning or detaching retained metadata");
|
|
assert(mapPatchWorkerSource.includes("const exactDeltas = { sourceMapDelta: rawSourceMapDelta, metaDelta: rawMetaDelta }")
|
|
&& !mapPatchWorkerSource.includes("structuredClone({ sourceMapDelta: rawSourceMapDelta, metaDelta: rawMetaDelta })"),
|
|
"transaction delta owns completed candidate metadata directly instead of cloning the graph before rollback");
|
|
assert(mapPatchWorkerSource.includes("transactional: true") && mapPatchWorkerSource.includes("buildCommittedMirrorDeltaFromTransaction")
|
|
&& appSource.includes("materializeCommittedWorldDeltaCooperative(world, event.data.worldDelta"), "production previews mutate the Worker mirror transactionally and materialize only the accepted delta on the main thread");
|
|
assert(appSource.includes("materializeCommittedWorldDelta")
|
|
&& !appSource.includes("previewWorld = structuredClone(world)"),
|
|
"main preview creation uses copy-on-write changed fields instead of cloning every committed raster and metadata layer");
|
|
assert(appSource.includes("Accepted preview hash mismatch") && appSource.includes("hashCommittedWorldAsync")
|
|
&& mapPatchWorkerSource.includes("acceptedWorldHash"),
|
|
"main-thread preview publication cooperatively rejects any transaction delta that does not reproduce the Worker-completed world hash");
|
|
assert(mapPatchWorkerSource.includes("changeTracker.mask") && mapPatchWorkerSource.includes("successDelta?.previewDelta"),
|
|
"transaction delta construction also produces preview statistics so the main thread does not repeat the field comparison");
|
|
assert(mapPatchWorkerSource.includes("const scanLocalRect = localEntry && rect")
|
|
&& mapPatchWorkerSource.includes("const scanStart = scanLocalRect"),
|
|
"transaction delta compares local fields only inside their captured mutation rectangle");
|
|
assert(mapPatchWorkerSource.includes("const transferRoot = payload.worldDelta")
|
|
&& mapPatchWorkerSource.includes("{ fields: payload.worldDelta.fields, generatedMask: payload.worldDelta.generatedMask }")
|
|
&& mapPatchWorkerSource.includes(": (payload.world || null)"),
|
|
"result transfer discovery scans only the transferable raster delta/full-world root instead of the complete diagnostic graph");
|
|
assert(!worldMapSource.includes("invalidatedRects") && !mapPatchSource.includes("addInvalidatedRect")
|
|
&& !worldMapSource.includes("humanPatchHistory") && !mapPatchSource.includes("humanPatchHistory"),
|
|
"unused invalidation and patch-history arrays are absent from world state, transactions, padding shifts, and Worker payloads");
|
|
assert(mapPatchSource.includes("PATCH_MUTABLE_SOURCE_KEYS")
|
|
&& mapPatchSource.includes("PATCH_MUTABLE_SOURCE_KEYS.has(key)")
|
|
&& mapPatchSource.includes("out[key] = PATCH_MUTABLE_SOURCE_KEYS.has(key)"),
|
|
"transaction snapshots recursively clone only patch-mutable sourceMap roots and share read-only production metadata");
|
|
assert(mapPatchWorkerSource.includes("isolateSourceMap: true")
|
|
&& mapPatchSource.includes("sourceMapIsolated: isolateSourceMap")
|
|
&& mapPatchSource.includes("world.sourceMap = snapshot.sourceMapRef || {}"),
|
|
"Worker candidates mutate an isolated sourceMap and rollback by reference without a second metadata clone");
|
|
assert(mapPatchSource.includes("generatedRects: lightweight ? null : (world.generatedRects || [])")
|
|
&& mapPatchSource.includes("lastPatchResult: lightweight ? null : (world.lastPatchResult ?? null)")
|
|
&& !mapPatchSource.includes("cloneTransactionValue(world.generatedRects || [])")
|
|
&& !mapPatchSource.includes("cloneTransactionValue(world.lastPatchResult ?? null)"),
|
|
"transaction rollback retains immutable history and prior diagnostics by reference instead of cloning their debug graphs");
|
|
assert(mapPatchSource.includes("baselineSourceMap") && mapPatchSource.includes("snapshotPoint = baselineSourceMap")
|
|
&& mapPatchSource.includes("capturePrefectureIdentitySnapshot(sourceMap, strictMetadataSnapshot)"),
|
|
"strict Regeneration metadata reuses the immutable transaction baseline across outside-point and identity snapshots");
|
|
assert(mapPatchSource.includes("rects.patchMode === PATCH_MODE_EXPANSION")
|
|
&& mapPatchSource.includes("protectedIndexLookup?.fill(-1)"),
|
|
"Regeneration strict snapshots do not allocate the Expansion-only random lookup table");
|
|
assert(mapPatchSource.includes("_strictBaselineTransactionSnapshot: transaction")
|
|
&& mapPatchSource.includes("transactionSnapshot?.fields?.has(name)"),
|
|
"large internal tiles reuse the outer transaction's field before-images instead of cloning strict fields per tile");
|
|
assert(mapPatchSource.includes("PATCH_TRANSACTION_READ_ONLY_FIELDS")
|
|
&& mapPatchSource.includes("PATCH_TRANSACTION_READ_ONLY_FIELDS.has(name)"),
|
|
"transaction and strict snapshots omit the read-only flowTo field instead of copying unused rollback values");
|
|
assert(appSource.includes("resolvedPatchMode: operation.resolvedPatchMode")
|
|
&& mapPatchWorkerSource.includes("copyGeneratedMask:")
|
|
&& mapPatchWorkerSource.includes("resolvedPatchMode || \"\").toLowerCase() !== \"regeneration\"")
|
|
&& mapPatchSource.includes("generatedMaskRef"),
|
|
"Regeneration transactions retain generatedMask by reference while Expansion keeps an exact writable before-image");
|
|
assert(mapPatchSource.includes("synchronizePatchMunicipalityField") && mapPatchSource.includes("municipalityWriteRects: aggregateSourceRects"),
|
|
"Regeneration administrative coherence avoids a whole-world municipality write followed by whole-world strict restoration");
|
|
assert(mapPatchSource.includes("municipalityWriteRects: rects,")
|
|
&& !mapPatchSource.includes("snapshot.globalFields.set(\"municipalityId\"")
|
|
&& !mapPatchSource.includes("new municipality.constructor(municipality)")
|
|
&& !mapPatchSource.includes("globalFields:"),
|
|
"Expansion and Regeneration scope municipality writes to patch alpha and omit the full-world rollback copy");
|
|
assert(mapPatchSource.includes("bestFallbackCellByPrefecture") && mapPatchSource.includes("previous || score > previous.score"),
|
|
"prefecture capital fallback collects best cells in one world pass instead of rescanning the world per missing prefecture");
|
|
assert(mapPatchSource.includes("_tileCoreWidth: MAP_W") && mapPatchSource.includes("_tileCoreHeight: MAP_H")
|
|
&& appSource.includes("buildLargeExpansionTiles(rect, world, regeneration ? {")
|
|
&& appSource.includes("_tileCoreWidth: MAP_W") && appSource.includes("_tileCoreHeight: MAP_H"),
|
|
"large Regeneration plans full-size production cores and the UI derives the same tile count from the production tiler");
|
|
assert(!mapPatchWorkerSource.includes("stableLayerText") && !appSource.includes("stableLayerText"), "preview feature comparison does not allocate full JSON strings");
|
|
assert(rendererSource.includes("MAX_BASE_CACHE_IMAGES = 1") && rendererSource.includes("MAX_OVERLAY_CACHE_IMAGES = 1")
|
|
&& !appSource.includes("snapshotVisibleCanvas"), "preview raster caches and publication rollback do not retain obsolete full canvases");
|
|
assert(worldMapSource.includes("initialQualityReference") && mapPatchSource.includes("world?.initialQualityReference"), "quality reference is fixed at initial generation instead of growing with patch history");
|
|
assert(worldMapSource.includes("generatedMask") && mapPatchSource.includes("addGeneratedFootprintToMask"), "generated coverage uses a world-sized mask rather than scanning all patch history per cell");
|
|
assert(appSource.includes("featureLayersChanged") && appSource.includes("transportReachRect") && appSource.includes("fieldNames"), "preview identical diagnostics cover all raster fields and feature/path layers in the affected range");
|
|
assert(mapPatchSource.includes("_deferInternalSeamDiagnostics") && mapPatchSource.includes("_coverageDistanceBaseline"), "large tiles reuse coverage distances and defer internal seam diagnostics to the whole selection");
|
|
assert(worldMapSource.includes("MAX_WORLD_WIDTH") && worldMapSource.includes("MAX_WORLD_HEIGHT"), "backing-world padding has an explicit upper bound");
|
|
assert(worldMapSource.includes("seaLevel: Number.isFinite(initialMap?.seaLevel)"), "world map persists the initial sea level as a world invariant");
|
|
assert(mapPatchSource.includes("capturePatchSeamSnapshot") && mapPatchSource.includes("analyzePatchSeam") && mapPatchSource.includes("roadPortalsBroken") && mapPatchSource.includes("duplicateBoundaryPairs"), "patch generation records coast, transport, and boundary seam diagnostics");
|
|
assert(appSource.includes("advancedSeamDiagnostics") && appSource.includes("showSeamDiagnostics") && appSource.includes("seamDiagnosticRows"), "seam diagnostics are exposed in the UI and map overlay controls");
|
|
assert(appSource.includes("state.world.sourceMap.patchSeamDiagnostics.enabled = false")
|
|
&& appSource.includes("state.showSeamDiagnostics = false")
|
|
&& appSource.includes("showSeamDiagnosticsInput.checked = false"),
|
|
"applying an additional-generation preview disables and unchecks the seam diagnostic overlay so red dotted diagnostics cannot become stuck");
|
|
assert(mapPatchSource.includes("patchTimings") && appSource.includes("result.patchTimings"), "patch generation returns and renders timing rows");
|
|
assert(!mapPatchSource.includes("patchCandidateCacheKey") && !mapPatchSource.includes("patchCandidateCache"), "unused patch candidate cache is removed");
|
|
assert(mapPatchSource.includes("getPatchAlphaCache") && mapPatchSource.includes("getPatchSourceIndexCache"), "patch generation caches alpha and source-index grids for merge work");
|
|
assert(mapPatchSource.includes("attemptsRemaining = options.maxAttempts") && mapPatchSource.includes("connectorAttempts"), "patch connector pathfinding uses bounded attempts");
|
|
assert(mapPatchSource.includes("minorRoads: 42") && mapPatchSource.includes("nationalRoads: 94")
|
|
&& mapPatchSource.includes("expressways: 164") && mapPatchSource.includes("railways: 188")
|
|
&& mapPatchSource.includes("PATCH_URBAN_RECALC_REACH = 112")
|
|
&& mapPatchSource.includes("regionalRecalculationProbability"),
|
|
"regional recomputation uses narrow local-road, wider national-road, and widest expressway/rail collars with distance-decaying selection probability");
|
|
assert(worldMapSource.includes("shiftSelectionShape") && worldMapSource.includes("selectionShape = shiftSelectionShape"), "world expansion shifts stored lasso patch polygons");
|
|
assert(municipalSource.includes("reconcileMunicipalMetadata") && mapOutputSource.includes("reconcileMunicipalMetadata") && mapPatchSource.includes("reconcileMunicipalMetadata"), "municipal metadata is reconciled in output and patch repair");
|
|
assert(appSource.includes("mappedPref === id") && !appSource.includes("return nearestNamedAdminCenter(map, cellIndex, maxDistance, null)"), "tooltip municipal fallback requires exact coherent ids");
|
|
assert(mapPatchSource.includes("expansionSelectionMask") && mapPatchSource.includes("coverageDistanceAt") && mapPatchSource.includes("patchBand"), "expansion lasso alpha uses a two-sided generated/new overlap while regeneration remains strict");
|
|
assert(mapPatchSource.includes("repairDiscreteSeamOwnership") && mapPatchSource.includes("chooseSeamOwnerValue"), "patch admin and prefecture seams use ownership repair");
|
|
assert(mapPatchSource.includes("featherTerrainSeam") && mapPatchSource.includes("terrainFeatherCells"), "patch terrain transition bands are feather-smoothed");
|
|
assert(mapPatchSource.includes("strongOnly") && mapPatchSource.includes("patchAlpha(x, y, rects, seed)"), "patch water topology avoids weak low-alpha seam flips");
|
|
assert(!municipalSource.includes("Municipality ${id + 1}") && !municipalSource.includes("Prefecture ${id + 1}") && municipalSource.includes("自治${id + 1}") && municipalSource.includes("県域${id + 1}"), "fallback municipal and prefecture metadata avoids generic English labels");
|
|
|
|
assert(map.elevation.length === size, "elevation length matches map size");
|
|
assert(map.sea.length === size, "sea length matches map size");
|
|
assert(map.ocean.length === size && map.lake.length === size, "ocean and lake masks match map size");
|
|
assert(map.river.length === size, "river length matches map size");
|
|
assert(map.landuse.length === size, "land-use length matches map size");
|
|
assert(map.adminId.length === size, "municipal id length matches map size");
|
|
assert(map.prefectureMask.length === size, "prefecture mask length matches map size");
|
|
assert(map.populationDensity.length === size, "population density length matches map size");
|
|
assert(map.ridgeField.length === size && map.valleyField.length === size && map.flowAccum.length === size, "causal terrain fields match map size");
|
|
assert(map.erosionField.length === size && map.depositionField.length === size, "erosion and deposition fields match map size");
|
|
assert(map.arcSpineField.length === size && map.branchRidgeField.length === size, "spine and branch ridge fields match map size");
|
|
assert(map.depositionalLowland.length === size && map.alluvialFanField.length === size && map.deltaField.length === size, "depositional debug fields match map size");
|
|
assert(map.naturalBarrierScore.length === size, "natural barrier score field matches map size");
|
|
assert(map.terrainTemplate && Number.isFinite(map.terrainTemplate.deposition) && Number.isFinite(map.terrainTemplate.erosion), "terrain template parameters are exposed");
|
|
assert(map.terrainDebug && Number.isFinite(map.terrainDebug.primarySpineStrength), "terrain debug metrics exist");
|
|
assert(map.terrainDebug.primarySpineStrength > 0.08, "primary mountain spine has visible strength");
|
|
assert(map.terrainDebug.largeInlandLakeCount <= 1, "large inland lakes are rare");
|
|
assert(map.terrainDebug.smallIslandCount <= 24, "small island/coast speckles stay limited");
|
|
assert(map.terrainDebug.depositionLowlandArea > 0, "depositional lowland area is tracked");
|
|
assert(["east-west", "north-south", "diagonal"].includes(map.terrainTemplate.coastAxis) && map.terrainTemplate.coastSides?.length === 2, "paired coast template parameters are exposed");
|
|
assert(map.settlementCluster.length === size, "settlement cluster field matches map size");
|
|
assert(map.prefectureRegionId.length === size && Array.isArray(map.regionalPrefectureBorders), "neighbor prefecture regions exist");
|
|
assert(map.prefectureRegionId.length === size, "final prefecture id field matches map size");
|
|
assert(map.naturalCompartmentId?.length === size && Array.isArray(map.naturalCompartments), "shared natural compartments are exposed");
|
|
assert(map.adminDebug?.naturalCompartmentCount > 0 && map.adminDebug?.finalMunicipalityCount > 0, "natural compartments are generated before municipalities");
|
|
assert(map.regionalDebug?.prefecturesGeneratedAfterMunicipalities === true && map.regionalDebug?.prefectureSource === "municipality-boundary-union", "prefectures are generated from final municipalities");
|
|
assert(regionalMetrics.regionCount >= 2 && regionalMetrics.maxLandmassComponents === 1, "larger non-sea prefecture regions remain connected after repair");
|
|
assert(regionalEnclaveCount(map) === 0, "final prefecture regions contain no one-region enclosed enclaves");
|
|
const regionalBorders = regionalBorderMetrics(map);
|
|
const hierarchyViolations = borderHierarchyViolations(map);
|
|
assert(hierarchyViolations.prefectureCutsMunicipality === 0, "no prefecture border cuts through a municipality");
|
|
assert(hierarchyViolations.municipalityCutsCompartment === 0, "no municipality border cuts through a natural compartment");
|
|
assert(regionalBorders.invalidSame === 0 && regionalBorders.actual === regionalBorders.expected, "rendered prefecture borders separate different final prefecture ids only");
|
|
assert(map.regionalDebug.finalRegionalPrefectureBorderCount === map.regionalPrefectureBorders.length, "regional prefecture borders are final output borders");
|
|
assert(!mapTerrainSource.includes("generateRegionalPrefectures") && !mapPipelineSource.includes("prefectureRegionId, sea") && mapAdminStageSource.includes("generatePrefecturesFromMunicipalities"), "administrative order is natural compartments to municipalities to prefectures");
|
|
assert(![mapTerrainSource, mapPipelineSource, mapAdminStageSource, mapOutputSource, rendererSource, appSource].some((source) => /displayRegionId|adminPrefectureRegionId/.test(source)), "prefecture pipeline does not use display/admin-prefecture id aliases");
|
|
assert(!("maritimePrefectureBorders" in map), "maritime prefecture borders are not emitted");
|
|
assert(!mapOutputSource.includes("maritimePrefectureBorders") && !rendererSource.includes("maritimePrefectureBorders"), "maritime prefecture borders are not generated or rendered");
|
|
assert(rendererSource.includes("drawPrefectureRegionFill") && rendererSource.includes("map.prefectureRegionId"), "prefecture fill renderer uses final prefecture id source");
|
|
assert(regionalMetrics.tinyCount <= Math.max(1, Math.floor(regionalMetrics.regionCount * 0.12)) && regionalMetrics.medianArea >= 1200 && regionalMetrics.minArea >= 520, "regional prefectures avoid excessive tiny slivers");
|
|
assert(Array.isArray(map.prefectureRegions) && map.prefectureRegions.length === regionalMetrics.regionCount, "prefecture region metadata exists for every region");
|
|
assert(map.prefectureRegions.every((region) => region.name && Number.isFinite(region.x) && Number.isFinite(region.y) && region.area > 0 && map.prefectureRegionId[indexOf(region.x, region.y)] === region.id), "every prefecture region has a name and valid label point");
|
|
assert(map.regionalDebug.finalRegionalMaxAreaShare < 0.85, "larger prefectures retain more than one meaningful regional jurisdiction");
|
|
assert(map.regionalDebug.finalRegionalMinMunicipalityCount >= 10, "each generated prefecture contains at least ten municipalities");
|
|
assert(longStraightLowBarrierSegments(map, map.regionalPrefectureBorders, 22) === 0, "prefecture borders avoid long straight low-barrier cuts");
|
|
assert(longStraightLowBarrierSegments(map, map.adminBorders, 20) === 0, "municipality borders avoid long straight low-barrier cuts");
|
|
assert(Array.isArray(map.tributaryRivers) && Array.isArray(map.smallStreams), "river hierarchy arrays exist");
|
|
assert(Array.isArray(map.icAccessRoads), "IC access road array exists");
|
|
assert(Array.isArray(map.satelliteCities), "satelliteCities is an array");
|
|
assert(Array.isArray(map.ringRoads) && Array.isArray(map.ringExpressways) && Array.isArray(map.ringRailways), "ring transport arrays exist");
|
|
|
|
assert(Array.isArray(map.mainRivers), "mainRivers is an array");
|
|
assert(Array.isArray(map.minorRoads), "minorRoads is an array");
|
|
assert(Array.isArray(map.externalGateways), "externalGateways is an array");
|
|
|
|
let prefectureComponents = 0;
|
|
const seenPrefecture = new Uint8Array(size);
|
|
for (let i = 0; i < size; i++) {
|
|
if (!map.prefectureMask[i] || seenPrefecture[i]) continue;
|
|
prefectureComponents++;
|
|
const queue = [i];
|
|
seenPrefecture[i] = 1;
|
|
for (let q = 0; q < queue.length; q++) {
|
|
const cur = queue[q];
|
|
const x = cur % MAP_W;
|
|
const y = Math.floor(cur / MAP_W);
|
|
for (let dy = -1; dy <= 1; dy++) {
|
|
for (let dx = -1; dx <= 1; dx++) {
|
|
if (dx === 0 && dy === 0) continue;
|
|
const nx = x + dx;
|
|
const ny = y + dy;
|
|
if (nx < 0 || ny < 0 || nx >= MAP_W || ny >= MAP_H) continue;
|
|
const ni = ny * MAP_W + nx;
|
|
if (!map.prefectureMask[ni] || seenPrefecture[ni]) continue;
|
|
seenPrefecture[ni] = 1;
|
|
queue.push(ni);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
assert(prefectureComponents === 1, "prefecture area is a single connected component");
|
|
assert(map.prefectureBorder.length > 0, "prefecture border exists");
|
|
assert([...map.ocean].some((value) => value === 1), "edge-connected ocean mask exists");
|
|
assert([...map.lake].every((value, i) => !value || (map.sea[i] && !map.ocean[i])), "lake mask only marks isolated non-ocean water");
|
|
assert([...map.sea].every((value, i) => !value || map.ocean[i] || map.lake[i]), "water cells are classified as ocean or lake");
|
|
assert(map.adminBorders.length > 0, "municipal borders exist");
|
|
const municipalVectors = municipalBorderVectorMetrics(map);
|
|
assert(municipalVectors.invalid === 0, "municipal border vectors never duplicate prefecture borders");
|
|
assert(municipalVectors.actual === municipalVectors.expected, "municipal border vectors exactly match final same-prefecture admin ID changes");
|
|
assert(map.mainRivers.length > 0, "at least one major river exists");
|
|
assert(map.tributaryRivers.length > 0, "tributary river network exists");
|
|
assert(map.smallStreams.length > 0, "small stream network exists");
|
|
assert(terrainMetrics.mountainRatio > 0.10 && terrainMetrics.mountainRatio < 0.72, "mountain and ridge area is meaningful but not total");
|
|
assert(terrainMetrics.lowlandRatio > 0.08 && terrainMetrics.lowlandRatio < 0.72, "lowlands exist without dominating every map");
|
|
assert(terrainMetrics.ridgeVariance > 0.004, "ridge field has nontrivial spatial variance");
|
|
assert(terrainMetrics.ridgeSinuosity > 0.015, "ridge centerlines are not perfectly straight bands");
|
|
assert(terrainMetrics.depositionSum > 0.2, "deposition field has nonzero values");
|
|
assert(terrainMetrics.depositionTargetMean >= terrainMetrics.depositionOtherMean * 0.85, "deposition favors rivers, basins, and coastal lowlands");
|
|
assert(terrainMetrics.riverValleyMean > terrainMetrics.nonRiverValleyMean * 1.08, "river cells overlap valley fields more than random non-river cells");
|
|
assert(terrainMetrics.alluvialMax > 0 || terrainMetrics.deltaMax > 0, "alluvial fan or delta fields are active");
|
|
assert(map.ports.some((p) => p.portClass === "major"), "at least one major port is classified");
|
|
assert(map.ports.every((p) => ["major", "regional", "fishing", "lake"].includes(p.portClass)), "ports have explicit classes");
|
|
assert(map.externalGateways.length > 0, "external gateways exist");
|
|
assert(map.minorRoads.length > 0, "minor roads exist");
|
|
assert(map.adminCenters.length >= 18, "municipality center count is sufficiently large");
|
|
const activeMunicipalityIds = new Set([...map.adminId].filter((id, i) => id >= 0 && !map.sea[i]));
|
|
const centerIds = new Set(map.adminCenters.map((center) => center.adminId ?? center.municipalityId ?? center.adminNumericId).filter((id) => Number.isFinite(id)));
|
|
assert(activeMunicipalityIds.size === map.adminCenters.length && [...activeMunicipalityIds].every((id) => centerIds.has(id)), "every active municipality has exactly one municipal center");
|
|
assert(map.adminCenters.every((center) => activeMunicipalityIds.has(center.adminId ?? center.municipalityId ?? center.adminNumericId)), "municipal centers do not point to inactive municipalities");
|
|
assert([...activeMunicipalityIds].every((id) => map.municipalityToPrefectureId?.[id] >= 0), "every active municipality maps to a prefecture");
|
|
const activePrefectureIds = new Set([...map.prefectureRegionId].filter((id, i) => id >= 0 && !map.sea[i]));
|
|
const prefMetadataIds = new Set((map.prefectureRegions || []).map((region) => region.id));
|
|
assert([...activePrefectureIds].every((id) => prefMetadataIds.has(id)), "every active prefecture id has metadata");
|
|
assert(adminMetrics.municipalityCount >= 10, "terrain snapping preserves a reasonable municipality count");
|
|
assert(adminMetrics.centerValidRatio >= 0.95, "municipality centers remain on valid assigned land cells");
|
|
assert(adminMetrics.maxLandmassComponents <= 4, "municipal topology repair prevents excessive disconnected fragments");
|
|
assert(adminMetrics.disconnectedMunicipalities <= Math.max(2, Math.ceil(adminMetrics.municipalityCount * 0.20)), "most municipalities remain connected after terrain snapping");
|
|
assert(adminMetrics.avgTarget > 0.18, "admin borders align with terrain target features often enough");
|
|
assert(map.adminDebug && map.adminDebug.compartmentCount > 0, "natural compartment debug is available");
|
|
assert(map.adminDebug.averageCompartmentArea > 0, "natural compartments have positive average area");
|
|
assert(Number.isFinite(map.adminDebug.changedAfterLandscapePartition) && Number.isFinite(map.adminDebug.changedAfterUrbanLock), "municipal changed-cell diagnostics exist");
|
|
assert(map.adminDebug.changedAfterCompartmentAssignment > 0 || map.adminDebug.changedAfterLandscapePartition > 0 || map.adminDebug.changedAfterUrbanLock > 0, "municipal compartment or hierarchy passes change admin cells");
|
|
assert(map.adminDebug.changedAfterFinalExclaveRemoval + map.adminDebug.changedAfterFinalMerge < Math.max(2800, (map.adminDebug.changedAfterLandscapePartition + map.adminDebug.changedAfterUrbanLock) * 1.35), "final municipal repair does not erase most terrain and urban changes");
|
|
assert(map.adminDebug.finalBorderNaturalBarrierAverage >= 0, "natural barrier score is tracked along final borders");
|
|
assert(map.adminDebug.voronoiLikeRateAfter <= Math.max(0.72, map.adminDebug.voronoiLikeRateBefore + 0.20), "natural compartment pass does not increase weak bisectors excessively");
|
|
assert(Number.isFinite(map.adminDebug.satelliteMunicipalitiesCreated) && Number.isFinite(map.adminDebug.averageSatelliteMunicipalityArea), "satellite municipality debug is available");
|
|
assert(satelliteMetrics.independent.length < 3 || satelliteMetrics.small.length / satelliteMetrics.independent.length <= 0.35, "tiny independent satellite municipalities are not the dominant pattern");
|
|
assert(satelliteMetrics.largeTooSmall.length === 0, "large independent satellites have meaningful municipal area");
|
|
assert(satelliteMetrics.independent.length < 3 || satelliteMetrics.average >= 140, "average independent satellite municipality area is meaningful");
|
|
assert(satelliteMetrics.rows.every((row) => row.area >= 80 || row.sat.municipalityClass === "smallTownAttachedToRuralMunicipality" || row.sat.municipalityClass === "suburbanDistrictMergedWithParent" || row.sat.municipalityClass === "newTownDistrict"), "tiny satellite areas are merged or explicitly classified as attached districts");
|
|
assert(adminMetrics.denseUrbanRate < 0.42, "admin borders avoid excessive dense urban crossings");
|
|
assert(adminMetrics.rightAngleRate < 0.46, "admin borders avoid excessive unsupported stair-step artifacts");
|
|
assert(adminMetrics.voronoiLikeRate < 0.58, "admin borders are not dominated by weak-terrain center bisectors");
|
|
assert(adminMetrics.lowScoreFlatRate < 0.40, "admin borders avoid excessive low-score flat-plain cuts");
|
|
assert(adminMetrics.areaDiversity > 1.45, "municipality areas retain natural size diversity");
|
|
assert(cityCoreIntegrity >= 0.62, "major city cores remain mostly inside one municipality");
|
|
assert(urbanCellCount > 1000, "large-city urbanized cells are broad enough");
|
|
const cbdCells = [...map.landuse].filter((value) => value === 3).length;
|
|
assert(cbdCells > 0, "CBD is represented as land-use cells rather than markers");
|
|
assert(map.modernCities.every((city) => Number.isFinite(city.population) && city.population > 0), "modern cities have population properties");
|
|
assert(maxPopulation / Math.max(1, minPopulation) > 3, "city populations vary strongly");
|
|
assert(map.totalPopulation >= cityPopulations.reduce((sum, value) => sum + value, 0), "total population includes city and satellite populations");
|
|
assert(Math.max(...map.populationDensity) > 0.9, "population density is normalized and populated");
|
|
assert([...map.populationDensity].some((value, i) => value === 0 && !map.sea[i]), "valid land cells can retain exactly zero population density");
|
|
assert(elevationStdDev > 0.18, "terrain relief has sufficient contrast");
|
|
assert(maxCoastalElevationStep < 0.12, "coastline and elevation do not create cliff artifacts");
|
|
assert(railExpressHighMountainCells === 0, "railways and expressways avoid huge mountain cells");
|
|
assert(trunkHighElevationCells === 0, "trunk roads, railways, and expressways avoid high-elevation cells");
|
|
assert(flatPlainCells >= 160, "broad flat plains exist as actual low-slope cells");
|
|
assert(largeMountainCities.length === 0, "large cities are not placed on unsuitable mountain sites");
|
|
assert(capitalInside, "prefectural capital is inside the prefecture");
|
|
assert(maxModernEndpointDegree <= 10, "modern transport endpoints are not over-centralized");
|
|
assert(map.entitiesForNames.every((item) => item.id && item.name), "nameable entities have ids and names");
|
|
assert(map.adminCenters.every((item) => item.id && item.name), "municipal centers have ids and names");
|
|
assert(map.entitiesForNames.some((item) => item.kind === "Municipal Center"), "municipal centers are included in label/name candidates");
|
|
assert(map.adminCenters.every((item) => item.name && Array.from(String(item.name)).length >= 2), "municipal center names are valid labels");
|
|
assert(map.adminCenters.some((item) => item.representativeFeatureName), "municipal centers keep representative feature metadata when available");
|
|
assert(map.adminCenters.every((item) => Array.from(String(item.name)).length >= 2), "municipal center names are not one-character labels");
|
|
assert(map.adminCenters.every((item) => {
|
|
const chars = Array.from(String(item.name || ""));
|
|
const suffix = chars.at(-1);
|
|
return chars.length >= 3 && ["市", "町", "村"].includes(suffix);
|
|
}), "municipal names use a valid multi-character Japanese administrative form");
|
|
const cityMunicipalityMetrics = cityMunicipalityAreaMetrics(map);
|
|
assert(cityMunicipalityMetrics.tooSmall.length === 0, "meaningful populated cities keep population-scaled municipality area");
|
|
const hoverCell = map.prefectureRegions.find((region) => region.area > 0);
|
|
assert(hoverCell && prefectureNameForTest(map, indexOf(hoverCell.x, hoverCell.y)) === hoverCell.name && appSource.includes("Prefecture:") && appSource.includes("prefectureNameForCell"), "tooltip can resolve prefecture name for a hovered cell");
|
|
assert(appSource.includes("maxLeft") && appSource.includes("maxTop"), "tooltip position is clamped inside map container");
|
|
const adminNameDuplicateRatio = map.adminCenters.length ? 1 - new Set(map.adminCenters.map((item) => item.name)).size / map.adminCenters.length : 0;
|
|
assert(adminNameDuplicateRatio < 0.22, "duplicate municipal names stay low");
|
|
assert(map.adminDebug.naturalCompartmentCount > adminMetrics.municipalityCount, "natural compartments are finer than municipalities");
|
|
assert(map.adminDebug.targetMunicipalityCount >= 20 && map.adminDebug.targetMunicipalityCount <= 62 && map.adminDebug.actualMunicipalityCount >= 18, "municipality target and actual counts are dense enough");
|
|
assert(map.adminDebug.changedAfterCompartmentAssignment > 0, "municipal compartment assignment is active");
|
|
assert(map.adminDebug.candidateSeedCount >= map.adminDebug.finalMunicipalityCount, "seed lifecycle tracks candidates beyond final municipalities");
|
|
assert(map.adminDebug.absorbedSeedCount >= 0 && map.adminDebug.candidateSeedCount >= map.adminDebug.municipalOfficePointCount, "candidate municipality seeds resolve to offices or absorption");
|
|
assert(map.adminDebug.finalTinyMunicipalityCount <= Math.max(3, Math.ceil(map.adminDebug.finalMunicipalityCount * 0.16)), "tiny municipalities remain a small fraction");
|
|
assert(Array.isArray(map.adminDebug.compartmentBorders) && map.adminDebug.compartmentBorders.length > map.adminBorders.length, "natural compartment borders are available for debug rendering");
|
|
assert(map.entitiesForNames.every((item) => typeof item.name === "string"), "nameable entity names are strings");
|
|
assert(map.entitiesForNames.every((item) => !String(item.name).includes("\uFFFD")), "generated names contain no replacement characters");
|
|
assert(map.entitiesForNames.every((item) => Array.from(String(item.name)).length >= 2), "one-character generated names are prevented");
|
|
assert(map.entitiesForNames.every((item) => validateGeneratedName(item.name, { allowAsciiDiagnostic: true }).valid), "generated names pass place-name validation");
|
|
assert(map.entitiesForNames.every((item) => String(item.name).length > 0), "empty generated names are prevented");
|
|
assert(duplicateNameRatio < 0.18, "generated place-name duplicates stay low");
|
|
assert(map.nameDebug && Array.isArray(map.nameDebug.emptyPools), "nameDebug reports empty pools");
|
|
assert((map.nameDebug.derivedNameCount || 0) === 0, "derived municipality suffix names are not generated");
|
|
assert((map.nameDebug.maxDerivedPerBase || 0) <= 2, "derived names per base stay small");
|
|
assert(map.nameDebug.emptyPools.length === Object.values(NAME_KANJI_POOLS).filter((pool) => pool.length === 0).length, "nameDebug empty pools match configured pools");
|
|
assert(map.nameDebug.selectedTemplateCounts && typeof map.nameDebug.selectedTemplateCounts === "object", "nameDebug selectedTemplateCounts exists");
|
|
assert(map.nameDebug.selectedContextCounts && typeof map.nameDebug.selectedContextCounts === "object", "nameDebug selectedContextCounts exists");
|
|
assert(
|
|
map.nameDebug.outputNamedEntityCount === namedEntityCount && map.nameDebug.totalGeneratedNameSelections >= namedEntityCount,
|
|
"nameDebug distinguishes packaged labels from all generated name selections"
|
|
);
|
|
assert(activePoolChars.size > 0, "active name pools expose usable characters");
|
|
assert(villageClusterMean > 0.16, "villages prefer clustered valley, basin, coastal, and agricultural cells");
|
|
assert(saneEndpointRatio >= 0.76, "transport endpoints stay near meaningful generated nodes");
|
|
const againA = generateTestMap(999);
|
|
const againB = generateTestMap(999);
|
|
assert(JSON.stringify(againA.modernCities) === JSON.stringify(againB.modernCities), "generation is deterministic for the same seed");
|
|
assert(JSON.stringify(againA.entitiesForNames.map((item) => [item.id, item.name])) === JSON.stringify(againB.entitiesForNames.map((item) => [item.id, item.name])), "generated names are deterministic for the same seed");
|
|
assert(JSON.stringify(againA.adminCenters.map((item) => [item.id, item.x, item.y, item.name])) === JSON.stringify(againB.adminCenters.map((item) => [item.id, item.x, item.y, item.name])), "municipal centers are deterministic for the same seed");
|
|
assert(arraysEqual(againA.adminId, againB.adminId), "municipal adminId snapping is deterministic for the same seed");
|
|
assert(arraysEqual(againA.naturalCompartmentId, againB.naturalCompartmentId), "natural compartments are deterministic for the same seed");
|
|
assert(arraysEqual(againA.prefectureRegionId, againB.prefectureRegionId), "regional prefecture ids are deterministic for the same seed");
|
|
assert(arraysEqual(againA.municipalityToPrefectureId, againB.municipalityToPrefectureId), "municipality-to-prefecture ids are deterministic for the same seed");
|
|
assert(JSON.stringify(againA.regionalPrefectureBorders) === JSON.stringify(againB.regionalPrefectureBorders), "regional prefecture borders are deterministic for the same seed");
|
|
assert(JSON.stringify(againA.adminDebug) === JSON.stringify(againB.adminDebug), "admin debug metrics are deterministic for the same seed");
|
|
assert(JSON.stringify(againA.regionalDebug) === JSON.stringify(againB.regionalDebug), "regional debug metrics are deterministic for the same seed");
|
|
assert(JSON.stringify(deterministicTransportDebug(againA.transportDebug)) === JSON.stringify(deterministicTransportDebug(againB.transportDebug)), "transport debug metrics are deterministic for the same seed");
|
|
assert(JSON.stringify(transportConnectivityMetrics(againA)) === JSON.stringify(transportConnectivityMetrics(againB)), "transport connectivity metrics are deterministic for the same seed");
|
|
assert(arraysEqual(againA.elevation, againB.elevation), "elevation is deterministic for the same seed");
|
|
assert(arraysEqual(againA.ridgeField, againB.ridgeField), "ridge field is deterministic for the same seed");
|
|
assert(arraysEqual(againA.river, againB.river), "river field is deterministic for the same seed");
|
|
assert(JSON.stringify(terrainCoreMetrics(againA)) === JSON.stringify(terrainCoreMetrics(againB)), "terrain debug metrics are deterministic for the same seed");
|
|
|
|
}
|
|
|
|
if (suiteEnabled("terrain-name")) {
|
|
const semeMap = generateTestMap(8363712);
|
|
const semeAdmin = (semeMap.adminCenters || []).find((center) => center.canonicalSettlementName);
|
|
assert(!semeAdmin || String(semeAdmin.name).replace(/[市町村]$/u, "") === String(semeAdmin.canonicalSettlementName).replace(/[市町村]$/u, ""),
|
|
"seed 8363712: municipality label preserves the canonical settlement root");
|
|
}
|
|
|
|
if (suiteEnabled("terrain")) {
|
|
const blockedCapitalName = "\u52A0\u8302";
|
|
const terrainSeeds = [114514, 12345, 54321, 777, 999];
|
|
const capitalNames = [];
|
|
terrainSeedSummaries = [];
|
|
for (const seedValue of terrainSeeds) {
|
|
// Evaluate and release each complete map before generating the next seed.
|
|
// Retaining five full raster worlds at once made this validation shard
|
|
// memory-pressure dependent without increasing its coverage.
|
|
const seeded = generateTestMap(seedValue);
|
|
if (seeded.prefecturalCapital?.name) capitalNames.push(seeded.prefecturalCapital.name);
|
|
const metrics = terrainCoreMetrics(seeded);
|
|
terrainSeedSummaries.push({
|
|
seed: seedValue,
|
|
deposition: seeded.terrainTemplate.deposition,
|
|
lowlandRatio: metrics.lowlandRatio,
|
|
featureCounts: [seeded.adminCenters.length, seeded.villages.length, seeded.markets.length],
|
|
});
|
|
assert(seeded.mainRivers.length > 0 && seeded.tributaryRivers.length > 0 && seeded.smallStreams.length > 0, `seed ${seedValue}: river hierarchy exists`);
|
|
assert(metrics.mountainRatio > 0.08 && metrics.lowlandRatio > 0.06, `seed ${seedValue}: mountain and lowland terrain both exist`);
|
|
assert(metrics.ridgeVariance > 0.003 && metrics.ridgeSinuosity > 0.010, `seed ${seedValue}: ridges have varied jagged structure`);
|
|
assert(metrics.depositionSum > 0.1 && metrics.depositionTargetMean >= metrics.depositionOtherMean * 0.75, `seed ${seedValue}: deposition is active in plausible lowlands`);
|
|
assert(metrics.riverValleyMean > metrics.nonRiverValleyMean, `seed ${seedValue}: rivers follow valley fields`);
|
|
assert(seeded.terrainDebug?.primarySpineStrength > 0.08, `seed ${seedValue}: primary spine is strong enough`);
|
|
assert((seeded.terrainDebug?.largeInlandLakeCount || 0) <= 1, `seed ${seedValue}: large inland lakes are rare`);
|
|
assert((seeded.terrainDebug?.smallIslandCount || 0) <= 24, `seed ${seedValue}: small island speckles are limited`);
|
|
assert(seeded.villages.length > 0 && seeded.markets.length > 0 && seeded.modernCities.length > 0, `seed ${seedValue}: settlements are generated`);
|
|
assert(seeded.premodernRoads.length > 0 && seeded.railways.length > 0, `seed ${seedValue}: roads and railways are generated`);
|
|
assert(seeded.adminId.length === size && seeded.adminBorders.length > 0 && seeded.regionalPrefectureBorders.length > 0, `seed ${seedValue}: admin and regional borders exist`);
|
|
assert(seeded.adminCenters.every((item) => item.name && Array.from(String(item.name)).length >= 2), `seed ${seedValue}: every admin center has a valid name`);
|
|
assert(seeded.entitiesForNames.some((item) => item.kind === "Municipal Center"), `seed ${seedValue}: admin labels are included in label candidates`);
|
|
assert(seeded.adminCenters.every((item) => {
|
|
const chars = Array.from(String(item.name || ""));
|
|
return chars.length >= 3 && ["市", "町", "村"].includes(chars.at(-1));
|
|
}), `seed ${seedValue}: municipality labels use valid administrative suffixes`);
|
|
assert((seeded.nameDebug?.derivedNameCount || 0) === 0, `seed ${seedValue}: derived suffix names stay disabled`);
|
|
const seededAdminMetrics = adminBoundaryMetrics(seeded);
|
|
const debug = seeded.adminDebug || {};
|
|
assert(seededAdminMetrics.municipalityCount >= 18 && seededAdminMetrics.municipalityCount <= 50, `seed ${seedValue}: municipality count stays in target range`);
|
|
assert(debug.naturalCompartmentCount >= debug.actualMunicipalityCount * 2.5, `seed ${seedValue}: natural compartments are substantially finer than municipalities`);
|
|
assert(debug.naturalCompartmentCount <= debug.actualMunicipalityCount * 11.5, `seed ${seedValue}: natural compartments do not become noisy cells`);
|
|
assert(debug.averageCompartmentsPerMunicipality >= 2.5, `seed ${seedValue}: municipalities group multiple compartments on average`);
|
|
assert(debug.singleCompartmentMunicipalityRatio < 0.35, `seed ${seedValue}: one-compartment municipalities are uncommon`);
|
|
assert(debug.finalMunicipalityCount >= 18 && debug.finalMunicipalityCount <= 50, `seed ${seedValue}: final municipality count remains bounded`);
|
|
assert(debug.finalTinyMunicipalityCount <= Math.max(3, Math.ceil(debug.finalMunicipalityCount * 0.16)), `seed ${seedValue}: tiny municipalities stay uncommon`);
|
|
assert(debug.absorbedSeedCount > 0 || debug.finalMunicipalityCount >= Math.min(20, debug.targetMunicipalityCount), `seed ${seedValue}: excess candidate seeds are absorbed or the target municipality density is met`);
|
|
const highMountainSeeds = (seeded.adminCenters || []).filter((p) => {
|
|
const i = indexOf(p.x, p.y);
|
|
return seeded.elevation[i] > 0.70 || seeded.slope[i] > 0.52 || seeded.ridgeField[i] > 0.62;
|
|
}).length;
|
|
assert(highMountainSeeds <= Math.max(2, Math.ceil((seeded.adminCenters || []).length * 0.12)), `seed ${seedValue}: high mountain admin seeds are rare`);
|
|
assert(seededAdminMetrics.avgTarget > 0.13, `seed ${seedValue}: municipal borders beat a loose lowland-random barrier baseline`);
|
|
assert(seededAdminMetrics.denseUrbanRate < 0.52, `seed ${seedValue}: dense urban boundary crossing rate remains low`);
|
|
assert(seededAdminMetrics.voronoiLikeRate < 0.66, `seed ${seedValue}: Voronoi-like municipal borders do not dominate`);
|
|
}
|
|
const featureCountA = terrainSeedSummaries.find((summary) => summary.seed === 12345)?.featureCounts;
|
|
const featureCountB = terrainSeedSummaries.find((summary) => summary.seed === 54321)?.featureCounts;
|
|
assert(featureCountA?.some((value, index) => value !== featureCountB?.[index]), "feature counts vary between seeds");
|
|
assert(new Set(capitalNames).size > 1, "prefectural capital names vary across seeds");
|
|
assert(capitalNames.some((name) => name !== blockedCapitalName), "prefectural capital is not always the repeated custom name");
|
|
}
|
|
|
|
if (TEST_SUITE === "determinism" || TEST_SUITE.startsWith("determinism-")) {
|
|
const suffix = TEST_SUITE.startsWith("determinism-") ? TEST_SUITE.slice("determinism-".length) : "";
|
|
const suffixSeed = suffix ? Number(suffix) : NaN;
|
|
const seedValue = Number.isFinite(suffixSeed) ? suffixSeed : DETERMINISM_SEED;
|
|
const a = generateTestMap(seedValue);
|
|
const b = generateTestMap(seedValue);
|
|
assert(arraysEqual(a.adminId, b.adminId), `seed ${seedValue}: adminId is deterministic`);
|
|
assert(JSON.stringify(a.adminDebug) === JSON.stringify(b.adminDebug), `seed ${seedValue}: admin debug metrics are deterministic`);
|
|
}
|
|
|
|
if (suiteEnabled("terrain")) {
|
|
const byDeposition = terrainSeedSummaries.slice().sort((a, b) => a.deposition - b.deposition);
|
|
assert(byDeposition[byDeposition.length - 1].lowlandRatio >= byDeposition[0].lowlandRatio * 0.72, "higher-deposition templates generally preserve or expand lowland area");
|
|
const originalCustomNames = [...CUSTOM_NAME_LIST];
|
|
CUSTOM_NAME_LIST.length = 0;
|
|
CUSTOM_NAME_LIST.push("青葉", "若松");
|
|
const listedCustomNames = Array.from({ length: 80 }, (_, n) => generateEntityName(9200 + n, `list-probe-${n}`, { x: 10, y: 10, kind: "Probe" }, {}, new Set()));
|
|
const listedCustomHits = listedCustomNames.filter((name) => name === "青葉" || name === "若松").length;
|
|
assert(NAME_PROBABILITIES.customNameList > 0 && listedCustomHits > 0 && listedCustomHits < listedCustomNames.length, "CUSTOM_NAME_LIST supplies probabilistic selected place names");
|
|
CUSTOM_NAME_LIST.length = 0;
|
|
CUSTOM_NAME_LIST.push(...originalCustomNames);
|
|
|
|
assert(!validateGeneratedName("青青").valid && validateGeneratedName("青青").reason === "repeatedKanji", "place names reject repeated kanji");
|
|
|
|
}
|
|
|
|
if (suiteEnabled("admin")) {
|
|
for (const seed of [101, 2026, 54321]) {
|
|
const seeded = generateTestMap(seed);
|
|
const metrics = adminBoundaryMetrics(seeded);
|
|
const seededRegional = regionalComponentMetrics(seeded);
|
|
const seededSatellites = satelliteMunicipalityMetrics(seeded);
|
|
const invalidLandCells = [...seeded.adminId].filter((id, i) => seeded.prefectureMask[i] && !seeded.sea[i] && id < 0).length;
|
|
const invalidRegionCells = [...seeded.prefectureRegionId].filter((id, i) => !seeded.sea[i] && id < 0).length;
|
|
assert(invalidLandCells === 0, `seed ${seed}: every prefecture land cell has a valid adminId`);
|
|
assert(invalidRegionCells === 0, `seed ${seed}: every regional land cell has a valid regionId`);
|
|
assert(seeded.adminBorders.length > 0, `seed ${seed}: municipal borders exist`);
|
|
const seededRegionalBorderMetrics = regionalBorderMetrics(seeded);
|
|
assert(seededRegionalBorderMetrics.expected === 0 || seeded.regionalPrefectureBorders.length > 0, `seed ${seed}: regional prefecture borders exist whenever land-adjacent prefectures exist`);
|
|
assert(seeded.regionalDebug?.prefecturesGeneratedAfterMunicipalities === true, `seed ${seed}: prefectures are generated after municipalities`);
|
|
assert(seeded.regionalDebug?.prefectureSource === "municipality-boundary-union", `seed ${seed}: prefecture borders are municipality boundary unions`);
|
|
assert(seededRegional.maxLandmassComponents === 1, `seed ${seed}: every final regional prefecture is connected`);
|
|
assert(regionalEnclaveCount(seeded) === 0, `seed ${seed}: final regional prefectures have no one-region enclosed enclaves`);
|
|
assert(seededRegionalBorderMetrics.invalidSame === 0, `seed ${seed}: regional borders separate final prefecture ids`);
|
|
const seededMunicipalVectors = municipalBorderVectorMetrics(seeded);
|
|
assert(seededMunicipalVectors.invalid === 0 && seededMunicipalVectors.actual === seededMunicipalVectors.expected, `seed ${seed}: municipal vectors respect final prefecture hierarchy`);
|
|
const seededHierarchy = borderHierarchyViolations(seeded);
|
|
assert(seededHierarchy.prefectureCutsMunicipality === 0, `seed ${seed}: prefecture borders do not cut municipalities`);
|
|
assert(seededHierarchy.municipalityCutsCompartment === 0, `seed ${seed}: municipal borders do not cut natural compartments`);
|
|
assert(seeded.adminDebug && seeded.adminDebug.compartmentCount > 0, `seed ${seed}: natural compartments are built`);
|
|
assert(seeded.adminDebug.naturalCompartmentCount > metrics.municipalityCount, `seed ${seed}: compartments are finer than municipalities`);
|
|
assert(seeded.adminDebug.targetMunicipalityCount >= 20 && seeded.adminDebug.actualMunicipalityCount >= 18, `seed ${seed}: municipality count is dense enough`);
|
|
assert(seeded.adminDebug.finalTinyMunicipalityCount <= Math.max(3, Math.ceil(seeded.adminDebug.finalMunicipalityCount * 0.18)), `seed ${seed}: tiny final municipalities are limited`);
|
|
assert(seeded.adminDebug.changedAfterCompartmentAssignment > 0, `seed ${seed}: compartment assignment is not a no-op`);
|
|
assert(Array.isArray(seeded.adminDebug.compartmentBorders) && seeded.adminDebug.compartmentBorders.length > seeded.adminBorders.length, `seed ${seed}: compartment border debug exists`);
|
|
assert(seeded.regionalDebug?.municipalityGraphNodeCount >= metrics.municipalityCount, `seed ${seed}: prefecture graph is based on municipalities`);
|
|
assert(seeded.adminDebug.changedAfterCompartmentAssignment > 0 || seeded.adminDebug.changedAfterLandscapePartition > 0 || seeded.adminDebug.changedAfterUrbanLock > 0, `seed ${seed}: municipal compartment or hierarchy passes change cells`);
|
|
assert(seededSatellites.largeTooSmall.length === 0, `seed ${seed}: large satellites are not tiny independent municipalities`);
|
|
assert(seededSatellites.independent.length < 3 || seededSatellites.small.length / seededSatellites.independent.length <= 0.35, `seed ${seed}: tiny satellite municipalities remain uncommon`);
|
|
assert(metrics.municipalityCount >= 18, `seed ${seed}: municipality count remains reasonable`);
|
|
assert(metrics.centerValidRatio >= 0.90, `seed ${seed}: municipality centers remain valid`);
|
|
assert(metrics.maxLandmassComponents <= 5, `seed ${seed}: topology repair limits disconnected fragments`);
|
|
assert(metrics.avgTarget > 0.14, `seed ${seed}: borders retain terrain-boundary affinity`);
|
|
assert(metrics.denseUrbanRate < 0.50, `seed ${seed}: borders avoid excessive dense urban cuts`);
|
|
assert(metrics.voronoiLikeRate < 0.66, `seed ${seed}: weak-terrain Voronoi-like border ratio stays bounded`);
|
|
assert(metrics.lowScoreFlatRate < 0.50, `seed ${seed}: low-score flat border ratio stays bounded`);
|
|
assert(metrics.areaDiversity > 1.25, `seed ${seed}: municipality sizes are not overly uniform`);
|
|
assert(majorCityCoreIntegrity(seeded) >= 0.55, `seed ${seed}: major city cores remain coherent`);
|
|
assert(seeded.prefecturalCapital && seeded.adminId[indexOf(seeded.prefecturalCapital.x, seeded.prefecturalCapital.y)] >= 0, `seed ${seed}: capital municipality is not deleted`);
|
|
}
|
|
}
|
|
|
|
if (suiteEnabled("patch")) {
|
|
const initial = generateTestMap(DETERMINISM_SEED);
|
|
const world = createWorldMap(initial);
|
|
assert(!world.sourceMap.sea && !world.sourceMap.elevation && world.initialQualityReference?.landCells > 0, "world metadata omits duplicate fixed-map raster fields while retaining the immutable quality reference");
|
|
const rect = {
|
|
x0: world.originX + 72,
|
|
y0: world.originY + 58,
|
|
x1: world.originX + 132,
|
|
y1: world.originY + 118,
|
|
};
|
|
const outsideIndex = (world.originY + 12) * world.width + world.originX + 12;
|
|
const outsideAdminId = world.fields.adminId?.[outsideIndex];
|
|
const patchStartedAt = typeof performance !== "undefined" ? performance.now() : Date.now();
|
|
const patch = generatePatch(world, rect, {
|
|
patchMode: "regeneration",
|
|
terrainType: "auto",
|
|
seed: 0x51a7c3d3,
|
|
variant: 1,
|
|
maxQualityRetries: 0,
|
|
qualityTerrainAttempts: 1,
|
|
includeSeamVisualization: true,
|
|
});
|
|
const patchElapsedMs = (typeof performance !== "undefined" ? performance.now() : Date.now()) - patchStartedAt;
|
|
assert(patch?.ok === true, "standard patch suite executes generatePatch and returns a preview candidate");
|
|
assert(patch?.variant === 1 && patch?.seed === 0x51a7c3d3, "executed patch preserves the explicitly requested variant and seed");
|
|
assert(patchElapsedMs < 30000, `small production patch completes within the 30 s budget (${Math.round(patchElapsedMs)} ms)`);
|
|
assert(patch?.seamDiagnostics?.hardPass === true && (patch?.seamDiagnostics?.prefectureSeamBreakEdges || 0) === 0, "small Regeneration repairs only the real ownership seam and passes the unchanged hard seam gate");
|
|
const restoredPrefectureCells = patch?.seamDiagnostics?.administrativeSeamRepair?.prefectureCellsRestored || 0;
|
|
const regeneratedArea = Math.max(1, (rect.x1 - rect.x0) * (rect.y1 - rect.y0));
|
|
// Administrative seam repair is allowed only as a narrow boundary repair.
|
|
// Use an area-relative cap rather than an obsolete fixture-specific count:
|
|
// this still catches accidental interior rewrites while allowing a handful
|
|
// of independent broken ownership edges to be restored deterministically.
|
|
const localizedAdminRepairCap = Math.max(12, Math.ceil(regeneratedArea * 0.005));
|
|
assert(restoredPrefectureCells <= localizedAdminRepairCap,
|
|
`administrative seam repair remains localized instead of rewriting the regenerated interior (restored=${restoredPrefectureCells}, cap=${localizedAdminRepairCap})`);
|
|
assert((patch?.candidateUnmappedActiveCells || 0) === 0, "executed patch maps every active write cell into the production candidate");
|
|
assert(world.fields.adminId?.[outsideIndex] === outsideAdminId,
|
|
`strict Regeneration preserves canonical administrative fields outside the selection (before=${outsideAdminId}, after=${world.fields.adminId?.[outsideIndex]})`);
|
|
const serializedRects = JSON.stringify(patch?.rects || {});
|
|
const serializedRectKeys = Object.getOwnPropertyNames(JSON.parse(serializedRects));
|
|
const serializedWorkCacheKeys = serializedRectKeys.filter((key) => key === "patchAlphaCache" || key === "patchSourceIndexCache");
|
|
if (serializedWorkCacheKeys.length > 0) {
|
|
failed += 1;
|
|
logLines.push(`NG: worker-only patch caches are absent from the serialized result payload (cacheKeys=${serializedWorkCacheKeys.join(",")})`);
|
|
} else {
|
|
logLines.push("OK: worker-only patch caches are absent from the serialized result payload");
|
|
}
|
|
const expectedPopulation = [...(world.sourceMap.modernCities || []), ...(world.sourceMap.satelliteCities || [])]
|
|
.reduce((sum, city) => sum + (Number(city?.population) || 0), 0);
|
|
assert(world.sourceMap.totalPopulation === expectedPopulation, "patch application refreshes total population metadata");
|
|
const generatedPoint = [
|
|
...(world.sourceMap.modernCities || []), ...(world.sourceMap.satelliteCities || []),
|
|
...(world.sourceMap.villages || []), ...(world.sourceMap.markets || []),
|
|
].find((point) => point?.patchGenerated && Number.isFinite(point.regionId));
|
|
if (generatedPoint && world.fields.regionId) {
|
|
const wx = Math.round((generatedPoint.worldX ?? generatedPoint.x + world.originX));
|
|
const wy = Math.round((generatedPoint.worldY ?? generatedPoint.y + world.originY));
|
|
const wi = wy * world.width + wx;
|
|
assert(generatedPoint.regionId === world.fields.regionId[wi], "patch-generated point regionId matches the persisted world raster regionId");
|
|
} else if (generatedPoint) {
|
|
// regionId remains point metadata in the current final world schema; the
|
|
// internal generation raster is intentionally not persisted by mapOutput.
|
|
assert(Number.isFinite(generatedPoint.regionId), "patch-generated point keeps a finite regionId when no persisted regionId raster exists");
|
|
} else {
|
|
assert(true, "executed patch produced no region-tagged point requiring a regionId check");
|
|
}
|
|
|
|
const expansionRect = {
|
|
x0: world.originX + MAP_W - 48,
|
|
y0: world.originY + 60,
|
|
x1: world.originX + MAP_W + 52,
|
|
y1: world.originY + 130,
|
|
};
|
|
const expansionBeforeGenerated = new Uint8Array(world.generatedMask);
|
|
const expansionBeforeElevation = new Float32Array(world.fields.elevation);
|
|
const expansionBeforeLanduse = new Uint8Array(world.fields.landuse);
|
|
let existingOverlapCells = 0;
|
|
const expansion = generatePatch(world, expansionRect, {
|
|
patchMode: "expansion",
|
|
terrainType: "auto",
|
|
seed: 0x1234abcd,
|
|
variant: 2,
|
|
maxQualityRetries: 0,
|
|
qualityTerrainAttempts: 1,
|
|
includeSeamVisualization: true,
|
|
acceptBestAvailableQuality: true,
|
|
});
|
|
let changedOverlapElevation = 0;
|
|
let changedOverlapLanduse = 0;
|
|
for (let y = expansionRect.y0; y < expansionRect.y1; y++) {
|
|
for (let x = expansionRect.x0; x < expansionRect.x1; x++) {
|
|
const i = y * world.width + x;
|
|
if (!expansionBeforeGenerated[i]) continue;
|
|
existingOverlapCells++;
|
|
if (Math.abs(world.fields.elevation[i] - expansionBeforeElevation[i]) > 1e-6) changedOverlapElevation++;
|
|
if (world.fields.landuse[i] !== expansionBeforeLanduse[i]) changedOverlapLanduse++;
|
|
}
|
|
}
|
|
assert(expansion?.ok === true && existingOverlapCells > 0
|
|
&& changedOverlapElevation > Math.max(24, existingOverlapCells * 0.15)
|
|
&& changedOverlapLanduse > 0,
|
|
`Expansion rewrites selected already-generated overlap instead of freezing it (elevation=${changedOverlapElevation}/${existingOverlapCells}, landuse=${changedOverlapLanduse})`);
|
|
const regionalTransport = expansion?.humanGeography?.regionalTransportDebug;
|
|
const regionalUrban = expansion?.humanGeography?.regionalUrbanRecalculation;
|
|
assert((regionalTransport?.consideredPaths || 0) > 0 && (regionalTransport?.reroutedPaths || 0) > 0,
|
|
`Expansion performs deterministic regional transport rerouting beyond the selected area (considered=${regionalTransport?.consideredPaths || 0}, rerouted=${regionalTransport?.reroutedPaths || 0})`);
|
|
assert((regionalUrban?.modifiedCells || 0) > 0 && (regionalUrban?.maxOutsideDistanceModified || 0) > 0,
|
|
`Expansion recalculates urban fields outside the selected area with distance-decaying probability (modified=${regionalUrban?.modifiedCells || 0}, outside=${Math.round(regionalUrban?.maxOutsideDistanceModified || 0)})`);
|
|
}
|
|
|
|
if (suiteEnabled("patch-large")) {
|
|
const initial = generateTestMap(DETERMINISM_SEED);
|
|
const world = createWorldMap(initial);
|
|
const rect = {
|
|
x0: world.originX,
|
|
y0: world.originY + 64,
|
|
x1: world.originX + MAP_W + 1,
|
|
y1: world.originY + 64 + PATCH_MIN_HEIGHT,
|
|
};
|
|
const serialBefore = world.patchGenerationSerial || 0;
|
|
const largeStartedAt = typeof performance !== "undefined" ? performance.now() : Date.now();
|
|
const large = generatePatch(world, rect, {
|
|
patchMode: "regeneration",
|
|
terrainType: "auto",
|
|
seed: 0x6d2b79f5,
|
|
variant: 1,
|
|
maxQualityRetries: 0,
|
|
qualityTerrainAttempts: 1,
|
|
includeSeamVisualization: true,
|
|
});
|
|
const largeElapsedMs = (typeof performance !== "undefined" ? performance.now() : Date.now()) - largeStartedAt;
|
|
assert(large?.ok === true && large?.tiledRegeneration === true && Number(large?.tileCount || 0) >= 2, "large Regeneration must publish a complete canonical-tile preview; rollback alone is not a passing result");
|
|
assert(largeElapsedMs < 60000, `large production Regeneration completes within the 60 s budget (${Math.round(largeElapsedMs)} ms)`);
|
|
assert(large?.seamDiagnostics?.hardPass === true, "published large Regeneration passes the whole-selection seam gate");
|
|
assert((large?.candidateUnmappedActiveCells || 0) === 0, "large Regeneration maps every active write cell across all tiles");
|
|
assert((world.patchGenerationSerial || 0) === serialBefore + 1, "large Regeneration records one logical operation rather than internal tile history");
|
|
}
|
|
|
|
const elapsedMs = (typeof performance !== "undefined" && performance.now ? performance.now() : Date.now()) - TEST_STARTED_AT;
|
|
const shardBudgetMs = Math.max(180000, fullMapGenerations * 75000);
|
|
assert(elapsedMs < shardBudgetMs, `test shard ${TEST_SUITE} completes within its complete-generation workload budget (${Math.round(elapsedMs)} / ${shardBudgetMs} ms)`);
|
|
logLines.push(`INFO: suite=${TEST_SUITE}; fullMapGenerations=${fullMapGenerations}; elapsedMs=${Math.round(elapsedMs)}`);
|
|
result.className = failed === 0 ? "ok" : "ng";
|
|
result.textContent = `${failed === 0 ? "All tests passed." : `${failed} tests failed.`}\n\n${logLines.join("\n")}`;
|
|
if (!IS_BROWSER) {
|
|
console.log(result.textContent);
|
|
if (failed > 0) process.exitCode = 1;
|
|
}
|
|
} catch (error) {
|
|
result.className = "ng";
|
|
result.textContent = String(error?.stack || error);
|
|
if (!IS_BROWSER) {
|
|
console.error(result.textContent);
|
|
process.exitCode = 1;
|
|
}
|
|
}
|