map/archive/generated-history/STEP17_VALIDATION.mjs
2026-08-08 17:41:30 +09:00

112 lines
5.5 KiB
JavaScript

import assert from 'node:assert/strict';
import { createHash } from 'node:crypto';
import { readFileSync } from 'node:fs';
import { Worker } from 'node:worker_threads';
import { performance } from 'node:perf_hooks';
import { generateMap } from './mapPipeline.js';
import { createWorldMap } from './worldMap.js';
import { MAP_H } from './mapUtils.js';
import { collectTransferableBuffers } from './transferUtils.js';
function hashView(view) {
return createHash('sha256').update(Buffer.from(view.buffer, view.byteOffset, view.byteLength)).digest('hex');
}
function derivePatchSeed(baseSeed, rect, terrainType, variant = 0) {
let h = (baseSeed >>> 0) ^ 0x9e3779b9;
h = Math.imul(h ^ (rect.x0 | 0), 1664525) >>> 0;
h = Math.imul(h ^ (rect.y0 | 0), 1013904223) >>> 0;
h = Math.imul(h ^ (rect.x1 | 0), 2246822519) >>> 0;
h = Math.imul(h ^ (rect.y1 | 0), 3266489917) >>> 0;
h = Math.imul(h ^ (variant >>> 0), 668265263) >>> 0;
for (const ch of String(terrainType || 'auto')) h = Math.imul(h ^ ch.charCodeAt(0), 16777619) >>> 0;
return h >>> 0;
}
function browserWorkerAdapter(moduleUrl) {
const target = moduleUrl.href;
const code = `
import { parentPort } from 'node:worker_threads';
globalThis.self = { onmessage: null, postMessage(message, transfer) { parentPort.postMessage(message, transfer); } };
await import(${JSON.stringify(target)});
parentPort.on('message', (data) => self.onmessage?.({ data }));
`;
return new Worker(new URL(`data:text/javascript,${encodeURIComponent(code)}`), { type: 'module', execArgv: [] });
}
async function runPatchWorker(baseWorld, rect, baseSeed, variant) {
const worker = browserWorkerAdapter(new URL('./mapPatchWorker.js', import.meta.url));
const preview = structuredClone(baseWorld);
const transfer = Array.from(collectTransferableBuffers(preview));
const started = performance.now();
const message = await new Promise((resolve, reject) => {
worker.on('message', (m) => { if (m?.type !== 'progress') resolve(m); });
worker.on('error', reject);
worker.postMessage({
id: variant + 1,
world: preview,
rect,
options: {
patchMode: 'expansion', terrainType: 'auto',
seed: derivePatchSeed(baseSeed, rect, 'auto', variant),
variant, maxQualityRetries: 0, qualityTerrainAttempts: 1,
},
}, transfer);
});
await worker.terminate();
assert.equal(message.ok, true, `variant ${variant} worker failed: ${message.error || 'unknown'}`);
assert.equal(message.result?.ok, true, `variant ${variant} patch failed: ${message.result?.code || message.result?.reason || 'unknown'}`);
return { message, seconds: (performance.now() - started) / 1000 };
}
const appSource = readFileSync(new URL('./app.js', import.meta.url), 'utf8');
const viewportSource = readFileSync(new URL('./worldViewport.js', import.meta.url), 'utf8');
const rendererSource = readFileSync(new URL('./renderer.js', import.meta.url), 'utf8');
assert.match(appSource, /maxQualityRetries:\s*0/);
assert.match(appSource, /patchBusy/);
assert.match(appSource, /previewPatchDelta/);
assert.match(appSource, /renderRevision/);
assert.match(appSource, /worker\.terminate\?\.\(\)/);
assert.match(viewportSource, /viewport\.renderRevision/);
assert.match(rendererSource, /map\?\.renderRevision/);
const baseSeed = 1;
const baseWorld = createWorldMap(generateMap(baseSeed, { terrainType: 'auto', onProgress() {} }));
const ox = baseWorld.originX;
const oy = baseWorld.originY;
const rect = { kind: 'lasso', polygon: [
{ x: ox - 145, y: oy - 14 },
{ x: ox + 8, y: oy - 14 },
{ x: ox + 8, y: oy + MAP_H + 14 },
{ x: ox - 145, y: oy + MAP_H + 14 },
] };
rect.x0 = ox - 145; rect.y0 = oy - 14; rect.x1 = ox + 8; rect.y1 = oy + MAP_H + 14;
const a = await runPatchWorker(baseWorld, rect, baseSeed, 0);
const b = await runPatchWorker(baseWorld, rect, baseSeed, 1);
const aWorld = a.message.world;
const bWorld = b.message.world;
const hashesA = {
elevation: hashView(aWorld.fields.elevation), sea: hashView(aWorld.fields.sea), admin: hashView(aWorld.fields.adminId),
};
const hashesB = {
elevation: hashView(bWorld.fields.elevation), sea: hashView(bWorld.fields.sea), admin: hashView(bWorld.fields.adminId),
};
assert.notEqual(hashesA.elevation, hashesB.elevation, 'Alternative variants generated identical elevation');
assert.notEqual(hashesA.sea, hashesB.sea, 'Alternative variants generated identical sea mask');
assert.notEqual(hashesA.admin, hashesB.admin, 'Alternative variants generated identical administration');
assert.equal(a.message.result.qualityRetryCount || 0, 0);
assert.equal(b.message.result.qualityRetryCount || 0, 0);
assert.equal(a.message.result.candidateQuality?.selectedVariant, 0, 'variant 0 did not render its exact requested terrain variant');
assert.equal(b.message.result.candidateQuality?.selectedVariant, 1, 'variant 1 did not render its exact requested terrain variant');
assert.equal(a.message.result.seamDiagnostics?.status, 'clean');
assert.equal(b.message.result.seamDiagnostics?.status, 'clean');
console.log(JSON.stringify({
ok: true,
interactiveRetryPolicy: 'one requested variant per click; no hidden whole-patch retry',
variants: [
{ variant: 0, seconds: Math.round(a.seconds * 100) / 100, selectedVariant: a.message.result.candidateQuality?.selectedVariant, hashes: hashesA },
{ variant: 1, seconds: Math.round(b.seconds * 100) / 100, selectedVariant: b.message.result.candidateQuality?.selectedVariant, hashes: hashesB },
],
alternativesDiffer: true,
explicitRenderRevision: true,
singlePatchWorkerLifetime: true,
}, null, 2));