// Owner's upward nozzle recipe, inline and with real shared-memory helpers. const fs = require('fs'); const {createHash} = require('crypto'); const {load, suite, enableThreads} = require('./harness'); const {run} = load(), {check, done} = suite(); for (const file of ['pile-cut', 'pile-blow']) run(fs.readFileSync(require.resolve('./'+file+'.js'), 'utf8')); const hash = () => {const h=createHash('sha256');for(const a of run('[mat,flags,life]'))h.update(a);return h.digest('hex');}; // A supported grain must release immediately, without relying on gravity underneath it. run(`pileBlowSetup();for(let y=270;y<340;y++)for(let x=356;x<376;x++)setCell(y*W+x,SAND,20,0); powder(300*W+356,356,300,SAND);`); check('a resting tall face topples promptly',run('mat[300*W+356]===EMPTY && mat[308*W+348]===SAND && (flags[299*W+356]&F_AVALANCHE)')); run(`pileBlowSetup();for(let y=270;y<340;y++)for(let x=356;x<376;x++)setCell(y*W+x,SAND,20,0); cleatMask[300*W+352]=1;powder(300*W+356,356,300,SAND);`); check('resting face spill stops at a cross-chunk cleat',run('mat[308*W+353]===SAND && mat[308*W+348]===EMPTY')); (async () => { let expected; for (const workers of [0, 1, 2, 8]) { run('pileBlowSetup()'); const stop = workers ? await enableThreads(run, workers) : null; try { run('for(let k=0;k<1200;k++){pileBlowStroke();simTick();}'); const before = run('pileBlowProfile()'); check(`fast upward nozzle builds a large pile (${workers} helpers)`, before.grains > 15000 && before.height > 100, JSON.stringify(before)); run('bot.x=700;bot.y=100;for(let k=0;k<480;k++)simTick();'); const after = run('pileBlowProfile()'); check(`blown sand slumps within eight seconds (${workers} helpers)`, after.cliff <= 2, JSON.stringify(after)); check('all blown sand is retained', after.grains + after.flying + after.bag === 40000); if (workers) { check('helpers actually process the blown pile', run('simPool.activeMax') > 0); check('blown collapse matches inline material and motion flags', hash() === expected); } else expected = hash(); run('for(let k=0;k<1200;k++){awake.fill(3);simTick();}'); const resting = hash(); run('for(let k=0;k<600;k++){awake.fill(3);simTick();}for(let k=0;k<3;k++)simTick();'); check('settled blown sand stays put and sleeps', hash() === resting && run('awake.every(v=>!v)')); } finally { if(stop) await stop(); } } const heights = []; for (const material of ['SAND','LCLAY']) { run(`pileBlowSetup();bag.fill(0);bagTotal=0;bot.x=700; for(let y=360;y<480;y++)for(let x=350;x<410;x++)setCell(y*W+x,${material},20,0); for(let k=0;k<1800;k++){awake.fill(3);simTick();}`); heights.push(run(`(()=>{let height=0;for(let x=0;xheights[0],JSON.stringify(heights)); done(); })().catch(e => { console.error(e); process.exitCode = 1; });