Check imported world size, buffers, materials and block codes (t80)

This commit is contained in:
2026-10-29 17:55:18 -04:00
parent cf762268ce
commit 0483fb8da4
4 changed files with 95 additions and 5 deletions
+7 -1
View File
@@ -20,7 +20,13 @@ check('save/load keeps fog', run('(()=>{fogLook(bot.x+bot.w/2,bot.y+bot.h/2);con
rect(run, 200, 160, 203, 163, run('EMPTY'));
run('placeBlock(BP_WALL, RSTONE, 50, 40, true)');
check('old save rebuilds exploration', run('(()=>{const s=serializeWorld();delete s.buffers.fog;deserializeWorld(s.meta,s.buffers);return fogSeen(62,104)>=200 && fogSeen(202,162)===255 && fogSeen(20,220)===0})()'));
check('wrong length fog rebuilds exploration', run('(()=>{const s=serializeWorld();s.buffers.fog=new Uint8Array(1);deserializeWorld(s.meta,s.buffers);return fogSeen(202,162)===255})()'));
check('wrong length fog is refused without changing exploration', run(`(() => {
const s = serializeWorld(), originalFog = fog, originalMat = mat;
s.buffers.fog = new Uint8Array(1);
try { deserializeWorld(s.meta, s.buffers); }
catch (e) { return /fog/i.test(e.message) && fog === originalFog && mat === originalMat && fogSeen(202,162) === 255; }
return false;
})()`));
const hash = () => run('mat.reduce((h,v)=>Math.imul(h^v,16777619)>>>0,2166136261)');
run('generateWorld(42, 256, 256, null)'); ticks(run, 300); const h = hash();
run('generateWorld(42, 256, 256, null); fogLook = function () {}'); ticks(run, 300);
+48
View File
@@ -1,8 +1,56 @@
// Reload wakes nearby areas first, without losing distant water, fire or saved state.
const fs = require('fs');
const path = require('path');
const { load, suite, flatWorld, rect, count, ticks } = require('./harness');
const { run } = load();
const { check, done } = suite();
// Exercise the real hover function headlessly, without starting a browser or GPU.
run("const localStorage = { getItem: () => '{}', setItem() {} };");
for (const f of ['ui/state.js', 'ui/hud.js', 'ui/cursor.js', 'ui/info.js'])
run(fs.readFileSync(path.join(__dirname, '../src', f), 'utf8'));
flatWorld(run, 256, 256, 200);
run('globalThis.validationSave = serializeWorld();');
function refusedBeforeAlloc(label, doctor, message) {
check(label, run(`(() => {
const s = { meta: { ...validationSave.meta, cols: { ...validationSave.meta.cols } }, buffers: { ...validationSave.buffers } };
${doctor}
const originalAlloc = alloc, originalMat = mat, originalBlocks = blockP;
const oldW = W, oldH = H, oldMoney = money, oldX = bot.x;
let allocations = 0, error;
alloc = () => { allocations++; throw new Error('Allocation reached'); };
try { deserializeWorld(s.meta, s.buffers); } catch (e) { error = e; }
finally { alloc = originalAlloc; }
return error instanceof Error && ${message}.test(error.message) && allocations === 0
&& W === oldW && H === oldH && mat === originalMat && blockP === originalBlocks
&& money === oldMoney && bot.x === oldX;
})()`));
}
for (const [w, h] of [[1000000, 1000000], [6145, 1024], [2048, 2049], [0, 1024], [2048, -1], [2048.5, 1024], [2048, NaN]])
refusedBeforeAlloc(`invalid size ${w} x ${h} is refused before touching the world`, `s.meta.W = ${w}; s.meta.H = ${h};`, '/world size/i');
for (const key of ['mat', 'temp', 'life', 'flags', 'part', 'blockP', 'blockV', 'bgm', 'fog']) {
refusedBeforeAlloc(`short ${key} buffer is refused before touching the world`, `s.buffers.${key} = s.buffers.${key}.subarray(1);`, `/${key}/i`);
refusedBeforeAlloc(`long ${key} buffer is refused before touching the world`, `s.buffers.${key} = new s.buffers.${key}.constructor(s.buffers.${key}.length + 1);`, `/${key}/i`);
}
for (const key of ['mat', 'temp', 'life', 'flags', 'part', 'blockP', 'blockV'])
refusedBeforeAlloc(`missing ${key} buffer is refused before touching the world`, `delete s.buffers.${key};`, `/${key}/i`);
for (const key of ['surfY', 'dirtY', 'graniteY', 'basaltY', 'magmaY'])
refusedBeforeAlloc(`short ${key} column is refused before touching the world`, `s.meta.cols.${key} = [];`, `/${key}/i`);
refusedBeforeAlloc('short slope phases are refused before touching the world', 's.meta.slopePhases = [0];', '/slopePhases/i');
for (const [label, doctor, clean] of [
['unknown cell material', 's.buffers.mat[20 * W + 20] = 250;', 'mat[20 * W + 20] === EMPTY'],
['unknown block code', 's.buffers.blockP[5 * BW + 5] = 250;', 'blockP[5 * BW + 5] === 0'],
]) {
check(label + ' loads cleaned and permits hover and removal', run(`(() => {
const s = serializeWorld(); ${doctor}
try {
deserializeWorld(s.meta, s.buffers);
SET.fog = false; player.lens = false;
return ${clean} && hoverInfo(20, 20).air && removePieceAt(20, 20) === '';
} catch (e) { return false; }
})()`));
}
flatWorld(run, 1024, 256, 200);
run(`
bot.x = 510; bot.y = 170;