97 lines
4 KiB
JavaScript
97 lines
4 KiB
JavaScript
import assert from 'node:assert/strict';
|
|
import { Worker } from 'node:worker_threads';
|
|
import { generateMap } from './mapGenerator.js';
|
|
import { createWorldMap } from './worldMap.js';
|
|
import { collectTransferableBuffers } from './transferUtils.js';
|
|
|
|
const BASE_SEED = 24681357;
|
|
const PATCH_SEED = 0x4a35b921;
|
|
const WIDTH = 600;
|
|
const HEIGHT = 400;
|
|
|
|
function footprintContains(fp, x, y) {
|
|
if (!fp || x < fp.x0 || y < fp.y0 || x >= fp.x1 || y >= fp.y1) return false;
|
|
const row = fp.rowRuns?.[y - fp.y0];
|
|
if (!Array.isArray(row)) return false;
|
|
for (let i = 0; i + 1 < row.length; i += 2) if (x >= row[i] && x < row[i + 1]) return true;
|
|
return false;
|
|
}
|
|
function recordContains(record, x, y) {
|
|
if (record?.generatedFootprint) return footprintContains(record.generatedFootprint, x, y);
|
|
const r = record?.coreRect || record;
|
|
return !!r && x >= r.x0 && y >= r.y0 && x < r.x1 && y < r.y1;
|
|
}
|
|
function generatedAt(world, x, y) {
|
|
return (world.generatedRects || []).some(record => recordContains(record, x, y));
|
|
}
|
|
function centeredRect(world, width, height) {
|
|
// Use a deliberately off-center but fully valid selection so this exercises
|
|
// a different outer seam than the existing centered stability test.
|
|
const x0 = Math.max(0, world.width - width);
|
|
const y0 = Math.max(0, world.height - height);
|
|
return { x0, y0, x1: x0 + width, y1: y0 + height };
|
|
}
|
|
function workerAdapter(url) {
|
|
const code = `import { parentPort } from 'node:worker_threads';\n`
|
|
+ `globalThis.self = { onmessage: null, postMessage(message, transfer) { parentPort.postMessage(message, transfer); } };\n`
|
|
+ `await import(${JSON.stringify(url.href)});\n`
|
|
+ `parentPort.on('message', data => self.onmessage?.({ data }));\n`;
|
|
return new Worker(new URL(`data:text/javascript,${encodeURIComponent(code)}`), { type: 'module', execArgv: [] });
|
|
}
|
|
|
|
const initial = generateMap(BASE_SEED, { terrainType: 'auto', onProgress() {} });
|
|
const baseWorld = createWorldMap(initial);
|
|
const rect = centeredRect(baseWorld, WIDTH, HEIGHT);
|
|
const needed = [];
|
|
for (let y = rect.y0; y < rect.y1; y++) {
|
|
for (let x = rect.x0; x < rect.x1; x++) {
|
|
if (!generatedAt(baseWorld, x, y)) needed.push([x, y]);
|
|
}
|
|
}
|
|
assert.ok(needed.length > 0, 'test selection must contain ungenerated cells');
|
|
|
|
const preview = structuredClone(baseWorld);
|
|
const transfer = Array.from(collectTransferableBuffers(preview));
|
|
const worker = workerAdapter(new URL('./mapPatchWorker.js', import.meta.url));
|
|
const startedAt = Date.now();
|
|
const message = await new Promise((resolve, reject) => {
|
|
const timer = setTimeout(() => reject(new Error('600x400 worker timeout')), 120000);
|
|
worker.on('message', message => {
|
|
if (message?.type === 'progress') return;
|
|
clearTimeout(timer);
|
|
resolve(message);
|
|
});
|
|
worker.on('error', error => { clearTimeout(timer); reject(error); });
|
|
worker.postMessage({
|
|
id: 1,
|
|
world: preview,
|
|
rect,
|
|
options: {
|
|
patchMode: 'expansion', terrainType: 'auto', seed: PATCH_SEED, variant: 1,
|
|
maxQualityRetries: 0, qualityTerrainAttempts: 1, acceptBestAvailableQuality: true,
|
|
},
|
|
}, transfer);
|
|
});
|
|
|
|
assert.equal(message?.ok, true, message?.error || 'worker outer failure');
|
|
assert.equal(message?.result?.ok, true, message?.result?.reason || message?.result?.code || 'patch failure');
|
|
assert.equal(message?.result?.seamDiagnostics?.hardPass, true, 'seam hard gate failed');
|
|
const returnedWorld = message.world;
|
|
let missing = 0;
|
|
for (const [x, y] of needed) if (!generatedAt(returnedWorld, x, y)) missing++;
|
|
assert.equal(missing, 0, `missing ${missing} previously-ungenerated selected cells`);
|
|
|
|
const out = {
|
|
ok: true,
|
|
size: `${WIDTH}x${HEIGHT}`,
|
|
selectedCells: WIDTH * HEIGHT,
|
|
previouslyUngeneratedSelectedCells: needed.length,
|
|
missingPreviouslyUngeneratedCells: missing,
|
|
tileCount: message.result?.tileCount || 1,
|
|
seam: message.result?.seamDiagnostics?.status || null,
|
|
hardPass: message.result?.seamDiagnostics?.hardPass ?? null,
|
|
ms: Date.now() - startedAt,
|
|
};
|
|
console.log(JSON.stringify(out, null, 2));
|
|
await worker.terminate();
|
|
process.exit(0);
|