Vacuum controls: pick-up modes and separate pick-up and blow marks in the bag (t6, t15)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+97
-20
@@ -21,6 +21,15 @@ with sync_playwright() as p:
|
||||
pg.on('pageerror', lambda e: logs.append(f'PAGEERROR: {e}'))
|
||||
pg.on('console', lambda m: logs.append(f'{m.type}: {m.text}') if m.type == 'error' else None)
|
||||
pg.goto(url); pg.evaluate("SET.fog = false"); pg.wait_for_timeout(300)
|
||||
check('empty marks default to first mode', pg.evaluate("GAME.pickMode === 'first'"))
|
||||
pg.evaluate("toggleCollect(CUORE); setPickMode('all')")
|
||||
pg.reload(); pg.wait_for_timeout(300)
|
||||
check('pick-up marks and a manual mode persist across reload', pg.evaluate("GAME.collect.has(CUORE) && GAME.pickMode === 'all'"))
|
||||
pg.evaluate("localStorage.removeItem('deepgrain.pickMode')")
|
||||
pg.reload(); pg.wait_for_timeout(300)
|
||||
check('old pick-up marks default to marked mode', pg.evaluate("GAME.collect.has(CUORE) && GAME.pickMode === 'marked'"))
|
||||
pg.evaluate("localStorage.removeItem('deepgrain.collect'); localStorage.removeItem('deepgrain.pickMode')")
|
||||
pg.reload(); pg.wait_for_timeout(300)
|
||||
check('storage transactions roll back when a write throws', pg.evaluate("""async () => {
|
||||
try { await DB.op('readwrite', st => { st.put('partial', 'abort-test'); throw new Error('test abort'); }); }
|
||||
catch (e) { return await DB.get('abort-test') === undefined; }
|
||||
@@ -228,20 +237,39 @@ with sync_playwright() as p:
|
||||
others = ev(f'bagTotal - bag[{tgt[2]}]')
|
||||
check('vacuum (left) picks the clicked material', got > 0, f"{ev(f'MD[{tgt[2]}].name')} x{got}")
|
||||
check('vacuum (left) leaves other materials', others == 0, others)
|
||||
# R ticks what is under the cursor; ticked materials come along with any left-click suck
|
||||
check('vacuum defaults to What I click', ev("GAME.pickMode === 'first'"))
|
||||
check('first mode locks onto dirt and leaves other grains', ev("""(() => {
|
||||
const x = Math.floor(bot.x) + 6, y = Math.floor(bot.y) - 6, cells = [], dirt = bag[LDIRT], stone = bag[RSTONE];
|
||||
for (let dy = -2; dy <= 2; dy++) for (let dx = -2; dx <= 2; dx++) { const i = (y+dy)*W+x+dx; cells.push([i, mat[i], temp[i], flags[i]]); setCell(i, dx < 0 ? RSTONE : LDIRT, 20, 0); }
|
||||
const aim = GAME.aim; GAME.aim = { x, y }; GAME.lmb = true; primaryPress(false); toolTick();
|
||||
const ok = GAME.vacFilter === LDIRT && bag[LDIRT] > dirt && bag[RSTONE] === stone;
|
||||
primaryRelease(false); GAME.lmb = false; GAME.aim = aim;
|
||||
for (const [i, m, t, f] of cells) setCell(i, m, t, f);
|
||||
bagTake(LDIRT, bag[LDIRT] - dirt); return ok;
|
||||
})()"""))
|
||||
check('middle click chooses a loose material even with none in the bag', ev("""(() => {
|
||||
const x = Math.floor(bot.x) + 6, y = Math.floor(bot.y) - 6, i = y*W+x;
|
||||
const old = [mat[i], temp[i], flags[i]], aim = GAME.aim, blow = GAME.blow, n = bag[LDIRT];
|
||||
setCell(i, LDIRT, 20, 0); bag[LDIRT] = 0; GAME.aim = { x, y }; pickUnderCursor();
|
||||
const ok = GAME.blow.size === 1 && GAME.blow.has(LDIRT);
|
||||
setCell(i, ...old); bag[LDIRT] = n; GAME.aim = aim; GAME.blow = blow; return ok;
|
||||
})()"""))
|
||||
# R marks pick-up without changing the blow choice
|
||||
blow_before = ev('[...GAME.blow]')
|
||||
tgt2 = ev(f'''(() => {{ for (let r = 0; r < 40; r++) for (let dy = -r; dy <= r; dy++) for (let dx = -r; dx <= r; dx++) {{
|
||||
const x = {bx + 6} + dx, y = {gy + 3} + dy, m = mat[y*W+x]; if (PH[m] === PH_POWDER && inReach(x, y)) return [x, y, m]; }} return null; }})()''')
|
||||
check('a loose grain is available for R marking', tgt2 is not None)
|
||||
if tgt2:
|
||||
cx, cy = cell_to_screen(tgt2[0], tgt2[1])
|
||||
pg.mouse.move(cx, cy); settle(80); pg.keyboard.press('r'); settle(80)
|
||||
check('R ticks the material under the cursor', ev(f'GAME.collect.has({tgt2[2]})'), ev('[...GAME.collect]'))
|
||||
check('R marks pick-up and switches mode without changing blow', ev(f"GAME.collect.has({tgt2[2]}) && GAME.pickMode === 'marked'") and ev('[...GAME.blow]') == blow_before)
|
||||
ev('renderBag()'); settle(50)
|
||||
ticks = pg.locator('#baglist .tick.on')
|
||||
if ticks.count(): ticks.first.click(); settle(80)
|
||||
check('bag tick unticks it', not ev(f'GAME.collect.has({tgt2[2]})'), ev('[...GAME.collect]'))
|
||||
marks = pg.locator('#baglist [data-pick][aria-pressed=true]')
|
||||
if marks.count(): marks.first.click(); settle(80)
|
||||
check('removing the last pick-up mark returns to first', ev(f"!GAME.collect.has({tgt2[2]}) && GAME.pickMode === 'first'"))
|
||||
pg.screenshot(path=str(out / '02-vacuum.png'))
|
||||
|
||||
# 2b. the vacuum filter is fixed when you click: ore with stone around it, ore ticked
|
||||
# 2b. the vacuum filter is fixed when you click: ore with stone around it, ore marked
|
||||
def mixed_pile(x0, y0):
|
||||
ev(f"{{ const r = mulberry32(3); for (let y = {y0}; y < {y0} + 6; y++) for (let x = {x0}; x < {x0} + 14; x++) setCell(y*W+x, r() < 0.5 ? CUORE : RSTONE, AMB[y], 0); }}")
|
||||
def suck_at(x, y, ms=900, shift=False, fly=None):
|
||||
@@ -262,14 +290,14 @@ with sync_playwright() as p:
|
||||
mixed_pile(px0, py0); settle(400)
|
||||
ore = find_ore()
|
||||
refill('bag.fill(0)')
|
||||
ev('GAME.collect = new Set([CUORE]); lastBagKey = "";')
|
||||
ev('GAME.collect = new Set([CUORE]); setPickMode("marked"); lastBagKey = "";')
|
||||
suck_at(*ore)
|
||||
check('with ore ticked, clicking ore never pulls in stone', ev('bag[CUORE]') > 0 and ev('bag[RSTONE]') == 0, f"ore {ev('bag[CUORE]')}, stone {ev('bag[RSTONE]')}")
|
||||
check('with ore marked, clicking ore never pulls in stone', ev('bag[CUORE]') > 0 and ev('bag[RSTONE]') == 0, f"ore {ev('bag[CUORE]')}, stone {ev('bag[RSTONE]')}")
|
||||
mixed_pile(px0, py0); settle(400); refill('bag.fill(0)')
|
||||
st = ev(f'(() => {{ for (let y = {py0} - 8; y < {gy} + 4; y++) for (let x = {px0} - 6; x < {px0} + 20; x++) if (mat[y*W+x] === RSTONE && mat[(y-1)*W+x] === EMPTY && inReach(x, y)) return [x, y]; return null; }})()')
|
||||
suck_at(*st)
|
||||
check('with ore ticked, starting on stone rubble still takes only the ore', ev('bag[CUORE]') > 0 and ev('bag[RSTONE]') == 0, f"ore {ev('bag[CUORE]')}, stone {ev('bag[RSTONE]')}")
|
||||
ev('GAME.collect = new Set(); lastBagKey = "";')
|
||||
check('with ore marked, starting on stone rubble still takes only the ore', ev('bag[CUORE]') > 0 and ev('bag[RSTONE]') == 0, f"ore {ev('bag[CUORE]')}, stone {ev('bag[RSTONE]')}")
|
||||
ev('GAME.collect = new Set(); setPickMode("first"); lastBagKey = "";')
|
||||
mixed_pile(px0, py0); settle(400); refill('bag.fill(0)')
|
||||
ore = find_ore()
|
||||
suck_at(*ore, ms=500, shift=True, fly='d')
|
||||
@@ -279,6 +307,26 @@ with sync_playwright() as p:
|
||||
ore = find_ore()
|
||||
suck_at(*ore, ms=500, shift=True)
|
||||
check('Shift held standing still takes everything', ev('bag[RSTONE]') > 0 and ev('bag[CUORE]') > 0, f"ore {ev('bag[CUORE]')}, stone {ev('bag[RSTONE]')}")
|
||||
pg.keyboard.press('t'); settle(80)
|
||||
check('T cycles first to marked', ev("GAME.pickMode === 'marked'"))
|
||||
before = ev('bagTotal')
|
||||
suck_at(*ore, ms=100)
|
||||
check('empty marked mode picks up nothing and explains why', ev('bagTotal') == before and 'Nothing marked to pick up. R over something, or ⬇ in the bag.' in ev('GAME.status'))
|
||||
pg.keyboard.press('t'); settle(80)
|
||||
check('T cycles marked to all', ev("GAME.pickMode === 'all'"))
|
||||
mixed_pile(px0, py0); settle(400); refill('bag.fill(0)')
|
||||
ore = find_ore(); suck_at(*ore, ms=500)
|
||||
check('Everything mode picks up both materials', ev('bag[RSTONE] > 0 && bag[CUORE] > 0'))
|
||||
pg.keyboard.press('t'); settle(80)
|
||||
check('T cycles all to first', ev("GAME.pickMode === 'first'"))
|
||||
for mode in ['marked', 'all', 'first']:
|
||||
pg.click('#selcard [data-pick-mode]'); settle(80)
|
||||
check(f'card chip cycles to {mode}', ev(f'GAME.pickMode === "{mode}"'))
|
||||
ev("toggleCollect(CUORE); setPickMode('all'); toggleCollect(RSTONE)")
|
||||
check('adding another mark switches a manual mode back to marked', ev("GAME.pickMode === 'marked'"))
|
||||
ev("setPickMode('all'); toggleCollect(RSTONE)")
|
||||
check('removing a mark keeps a manual mode', ev("GAME.pickMode === 'all'"))
|
||||
ev("GAME.collect.clear(); setPickMode('first'); lastBagKey = ''; hudDirty = true")
|
||||
# zoom jumps straight to the bot
|
||||
pg.mouse.move(640, 400); settle(600)
|
||||
pg.mouse.wheel(0, -100); pg.wait_for_timeout(60)
|
||||
@@ -289,8 +337,8 @@ with sync_playwright() as p:
|
||||
|
||||
# 3. right button blows the chosen material out of the bot; the choice is sticky
|
||||
refill('bag[COAL] = 120; bag[RSTONE] = 600; bag[CUORE] = 60; bag[LCLAY] = 200; bag[LWOOD] = 120')
|
||||
ok = select('2', 'GAME.dropMat === COAL && !GAME.blowMix')
|
||||
check('Q/E chooses coal to blow', ok, ev('GAME.dropMat'))
|
||||
ok = select('2', 'GAME.blow.size === 1 && GAME.blow.has(COAL)')
|
||||
check('Q/E chooses coal to blow', ok, ev('[...GAME.blow]'))
|
||||
dx_, dy_ = cell_to_screen(bx - 14, gy - 6)
|
||||
c0 = ev('bag[COAL]')
|
||||
pg.mouse.move(dx_, dy_); pg.mouse.down(button='right'); settle(150)
|
||||
@@ -300,21 +348,43 @@ with sync_playwright() as p:
|
||||
check('right click blows coal out of the bag onto the ground', ev('bag[COAL]') < c0 and ev('flyers.length') == 0, f"{c0}->{ev('bag[COAL]')}")
|
||||
refill('bag[COAL] = 3')
|
||||
hold(dx_, dy_, 400, 'right'); settle(400)
|
||||
check('chosen material stays chosen after it runs out', ev('bag[COAL] === 0 && GAME.dropMat === COAL'), ev('[bag[COAL], GAME.dropMat]'))
|
||||
# tick coal and ore: Shift no longer changes what is blown, the selection does
|
||||
check('chosen material stays chosen after it runs out', ev('bag[COAL] === 0 && GAME.blow.size === 1 && GAME.blow.has(COAL)'), ev('[bag[COAL], ...GAME.blow]'))
|
||||
# mark coal and ore: Shift no longer changes what is blown, the selection does
|
||||
refill('bag[COAL] = 60; bag[CUORE] = 60')
|
||||
ev('GAME.collect = new Set([COAL, CUORE]); GAME.dropMat = COAL; GAME.blowMix = false; lastBagKey = ""; hudDirty = true;')
|
||||
ev('GAME.collect = new Set([COAL, CUORE]); GAME.blow = new Set([COAL]); lastBagKey = ""; hudDirty = true;')
|
||||
mx_, my_ = cell_to_screen(bx - 26, gy - 6)
|
||||
pg.mouse.move(mx_, my_); pg.keyboard.down('Shift'); pg.mouse.down(button='right'); settle(400); pg.mouse.up(button='right'); pg.keyboard.up('Shift'); settle(600)
|
||||
check('Shift + right click still blows only the chosen coal', ev('bag[CUORE]') == 60 and ev('bag[COAL]') < 60, ev('[bag[COAL], bag[CUORE]]'))
|
||||
refill('bag[COAL] = 60; bag[CUORE] = 60'); settle(200)
|
||||
pg.click('#baglist .mixrow .pick'); settle(200)
|
||||
check('the bag offers the ticked mix as a choice', ev('GAME.blowMix') and 'Ticked mix' in pg.inner_text('#selcard'), pg.inner_text('#selcard')[:120])
|
||||
pg.locator('#baglist .pick').filter(has_text='Coal').click(); settle(80)
|
||||
check('clicking a bag name chooses one blow material', ev('GAME.blow.size === 1 && GAME.blow.has(COAL)'))
|
||||
pick_before = ev('[...GAME.collect]')
|
||||
pg.click(f'#baglist [data-blow="{ev("CUORE")}"]'); settle(100)
|
||||
check('blow marks make a mix without changing pick-up marks', ev('GAME.blow.has(COAL) && GAME.blow.has(CUORE) && GAME.blow.size === 2') and ev('[...GAME.collect]') == pick_before and 'Mix of 2' in pg.inner_text('#selcard'))
|
||||
blow_before = ev('[...GAME.blow]')
|
||||
pg.click(f'#baglist [data-pick="{ev("RSTONE")}"]'); settle(80)
|
||||
check('pick-up marks do not change blow marks', ev('[...GAME.blow]') == blow_before)
|
||||
check('vacuum bag rows have both toggles', pg.locator('#baglist [data-pick]').count() == pg.locator('#baglist .bagrow').count() == pg.locator('#baglist [data-blow]').count())
|
||||
pg.mouse.move(mx_, my_); pg.mouse.down(button='right'); settle(500); pg.mouse.up(button='right'); settle(600)
|
||||
dc, do_ = 60 - ev('bag[COAL]'), 60 - ev('bag[CUORE]')
|
||||
check('choosing Ticked mix, right click blows an even ore/coal mix', dc > 5 and abs(dc - do_) <= 2, f'coal {dc}, ore {do_}')
|
||||
ev('GAME.blowMix = false;')
|
||||
ev('GAME.collect = new Set(); lastBagKey = "";')
|
||||
check('right click blows an even marked ore/coal mix', dc > 5 and abs(dc - do_) <= 2, f'coal {dc}, ore {do_}')
|
||||
pg.keyboard.press('e'); settle(80)
|
||||
check('Q/E replaces the mix with one material', ev('GAME.blow.size === 1'))
|
||||
ev("GAME.collect = new Set([COAL, CUORE]); setPickMode('marked'); GAME.blow = new Set([COAL, RSTONE]); GAME.status = ''; lastBagKey = ''; hudDirty = true")
|
||||
refill('bag[COAL] = 120; bag[CUORE] = 100; bag[RSTONE] = 600')
|
||||
for width, height, suffix in [(1280, 800, ''), (900, 700, '-900')]:
|
||||
pg.set_viewport_size({'width': width, 'height': height}); settle(200)
|
||||
pg.screenshot(path=str(out / f'vacuum-card{suffix}.png'))
|
||||
check(f'vacuum panel fits at {width}', ev("(() => { const c = $('selcard'), b = $('bagpanel'); return [c, b].every(e => { const r = e.getBoundingClientRect(); return r.left >= 0 && r.right <= innerWidth && r.bottom <= innerHeight && e.scrollWidth <= e.clientWidth; }) && [...$('baglist').querySelectorAll('.bagrow')].every(e => e.scrollWidth <= e.clientWidth); })()"))
|
||||
pg.set_viewport_size({'width': 1280, 'height': 800})
|
||||
pg.keyboard.press('3'); settle(80)
|
||||
check('build bag has no vacuum toggles', pg.locator('#baglist .mark').count() == 0)
|
||||
pg.keyboard.press('2'); settle(80)
|
||||
ev("toggleBlow(BARCU)")
|
||||
check('a bar blow mark replaces the mix', ev('GAME.blow.size === 1 && GAME.blow.has(BARCU)'))
|
||||
ev("toggleBlow(COAL)")
|
||||
check('a grain blow mark replaces a bar', ev('GAME.blow.size === 1 && GAME.blow.has(COAL)'))
|
||||
ev("GAME.collect.clear(); setPickMode('first'); lastBagKey = ''; hudDirty = true")
|
||||
refill('bag[COAL] = 120')
|
||||
|
||||
# 4. igniter lights it
|
||||
@@ -654,6 +724,13 @@ with sync_playwright() as p:
|
||||
check('gamepad RT digs where the right stick aims', ev('stats.dug') > d0, f"{d0}->{ev('stats.dug')}")
|
||||
ev("__pad.buttons[5].pressed = true;"); settle(100); ev("__pad.buttons[5].pressed = false;"); settle(100)
|
||||
check('gamepad RB switches to the vacuum', ev('GAME.tool') == 1)
|
||||
ev("GAME.collect.clear(); setPickMode('first'); GAME.aim = { x: Math.floor(bot.x), y: Math.floor(bot.y) }; setCell(Math.floor(bot.y)*W+Math.floor(bot.x), COAL, 20, 0); __pad.buttons[2].pressed = true; pollPad(); __pad.buttons[2].pressed = false; pollPad()")
|
||||
check('gamepad X keeps R pick-up marking', ev("GAME.collect.has(COAL) && GAME.pickMode === 'marked'"))
|
||||
ev("__pad.buttons[1].pressed = true; pollPad(); __pad.buttons[1].pressed = false; pollPad()")
|
||||
check('gamepad B cycles pick-up mode', ev("GAME.pickMode === 'all'"))
|
||||
ev("__pad.buttons[15].pressed = true; pollPad(); __pad.buttons[15].pressed = false; pollPad()")
|
||||
check('gamepad d-pad chooses a single blow material', ev('GAME.blow.size === 1'))
|
||||
|
||||
ev("__pad.buttons[9].pressed = true;"); settle(100); ev("__pad.buttons[9].pressed = false;"); settle(100)
|
||||
check('gamepad Start opens menu', ev("!$('m-pause').hidden"))
|
||||
ev("__pad.buttons[13].pressed = true;"); settle(80); ev("__pad.buttons[13].pressed = false;"); settle(80)
|
||||
|
||||
@@ -25,6 +25,7 @@ run('for (let k=0;k<5;k++) { toolBlow(80, 70, 1, COAL); simTick(); }');
|
||||
check('blown coal flies out of the bot', run('flyers.length') > 0, run('flyers.length'));
|
||||
ticks(run, 120);
|
||||
check('blown coal leaves the bag and lands on the ground', run('bag[COAL]') < c0 && run('flyers.length') === 0 && count(run, run('COAL'), 60, 80, 100, 99) > 0, `${c0}->${run('bag[COAL]')} landed ${count(run, run('COAL'), 60, 80, 100, 99)}`);
|
||||
check('empty blow marks explain that nothing is left', run('toolBlow(80, 70, 1, COAL, [CUORE, BARCU]).status') === 'No marked materials left to blow.');
|
||||
// bot flies through built walls but not through natural rock
|
||||
run('bag[RSTONE] += 64; bagTotal += 64; placeBlocks(BP_WALL, RSTONE, blockRect(30, 14, 30, 17))');
|
||||
check('bot can overlap a built wall', !run('botOverlapSolid(121, 58)'));
|
||||
|
||||
Reference in New Issue
Block a user