Sloped belts hold their load with moving cleats (t42); sloped filters are downhill shaking screens (t43)
The magic grip is gone: cleats every 8 face cells, 6 grains tall, carry pockets uphill (about 71 grains/s against 90 flat) and overflow rolls back. Sloped filters always run downhill; grains travel by gravity and a shake, and sorting stays exact. Drawing needs another pass. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -698,6 +698,27 @@ with sync_playwright() as p:
|
||||
check('Wall diagonal defaults back to solid below', ev(f'Array.from({{length:4}}, (_,k)=>blockP[({bby}-12+k)*BW+{bbx}-5+k]).every(c=>c===BP_SLOPE_BL)'))
|
||||
pg.keyboard.press('Control+z'); settle(60)
|
||||
|
||||
# Cleated belts and downhill screens use real drags, previews, picking and R.
|
||||
for piece_id in ['belt', 'filter']:
|
||||
check('select ' + piece_id + ' for a diagonal drag', select('3', "piece().id === '" + piece_id + "'"))
|
||||
ev('money=1e6')
|
||||
drag(lambda: blk(bbx - 5, bby - 9), lambda: blk(bbx - 2, bby - 12))
|
||||
code = 'BP_BELT_UP' if piece_id == 'belt' else 'BP_FILTER_UP'
|
||||
check(piece_id + ' uphill drag lays the expected shape', ev(f'Array.from({{length:4}},(_,k)=>blockP[({bby}-9-k)*BW+{bbx}-5+k]).every(c=>c==={code})'))
|
||||
if piece_id == 'filter':
|
||||
check('uphill filter drag runs downhill left', ev(f'Array.from({{length:4}},(_,k)=>blockV[({bby}-9-k)*BW+{bbx}-5+k]).every(v=>v===0)'))
|
||||
pg.mouse.move(*blk(bbx - 3, bby - 11)); settle(60)
|
||||
check('screen hover says down-left', ev(f"hoverInfo(({bbx}-3)*4+1,({bby}-11)*4+2).t1.includes('down-left')"))
|
||||
pg.mouse.click(*blk(bbx - 3, bby - 11), button='middle'); settle(60)
|
||||
pg.keyboard.press('r'); settle(60)
|
||||
check('R leaves a picked screen downhill', ev('pieceCode(piece())===BP_FILTER_UP && pieceV(piece())===0'))
|
||||
pg.keyboard.press('f'); settle(100)
|
||||
check('screen picker offers only downhill travel', pg.locator('#dlg-body [data-dir]').count() == 0 and 'down-left' in pg.locator('#dlg-body').inner_text())
|
||||
pg.locator('#dlg-foot').get_by_role('button', name='Cancel', exact=True).click()
|
||||
pg.keyboard.press('Control+z'); settle(60)
|
||||
|
||||
ev('GAME.pieceShape = {}; GAME.pieceDir.filter = 1; GAME.editRun = null')
|
||||
|
||||
# Diagonal screws use real mouse placement, preview arrows, R and picking.
|
||||
check('select screw conveyor', select('3', "piece().id === 'screw'"))
|
||||
ev('GAME.pieceDir.screw = 0')
|
||||
@@ -1201,6 +1222,7 @@ with sync_playwright() as p:
|
||||
point = fp.evaluate(f'(() => {{ const p = worldToScreenPx(testSilo.x + {dx} + 0.5, testSilo.y + {dy} + 0.5); return [p.x * innerWidth / R.canvas.width, p.y * innerWidth / R.canvas.width]; }})()')
|
||||
fp.mouse.move(*point); fp.wait_for_timeout(200)
|
||||
want = 'sand 2,000 / 4,000' if amount else 'empty'
|
||||
fp.wait_for_function(f"$('info').textContent.includes('{('holds 2,000 / 4,000 sand' if amount else 'empty')}')")
|
||||
check(f'silo {name} hover shows {want}', fp.evaluate(f"hoverInfo(Math.floor(GAME.aim.x), Math.floor(GAME.aim.y)).t1 === 'Silo' && hoverInfo(Math.floor(GAME.aim.x), Math.floor(GAME.aim.y)).contents === '{want}' && $('info').textContent.includes('{('holds 2,000 / 4,000 sand' if amount else 'empty')}')"))
|
||||
check(f'silo {name} cursor label draws {want} in normal colour', fp.evaluate("""() => {
|
||||
const calls = [], ctx = R.ctx, text = ctx.fillText;
|
||||
|
||||
@@ -97,4 +97,24 @@ check('drawing leaves the seed, cells and saved block data unchanged', run(`(()
|
||||
}
|
||||
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'},1,12+k,${grade<0?20:12}+k*${grade},true);`);
|
||||
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] && (mat[i]===EMPTY || PH[mat[i]]===PH_GAS)))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'));
|
||||
done();
|
||||
|
||||
+53
-26
@@ -10,18 +10,19 @@ fresh();
|
||||
check('slope belts use new codes, without renumbering old slopes', run("typeof BP_BELT_UP !== 'undefined' && BP_BELT_UP > BP_SLOPE_TR"));
|
||||
function line(grade, filter = false, dir = 1) {
|
||||
fresh();
|
||||
if (filter) dir = grade > 0 ? 1 : 0;
|
||||
const by = grade < 0 ? 32 : 16, inlet = by + (grade < 0 ? 1 : 0), outlet = by + 9 * grade + (grade > 0 ? 1 : 0);
|
||||
run(`for(let k=0;k<10;k++) placeBlock(${filter ? 'BP_FILTER_UP' : 'BP_BELT_UP'}+${grade>0?1:0},${filter?dir*128:dir},16+k,${by}+k*${grade},true,{m:[SAND],inv:0});
|
||||
for(let x=10;x<16;x++)placeBlock(BP_BELT,${dir},x,${inlet},true);
|
||||
for(let x=26;x<32;x++)placeBlock(BP_BELT,${dir},x,${outlet},true);`);
|
||||
return { by, inlet, outlet, grade };
|
||||
return { by, inlet: filter && grade < 0 ? outlet : inlet, outlet, grade, left: filter && grade < 0, feedX: filter && grade < 0 ? 120 : 44 };
|
||||
}
|
||||
function collect(s, steps, feed = false, sink = 128) {
|
||||
return run(`(()=>{let out=0,added=0;for(let k=0;k<${steps};k++){
|
||||
${feed ? `for(let l=1;l<=4;l++){const i=(${s.inlet}*4-l)*W+44;if(mat[i]===EMPTY){setCell(i,SAND,20,0);added++;}}` : ''}
|
||||
${feed ? `for(let l=1;l<=4;l++){const i=(${s.inlet}*4-l)*W+${s.feedX || 44};if(mat[i]===EMPTY){setCell(i,SAND,20,0);added++;}}` : ''}
|
||||
simTick();
|
||||
for(let i=0;i<N;i++)if(mat[i]===SAND && i%W>=${sink}){out++;setCell(i,EMPTY,20,0);}
|
||||
for(let j=flyers.length-1;j>=0;j--)if(flyers[j].m===SAND && flyers[j].x>=${sink}){out++;flyers.splice(j,1);}
|
||||
for(let i=0;i<N;i++)if(mat[i]===SAND && ${s.left ? "i%W<40" : "i%W>="+sink}){out++;setCell(i,EMPTY,20,0);}
|
||||
for(let j=flyers.length-1;j>=0;j--)if(flyers[j].m===SAND && ${s.left ? "flyers[j].x<40" : "flyers[j].x>="+sink}){out++;flyers.splice(j,1);}
|
||||
}return {out,added,left:mat.reduce((n,m)=>n+(m===SAND),0)+flyers.filter(f=>f.m===SAND).length};})()`);
|
||||
}
|
||||
const rates=[];
|
||||
@@ -31,7 +32,7 @@ for(const grade of [0,-1,1]) {
|
||||
else {fresh();run('for(let bx=10;bx<32;bx++)placeBlock(BP_BELT,1,bx,24,true);');s={inlet:24};}
|
||||
collect(s,220,true);
|
||||
const r=collect(s,600,true);rates.push(r.out/10);
|
||||
check(`grade ${grade} moves a four-grain load at flat belt speed`,r.out>0 && (grade===0 || Math.abs(r.out/10-rates[0])<=rates[0]*0.05),JSON.stringify(r));
|
||||
check(`grade ${grade} carries a generous feed at least 60 grains/s`,r.out>0 && r.out/10>=60,JSON.stringify(r));
|
||||
const end=collect(s,600);
|
||||
check(`grade ${grade} leaves no load stuck`,end.left===0,JSON.stringify(end));
|
||||
}
|
||||
@@ -43,7 +44,7 @@ for(const grade of [-1,1]) {
|
||||
const r=collect(s,1000);
|
||||
check(`grade ${grade} conserves and delivers every grain across both flat joins`,r.out===128 && r.left===0,JSON.stringify(r));
|
||||
line(grade);
|
||||
run(`spawnBar(BARCU,44,${s.inlet*4-2},0,0);`);
|
||||
run(`spawnBar(BARCU,${s.feedX || 44},${s.inlet*4-2},0,0);`);
|
||||
ticks(run,500);
|
||||
check(`grade ${grade} carries a bar through both flat joins`,run('ents.length===1 && ents[0].ix>=128'),run('ents.map(e=>[e.ix,e.iy])'));
|
||||
}
|
||||
@@ -59,14 +60,14 @@ for(const grade of [-1,1]) {
|
||||
rect(run,104,0,104,s.outlet*4-1,run('STONE'));
|
||||
ticks(run,300);
|
||||
const held=run('mat.reduce((n,m)=>n+(m===SAND),0)+flyers.filter(f=>f.m===SAND).length');
|
||||
check(`blocked grade ${grade} holds every grain without slipping below its face`,held===160 && count(run,run('SAND'),0,0,63,207)===0 && count(run,run('SAND'),0,160,255,207)===0,held);
|
||||
check(`blocked grade ${grade} conserves its load and lets overflow roll back`,held===160 && (grade>0 || count(run,run('SAND'),0,0,63,207)>0),held);
|
||||
rect(run,104,0,104,s.outlet*4-1,run('EMPTY'));
|
||||
const released=collect(s,800);
|
||||
check(`grade ${grade} resumes after the head clears`,released.out===160 && released.left===0,JSON.stringify(released));
|
||||
line(grade);
|
||||
run(`for(let bx=26;bx<32;bx++)removeBlock(bx,${s.outlet});setCell((${s.by+9*grade}*4+${grade<0?0:3}-1)*W+103,SAND,20,0);tick=2;tickStamp++;machinesTick();`);
|
||||
check(`grade ${grade} launches off an open end along its face`,run(`flyers.length===1 && flyers[0].vx>0 && Math.sign(flyers[0].vy)===${grade}`));
|
||||
run(`spawnBar(BARCU,44,${s.inlet*4-2},0,0);`);
|
||||
run(`spawnBar(BARCU,${s.feedX || 44},${s.inlet*4-2},0,0);`);
|
||||
const barLaunch=run('(()=>{for(let k=0;k<400;k++){simTick();if(ents[0].ix>=104)return ents[0].vx>0;}return false;})()');
|
||||
check(`grade ${grade} keeps a bar moving forward off an open end`,barLaunch);
|
||||
for(const diagonal of [false,true]) {
|
||||
@@ -76,7 +77,7 @@ for(const grade of [-1,1]) {
|
||||
rect(run,39,20,39,s.inlet*4-1,run('STONE'));
|
||||
const r=collect(s,1000,false,116);
|
||||
check(`grade ${grade} unloads grains into a ${diagonal?'diagonal':'straight'} screw`,r.out===128 && r.left===0,JSON.stringify(r));
|
||||
run(`spawnBar(BARCU,44,${s.inlet*4-2},0,0);`);
|
||||
run(`spawnBar(BARCU,${s.feedX || 44},${s.inlet*4-2},0,0);`);
|
||||
ticks(run,600);
|
||||
check(`grade ${grade} unloads a bar into a ${diagonal?'diagonal':'straight'} screw`,run('ents.length===1 && ents[0].ix>=116 && !mat.some((m,i)=>occ[i] && PH[m]===PH_SOLID)'),run('ents.map(e=>[e.ix,e.iy])'));
|
||||
|
||||
@@ -88,18 +89,18 @@ for(const grade of [-1,1]) {
|
||||
collect(s,220,true);
|
||||
const r=collect(s,600,true);
|
||||
console.log(`Filter throughput grade ${grade}, grains/s: ${r.out/10}`);
|
||||
check(`carrying filter grade ${grade} matches the belt feed`,r.out/10>=rates[0]*0.95,JSON.stringify(r));
|
||||
check(`downhill screen grade ${grade} discharges its generous feed`,r.out/10>=45 && r.out/10<=90,JSON.stringify(r));
|
||||
}
|
||||
for(const grade of [-1,1]) for(const inv of [0,1]) {
|
||||
const s=line(grade,true);
|
||||
run(`for(const b of filterCfg.keys())setFilter(b,{m:[SAND],inv:${inv}});`);
|
||||
rect(run,39,20,39,s.inlet*4-1,run('STONE'));
|
||||
run(`for(let y=${s.inlet*4-8};y<${s.inlet*4};y++)for(let x=40;x<56;x++)setCell(y*W+x,(x+y)&1?SAND:GRAVEL,20,0);`);
|
||||
rect(run,s.left?128:39,20,s.left?128:39,s.inlet*4-1,run('STONE'));
|
||||
run(`for(let y=${s.inlet*4-8};y<${s.inlet*4};y++)for(let x=${s.left?112:40};x<${s.left?128:56};x++)setCell(y*W+x,(x+y)&1?SAND:GRAVEL,20,0);`);
|
||||
const result=run(`(()=>{const out=[0,0],below=[0,0];for(let k=0;k<1200;k++){
|
||||
simTick();
|
||||
for(let i=0;i<N;i++)if(mat[i]===SAND || mat[i]===GRAVEL){const m=mat[i]===SAND?0:1,x=i%W,y=(i/W)|0;
|
||||
if(x>=128){out[m]++;setCell(i,EMPTY,20,0);}else if(y>=180){below[m]++;setCell(i,EMPTY,20,0);}}
|
||||
for(let f=flyers.length-1;f>=0;f--){const p=flyers[f];if((p.m===SAND || p.m===GRAVEL) && p.x>=128){out[p.m===SAND?0:1]++;flyers.splice(f,1);}}
|
||||
if(${s.left?"x<40":"x>=128"}){out[m]++;setCell(i,EMPTY,20,0);}else if(y>=180){below[m]++;setCell(i,EMPTY,20,0);}}
|
||||
for(let f=flyers.length-1;f>=0;f--){const p=flyers[f];if((p.m===SAND || p.m===GRAVEL) && ${s.left?"p.x<40":"p.x>=128"}){out[p.m===SAND?0:1]++;flyers.splice(f,1);}}
|
||||
}return {out,below,left:mat.reduce((n,m)=>n+(m===SAND || m===GRAVEL),0)+flyers.filter(f=>f.m===SAND || f.m===GRAVEL).length};})()`);
|
||||
console.log(`Sorting grade ${grade} inverse ${inv}: `+JSON.stringify(result));
|
||||
check(`grade ${grade} ${inv?'carries selected':'drops selected'} and conserves the full mix`,result.out[inv?0:1]===64 && result.below[inv?1:0]===64 && result.left===0 && result.out[inv?1:0]===0 && result.below[inv?0:1]===0,JSON.stringify(result));
|
||||
@@ -107,21 +108,25 @@ for(const grade of [-1,1]) for(const inv of [0,1]) {
|
||||
for(const c of ['BP_BELT_UP','BP_BELT_DOWN','BP_FILTER_UP','BP_FILTER_DOWN'])for(const d of [0,1]) {
|
||||
fresh();
|
||||
run(`placeBlock(${c},${c.includes('FILTER')?d*128:d},20,20,true,{m:[SAND,BARCU],inv:1});globalThis.saved=serializeWorld();deserializeWorld(saved.meta,saved.buffers);`);
|
||||
check(`${c} ${d} saves its shape, direction and settings`,run(`blockP[20*BW+20]===${c} && blockV[20*BW+20]===${c.includes('FILTER')?d*128:d} && blockIntegrity(20*BW+20)===10 ${c.includes('FILTER')?'&& sameFilter(filterInfo(20*BW+20),{m:[SAND,BARCU].sort((a,b)=>a-b),inv:1})':''}`));
|
||||
check(`${c} ${d} saves its shape, direction and settings`,run(`blockP[20*BW+20]===${c} && blockV[20*BW+20]===${c.includes('FILTER')?'filterVariant('+c+','+d+'*128)':d} && blockIntegrity(20*BW+20)===10 ${c.includes('FILTER')?'&& sameFilter(filterInfo(20*BW+20),{m:[SAND,BARCU].sort((a,b)=>a-b),inv:1})':''}`));
|
||||
if(c.includes('BELT')) {
|
||||
run(`slopePhase[20*BW+20]=${d?1:7};rebuildPower();slopeCleatsTick(false);globalThis.savedPhase=serializeWorld();deserializeWorld(savedPhase.meta,savedPhase.buffers);`);
|
||||
check(`${c} saves the moving cleat phase and immediately restores its barriers`,run(`slopePhase[20*BW+20]===${d?1:7} && cleatCells.length>0`));
|
||||
}
|
||||
run(`globalThis.clip=copyRegion(80,80,83,83);bot.x=96;bot.y=80;undoRecord('paste',()=>pasteGhost(clip,24,20));ghostTick();`);
|
||||
check(`${c} ${d} copy/paste keeps all settings`,run('blockP[20*BW+24]===blockP[20*BW+20] && blockV[20*BW+24]===blockV[20*BW+20] && sameFilter(filterInfo(20*BW+20),filterInfo(20*BW+24))'));
|
||||
run('undoLast();');
|
||||
check(`${c} ${d} undo clears the paste including its seal`,run(`blockP[20*BW+24]===0 && blockCellList(24,20).every(i=>mat[i]===EMPTY && !part[i]) && mat[slopeSeal(${c},24,20)]===EMPTY && !part[slopeSeal(${c},24,20)]`));
|
||||
run("undoRecord('cut',()=>cutRegion(80,80,83,83));undoLast();");
|
||||
check(`${c} ${d} undo restores a cut`,run(`blockP[20*BW+20]===${c} && blockIntegrity(20*BW+20)===10`));
|
||||
check(`${c} ${d} mirrors its shape and reverses horizontally`,run(`(()=>{const b=flipBlueprint(clip).blocks[0];return b[2]===${c}+${c.endsWith('UP')?1:-1} && b[3]===${c.includes('FILTER')?(1-d)*128:1-d};})()`));
|
||||
check(`${c} ${d} mirrors its shape and reverses horizontally`,run(`(()=>{const b=flipBlueprint(clip).blocks[0];return b[2]===${c}+${c.endsWith('UP')?1:-1} && b[3]===${c.includes('FILTER')?'filterVariant('+c+'+'+(c.endsWith('UP')?1:-1)+',0)':1-d};})()`));
|
||||
}
|
||||
for(const grade of [-1,1]) {
|
||||
const s=line(grade,true);
|
||||
run(`spawnBar(BARCU,44,${s.inlet*4-2},0,0);`);ticks(run,500);
|
||||
check(`sloped filter grade ${grade} carries a bar it does not pass`,run('ents.length===1 && ents[0].ix>=128'));
|
||||
run(`spawnBar(BARCU,${s.feedX || 44},${s.inlet*4-2},0,0);`);ticks(run,500);
|
||||
check(`sloped filter grade ${grade} slides a bar it does not pass downhill`,run(`ents.length===1 && ${s.left?'ents[0].ix<40':'ents[0].ix>=128'}`));
|
||||
line(grade,true);
|
||||
run(`for(const b of filterCfg.keys())setFilter(b,{m:[BARCU],inv:0});spawnBar(BARCU,44,${s.inlet*4-2},0,0);`);ticks(run,500);
|
||||
run(`for(const b of filterCfg.keys())setFilter(b,{m:[BARCU],inv:0});spawnBar(BARCU,${s.feedX || 44},${s.inlet*4-2},0,0);`);ticks(run,500);
|
||||
check(`sloped filter grade ${grade} drops a selected bar through its open frame`,run('ents.length===1 && ents[0].ix<104 && ents[0].iy===206 && !mat.some((m,i)=>occ[i] && PH[m]===PH_SOLID)'));
|
||||
}
|
||||
// Reverse the motor without flipping the upward-facing triangle.
|
||||
@@ -135,8 +140,30 @@ for(const grade of [-1,1]) {
|
||||
check(`reversed grade ${grade} carries bars left across both joins`,run('ents.length===1 && ents[0].ix<40'),run('ents.map(e=>[e.ix,e.iy])'));
|
||||
}
|
||||
fresh();
|
||||
run('placeBlock(BP_BELT_UP,1,20,32,true);for(let l=1;l<=CARRY_DEPTH;l++)setCell((131-l)*W+80,SAND,20,0);tickStamp++;carryColumn(130*W+80,1,null);');
|
||||
check('a slope carries the same full 96-grain column as a flat belt',run('Array.from({length:CARRY_DEPTH},(_,k)=>mat[(129-k)*W+81]===SAND).every(Boolean)'));
|
||||
run('placeBlock(BP_BELT_UP,1,20,32,true);');
|
||||
check('pocket capacity is bounded by the real cleat height',run('Array.from({length:CLEAT_SPACING},(_,k)=>pocketDepth(80+k,32*BW+20)).every(d=>d>=0 && d<=CLEAT_REACH*2)'));
|
||||
// A stationary cleat blocks downhill crossings; the bare face still lets grains slide.
|
||||
for (const grade of [-1,1]) {
|
||||
const s=line(grade);
|
||||
run('rebuildPower();slopeCleatsTick(false);');
|
||||
check(`grade ${grade} cleats block every downhill crossing`,run(`cleatCells.length>0 && cleatCells.every(i=>!enter(SAND,PH_POWDER,i,${grade},1) && !enter(SAND,PH_POWDER,i+W+${grade},${grade},1))`));
|
||||
check(`grade ${grade} bare face slides between steps without stamping the load`,run(`(()=>{
|
||||
for(let x=72;x<96;x++){
|
||||
const by=${s.by}+((x>>2)-16)*${grade}, y=beltFaceY(blockP[by*BW+(x>>2)],x>>2,by,x)-1,i=y*W+x;
|
||||
if(cleatMask[i] || cleatMask[i+W+${grade}])continue;
|
||||
setCell(i,SAND,20,0);tick=3;tickStamp++;machinesTick();
|
||||
const unstamped=stamp[i]!==tickStamp;
|
||||
for(let k=0;k<200 && mat[i]===SAND;k++)powder(i,x,y,SAND);
|
||||
return unstamped && mat[i]===EMPTY;
|
||||
} return false;
|
||||
})()`));
|
||||
}
|
||||
fresh();
|
||||
run('placeBlock(BP_FILTER_DOWN,128,20,20,true,{m:[],inv:0});setCell(79*W+80,GRAVEL,20,0);tickStamp++;tick=SCREEN_NUDGE_TICKS-1;screenNudge(80*W+80,1,null);');
|
||||
check('screen waits between its configured nudges',run('mat[79*W+80]===GRAVEL && stamp[79*W+80]!==tickStamp'));
|
||||
run('tick++;screenNudge(80*W+80,1,null);');
|
||||
check('screen nudges just its mesh contact downhill at the configured rate',run('mat[80*W+81]===GRAVEL && mat[79*W+80]===EMPTY'));
|
||||
fresh();run('placeBlock(BP_BELT_UP,1,20,32,true);');
|
||||
run(fs.readFileSync('src/render/slope-belts.js','utf8'));
|
||||
// The renderer prepares joins and power first; stripe pitch is measured along the rotated band.
|
||||
run('prepareSlopeBelts(0,0,W,H);');
|
||||
@@ -147,18 +174,18 @@ run("globalThis.GAME={pieceDir:{belt:1,filter:1},filterCfg:{m:[SAND],inv:0},wall
|
||||
for(const id of ['belt','filter'])for(const [dx,dy] of [[5,-4],[4,5],[-5,4],[-4,-5]]) {
|
||||
run('GAME.drag={bx:20,by:20};');
|
||||
const c=(id==='belt'?19:21)+(dx*dy>0?1:0);
|
||||
check(`${id} drag ${dx},${dy} picks an upward face and runs along the drag`,run(`pieceCode(PIECE_BY_ID.${id},{bx:${20+dx},by:${20+dy}})===${c} && dragDirFor(PIECE_BY_ID.${id},GAME.drag,{bx:${20+dx},by:${20+dy}})===${dx>0?1:0}`));
|
||||
check(`${id} drag ${dx},${dy} picks the slope shape and permitted travel direction`,run(`pieceCode(PIECE_BY_ID.${id},{bx:${20+dx},by:${20+dy}})===${c} && dragDirFor(PIECE_BY_ID.${id},GAME.drag,{bx:${20+dx},by:${20+dy}})===${id==='filter'?(dx*dy>0?1:0):(dx>0?1:0)}`));
|
||||
}
|
||||
run("globalThis.setStatus=()=>{};globalThis.catOf=()=>0;globalThis.selectTool=()=>{};globalThis.aimBlock=()=>blockOf(GAME.aim.x,GAME.aim.y);");
|
||||
for(const id of ['belt','filter'])for(const [dx,dy] of [[3,-3],[3,3],[-3,3],[-3,-3]]) {
|
||||
fresh();
|
||||
run(`globalThis.piece=()=>PIECE_BY_ID.${id};GAME.drag={bx:20,by:20};GAME.aim={x:${(20+dx)*4},y:${(20+dy)*4}};bot.x=80;bot.y=80;primaryRelease(false);`);
|
||||
const c=(id==='belt'?19:21)+(dx*dy>0?1:0), v=dx>0?(id==='filter'?128:1):0;
|
||||
const c=(id==='belt'?19:21)+(dx*dy>0?1:0), v=id==='filter'?(dx*dy>0?128:0):(dx>0?1:0);
|
||||
check(`${id} release really builds the diagonal in direction ${dx},${dy}`,run(`Array.from({length:4},(_,k)=>{const b=(20+k*${Math.sign(dy)})*BW+20+k*${Math.sign(dx)};return blockP[b]===${c} && blockV[b]===${v};}).every(Boolean)`));
|
||||
run('pickUnderCursor();rotate();');
|
||||
check(`${id} pick and R reverse the motor while preserving a single slope shape`,run(`pieceCode(PIECE_BY_ID.${id})===${c} && pieceV(PIECE_BY_ID.${id})===${id==='filter'?128-v:1-v}`));
|
||||
check(`${id} pick and R preserve the slope and use its permitted direction`,run(`pieceCode(PIECE_BY_ID.${id})===${c} && pieceV(PIECE_BY_ID.${id})===${id==='filter'?v:1-v}`));
|
||||
run('GAME.drag=aimBlock();GAME.replace=true;primaryRelease(false);GAME.replace=false;');
|
||||
check(`${id} a single replacement reverses the picked slope`,run(`blockP[${20+dy}*BW+${20+dx}]===${c} && blockV[${20+dy}*BW+${20+dx}]===${id==='filter'?128-v:1-v}`));
|
||||
check(`${id} a single replacement keeps the selected direction`,run(`blockP[${20+dy}*BW+${20+dx}]===${c} && blockV[${20+dy}*BW+${20+dx}]===${id==='filter'?v:1-v}`));
|
||||
|
||||
}
|
||||
run(fs.readFileSync('src/ui/selection-pickers.js','utf8'));
|
||||
@@ -166,7 +193,7 @@ const filterLine=line(-1,true);
|
||||
run("applyFilterRun(filterRun(32*BW+16),{m:[GRAVEL],inv:1,dir:0});");
|
||||
check('the settings UI updates the matching diagonal filter run',run('filterCfg.size===10 && [...filterCfg.keys()].every(b=>sameFilter(filterInfo(b),{m:[GRAVEL],inv:1}) && blockV[b]===0)'));
|
||||
run('undoLast();');
|
||||
check('undo restores diagonal filter settings and direction',run('filterCfg.size===10 && [...filterCfg.keys()].every(b=>sameFilter(filterInfo(b),{m:[SAND],inv:0}) && blockV[b]===128)'));
|
||||
check('undo restores diagonal filter settings and direction',run('filterCfg.size===10 && [...filterCfg.keys()].every(b=>sameFilter(filterInfo(b),{m:[SAND],inv:0}) && blockV[b]===0)'));
|
||||
// An actual v2 fixture, before either diagonal belt or filter existed.
|
||||
const legacy=JSON.parse(require('zlib').gunzipSync(fs.readFileSync(__dirname+'/fixtures/slopes-v2.json.gz')));
|
||||
globalThis.legacyBeltSave=legacy;
|
||||
|
||||
Reference in New Issue
Block a user