109 lines
5.9 KiB
JavaScript
109 lines
5.9 KiB
JavaScript
import assert from 'node:assert/strict';
|
|
import { readFileSync } from 'node:fs';
|
|
import { generateMap } from './mapGenerator.js';
|
|
import { createWorldMap } from './worldMap.js';
|
|
import { generatePatch } from './mapPatch.js';
|
|
import { MAP_W, MAP_H, worldIndexOf } from './mapUtils.js';
|
|
|
|
const appSource = readFileSync(new URL('./app.js', import.meta.url), 'utf8');
|
|
const htmlSource = readFileSync(new URL('./index.html', import.meta.url), 'utf8');
|
|
assert.match(htmlSource, /id="clearPatchSelection"/);
|
|
assert.match(appSource, /clearPatchSelectionButton\?\.addEventListener\("click", \(\) => hideSelectionOverlay\(\{ discardPreview: true \}\)\)/);
|
|
assert.match(appSource, /function cancelPatchGeneration/);
|
|
assert.match(appSource, /PATCH_WORKER_INACTIVITY_WATCHDOG_MS/);
|
|
|
|
const initial = generateMap(24681357, { terrainType: 'auto', onProgress() {} });
|
|
const world = createWorldMap(structuredClone(initial));
|
|
const establishedEdgeX = world.originX + MAP_W - 1;
|
|
const y0 = world.originY + Math.floor(MAP_H * 0.22);
|
|
const y1 = world.originY + Math.floor(MAP_H * 0.78);
|
|
const rect = {
|
|
x0: establishedEdgeX - 33,
|
|
y0,
|
|
x1: establishedEdgeX + 93,
|
|
y1,
|
|
};
|
|
const result = generatePatch(world, rect, {
|
|
patchMode: 'expansion', terrainType: 'auto', seed: 97531, variant: 1,
|
|
maxQualityRetries: 0, qualityTerrainAttempts: 1, acceptBestAvailableQuality: true,
|
|
});
|
|
assert.equal(result.ok, true, result.reason || result.code || 'patch failed');
|
|
|
|
const source = world.sourceMap;
|
|
const sea = world.fields.sea;
|
|
let riverSeaPoints = 0;
|
|
const writeRect = result.rects?.writeRect || rect;
|
|
for (const key of ['mainRivers','tributaryRivers','smallStreams','riverPaths']) {
|
|
for (const path of source[key] || []) for (const t of path || []) {
|
|
const x = Math.round((t?.[0] || 0) + world.originX);
|
|
const y = Math.round((t?.[1] || 0) + world.originY);
|
|
const i = worldIndexOf(world,x,y);
|
|
if (i >= 0 && sea[i] && x >= writeRect.x0 && x < writeRect.x1 && y >= writeRect.y0 && y < writeRect.y1) riverSeaPoints++;
|
|
}
|
|
}
|
|
assert.equal(riverSeaPoints, 0, `river path points over sea inside patch writeRect: ${riverSeaPoints}`);
|
|
|
|
const byPref = new Map();
|
|
for (const city of source.modernCities || []) {
|
|
const capital = city.isPrefecturalCapital || city.isRegionalCapital || /Capital/i.test(String(city.rank || '')) || /Capital/i.test(String(city.kind || ''));
|
|
if (!capital) continue;
|
|
const x = Math.round((city.x || 0) + world.originX);
|
|
const y = Math.round((city.y || 0) + world.originY);
|
|
const i = worldIndexOf(world,x,y);
|
|
if (i < 0 || sea[i] || world.fields.prefectureRegionId[i] < 0) continue;
|
|
const id = world.fields.prefectureRegionId[i];
|
|
byPref.set(id, (byPref.get(id) || 0) + 1);
|
|
}
|
|
const duplicateCapitalPrefs = [...byPref.entries()].filter(([,n]) => n > 1);
|
|
assert.equal(duplicateCapitalPrefs.length, 0, `duplicate capital prefectures: ${JSON.stringify(duplicateCapitalPrefs)}`);
|
|
const activePrefIds = new Set();
|
|
for (let i=0;i<world.fields.prefectureRegionId.length;i++) if (!sea[i] && world.fields.prefectureRegionId[i] >= 0) activePrefIds.add(Math.floor(world.fields.prefectureRegionId[i]));
|
|
const missingCapitalPrefs = [...activePrefIds].filter((id) => (byPref.get(id) || 0) === 0);
|
|
assert.equal(missingCapitalPrefs.length, 0, `missing capital prefectures: ${JSON.stringify(missingCapitalPrefs)}`);
|
|
|
|
const prefCounts = new Map();
|
|
for (let i=0;i<world.fields.prefectureRegionId.length;i++) if (!sea[i] && world.fields.prefectureRegionId[i] >= 0) {
|
|
const id=world.fields.prefectureRegionId[i];
|
|
prefCounts.set(id,(prefCounts.get(id)||0)+1);
|
|
}
|
|
const generatedRegions = (source.prefectureRegions || []).filter(p => p.patchGenerated).map(p => p.prefectureRegionId ?? p.id).filter(Number.isFinite);
|
|
const generatedSizes = [...new Set(generatedRegions)].map(id => [id,prefCounts.get(id)||0]);
|
|
const tinyGenerated = generatedSizes.filter(([,n]) => n > 0 && n < 650);
|
|
assert.equal(tinyGenerated.length, 0, `tiny generated prefectures survived: ${JSON.stringify(tinyGenerated)}`);
|
|
|
|
let landLandPairs = 0;
|
|
let maxEstablishedFrontierElevationJump = 0;
|
|
let adminBreaksOnEstablishedFrontier = 0;
|
|
let prefectureBreaksOnEstablishedFrontier = 0;
|
|
for (let y=y0; y<y1; y++) {
|
|
const a = worldIndexOf(world, establishedEdgeX, y);
|
|
const b = worldIndexOf(world, establishedEdgeX + 1, y);
|
|
if (a < 0 || b < 0 || sea[a] || sea[b]) continue;
|
|
landLandPairs++;
|
|
maxEstablishedFrontierElevationJump = Math.max(maxEstablishedFrontierElevationJump, Math.abs(world.fields.elevation[a] - world.fields.elevation[b]));
|
|
if (world.fields.adminId[a] !== world.fields.adminId[b]) adminBreaksOnEstablishedFrontier++;
|
|
if (world.fields.prefectureRegionId[a] !== world.fields.prefectureRegionId[b]) prefectureBreaksOnEstablishedFrontier++;
|
|
}
|
|
assert.ok(landLandPairs > 20, 'frontier probe did not include enough land pairs');
|
|
assert.ok(maxEstablishedFrontierElevationJump < 0.08, `frontier elevation jump too large: ${maxEstablishedFrontierElevationJump}`);
|
|
assert.equal(adminBreaksOnEstablishedFrontier, 0, 'municipal boundary still follows generation frontier');
|
|
assert.equal(prefectureBreaksOnEstablishedFrontier, 0, 'prefecture boundary still follows generation frontier');
|
|
assert.equal(result.seamDiagnostics?.hardPass, true, `seam gate failed: ${JSON.stringify(result.seamDiagnostics?.gateReasons || [])}`);
|
|
|
|
console.log(JSON.stringify({
|
|
ok: true,
|
|
riverSeaPoints,
|
|
duplicateCapitalPrefs,
|
|
missingCapitalPrefs,
|
|
generatedSizes,
|
|
tinyGenerated,
|
|
landLandPairs,
|
|
maxEstablishedFrontierElevationJump: Math.round(maxEstablishedFrontierElevationJump * 10000) / 10000,
|
|
adminBreaksOnEstablishedFrontier,
|
|
prefectureBreaksOnEstablishedFrontier,
|
|
frontierAdminCellsAligned: result.humanGeography?.frontierAdminCellsAligned || 0,
|
|
establishedFrontierAdminCellsRestored: result.humanGeography?.establishedFrontierAdminCellsRestored || 0,
|
|
frontierHarmonizedValues: result.humanGeography?.frontierHarmonizedValues || 0,
|
|
seamStatus: result.seamDiagnostics?.status,
|
|
seamGateReasons: result.seamDiagnostics?.gateReasons || [],
|
|
}, null, 2));
|