Belts carry 4 grains deep; stalled belts warn and stop animating (t45)
This commit is contained in:
Vendored
+6
@@ -0,0 +1,6 @@
|
||||
// Closed belt/screw loop, with a low overhead return and a deep mixed heap.
|
||||
for(let bx=12;bx<28;bx++)placeBlock(BP_BELT,1,bx,32,true);
|
||||
for(let by=28;by<=32;by++)placeBlock(BP_SCREW,0,28,by,true);
|
||||
for(let bx=12;bx<=28;bx++)placeBlock(BP_SCREW,3,bx,27,true);
|
||||
for(let by=28;by<32;by++)placeBlock(BP_SCREW,2,12,by,true);
|
||||
for(let y=112;y<128;y++)for(let x=56;x<108;x++)setCell(y*W+x,y>=124?SAND:CUORE,20,0);
|
||||
+5
-7
@@ -56,11 +56,11 @@ for (const horizontal of [false, true]) {
|
||||
check(horizontal ? 'a right screw takes grains from above into every row' : 'an up screw takes grains from the side into every column', filled.join() === '3,2,1,0', filled);
|
||||
}
|
||||
|
||||
// A finite heap rides into the side of a six-block lift; remove each mouth flyer once.
|
||||
// A finite four-grain bed feeds a six-block lift; compare with the same open belt.
|
||||
const sideSeed = run('RS');
|
||||
fresh();
|
||||
run('RS = 7; for (let bx = 16; bx < 32; bx++) placeBlock(BP_BELT, 1, bx, 25, true); for (let by = 19; by <= 24; by++) placeBlock(BP_SCREW, 0, 32, by, true);');
|
||||
rect(run, 64, 40, 119, 99, run('SAND'));
|
||||
rect(run, 64, 96, 119, 99, run('SAND'));
|
||||
const sideLift = run(`(() => {
|
||||
let out = 0; const lanes = [0, 0, 0, 0];
|
||||
const grains = () => mat.reduce((n, m) => n + (m === SAND), 0) + flyers.filter(f => f.m === SAND).length;
|
||||
@@ -75,15 +75,12 @@ const sideLift = run(`(() => {
|
||||
console.log('Side-fed lift, 3000 ticks: ' + JSON.stringify(sideLift));
|
||||
check('a belt feeding a lift from the side fills all four lanes', sideLift.lanes.every(n => n > 0), sideLift.lanes);
|
||||
check('the side-fed lift conserves its sand, including mouth flyers', sideLift.before === sideLift.after, `${sideLift.before} -> ${sideLift.after}`);
|
||||
// Before side spreading: 1459 grains left this lift in 3000 ticks (one lane, nearly full).
|
||||
// The same belt and heap deliver 2243 into an open drop, the most the lift can be given.
|
||||
check('a side-fed lift carries at least 90% of what its belt brings (it was 65%)', sideLift.out >= Math.floor(0.9 * 2243), sideLift.out);
|
||||
|
||||
// Same belt and finite heap, with a non-blocking sink at the screw side.
|
||||
// Same belt and four-grain feed, with a non-blocking sink at the screw side.
|
||||
run(`RS = ${sideSeed}`);
|
||||
fresh();
|
||||
run('RS = 7; for (let bx = 16; bx < 32; bx++) placeBlock(BP_BELT, 1, bx, 25, true);');
|
||||
rect(run, 64, 40, 119, 99, run('SAND'));
|
||||
rect(run, 64, 96, 119, 99, run('SAND'));
|
||||
const beltDelivery = run(`(() => {
|
||||
let out = 0;
|
||||
for (let k = 0; k < 3000; k++) {
|
||||
@@ -97,6 +94,7 @@ const beltDelivery = run(`(() => {
|
||||
return out;
|
||||
})()`);
|
||||
console.log('Belt delivery to a non-blocking sink, 3000 ticks: ' + beltDelivery);
|
||||
check('a side-fed lift carries at least 90% of what its four-grain belt brings', sideLift.out >= Math.floor(0.9 * beltDelivery), `${sideLift.out} / ${beltDelivery}`);
|
||||
|
||||
// --- throughput
|
||||
fresh();
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
// t45: finite carrying bed, local sustained jams, recovery, filters, screws and bars.
|
||||
const fs=require('fs');
|
||||
const {load,flatWorld,suite,ticks}=require('./harness');
|
||||
const {run}=load(), {check,done}=suite();
|
||||
function fresh(){flatWorld(run,256,224,208);run('RS=7;AMB.fill(20);temp.fill(20);weather.next=1e15;nextPowerBuild=0;for(const k of Object.keys(stats))stats[k]=0;');}
|
||||
fresh();
|
||||
run('placeBlock(BP_BELT,1,12,32,true);for(let l=1;l<=8;l++)setCell((128-l)*W+49,SAND,20,0);carryColumn(127*W+49,1,null);');
|
||||
check('exactly four grains ride; the upper heap stays loose',run('Array.from({length:8},(_,k)=>mat[(127-k)*W+(k<4?50:49)]===SAND).every(Boolean)'));
|
||||
check('stall tracking exists',run('typeof transportStalled==="function"'));
|
||||
if(run('typeof transportStalled!=="function"')) {done();process.exit();}
|
||||
for(const code of ['BP_BELT','BP_FILTER','BP_SCREW']) {
|
||||
fresh();
|
||||
run(`for(let bx=12;bx<20;bx++)placeBlock(${code},${code==='BP_FILTER'?128:1},bx,32,true,{m:[],inv:0});
|
||||
for(let y=120;y<132;y++)for(let x=48;x<80;x++)if(mat[y*W+x]===EMPTY)setCell(y*W+x,SAND,20,0);
|
||||
for(let y=110;y<136;y++)setCell(y*W+80,STONE,20,0);
|
||||
placeBlock(BP_BELT,1,30,32,true);
|
||||
for(let bx=19;bx<=30;bx++)placeBlock(BP_SHAFT,0,bx,34,true);
|
||||
placeBlock(BP_SHAFT,0,19,33,true);placeBlock(BP_SHAFT,0,30,33,true);`);
|
||||
ticks(run,10);
|
||||
check(`${code}: a brief wait is not a stall`,run('!transportStalled(32*BW+19)'));
|
||||
ticks(run,240);
|
||||
check(`${code}: loaded blocked tail and upstream run warn`,run('transportStalled(32*BW+19)&&transportStalled(32*BW+15)'));
|
||||
check(`${code}: an unrelated empty belt stays clear (same power net for belts/screws)`,run('!transportStalled(32*BW+30)') && (code==='BP_FILTER' || run('netId[128*W+60]===netId[128*W+120]')));
|
||||
const clock=run('transportClock(32*BW+19)');ticks(run,20);
|
||||
check(`${code}: animation clock freezes`,run('transportClock(32*BW+19)')===clock);
|
||||
run('for(let y=110;y<136;y++)setCell(y*W+80,EMPTY,20,0);');ticks(run,30);
|
||||
check(`${code}: clearing the blockage resumes motion`,run('!transportStalled(32*BW+19) && transportClock(32*BW+19)')>clock);
|
||||
}
|
||||
fresh();run('for(let bx=12;bx<40;bx++)placeBlock(BP_BELT,1,bx,32,true);spawnBar(BARCU,48,126,0,0);');
|
||||
check('a steadily moving bar never reports a stall between whole-grain steps',run(`(()=>{for(let k=0;k<120;k++){simTick();if([...transportState.keys()].some(transportStalled))return false;}return ents[0].ix>90;})()`));
|
||||
fresh();run('placeBlock(BP_BELT,1,12,32,true);placeBlock(BP_WALL,RSTONE,13,31,true);spawnBar(BARCU,48,126,0,0);');ticks(run,100);
|
||||
check('a blocked bar also stalls its carrying belt',run('transportStalled(32*BW+12)'));
|
||||
fresh();
|
||||
run(`for(let k=0;k<8;k++)placeBlock(BP_BELT_UP,1,16+k,40-k,true);
|
||||
for(let y=110;y<132;y++)for(let x=96;x<100;x++)setCell(y*W+x,STONE,20,0);
|
||||
for(let y=110;y<166;y++)for(let x=56;x<96;x++)if(!part[y*W+x]&&mat[y*W+x]===EMPTY)setCell(y*W+x,SAND,20,0);`);
|
||||
ticks(run,100);
|
||||
check('a blocked cleat stops its whole slope run',run('Array.from({length:8},(_,k)=>transportStalled((40-k)*BW+16+k)).every(Boolean)'));
|
||||
const phase=run('slopePhase[40*BW+16]'), slopeClock=run('transportClock(40*BW+16)');ticks(run,20);
|
||||
check('stalled cleats and stripe clocks stay still together',run('slopePhase[40*BW+16]')===phase && run('transportClock(40*BW+16)')===slopeClock);
|
||||
fresh();
|
||||
run(`for(let k=0;k<8;k++)placeBlock(BP_FILTER_DOWN,128,16+k,24+k,true,{m:[],inv:0});
|
||||
for(let y=100;y<140;y++)setCell(y*W+96,STONE,20,0);
|
||||
for(let x=64;x<96;x++){const by=24+(x>>2)-16,y=beltFaceY(BP_FILTER_DOWN,x>>2,by,x);for(let l=1;l<=4;l++)setCell((y-l)*W+x,SAND,20,0);}`);
|
||||
ticks(run,300);
|
||||
check('a jammed downhill screen warns on its slower nudge cadence',run('transportStalled(31*BW+23)'));
|
||||
fresh();run(fs.readFileSync(__dirname+'/fixtures/t45-loop.js','utf8'));ticks(run,1800);
|
||||
console.log('Overloaded loop: '+run('JSON.stringify({moved:stats.moved,grains:mat.reduce((n,m)=>n+(m===SAND||m===CUORE),0),flyers:flyers.length,stalled:[...transportState].filter(([b,s])=>s.age>=STALL_TICKS).length})'));
|
||||
check('overloaded loop conserves its 832 grains',run('mat.reduce((n,m)=>n+(m===SAND||m===CUORE),0)+flyers.length')===832);
|
||||
done();
|
||||
@@ -0,0 +1,71 @@
|
||||
"""t45: real transport warnings, hover, frozen animation and recovery in both looks."""
|
||||
import pathlib
|
||||
import sys
|
||||
sys.path.insert(0, str(pathlib.Path(__file__).resolve().parents[1] / 'tools'))
|
||||
from use_venv import ensure_playwright; ensure_playwright()
|
||||
from browser import launch, consume_gpu_option
|
||||
consume_gpu_option()
|
||||
ROOT = pathlib.Path(__file__).resolve().parents[1]
|
||||
with launch(viewport={'width': 1280, 'height': 760}, page_path=sys.argv[1] if len(sys.argv)>1 else None) as (_, page, url):
|
||||
errors=[]
|
||||
page.on('pageerror', lambda e: errors.append(str(e)))
|
||||
page.goto(url)
|
||||
page.evaluate("""() => {
|
||||
const enter=enterGame;enterGame=()=>{enter();GAME.paused=true;};
|
||||
SET.look='pixel';SET.workerCap=0;SET.autosave=0;startNewWorld(7,768,600);
|
||||
}""")
|
||||
page.wait_for_function('GAME.running', timeout=60000)
|
||||
page.evaluate("""() => {
|
||||
mat.fill(EMPTY);part.fill(0);blockP.fill(0);blockV.fill(0);isPow.fill(0);
|
||||
flags.fill(0);temp.fill(20);AMB.fill(20);bgm.fill(0);fog.fill(255);
|
||||
machines=[null];molds=[];ents=[];flyers=[];treeBases=[];saplings=[];
|
||||
pcells.clear();beltL=[];beltR=[];screwLists=Array.from({length:IS_SCREW.length},()=>[]);
|
||||
weather.next=1e15;weather.shower=null;surfY.fill(300);updateRoof();
|
||||
bot.x=320;bot.y=210;money=500;ledger={};nextPowerBuild=0;
|
||||
for(const [code,v,by] of [[BP_BELT,1,46],[BP_FILTER,128,52],[BP_SCREW,1,58]]) {
|
||||
for(let bx=68;bx<86;bx++)placeBlock(code,v,bx,by,true,{m:[],inv:0});
|
||||
// The return screw hangs just above the loaded belt.
|
||||
if(code===BP_BELT)for(let bx=70;bx<86;bx++)placeBlock(BP_SCREW,3,bx,44,true);
|
||||
for(let y=by*4-4;y<by*4+(code===BP_SCREW?4:0);y++)
|
||||
for(let x=272;x<344;x++)setCell(y*W+x,SAND,20,0);
|
||||
for(let y=by*4-8;y<by*4+8;y++)setCell(y*W+344,STONE,20,0);
|
||||
}
|
||||
for(let k=0;k<180;k++)simTick();
|
||||
$('hud').hidden=true;
|
||||
SET.fog=false;SET.zoom=9;SET.lighting='off';SET.glow='off';SET.minimap=false;
|
||||
SET.cameraLead=false;GAME.infoOverlay=false;GAME.hidePreviews=true;
|
||||
document.querySelectorAll('.card-pop,.toast').forEach(e=>e.remove());R.floaters=[];
|
||||
resizeRender();snapCamera();renderDirty.fill(1);GPU.world=null;SM.surf=-1;
|
||||
}""")
|
||||
assert page.evaluate("hoverInfo(330,185).warn==='stalled'"), 'jam has no hover warning'
|
||||
assert page.evaluate("hoverInfo(330,185).t2.some(t=>t.includes('Clear the outlet or the top of the heap'))")
|
||||
assert page.evaluate("[46,52,58].every(by=>transportStalled(by*BW+82))")
|
||||
for look in ['pixel','smooth']:
|
||||
page.evaluate(f"SET.look='{look}'")
|
||||
page.wait_for_function(f"R.smoothOn === {'true' if look=='smooth' else 'false'}")
|
||||
page.wait_for_timeout(150)
|
||||
assert page.evaluate("""() => {
|
||||
const p=worldToScreenPx(330,185),ctx=R.ctx;
|
||||
ctx.clearRect(0,0,R.canvas.width,R.canvas.height);drawTransportStalls();
|
||||
const shown=ctx.getImageData(p.x,p.y,1,1).data[3];
|
||||
SET.fog=true;fog.fill(0);ctx.clearRect(0,0,R.canvas.width,R.canvas.height);drawTransportStalls();
|
||||
const hidden=ctx.getImageData(p.x,p.y,1,1).data[3];SET.fog=false;fog.fill(255);
|
||||
return shown>0 && hidden===0;
|
||||
}""")
|
||||
# Compare actual built pixels while simulation time advances under the jam.
|
||||
page.wait_for_timeout(60)
|
||||
clips=page.evaluate("""() => [184,208,233].map(y=>{const p=worldToScreenPx(329,y);return {x:p.x,y:p.y,width:18,height:9};})""")
|
||||
before=[page.screenshot(clip=c) for c in clips]
|
||||
page.evaluate('for(let k=0;k<10;k++)simTick()')
|
||||
page.wait_for_timeout(60)
|
||||
assert [page.screenshot(clip=c) for c in clips]==before, f'{look}: stalled transport still animates'
|
||||
path=ROOT / ('shots/t45-stalled.png' if look=='pixel' else 'shots/t45-stalled-smooth.png')
|
||||
page.screenshot(path=str(path))
|
||||
print(f'PASS {look}: marker with overlay off, fog, hover, frozen rendered belt and screenshot')
|
||||
page.evaluate("""() => {
|
||||
for(const by of [46,52,58])for(let y=by*4-8;y<by*4+8;y++)for(let x=344;x<360;x++)setCell(y*W+x,EMPTY,20,0);
|
||||
for(let k=0;k<60;k++)simTick();
|
||||
}""")
|
||||
assert page.evaluate('!transportStalled(46*BW+82)'), 'belt did not recover'
|
||||
assert not errors, errors
|
||||
print('PASS cleared blockage resumes transport; no page errors')
|
||||
+7
-1
@@ -26,6 +26,11 @@ function setup() {
|
||||
const m=materials[(random()*materials.length)|0];
|
||||
setCell(y*W+x,m,m===MAGMA?1450:m===MCU?1200:m===STEAM?110:20,0);
|
||||
}
|
||||
for(let y=208;y<230;y++)for(let x=164;x<220;x++)setCell(y*W+x,EMPTY,20,0);
|
||||
for(let bx=42;bx<53;bx++)placeBlock(BP_BELT,1,bx,55,true);
|
||||
for(let y=210;y<222;y++)for(let x=212;x<216;x++)setCell(y*W+x,STONE,20,0);
|
||||
for(let y=216;y<220;y++)for(let x=168;x<212;x++)setCell(y*W+x,SAND,20,0);
|
||||
for(let k=0;k<6;k++)placeBlock(BP_BELT_UP,1,70+k,48-k,true);
|
||||
// A cohesive vertical face spans chunk rows and must collapse identically in every pool.
|
||||
for(let y=50;y<90;y++)for(let x=260;x<280;x++)
|
||||
setCell(y*W+x,(x+y)%5===0?LCLAY:RSTONE,20,0);
|
||||
@@ -42,7 +47,7 @@ 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})'));
|
||||
h.update(run('JSON.stringify({RS,stats,EV,flyers,brokenMachines,pcells:[...pcells],powerDirty,weather,machines,transport:[...transportState],slopePhase:Array.from(slopePhase)})'));
|
||||
return h.digest('hex');
|
||||
}
|
||||
(async () => {
|
||||
@@ -51,6 +56,7 @@ function hash() {
|
||||
setup(); const stop = n ? await enableThreads(run,n) : null;
|
||||
try {
|
||||
run('for(let k=0;k<320;k++)simTick();');
|
||||
assert(run('transportStalled(55*BW+48)'), 'worker fixture must include a loaded stalled belt');
|
||||
const actual=hash();
|
||||
if(!expected)expected=actual;
|
||||
assert.strictEqual(actual,expected,`world and effects differ with ${n} workers`);
|
||||
|
||||
Reference in New Issue
Block a user