Files

76 lines
4.2 KiB
JavaScript

// Shared allocation, checkerboard independence, and real worker-count determinism.
const assert = require('assert');
const { load, flatWorld } = require('./harness');
const { run } = load();
flatWorld(run, 131, 129, 110);
assert(run('[mat,temp,life,flags,stamp,part,mach,digp,noiseB,bgm,airS,netId,isPow,occ,awake,hot,hotLvl,renderDirty,powderArea,cleatMask,cleatChunks,roofDirty,blockP,blockV,slopePhase,fog,AMB,roofY].every(a=>a.buffer instanceof SharedArrayBuffer)'));
console.log('PASS world arrays use shared memory');
const crypto = require('crypto');
const { enableThreads } = require('./harness');
flatWorld(run, 192, 96, 31);
run(`setCell(30*W+32,WATER,20,0);setCell(31*W+130,EMPTY,20,0);
tick++;tickStamp++;var draws=0,savedRnd=rnd;rnd=()=>{draws++;return .99};
var searchWork=inlineChunkWork();runChunks([1],0,searchWork);`);
assert(run('searchWork.events.some(e=>e[1]==="liquid")'), 'fixture must defer a long search');
run('mergeChunkWork([searchWork]);rnd=savedRnd;');
assert.strictEqual(run('draws'),1,'resuming a liquid search must not reroll its movement chance');
assert.strictEqual(run('mat[30*W+40]'),run('WATER'),'the resumed search finds the distant drop');
console.log('PASS long liquid search resumes without rerolling movement');
function setup() {
flatWorld(run, 387, 257, 214);
run(`{RS=123;WS=456;resetWeather();weather.next=1e15;seen.fill(0);ledger={};EV.length=0;for(const k of Object.keys(stats))stats[k]=0;
resetEconomy();nextPowerBuild=0;bot.x=185;bot.y=120;
const random=mulberry32(99), materials=[SAND,COAL,WATER,MAGMA,MCU,SNOW,LWOOD,STEAM,SMOKE];
for(let y=40;y<210;y++)for(let x=8;x<W-8;x++) {
if(random()<.68)continue;
const m=materials[(random()*materials.length)|0];
setCell(y*W+x,m,m===MAGMA?1450:m===MCU?1200:m===STEAM?110:20,0);
}
addMachine('wheel',40,35,0);
for(let x=0;x<W;x++)setCell(31*W+x,STONE,20,F_NAT);
setCell(31*W+W-10,EMPTY,20,0);
for(let x=32;x<W-10;x+=64)setCell(30*W+x,WATER,20,0);
for(let bx=12;bx<40;bx++)placeBlock(BP_FILTER,128,bx,48,true,{m:[SAND],inv:0});
for(let x=80;x<210;x++)setCell(120*W+x,COAL,1000,F_BURN);
hot.fill(6);hotLvl.fill(2);awake.fill(3);updateRoof();}
`);
}
function hash() {
const h = crypto.createHash('sha256');
for (const a of run('[mat,temp,life,flags,stamp,part,mach,digp,bgm,isPow,netId,awake,hot,hotLvl,powderArea,roofDirty,seen,renderDirty]'))
h.update(Buffer.from(a.buffer,a.byteOffset,a.byteLength));
h.update(run('JSON.stringify({RS,stats,EV,flyers,brokenMachines,pcells:[...pcells],powerDirty,weather,machines})'));
return h.digest('hex');
}
(async () => {
let expected;
for (const n of [0,1,2,8,8,2,1,0]) {
setup(); const stop = n ? await enableThreads(run,n) : null;
try {
run('for(let k=0;k<320;k++)simTick();');
const actual=hash();
if(!expected)expected=actual;
assert.strictEqual(actual,expected,`world and effects differ with ${n} workers`);
console.log(`PASS identical world and side effects with ${n} workers (${actual.slice(0,16)})`);
} finally { if(stop)await stop(); }
}
setup();
run('const forwardChunks=runChunks;runChunks=(chunks,pass,work)=>forwardChunks(chunks.slice().reverse(),pass,work);for(let k=0;k<320;k++)simTick();runChunks=forwardChunks;');
assert.strictEqual(hash(),expected,'chunk order within a colour must not matter');
console.log('PASS reversing chunks within every pass preserves all bytes and effects');
const {execFileSync}=require('child_process');
const fallback=execFileSync(process.execPath,['-e',`
global.SharedArrayBuffer=undefined;
const {load,flatWorld}=require(${JSON.stringify(require.resolve('./harness'))});
const {run}=load();flatWorld(run,64,64,50);
if(!run('mat.buffer instanceof ArrayBuffer'))throw Error('fallback allocation');
run('setCell(20*W+20,SAND,20,0);for(let k=0;k<60;k++)simTick();');
process.stdout.write(JSON.stringify(run('Array.from(mat)')));
`],{encoding:'utf8'});
flatWorld(run,64,64,50);
run('setCell(20*W+20,SAND,20,0);for(let k=0;k<60;k++)simTick();');
assert.strictEqual(JSON.stringify(run('Array.from(mat)')),fallback);
console.log('PASS Node ArrayBuffer fallback matches shared inline movement');
console.log('ALL PASS');
})().catch(e=>{console.error(e);process.exitCode=1;});