q
This commit is contained in:
parent
810ad6f5cb
commit
de7c6c32bd
123 changed files with 13201 additions and 6213 deletions
166
tests/test.js
166
tests/test.js
|
|
@ -22,6 +22,10 @@ const TEST_SUITE = (() => {
|
|||
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;
|
||||
|
|
@ -43,22 +47,36 @@ async function readLocalText(path) {
|
|||
return readFile(new URL(path, import.meta.url), "utf8");
|
||||
}
|
||||
|
||||
const [namesSource, mapGeneratorSource, mapOutputSource, mapTerrainSource, rendererSource, appSource, mapPipelineSource, mapAdminStageSource, mapPatchSource, mapPatchWorkerSource, committedWorldDeltaSource, worldMapSource, municipalSource, testSource] = await Promise.all([
|
||||
readLocalText("../src/names.js"),
|
||||
readLocalText("../src/mapPipeline.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"),
|
||||
readLocalText("./test.js"),
|
||||
]);
|
||||
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
|
||||
|
|
@ -707,7 +725,6 @@ try {
|
|||
let terrainSeedSummaries = [];
|
||||
if (suiteEnabled("core")) {
|
||||
const map = generateTestMap(12345);
|
||||
const other = generateTestMap(54321);
|
||||
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);
|
||||
|
|
@ -827,7 +844,7 @@ try {
|
|||
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) && !testSource.includes(removedContextModule), "removed name-context import is absent");
|
||||
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";
|
||||
|
|
@ -837,6 +854,18 @@ try {
|
|||
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");
|
||||
|
|
@ -848,7 +877,7 @@ try {
|
|||
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("single-explicit-production-candidate-v2") && mapPatchSource.includes("const requestedAttempts = 1"), "each search candidate runs exactly one explicit complete production variant");
|
||||
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");
|
||||
|
|
@ -1051,10 +1080,19 @@ try {
|
|||
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");
|
||||
|
|
@ -1090,7 +1128,7 @@ try {
|
|||
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.maxLandmassComponents === 1, "each non-sea prefecture region is connected after repair");
|
||||
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);
|
||||
|
|
@ -1106,7 +1144,8 @@ try {
|
|||
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(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");
|
||||
|
|
@ -1234,7 +1273,7 @@ try {
|
|||
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 <= 50 && map.adminDebug.actualMunicipalityCount >= 18, "municipality target and actual counts are dense enough");
|
||||
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");
|
||||
|
|
@ -1259,30 +1298,23 @@ try {
|
|||
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");
|
||||
assert(
|
||||
map.adminCenters.length !== other.adminCenters.length ||
|
||||
map.villages.length !== other.villages.length ||
|
||||
map.markets.length !== other.markets.length,
|
||||
"feature counts vary between seeds"
|
||||
);
|
||||
|
||||
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(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(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(JSON.stringify([...againA.elevation]) === JSON.stringify([...againB.elevation]), "elevation is deterministic for the same seed");
|
||||
assert(JSON.stringify([...againA.ridgeField]) === JSON.stringify([...againB.ridgeField]), "ridge field is deterministic for the same seed");
|
||||
assert(JSON.stringify([...againA.river]) === JSON.stringify([...againB.river]), "river field is 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");
|
||||
|
||||
}
|
||||
|
|
@ -1306,7 +1338,12 @@ try {
|
|||
const seeded = generateTestMap(seedValue);
|
||||
if (seeded.prefecturalCapital?.name) capitalNames.push(seeded.prefecturalCapital.name);
|
||||
const metrics = terrainCoreMetrics(seeded);
|
||||
terrainSeedSummaries.push({ deposition: seeded.terrainTemplate.deposition, lowlandRatio: metrics.lowlandRatio });
|
||||
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`);
|
||||
|
|
@ -1344,16 +1381,20 @@ try {
|
|||
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 suffixSeed = Number(TEST_SUITE.slice("determinism-".length));
|
||||
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(JSON.stringify([...a.adminId]) === JSON.stringify([...b.adminId]), `seed ${seedValue}: adminId is deterministic`);
|
||||
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`);
|
||||
}
|
||||
|
||||
|
|
@ -1384,12 +1425,13 @@ try {
|
|||
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`);
|
||||
assert(seeded.regionalPrefectureBorders.length > 0, `seed ${seed}: regional prefecture 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(regionalBorderMetrics(seeded).invalidSame === 0, `seed ${seed}: regional borders separate final prefecture ids`);
|
||||
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);
|
||||
|
|
@ -1485,6 +1527,48 @@ try {
|
|||
} 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")) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue