Bot can never be trapped in rock: nudge, retrace trail, fly out (t72)
This commit is contained in:
@@ -0,0 +1,56 @@
|
||||
// t72: rescue uses visited space before allowing a bounded escape through rock.
|
||||
const {load, suite, flatWorld} = require('./harness');
|
||||
const {run} = load(), {check, done} = suite();
|
||||
function setup() {
|
||||
flatWorld(run, 320, 240, 220);
|
||||
run('mat.fill(EMPTY); flags.fill(0); fog.fill(255); EV.length=0; bot.x=24; bot.y=100; bot.vx=bot.vy=0; botStep(0,0,false);');
|
||||
}
|
||||
function bury() { run('mat.fill(BASALT); flags.fill(F_NAT);'); }
|
||||
function clear(x,y,w=5,h=5) { run(`for(let y=${y};y<${y+h};y++) for(let x=${x};x<${x+w};x++) {mat[y*W+x]=EMPTY;flags[y*W+x]=0;}`); }
|
||||
setup();
|
||||
run('for(let x=28;x<=140;x+=4){bot.x=x;botStep(0,0,false);}');
|
||||
bury(); clear(24,100);
|
||||
run('botStep(0,0,false)');
|
||||
check('deep basalt retraces to the last open pool edge', run('bot.x===24 && bot.y===100'));
|
||||
check('retrace explains the move', run('EV.some(e=>e.text==="Dug out: back where you came from")'));
|
||||
setup(); bury(); clear(24,95); run('botStep(0,0,false)');
|
||||
check('plain pop to the top is preserved', run('bot.x===24 && bot.y===95'));
|
||||
setup(); bury(); clear(24,95); clear(29,100); run('botStep(0,0,false)');
|
||||
check('equal-distance nudge prefers up', run('bot.x===24 && bot.y===95'));
|
||||
setup(); bury(); clear(29,105); run('botStep(0,0,false)');
|
||||
check('nudge finds a diagonal downward opening', run('bot.x===29 && bot.y===105'));
|
||||
setup(); bury(); clear(24,95); run('fog.fill(0);botStep(0,0,false)');
|
||||
check('unexplored pocket is never chosen', run('bot.x===24 && bot.y===100'));
|
||||
check('buried trail enables escape with a message', run('EV.some(e=>e.text && e.text.includes("Fly through"))'));
|
||||
run('for(let k=0;k<80;k++) botStep(1,0,false)');
|
||||
check('escape allows movement through solid rock', run('bot.x>55 && bot.y===100'));
|
||||
check('escape message appears once', run('EV.filter(e=>e.t==="toast").length===1'));
|
||||
setup(); bury(); clear(55,90,30,30);
|
||||
run('for(let k=0;k<160;k++) botStep(1,0,false)');
|
||||
check('escape ends in open space and next rock stops the bot', run('bot.x>=55 && bot.x<=80 && !botOverlapSolid(bot.x,bot.y)'));
|
||||
setup();
|
||||
run('for(let k=0;k<600;k++){bot.x=24+(k%60)*4;botStep(0,0,false);}');
|
||||
check('trail storage is fixed and wraps', run('botTrailCount===256 && botTrailX.length===256 && botTrailY.length===256'));
|
||||
run('const savedHead=botTrailHead; for(let k=0;k<60;k++) botStep(0,0,false);');
|
||||
check('stationary bot adds no points', run('savedHead===botTrailHead'));
|
||||
run('const trailX=botTrailX; alloc(320,240);');
|
||||
check('new world clears trail and escape without reallocating storage', run('botTrailCount===0 && !botEscape && trailX===botTrailX'));
|
||||
setup();
|
||||
run('const rescueSave=serializeWorld(); bot.x=100;botStep(0,0,false);deserializeWorld(rescueSave.meta,rescueSave.buffers);botStep(0,0,false);');
|
||||
check('load rebuilds trail from the restored bot', run('botTrailCount===1 && botTrailX[0]===24 && botTrailY[0]===100'));
|
||||
setup();
|
||||
run('bot.x=28;botStep(0,0,false);bot.x=140;botStep(0,0,false);');
|
||||
bury(); clear(24,100); clear(28,100);
|
||||
run('botStep(0,0,false)');
|
||||
check('retrace chooses the newest open point', run('bot.x===28'));
|
||||
setup(); bury();
|
||||
run(`const originalOverlap=botOverlapSolid;let rescueChecks=0;
|
||||
botOverlapSolid=(x,y)=>{rescueChecks++;return originalOverlap(x,y);};
|
||||
botStep(0,0,false); const firstChecks=rescueChecks;rescueChecks=0;
|
||||
botStep(0,0,false);const escapeChecks=rescueChecks;botOverlapSolid=originalOverlap;`);
|
||||
check('rescue work has a fixed per-tick cap', run('firstChecks<=BOT_NUDGES.length+BOT_TRAIL_SIZE+3'));
|
||||
check('active escape does not repeat searches', run('escapeChecks<=2'));
|
||||
setup();
|
||||
run('const originalRescue=botRescue;let searches=0;botRescue=()=>{searches++;originalRescue();};for(let k=0;k<120;k++)botStep(1,0,false);botRescue=originalRescue;');
|
||||
check('normal flight never runs rescue searches', run('searches===0'));
|
||||
done();
|
||||
@@ -0,0 +1,48 @@
|
||||
"""t72: rescue messages and keyboard escape in the pixel game."""
|
||||
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}) 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.cameraLead=false;SET.autosave=0;
|
||||
startNewWorld(7,768,600);
|
||||
}""")
|
||||
page.wait_for_function('GAME.running', timeout=60000)
|
||||
page.evaluate("""() => {
|
||||
mat.fill(EMPTY);flags.fill(0);temp.fill(20);bgm.fill(0);fog.fill(255);
|
||||
machines=[null];molds=[];ents=[];flyers=[];treeBases=[];saplings=[];
|
||||
weather.next=1e15;weather.shower=null;surfY.fill(300);updateRoof();
|
||||
botTrailCount=botTrailHead=0;botEscape=false;bot.vx=bot.vy=0;bot.y=200;
|
||||
for(let x=200;x<=360;x+=4){bot.x=x;botStep(0,0,false);}
|
||||
for(let y=140;y<280;y++)for(let x=240;x<450;x++)setCell(y*W+x,BASALT,20,F_NAT);
|
||||
document.querySelectorAll('.card,.toast').forEach(e=>e.remove());
|
||||
botStep(0,0,false);drainEvents();
|
||||
SET.zoom=6;resizeRender();snapCamera();renderDirty.fill(1);GPU.world=null;SM.surf=-1;
|
||||
}""")
|
||||
assert page.evaluate('bot.x===232 && !botOverlapSolid(bot.x,bot.y)')
|
||||
assert page.get_by_text('Dug out: back where you came from', exact=True).is_visible()
|
||||
page.screenshot(path=str(ROOT / 'shots/t72-retrace.png'))
|
||||
page.evaluate("""() => {
|
||||
document.querySelectorAll('.toast').forEach(e=>e.remove());
|
||||
botTrailCount=botTrailHead=0;bot.x=360;bot.y=200;bot.vx=bot.vy=0;
|
||||
botStep(0,0,false);drainEvents();snapCamera();
|
||||
}""")
|
||||
assert page.get_by_text('Dug out: Fly through the rock to open space', exact=True).is_visible()
|
||||
page.screenshot(path=str(ROOT / 'shots/t72-escape.png'))
|
||||
page.keyboard.down('d')
|
||||
page.evaluate('GAME.paused=false')
|
||||
page.wait_for_function('bot.x>=450 && !botEscape', timeout=15000)
|
||||
page.keyboard.up('d')
|
||||
page.evaluate('GAME.paused=true')
|
||||
assert page.evaluate('!botOverlapSolid(bot.x,bot.y)')
|
||||
page.screenshot(path=str(ROOT / 'shots/t72-open.png'))
|
||||
assert not errors, errors
|
||||
print('PASS pixel retrace toast, fallback toast, real keyboard flight through basalt and exit; no page errors')
|
||||
Reference in New Issue
Block a user