Multi-core simulation: grain movement and heat across a worker pool (t88 phase 3)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
// 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;});
|
||||
Reference in New Issue
Block a user