81 lines
4.8 KiB
JavaScript
81 lines
4.8 KiB
JavaScript
import assert from 'node:assert/strict';
|
|
import { Worker } from 'node:worker_threads';
|
|
|
|
function pathLength(paths) {
|
|
return (paths || []).reduce((sum, path) => sum + Math.max(0, (path?.length || 0) - 1), 0);
|
|
}
|
|
function prefMunicipalityCounts(map) {
|
|
const groups = new Map();
|
|
for (let i = 0; i < map.adminId.length; i++) {
|
|
if (map.sea[i]) continue;
|
|
const a = Number(map.adminId[i]), p = Number(map.prefectureRegionId[i]);
|
|
if (a < 0 || p < 0) continue;
|
|
if (!groups.has(p)) groups.set(p, new Set());
|
|
groups.get(p).add(a);
|
|
}
|
|
return [...groups.values()].map((s) => s.size);
|
|
}
|
|
function edgeTouches(paths, width, height) {
|
|
let hits = 0;
|
|
for (const path of paths || []) {
|
|
if ((path || []).some(([x, y]) => x <= 0 || y <= 0 || x >= width - 1 || y >= height - 1)) hits++;
|
|
}
|
|
return hits;
|
|
}
|
|
function runWorker(seed) {
|
|
return new Promise((resolve, reject) => {
|
|
const worker = new Worker(new URL('./helpers-generation-worker-node-wrapper.mjs', import.meta.url), { type: 'module' });
|
|
const timer = setTimeout(() => { worker.terminate(); reject(new Error(`worker timeout for seed ${seed}`)); }, 90000);
|
|
worker.on('error', (error) => { clearTimeout(timer); reject(error); });
|
|
worker.on('message', (message) => {
|
|
if (message?.type !== 'result' || message?.id !== seed) return;
|
|
clearTimeout(timer);
|
|
worker.terminate();
|
|
if (!message.ok) reject(new Error(message.error || 'generation failed'));
|
|
else resolve(message.map);
|
|
});
|
|
worker.postMessage({ id: seed, seed, options: { terrainType: 'auto' } });
|
|
});
|
|
}
|
|
|
|
for (const seed of [114514, 999]) {
|
|
const map = await runWorker(seed);
|
|
assert.equal(map.width, 258, `seed ${seed}: published width`);
|
|
assert.equal(map.height, 183, `seed ${seed}: published height`);
|
|
const over = map.initialGenerationOverscan;
|
|
assert.equal(over?.version, 'literal-hidden-raster-center-crop-v1', `seed ${seed}: literal overscan marker`);
|
|
assert(over.fullWidth > map.width && over.fullHeight > map.height, `seed ${seed}: production was generated on a larger raster`);
|
|
assert(over.marginX >= 40 && over.marginY >= 40, `seed ${seed}: meaningful hidden halo exists`);
|
|
const visibleSize = map.width * map.height;
|
|
const hiddenSize = over.fullWidth * over.fullHeight;
|
|
for (const key of ['sea','elevation','slope','populationDensity','landuse','adminId','prefectureRegionId']) {
|
|
assert.equal(map[key]?.length, visibleSize, `seed ${seed}: authoritative raster ${key} is center-cropped`);
|
|
}
|
|
for (const [key, value] of Object.entries(map)) {
|
|
if (ArrayBuffer.isView(value)) assert.notEqual(value.length, hiddenSize, `seed ${seed}: hidden full raster ${key} is not leaked to published map`);
|
|
}
|
|
for (let id = 0; id < (map.adminCenters || []).length; id++) {
|
|
const c = map.adminCenters[id];
|
|
if (!c) continue;
|
|
assert(c.x >= 0 && c.y >= 0 && c.x < map.width && c.y < map.height, `seed ${seed}: visible municipality seat is in crop`);
|
|
assert.equal(map.adminId[Math.round(c.y) * map.width + Math.round(c.x)], id, `seed ${seed}: municipal seat belongs to municipality ${id}`);
|
|
}
|
|
const pops = (map.adminCenters || []).filter(Boolean).map((c) => Number(c.municipalityPopulation || 0));
|
|
assert(pops.length >= 30, `seed ${seed}: sufficient municipalities survive central crop`);
|
|
assert(Math.min(...pops) >= 1000, `seed ${seed}: municipal population floor`);
|
|
assert(pops.filter((p) => p > 10000).length / pops.length <= 0.30, `seed ${seed}: >10k municipalities are minority`);
|
|
const cityNames = (map.adminCenters || []).filter(Boolean).map((c) => String(c.name || ''));
|
|
assert(cityNames.filter((n) => n.endsWith('市')).length / cityNames.length <= 0.35, `seed ${seed}: 市 does not dominate`);
|
|
const prefCounts = prefMunicipalityCounts(map);
|
|
assert(prefCounts.length >= 2 && Math.min(...prefCounts) >= 10, `seed ${seed}: every visible prefecture has substantial municipal subdivision`);
|
|
const nationalLen = pathLength(map.nationalRoads);
|
|
const railLen = pathLength([...(map.railways || []), ...(map.branchRailways || [])]);
|
|
assert(nationalLen > 0 && railLen / nationalLen >= 0.70 && railLen / nationalLen <= 1.05, `seed ${seed}: published rail network is dense and close to national-road density`);
|
|
const service = map.transportDebug?.postAdminTransportFinalization?.visibleCropMajorCityService;
|
|
assert(service && service.checked >= 1, `seed ${seed}: published major-city service is audited`);
|
|
assert.equal(service.missing.length, 0, `seed ${seed}: no interior visible major city lacks national+rail+expressway service`);
|
|
const trunkBoundaryHits = edgeTouches([...(map.nationalRoads || []), ...(map.railways || []), ...(map.branchRailways || []), ...(map.expressways || [])], map.width, map.height);
|
|
assert(trunkBoundaryHits >= 2, `seed ${seed}: real hidden-context trunk corridors cross the published crop boundary`);
|
|
}
|
|
|
|
console.log('All r11.4 literal initial-overscan worker checks passed.');
|