Liquids in the bag can be blown out without the Flask (t103)

This commit is contained in:
2026-10-29 23:33:52 -04:00
parent ac5f5a06d1
commit af411d5b21
9 changed files with 107 additions and 7 deletions
+45 -1
View File
@@ -101,6 +101,50 @@ with launch() as (browser, page, url):
page.wait_for_function(f'tick === {tick + 5} && devSteps === 0')
page.wait_for_timeout(150)
check('step advances exactly five ticks then stays paused', page.evaluate('tick') == tick + 5)
# t103: real bag selection and mouse input, with deterministic dev stepping into a pit.
page.evaluate('''() => {
player.flask = false; bag.fill(0); bagTotal = 0; flyers.length = 0;
for (let y = 20; y <= 150; y++) for (let x = 60; x <= 190; x++) {
const i = y * W + x;
part[i] = mach[i] = bgm[i] = 0;
setCell(i, y >= 145 || x === 60 || x === 190 ? STONE : EMPTY, 20, 0);
}
bot.x = 100; bot.y = 100; bot.vx = bot.vy = 0;
SET.fog = false; SET.lighting = 'off'; SET.cameraLead = false; SET.zoom = 4;
SET.cardSeconds = 0; $('cards').replaceChildren();
resizeRender(); snapCamera(); selectTool(1); GAME.blow.clear();
lastBagKey = ''; hudDirty = true;
}''')
command('give water 500')
page.keyboard.press('Escape')
page.locator('#baglist .pick[title="Select Water"]').click()
check('console water is visible and selectable without the Flask', page.evaluate('!player.flask && bag[WATER] === 500 && GAME.blow.has(WATER)'))
point = page.evaluate('worldToScreenPx(130, 135)')
scale = page.evaluate('R.canvas.width / window.innerWidth')
page.mouse.move(point['x'] / scale, point['y'] / scale)
page.mouse.down(button='right')
page.evaluate('runDevCommand("step 4")')
page.wait_for_function('devSteps === 0')
page.mouse.up(button='right')
check('right click blows water without the Flask', page.evaluate('!player.flask && bag[WATER] < 500 && flyers.some(f => f.m === WATER)'))
page.evaluate('runDevCommand("step 240")')
page.wait_for_function('devSteps === 0')
check('water lands in the pit and leaves the bag count reduced', page.evaluate('''() => {
let landed = 0;
for (let y = 20; y < 145; y++) for (let x = 61; x < 190; x++) if (mat[y * W + x] === WATER) landed++;
return !player.flask && !flyers.length && landed > 0 && landed + bag[WATER] === 500;
}'''))
page.evaluate('runDevCommand("give sand 100"); hudDirty = true;')
page.keyboard.down('Shift')
page.wait_for_function("$('selcard').textContent.includes('Everything')")
check('Shift preview includes carried water without the Flask', page.evaluate("blowAllList().includes(WATER) && [...$('baglist').querySelectorAll('.bagrow.on .nm')].some(e => e.textContent === 'Water')"))
page.screenshot(path=str(SHOTS / 't103-water-pit.png'))
water = page.evaluate('bag[WATER]')
page.mouse.down(button='right')
page.evaluate('runDevCommand("step 4")')
page.wait_for_function('devSteps === 0')
page.mouse.up(button='right'); page.keyboard.up('Shift')
check('Shift right click blows both water and sand without the Flask', page.evaluate(f'!player.flask && bag[WATER] < {water} && bag[SAND] < 100'))
# Drive the actual frame accumulator with controlled elapsed time and no scheduled frames.
counts = page.evaluate('''() => {
requestAnimationFrame = () => 0;
@@ -133,7 +177,7 @@ with launch() as (browser, page, url):
}'''))
page.reload()
page.wait_for_function("!$('title').hidden")
check('history is restored on a fresh page', page.evaluate("devInputHistory.items.slice(-1)[0] === 'step 5' && devRecall(devInputHistory, -1) === 'step 5'"))
check('history is restored on a fresh page', page.evaluate("devInputHistory.items.slice(-1)[0] === 'give water 500' && devRecall(devInputHistory, -1) === 'give water 500'"))
check('no browser errors', not errors)
browser.close()
+35
View File
@@ -0,0 +1,35 @@
// t103: carried liquids can always leave the bag; collecting them still needs the Flask.
const { load, suite, flatWorld, count } = require('./harness');
const { run } = load();
const { check, done } = suite();
for (const flask of [false, true]) {
flatWorld(run, 240, 160, 110);
run(`enableDevConsole(); player.flask = ${flask}; devCommand('give water 500');`);
check(`give water keeps Flask=${flask}`, run(`bag[WATER] === 500 && player.flask === ${flask}`));
const blown = run('toolBlow(135, 100, 6, WATER).placed');
check(`water leaves the bag with Flask=${flask}`, blown > 0 && run(`bag[WATER] === 500 - ${blown} && bagTotal === bag[WATER] && flyers.length === ${blown}`));
run('for (let k = 0; k < 240; k++) flyTick();');
check(`blown water lands with Flask=${flask}`, blown > 0 && count(run, run('WATER')) === blown && run('flyers.length === 0'), blown);
run("devCommand('give sand 500'); blowRR = 0;");
const mixed = run('toolBlow(135, 100, 6, SAND, [SAND, WATER]).placed');
check(`mixed blow includes water with Flask=${flask}`, mixed > 1 && run('flyers.some(f => f.m === WATER) && flyers.some(f => f.m === SAND)'));
const before = run('bag[WATER]');
run('toolVacuum(135, 100, 20, m => m === WATER);');
check(`liquid pickup ${flask ? 'works with' : 'still needs'} the Flask`, run('bag[WATER]') >= before && (flask ? run('bag[WATER]') > before : run('bag[WATER]') === before));
}
flatWorld(run, 240, 160, 110);
run('player.flask = false; setCell(100 * W + 125, WATER, 20, 0);');
check('water on the ground cannot be targeted or collected without the Flask', run('vacuumTargetAt(125, 100, 1) === 0 && toolVacuum(125, 100, 1, () => true).got === 0 && mat[100 * W + 125] === WATER'));
run('flyers.push({x:125, y:100, m:WATER});');
check('flying water cannot be targeted or collected without the Flask', run('vacuumTargetAt(125, 100, 1) === 0 && toolVacuum(125, 100, 1, () => true).got === 0 && flyers.length === 1'));
// Molten liquids use the same blow path, retaining their pouring temperature.
run('flyers.length = 0; enableDevConsole();');
for (const m of run('Array.from({length: NMAT}, (_, m) => m).filter(m => PH[m] === PH_LIQUID)')) {
run(`devCommand('give ' + MD[${m}].name + ' 2');`);
check(`carried ${run(`MD[${m}].name`)} blows without the Flask`, run(`toolBlow(135, 100, 0, ${m}).placed === 1 && flyers[flyers.length - 1].T === bagTemp(${m})`));
}
done();