Files

160 lines
8.0 KiB
JavaScript

// 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, artGrade > 0 ? 1 : 0, 12 + k, 12 + artGrade * k, true);
prepareSlopeBelts(0, 0, W, H);
`);
check(`${code}: downhill 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, artGrade > 0 ? 1 : 0, 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;i<N;i++){const x=i%W,y=(i/W)|0,detail=slopeDetailAt(i,x,y);if(detail===1)drawn++;
if((detail===1)!==Boolean(cleatMask[i] && !part[i] && !occ[i] && !mach[i] && PH[mat[i]]!==PH_SOLID))return false;
}return drawn>0;
})()`));
}
}
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;i<N;i++)if(slopeDetailAt(i,i%W,(i/W)|0))n++;return n===0 && cleatCells.length===0;})()'));
flatWorld(run,320,240,232);
run('for(let k=0;k<8;k++)placeBlock(BP_BELT_UP,1,12+k,30-k,true);for(let t=0;t<120;t++)simTick();');
check('idle empty slope lets every moving area sleep',run('awake.every(v=>v===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<W && yy>=0 && yy<H && !cleatChunks[(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();