not good but not bad
This commit is contained in:
parent
5c82bfcab7
commit
4f0df3f6c5
11 changed files with 1284 additions and 622 deletions
228
test.js
228
test.js
|
|
@ -15,11 +15,15 @@ const result = document.getElementById("result");
|
|||
const logLines = [];
|
||||
let failed = 0;
|
||||
|
||||
const [namesSource, mapGeneratorSource, mapOutputSource, rendererSource, testSource] = await Promise.all([
|
||||
const [namesSource, mapGeneratorSource, mapOutputSource, mapTerrainSource, rendererSource, appSource, mapPipelineSource, mapAdminStageSource, testSource] = await Promise.all([
|
||||
fetch("./names.js").then((response) => response.text()),
|
||||
fetch("./mapGenerator.js").then((response) => response.text()),
|
||||
fetch("./mapOutput.js").then((response) => response.text()),
|
||||
fetch("./mapTerrain.js").then((response) => response.text()),
|
||||
fetch("./renderer.js").then((response) => response.text()),
|
||||
fetch("./app.js").then((response) => response.text()),
|
||||
fetch("./mapPipeline.js").then((response) => response.text()),
|
||||
fetch("./mapAdminStage.js").then((response) => response.text()),
|
||||
fetch("./test.js").then((response) => response.text()),
|
||||
]);
|
||||
|
||||
|
|
@ -193,10 +197,13 @@ function regionalComponentMetrics(map) {
|
|||
const ids = new Set([...map.prefectureRegionId].filter((id, i) => id >= 0 && !map.sea[i]));
|
||||
const seen = new Uint8Array(MAP_W * MAP_H);
|
||||
let maxComponents = 0;
|
||||
const areas = [];
|
||||
for (const id of ids) {
|
||||
seen.fill(0);
|
||||
let comps = 0;
|
||||
let area = 0;
|
||||
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 queue = [i];
|
||||
|
|
@ -217,8 +224,167 @@ function regionalComponentMetrics(map) {
|
|||
}
|
||||
}
|
||||
maxComponents = Math.max(maxComponents, comps);
|
||||
areas.push(area);
|
||||
}
|
||||
return { regionCount: ids.size, maxComponents };
|
||||
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, 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 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) {
|
||||
|
|
@ -532,14 +698,29 @@ try {
|
|||
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.regionalDebug && Number.isFinite(map.regionalDebug.regionalChangedAfterNaturalPartition), "regional changed-cell debug exists");
|
||||
assert(map.regionalDebug.regionalChangedAfterNaturalPartition > 0, "regional natural partition changes region cells");
|
||||
assert(map.regionalDebug.regionalBorderCountBefore > 0 && map.regionalDebug.regionalBorderCountAfter > 0, "regional border counts are tracked");
|
||||
assert(map.regionalDebug.regionalNaturalBarrierAverageAfter >= map.regionalDebug.regionalNaturalBarrierAverageBefore - 0.08, "regional border natural-barrier affinity does not degrade meaningfully");
|
||||
assert(map.regionalDebug.regionalVoronoiLikeRateAfter <= map.regionalDebug.regionalVoronoiLikeRateBefore + 0.22, "regional weak Voronoi-like border rate stays bounded");
|
||||
assert(map.regionalDebug.compartmentCount > 0 && map.regionalDebug.changedAfterCompartmentAssignment > 0, "regional compartment assignment debug is available");
|
||||
assert(Number.isFinite(map.regionalDebug.borderNaturalBarrierAverage) && Number.isFinite(map.regionalDebug.voronoiLikeRate), "regional natural-border aliases are exposed");
|
||||
assert(regionalMetrics.regionCount >= 4 && regionalMetrics.maxComponents <= 5, "regional prefecture regions remain connected enough for display");
|
||||
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 >= 4 && regionalMetrics.maxComponents === 1, "each non-sea prefecture region is 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.38, "no single prefecture dominates regional land area");
|
||||
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");
|
||||
|
|
@ -626,6 +807,7 @@ try {
|
|||
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");
|
||||
|
|
@ -641,6 +823,11 @@ try {
|
|||
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) => !(item.representativeFeatureName && String(item.name).startsWith(item.representativeFeatureName) && Array.from(String(item.name)).length === Array.from(String(item.representativeFeatureName)).length + 1)), "municipal names avoid dangling one-kanji suffix fallback");
|
||||
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");
|
||||
|
|
@ -684,7 +871,10 @@ try {
|
|||
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(JSON.stringify([...againA.adminId]) === JSON.stringify([...againB.adminId]), "municipal adminId snapping is deterministic for the same seed");
|
||||
assert(JSON.stringify([...againA.naturalCompartmentId]) === JSON.stringify([...againB.naturalCompartmentId]), "natural compartments are deterministic for the same seed");
|
||||
assert(JSON.stringify([...againA.prefectureRegionId]) === JSON.stringify([...againB.prefectureRegionId]), "regional prefecture ids are deterministic for the same seed");
|
||||
assert(JSON.stringify([...againA.municipalityToPrefectureId]) === JSON.stringify([...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(againA.transportDebug) === JSON.stringify(againB.transportDebug), "transport debug metrics are deterministic for the same seed");
|
||||
|
|
@ -699,6 +889,9 @@ try {
|
|||
const capitalNames = capitalNameMaps.map((seeded) => seeded.prefecturalCapital?.name).filter(Boolean);
|
||||
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");
|
||||
const semeMap = generateMap(8363712);
|
||||
const semeAdmin = (semeMap.adminCenters || []).find((center) => center.canonicalSettlementName);
|
||||
assert(!semeAdmin || semeAdmin.name === semeAdmin.canonicalSettlementName, "seed 8363712: municipality label uses canonical settlement name");
|
||||
for (const [n, seeded] of capitalNameMaps.entries()) {
|
||||
const seedValue = [114514, 12345, 54321, 777, 999][n];
|
||||
const metrics = terrainCoreMetrics(seeded);
|
||||
|
|
@ -769,10 +962,14 @@ try {
|
|||
assert(invalidRegionCells === 0, `seed ${seed}: every regional land cell has a valid regionId`);
|
||||
assert(seeded.adminBorders.length > 0, `seed ${seed}: municipal borders exist`);
|
||||
assert(seeded.regionalPrefectureBorders.length > 0, `seed ${seed}: regional prefecture borders exist`);
|
||||
assert(seeded.regionalDebug?.regionalChangedAfterNaturalPartition > 0, `seed ${seed}: regional natural partition changes cells`);
|
||||
assert(seeded.regionalDebug.regionalNaturalBarrierAverageAfter >= seeded.regionalDebug.regionalNaturalBarrierAverageBefore - 0.10, `seed ${seed}: regional border natural affinity is stable`);
|
||||
assert(seeded.regionalDebug.regionalVoronoiLikeRateAfter <= seeded.regionalDebug.regionalVoronoiLikeRateBefore + 0.25, `seed ${seed}: regional Voronoi-like rate is bounded`);
|
||||
assert(seededRegional.maxComponents <= 5, `seed ${seed}: regional regions remain connected enough`);
|
||||
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.maxComponents === 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(regionalBorderMetrics(seeded).invalidSame === 0, `seed ${seed}: regional borders separate final prefecture ids`);
|
||||
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`);
|
||||
|
|
@ -780,8 +977,7 @@ try {
|
|||
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?.changedAfterCompartmentAssignment > 0, `seed ${seed}: regional compartment assignment changes cells`);
|
||||
assert(seeded.regionalDebug?.borderNaturalBarrierAverage > 0.12, `seed ${seed}: regional borders have natural barrier affinity`);
|
||||
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.changedAfterSnap > 0, `seed ${seed}: municipal compartment or terrain 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`);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue