// Drawing geometry against real parts, without changing transport. const fs = require('fs'); const path = require('path'); const { load, suite, flatWorld } = require('./harness'); const { run } = load(); const { check, done } = suite(); run(fs.readFileSync(path.join(__dirname, '../src/render/slope-belts.js'), 'utf8')); flatWorld(run, 160, 120, 112); for (const code of ['BP_BELT_UP', 'BP_BELT_DOWN', 'BP_FILTER_UP', 'BP_FILTER_DOWN']) { run(` mat.fill(EMPTY); part.fill(0); blockP.fill(0); blockV.fill(0); var artCode = ${code}, artGrade = beltGrade(artCode), artB = 12 * BW + 12; for (let k = -2; k <= 2; k++) placeBlock(artCode, 1, 12 + k, 12 + artGrade * k, true); prepareSlopeBelts(0, 0, W, H); `); check(`${code}: diagonal neighbours continue the band`, run('(slopeBeltJoins[artB] & 12) === 12')); check(`${code}: corner seals use the same band`, run(`(() => { const i = slopeSeal(artCode, 12, 12); return i < 0 || slopeBeltAt(i, i % W, (i / W) | 0) >= 0; })()`)); check(`${code}: band is as thick as a flat belt`, run(`(() => { const g = artGrade, x = 50 - g * 1.85 * Math.SQRT1_2, y = 50 + 1.85 * Math.SQRT1_2; return slopeBeltShape(x, y, artB) < -1.8 && slopeBeltShape(x - g * 1.7 * Math.SQRT1_2, y + 1.7 * Math.SQRT1_2, artB) < 0 && slopeBeltShape(x - g * 2.1 * Math.SQRT1_2, y + 2.1 * Math.SQRT1_2, artB) > 0; })()`)); check(`${code}: air below the triangle gets the continuous band`, run(`(() => { for (let y = 48; y < 57; y++) for (let x = 48; x < 56; x++) { const i = y * W + x, own = (y >> 2) * BW + (x >> 2); if (!blockP[own] && mat[i] === EMPTY && slopeBeltAt(i, x, y) >= 0) return true; } return false; })()`)); run(` for (let end = 0; end < 2; end++) { const side = end ? 1 : -1; removeBlock(12 + side, 12 + side * artGrade, true); placeBlock(BP_BELT, 1, 12 + side, 12 + (end ? artGrade > 0 : artGrade < 0), true); } prepareSlopeBelts(0, 0, W, H); `); check(`${code}: both flat joints form elbows`, run('(slopeBeltJoins[artB] & 3) === 3')); check(`${code}: adjoining flats do not round off at the joint`, run(`(() => { const l = (12 + (artGrade < 0)) * BW + 11, r = (12 + (artGrade > 0)) * BW + 13; return !(flatBeltCaps(l) & 2) && !(flatBeltCaps(r) & 1); })()`)); check(`${code}: overhang never covers terrain, parts, machines or bars`, run(`(() => { for (let y = 48; y < 57; y++) for (let x = 48; x < 56; x++) { const i = y * W + x, own = (y >> 2) * BW + (x >> 2); if (blockP[own] || mat[i] !== EMPTY || slopeBeltAt(i, x, y) < 0) continue; mat[i] = STONE; const ground = slopeBeltAt(i, x, y); mat[i] = EMPTY; part[i] = P_GRATE; const built = slopeBeltAt(i, x, y); part[i] = 0; mach[i] = 1; const machine = slopeBeltAt(i, x, y); mach[i] = 0; occ[i] = 1; const bar = slopeBeltAt(i, x, y); occ[i] = 0; return ground < 0 && built < 0 && machine < 0 && bar < 0; } return false; })()`)); } check('free end rounds its corners and stops at the end', run(`(() => { const b = artB, x = 48 - Math.SQRT2, y = 48 + Math.SQRT2; blockP[b] = BP_BELT_DOWN; return slopeBeltShape(x + 0.8, y + 0.8, b, 0) < 0 && slopeBeltShape(x - 1, y - 1, b, 0) > 0; })()`)); check('flat belts still meet diagonal screw elbows without an end cap', run(`(() => { blockP[artB - 1] = BP_BELT; blockP[artB] = BP_SCREW; blockV[artB] = 5; // same-row flat at the high end const joined = !(flatBeltCaps(artB - 1) & 2); blockP[artB] = BP_BELT_DOWN; return joined; })()`)); check('unpowered rollers and studs stand still', run(`(() => { slopeBeltJoins[artB] &= ~16; for (let y = 48; y < 56; y++) for (let x = 48; x < 56; x++) if (slopeBeltColor(artB, x, y, 0, 150, 170, 190) !== slopeBeltColor(artB, x, y, 20, 150, 170, 190)) return false; return true; })()`)); check('powered rollers and studs reverse with the belt', run(`(() => { slopeBeltJoins[artB] |= 16; let moves = false; for (let y = 48; y < 56; y++) for (let x = 48; x < 56; x++) { blockV[artB] = 1; const a = slopeBeltColor(artB, x, y, 12, 150, 170, 190), stopped = slopeBeltColor(artB, x, y, 0, 150, 170, 190); blockV[artB] = 0; if (a !== slopeBeltColor(artB, x, y, -12, 150, 170, 190)) return false; if (a !== stopped) moves = true; } return moves; })()`)); check('drawing leaves the seed, cells and saved block data unchanged', run(`(() => { const snapshot = () => JSON.stringify([RS, Array.from(mat), Array.from(part), Array.from(blockP), Array.from(blockV)]); const before = snapshot(); prepareSlopeBelts(0, 0, W, H); for (let y = 40; y < 64; y++) for (let x = 40; x < 72; x++) { const b = slopeBeltAt(y * W + x, x, y); if (b >= 0) slopeBeltColor(b, x, y, 12, 150, 170, 190); } return snapshot() === before; })()`)); for (const grade of [-1,1]) { flatWorld(run,160,120,112); run(`for(let k=0;k<6;k++)placeBlock(${grade<0?'BP_BELT_UP':'BP_BELT_DOWN'},${grade<0?1:0},12+k,${grade<0?20:12}+k*${grade},true);`); // uphill, the cleated way for(let phase=0;phase<8;phase++) { run(`rebuildPower();slopePhase.fill(${phase});slopeCleatsTick(false);prepareSlopeBelts(0,0,W,H);`); check(`grade ${grade} phase ${phase} draws exactly the physical cleat raster`,run(`(()=>{ let drawn=0; for(let i=0;i0; })()`)); } } flatWorld(run,160,120,112); run('placeBlock(BP_FILTER_DOWN,128,12,12,true);tick=0;prepareSlopeBelts(0,0,W,H);var idleJoins=slopeBeltJoins[12*BW+12];tick=2;prepareSlopeBelts(0,0,W,H);'); check('empty screen has a stable frame',run('slopeBeltJoins[12*BW+12]===idleJoins')); run('setCell(47*W+48,SAND,20,0);prepareSlopeBelts(0,0,W,H);'); check('loaded screen shakes the frame with the simulation tick',run('(slopeBeltJoins[12*BW+12]&64)!==0')); run('tick=4;prepareSlopeBelts(0,0,W,H);'); check('loaded frame returns on the next shake beat',run('(slopeBeltJoins[12*BW+12]&64)===0')); flatWorld(run,160,120,112); run('for(let k=0;k<6;k++)placeBlock(BP_BELT_UP,1,12+k,20-k,true);rebuildPower();slopeCleatsTick(false);'); check('wall reaches the belt and cleat height, and clips its free ends',run(`(()=>{ const b=20*BW+12,g=-1,sy=84; for(const depth of [0.5,3,5.5]) if(slopeWallShape(50+g*depth*Math.SQRT1_2,sy-2-depth*Math.SQRT1_2,b)>0)return false; return slopeWallShape(46,sy+2,b)>0 && slopeWallShape(54,sy-6,b)>0; })()`)); check('far wall is filled down to the belt and grains cover it',run(`(()=>{ prepareSlopeBelts(0,0,W,H); let wall=0; for(let y=8;y<100;y++)for(let x=8;x<140;x++) { const i=y*W+x; if(slopeDetailAt(i,x,y)!==2)continue; wall++; mat[i]=SAND;const covered=slopeDetailAt(i,x,y)===0;mat[i]=EMPTY; if(!covered)return false; } return wall>20; })()`)); flatWorld(run,160,120,112); run('for(let k=0;k<6;k++)placeBlock(BP_BELT_UP,0,12+k,20-k,true);rebuildPower();slopeCleatsTick(false);prepareSlopeBelts(0,0,W,H);'); check('a downhill belt draws no wall and no ribs',run('(()=>{let n=0;for(let i=0;iv===0)'),run('awake.reduce((n,v)=>n+(v>0),0)')); check('cleat chunk flags include neighbours across chunk borders',run(`(()=>{ for(const i of cleatCells) { const x=i%W,y=(i/W)|0; for(let yy=y-1;yy<=y+1;yy++)for(let xx=x-1;xx<=x+1;xx++) if(xx>=0 && xx=0 && yy>5)*CW+(xx>>5)])return false; } return cleatCells.length>0 && cleatChunks.some(v=>v===0); })()`)); check('a load next to a cleat wakes after the empty line slept',run(`(()=>{ const i=cleatCells[0];setCell(i,SAND,20,0);awake.fill(0); slopeCleatsTick(false); return awake.some(v=>v>0) && awake[0]===0; })()`)); done();