Bot can never be trapped in rock: nudge, retrace trail, fly out (t72)
This commit is contained in:
@@ -623,3 +623,10 @@ above, including:
|
||||
count toward the raised nozzle allowance (4,000); only rain counts toward rain's 2,500.
|
||||
Factory launches remain uncapped. A full nozzle allowance says “Too much in the air.”
|
||||
The 4,000 tuning preserves the existing eight-second blown-pile collapse check; 6,000 did not.
|
||||
|
||||
- **Buried bot rescue (t72, 2026-10-29).** The owner chose a small nearest-first nudge,
|
||||
then the newest still-open visited position with a message, then flight through rock until
|
||||
open space with a message. This prevents cooling magma from trapping the bot. The nudge
|
||||
requires fully explored space and prefers up on equal distances. A 256-point trail records
|
||||
every four grains without per-tick allocations; loading rebuilds it from the bot position.
|
||||
Searches happen only on burial, capped at 440 nudge and 256 trail probes per escape episode.
|
||||
|
||||
+11
-7
@@ -244,7 +244,7 @@ Starter furnace designs and the milestone list shown in the notebook.
|
||||
- functions: smelterBlueprint
|
||||
- variables: STARTER_DESIGNS, MILESTONES
|
||||
|
||||
### src/sim/world.js (123 lines)
|
||||
### src/sim/world.js (124 lines)
|
||||
World state: the per-cell typed arrays, alloc(), ambient temperature by depth, waking.
|
||||
- functions: emit, bagCap, discover, sharedArray, alloc, depthU, depthY, meanSurface, computeAmbient, fillNoise, renderDirtyXY, renderDirtyChunk, renderDirtyIdx, wakeXY, wakeHeatXY, wakeIdx, wakeHeatIdx
|
||||
- variables: W, H, N, CW, CH, NCH, mat, temp, life, flags, stamp, part, mach, digp, noiseB, airS, netId, isPow, occ, surfY, dirtY, graniteY, basaltY, magmaY, roofY, AMB, worldSeed, surfBase, underD, shallowD, lapse, BW, BH, NB, blockP, blockV, ents, bgm, flyers, ghostBlocks, ghostMolds, renderDirty, awake, hot, hotLvl, powderArea, roofDirty, loadWakeOrder, loadWakePending, loadWakeIndex, tick, tickStamp, machines, molds, nets, pcells, powerDirty, beltL, beltR, screwLists, brokenMachines, ghostMachines, ghostBlocked, EV, bot, bag, bagTotal, player, seen, ledger, stats
|
||||
@@ -337,7 +337,7 @@ Trees (background layer), acorns, saplings, and waking freezing water.
|
||||
- functions: cutTree, treeHeld, findTreeBases, treeSupportTick, woodRoom, treeFall, coldTick, plantTick, setTreeCell, saplingTick, igniteTree
|
||||
- variables: treeBases, saplings, treeDraw
|
||||
|
||||
### src/sim/vacuum.js (200 lines)
|
||||
### src/sim/vacuum.js (203 lines)
|
||||
The vacuum, blowing grains out, grains in flight (flyers), and the flame tool. Material the vacuum would start on at the cursor: a bar, else the nearest loose grain.
|
||||
- functions: vacuumTargetAt, toolVacuum, nozzle, toolBlow, flyOpen, depositNear, bagTemp, landFlyer, flyTick, toolIgnite
|
||||
- variables: blowRR, FLY_G, FLY_Q
|
||||
@@ -355,16 +355,16 @@ Undo (Ctrl+Z) for building, and the bot building ghosts.
|
||||
- functions: undoNote, undoPush, undoRecord, undoLast, clearAllGhosts, ghostTick, ghostTick0
|
||||
- variables: UNDO_MAX, undoStack, undoRec, ghostMissing, ghostIter
|
||||
|
||||
### src/sim/bot.js (55 lines)
|
||||
### src/sim/bot.js (99 lines)
|
||||
The bot: movement and collision with the world.
|
||||
- functions: botBlocked, botOverlapSolid, botSlide, botStep
|
||||
- variables: SLIDE_MAX, SLIDE_STEP
|
||||
- functions: botBlocked, botOverlapSolid, botTrailRecord, botNudgeOpen, botRescue, botSlide, botStep
|
||||
- variables: BOT_TRAIL_SIZE, botTrailX, botTrailY, botTrailHead, botTrailCount, botEscape, BOT_NUDGES, SLIDE_MAX, SLIDE_STEP
|
||||
|
||||
### src/sim/ledger.js (54 lines)
|
||||
Milestone checks (the ledger) and the sky roof line used for cooling.
|
||||
- functions: checkLedger, updateRoof, updateDirtyRoof, scanRoof
|
||||
|
||||
### src/sim/weather.js (87 lines)
|
||||
### src/sim/weather.js (90 lines)
|
||||
Weather: clouds from steam, rain and snow, and the world water count.
|
||||
- functions: wrnd, resetWeather, rainGap, worldWater, absorbSteam, spawnRain, rainAt, weatherTick
|
||||
- variables: WBAND, WS, weather
|
||||
@@ -507,7 +507,7 @@ Startup capabilities and title art.
|
||||
- functions: checkCapabilities, renderCapabilityNotice, canStartGame, buildTitleCells, drawTitle
|
||||
- variables: REQUIRE_WEBGPU, REQUIRE_THREADS, CAPS, CAPS_CHECK, FONT5, TITLE
|
||||
|
||||
### src/ui/saves.js (224 lines)
|
||||
### src/ui/saves.js (237 lines)
|
||||
Saves in IndexedDB, world files (export / import) and the blueprint library.
|
||||
- functions: newWorldId, worldWrite, listWorlds, putWorldEntry, worldNewest, deleteWorld, cleanWorldName, renameWorld, migrateWorlds, saveWorld, newestSave, b64FromBytes, bytesFromB64, gz, packBuffers, unpackBuffers, exportWorld, importWorldFile, libLoad, libSave, bpEncode, bpValid, bpDecode
|
||||
- variables: DB, worldIdTime, worldIdCounter, worldWrites, savingCount
|
||||
@@ -639,6 +639,9 @@ The frame loop and boot.
|
||||
- `test/t_basics.js`: Basics: drilling to rubble, the vacuum and blowing, walls, the ingot mold and bars, belts, blueprints, save/load.
|
||||
- `test/t_blow_collapse.js`: Owner's upward nozzle recipe, inline and with real shared-memory helpers.
|
||||
- `test/t_blow_collapse.py`: Straight-up bagged sand nozzle in the real pixel frame loop.
|
||||
- `test/t_blueprint_valid.js`: Shared blueprint codes must be safe before entering the clipboard or ghost queues.
|
||||
- `test/t_bot_rescue.js`: t72: rescue uses visited space before allowing a bounded escape through rock.
|
||||
- `test/t_bot_rescue.py`: t72: rescue messages and keyboard escape in the pixel game.
|
||||
- `test/t_build.js`: Building: undo (Ctrl+Z), cut, replace a machine with Ctrl, pasting ghosts, and the fan heat warning.
|
||||
- `test/t_caps.py`: Capability detection, title notices and the required-WebGPU startup gate.
|
||||
- `test/t_conveyors.js`: Screw conveyors that never carry liquid (it flows through), screw and belt throughput, filters.
|
||||
@@ -655,6 +658,7 @@ The frame loop and boot.
|
||||
- `test/t_gpu.py`: Dirty uploads, default limits, warm pipelines, world replacement and device recovery.
|
||||
- `test/t_gpu_golden.py`: Frozen world references; seven smooth crops corrected for continuous noise in t91.
|
||||
- `test/t_gpu_light.py`: The former CPU light behavioural checks, now read from the actual GPU light grid.
|
||||
- `test/t_import_save.js`: Import must preserve the running world before reading or replacing it.
|
||||
- `test/t_joins.js`: The oracle states the geometric rule independently of the precomputed neighbour tables.
|
||||
- `test/t_lakes.js`: Generated lakes wake without a tool, settle on their own, and have dry tree bases.
|
||||
- `test/t_load.js`: Reload wakes nearby areas first, without losing distant water, fire or saved state.
|
||||
|
||||
@@ -74,6 +74,12 @@ checks waking within eight ticks separately from the probabilistic consumption o
|
||||
wake/background-tree change. It also checks generated tree bases, startup wakes, and the
|
||||
seed-7 lake leveling within two grains after 6,000 undisturbed ticks, fresh and reloaded.
|
||||
|
||||
`test/t_bot_rescue.js` covers deep basalt burial, nearest/upward and diagonal nudges,
|
||||
unexplored-pocket rejection, newest open trail points, ring wrap/load reset, bounded searches,
|
||||
and flight through rock stopping at open space. Run `python3 test/t_bot_rescue.py` for the
|
||||
focused pixel-browser check: both rescue messages and real keyboard flight out of basalt;
|
||||
screenshots go to `shots/t72-{retrace,escape,open}.png`.
|
||||
|
||||
## Browser tests
|
||||
|
||||
- They need the `.venv` from README.md → Develop; plain `python3` switches to it on its own.
|
||||
|
||||
@@ -4,6 +4,21 @@ What agents have done, newest first. Add an entry for every piece of work: the d
|
||||
note or request it was for, what changed, the files touched, the tests run, and anything left
|
||||
open or not verified. Keep each entry short; the git history has the details.
|
||||
|
||||
## 2026-10-29: t72 rescue the bot from buried natural rock (Codex)
|
||||
|
||||
- Added explored nearest/up-first nudges, a 256-point typed-array trail and messaged
|
||||
retrace/fly-through escape. Trail update O(1), allocation-free; searches capped at 696
|
||||
candidates per burial. Trail resets/rebuilds on load; no worldgen/save format changes.
|
||||
- Files: src/sim/{bot,world}.js, test/t_bot_rescue.{js,py}, docs/{DESIGN,TESTING,PROJECT_MAP}.md,
|
||||
tasks/t72* and this log. Regressions failed first; all 39 headless suites pass (18 rescue checks).
|
||||
- Only browser test: python3 test/t_bot_rescue.py (messages, real keyboard escape, pixel,
|
||||
no page errors); inspected shots/t72-{retrace,escape,open}.png. No full browser suite.
|
||||
- tools/perf.py settled/pixel before -> after: median frame interval 16.67 -> 16.67 ms;
|
||||
CPU frame p50/p95 zoom 2: 9.71/11.34 -> 9.02/10.40 ms, zoom 6: 5.26/7.77 -> 4.19/5.01 ms.
|
||||
Short samples show no regression, not a proven speedup. Sandbox WebGPU failed; host GPU passed.
|
||||
Evidence: /tmp/t72-{red,headless,browser,before,after}.log and before/after JSON.
|
||||
Owner playtest pending. No commit.
|
||||
|
||||
## 2026-10-29: t96 wake new-world lakes and keep trees off their banks (Codex)
|
||||
|
||||
- Wake water after startup reset; omit whole tree candidates on wet/water-connected low banks.
|
||||
|
||||
+52
-8
@@ -8,6 +8,46 @@ function botOverlapSolid(x, y) {
|
||||
for (let cy = y0; cy <= y1; cy++) for (let cx = x0; cx <= x1; cx++) if (botBlocked(cy * W + cx)) return true;
|
||||
return false;
|
||||
}
|
||||
// Only visited, open positions enter this fixed ring; normal recording is O(1).
|
||||
const BOT_TRAIL_SIZE = 256;
|
||||
const botTrailX = new Float64Array(BOT_TRAIL_SIZE), botTrailY = new Float64Array(BOT_TRAIL_SIZE);
|
||||
let botTrailHead = 0, botTrailCount = 0, botEscape = false;
|
||||
function botTrailRecord() {
|
||||
const last = (botTrailHead + BOT_TRAIL_SIZE - 1) % BOT_TRAIL_SIZE;
|
||||
const dx = bot.x - botTrailX[last], dy = bot.y - botTrailY[last];
|
||||
if (botTrailCount && dx * dx + dy * dy < BS * BS) return;
|
||||
botTrailX[botTrailHead] = bot.x; botTrailY[botTrailHead] = bot.y;
|
||||
botTrailHead = (botTrailHead + 1) % BOT_TRAIL_SIZE;
|
||||
botTrailCount = Math.min(BOT_TRAIL_SIZE, botTrailCount + 1);
|
||||
}
|
||||
// Precompute once: 440 probes within three blocks, nearest first and up on ties.
|
||||
const BOT_NUDGES = [];
|
||||
for (let y = -12; y <= 12; y++) for (let x = -12; x <= 12; x++) {
|
||||
if (x * x + y * y > 0 && x * x + y * y <= 144) BOT_NUDGES.push([x, y]);
|
||||
}
|
||||
BOT_NUDGES.sort((a, b) => a[0] * a[0] + a[1] * a[1] - b[0] * b[0] - b[1] * b[1] || a[1] - b[1] || a[0] - b[0]);
|
||||
function botNudgeOpen(x, y) {
|
||||
if (botOverlapSolid(x, y)) return false;
|
||||
for (let cy = Math.floor(y); cy < Math.ceil(y + bot.h); cy++)
|
||||
for (let cx = Math.floor(x); cx < Math.ceil(x + bot.w); cx++) if (fogSeen(cx, cy) < 255) return false;
|
||||
return true;
|
||||
}
|
||||
function botRescue() {
|
||||
for (let k = 0; k < BOT_NUDGES.length; k++) {
|
||||
const x = bot.x + BOT_NUDGES[k][0], y = bot.y + BOT_NUDGES[k][1];
|
||||
if (botNudgeOpen(x, y)) { bot.x = x; bot.y = y; return; }
|
||||
}
|
||||
// At most 256 checks. Discard the buried end so the next retrace stays behind us.
|
||||
for (let k = 0; k < botTrailCount; k++) {
|
||||
const i = (botTrailHead + BOT_TRAIL_SIZE - 1 - k) % BOT_TRAIL_SIZE;
|
||||
if (botOverlapSolid(botTrailX[i], botTrailY[i])) continue;
|
||||
bot.x = botTrailX[i]; bot.y = botTrailY[i]; bot.vx = bot.vy = 0;
|
||||
botTrailHead = (i + 1) % BOT_TRAIL_SIZE; botTrailCount -= k;
|
||||
emit({t: 'toast', text: 'Dug out: back where you came from'}); return;
|
||||
}
|
||||
botEscape = true;
|
||||
emit({t: 'toast', text: 'Dug out: Fly through the rock to open space'});
|
||||
}
|
||||
// Flying straight into a lip of rock a grain or two deep at the edge of the bot, it slips
|
||||
// sideways round it instead of stopping dead, so it threads tunnels and holes it nearly fits.
|
||||
const SLIDE_MAX = 2.5, SLIDE_STEP = 0.34;
|
||||
@@ -37,19 +77,23 @@ function botStep(ix, iy, boost) {
|
||||
bot.vx *= 0.885; bot.vy *= 0.885;
|
||||
if (ix) bot.face = ix > 0 ? 1 : -1;
|
||||
bot.thrust = Math.min(1, Math.hypot(ix, iy));
|
||||
if (botOverlapSolid(bot.x, bot.y)) {
|
||||
// unstick: search upward then sideways for a free spot
|
||||
for (let k = 1; k < 40; k++) {
|
||||
if (!botOverlapSolid(bot.x, bot.y - k)) { bot.y -= k; break; }
|
||||
if (!botOverlapSolid(bot.x - k, bot.y)) { bot.x -= k; break; }
|
||||
if (!botOverlapSolid(bot.x + k, bot.y)) { bot.x += k; break; }
|
||||
}
|
||||
}
|
||||
const buried = botOverlapSolid(bot.x, bot.y);
|
||||
if (!buried) {
|
||||
botEscape = false;
|
||||
if (!botTrailCount) botTrailRecord();
|
||||
} else if (!botEscape) botRescue();
|
||||
const steps = Math.ceil(Math.max(Math.abs(bot.vx), Math.abs(bot.vy)) / 0.45) || 1;
|
||||
const sx = bot.vx / steps, sy = bot.vy / steps;
|
||||
for (let s = 0; s < steps; s++) {
|
||||
if (botEscape) {
|
||||
bot.x = Math.max(0, Math.min(W - bot.w, bot.x + sx));
|
||||
bot.y = Math.max(0, Math.min(H - bot.h, bot.y + sy));
|
||||
if (!botOverlapSolid(bot.x, bot.y)) botEscape = false;
|
||||
continue;
|
||||
}
|
||||
if (sx) { if (!botOverlapSolid(bot.x + sx, bot.y)) bot.x += sx; else if (!(ix && !iy && botSlide(0, sx))) { bot.vx *= -0.15; } }
|
||||
if (sy) { if (!botOverlapSolid(bot.x, bot.y + sy)) bot.y += sy; else if (!(iy && !ix && botSlide(1, sy))) { bot.vy *= -0.15; } }
|
||||
}
|
||||
if (!botEscape) botTrailRecord();
|
||||
// loose grains, liquids and gases pass straight through the bot: it never moves them
|
||||
}
|
||||
|
||||
@@ -46,6 +46,7 @@ function sharedArray(Type, length) {
|
||||
}
|
||||
function alloc(w, h) {
|
||||
resetDevConsole();
|
||||
botTrailHead = botTrailCount = 0; botEscape = false;
|
||||
W = w; H = h; N = w * h;
|
||||
fogAlloc();
|
||||
CW = Math.ceil(w / 32); CH = Math.ceil(h / 32); NCH = CW * CH;
|
||||
|
||||
@@ -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