From ec687b8c6cc499c19001289fbb682f57dfcb121d Mon Sep 17 00:00:00 2001 From: 44r0n7 <44r0n7+gitea@pm.me> Date: Sat, 24 Oct 2026 11:59:05 -0400 Subject: [PATCH] Vacuum: holding Shift shows the blow-everything mix on the card and in the bag (t19) Co-Authored-By: Claude Opus 5.5 --- docs/PROJECT_MAP.md | 4 ++-- docs/agent-log.md | 12 ++++++++++++ src/ui/hud.js | 1 + src/ui/previews.js | 2 +- src/ui/selection.js | 10 +++++----- test/play.py | 12 ++++++++++++ 6 files changed, 33 insertions(+), 8 deletions(-) diff --git a/docs/PROJECT_MAP.md b/docs/PROJECT_MAP.md index 61a9d17..ac70253 100644 --- a/docs/PROJECT_MAP.md +++ b/docs/PROJECT_MAP.md @@ -395,9 +395,9 @@ The notebook (N): milestones, market, wallet, blueprints, controls. - functions: openNotebook, renderNotebook, fmtClock, orderName, orderLeft, renderMarketTab, fmtPrice, itemsText, renderWallet, renderBlueprintTab, copyCode, controlsHtml - variables: nbTab, MARKET_GROUPS -### src/ui/hud.js (39 lines) +### src/ui/hud.js (40 lines) The tool hotbar and what is selected. -- functions: renderHotbar, toolId, steering, modShift, grainAt, selectTool, bagMaterials, blowList, blowAllList, ensureBlow, piece +- functions: renderHotbar, toolId, steering, modShift, grainAt, selectTool, bagMaterials, blowList, blowAllList, blowingAll, ensureBlow, piece ### src/ui/build-menu.js (102 lines) The build menu along the bottom: categories, piece tiles and their icons. diff --git a/docs/agent-log.md b/docs/agent-log.md index 1a11730..1bdecc3 100644 --- a/docs/agent-log.md +++ b/docs/agent-log.md @@ -4,6 +4,18 @@ 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-24: t19 Vacuum blow all visual (Claude Code, Codex) + +- The owner's description was lost in kandown; chosen in chat: bag rows light up and the card + shows it while Shift is held. t18 was deleted at the owner's request (to decide later: what + blow does when the chosen material runs out). +- `hud.js` `blowingAll()` (Shift held and not flying, or blowing everything); `selection.js` card + ("Everything · mix of N", no swatches so it stays one line) and bag `.on` rows; `previews.js` + cursor swatches. +- `test/play.py`: Shift preview on and off, flying doesn't preview (the bot is put back after, + or a later gamepad dig check fails). Tests: `run_all.py --browser` all pass (220 `play.py` + checks). + ## 2026-10-24: t16 follow-up: Shift blows all, Ctrl+click in the bag, compact menu (Claude Code) - The owner's subtask "shift to blow all" and two chat requests. Codex was out of credits, so diff --git a/src/ui/hud.js b/src/ui/hud.js index 39d70d1..7e6e9e6 100644 --- a/src/ui/hud.js +++ b/src/ui/hud.js @@ -31,6 +31,7 @@ function bagMaterials() { return BAG_ORDER.filter(m => bag[m] > 0); } function blowList() { return [...GAME.blow]; } // Shift + right click: an even mix of everything loose in the bag (bars never mix) function blowAllList() { return bagMaterials().filter(m => PH[m] !== PH_ITEM && (PH[m] !== PH_LIQUID || player.flask)); } +function blowingAll() { return toolId() === 'vacuum' && (GAME.rmb ? !GAME.rmbConsumed && GAME.blowAll : modShift()); } // Empty materials stay chosen until the player changes the marks. function ensureBlow() { if (GAME.blow.size) return; diff --git a/src/ui/previews.js b/src/ui/previews.js index 67d3b67..e5a2793 100644 --- a/src/ui/previews.js +++ b/src/ui/previews.js @@ -153,7 +153,7 @@ function drawPreviews() { if (t === 'vacuum') { // what the right button would blow, next to the cursor; what the left is sucking, while it sucks const sz = Math.max(6, zp * 1.6), qx = p.x + (r + 1.2) * zp, qy = p.y - (r + 1.2) * zp; - const swatches = GAME.lmb ? GAME.vacFilter >= 0 && GAME.collect.size ? [...GAME.collect] : [] : GAME.rmb && GAME.blowAll ? blowAllList() : blowList(); + const swatches = GAME.lmb ? GAME.vacFilter >= 0 && GAME.collect.size ? [...GAME.collect] : [] : blowingAll() ? blowAllList() : blowList(); swatches.slice(0, 4).forEach((m, k) => { ctx.fillStyle = rgbOf(m); ctx.fillRect(qx + k * (sz + 2), qy, sz, sz); }); if (blowing || GAME.lmb) { // faint line from the nozzle: the path the grains take diff --git a/src/ui/selection.js b/src/ui/selection.js index f3c5f8b..89074e4 100644 --- a/src/ui/selection.js +++ b/src/ui/selection.js @@ -229,11 +229,11 @@ function renderSelCard() { + '
Q/E piece · Shift+Q/E tab · R turn or material · F material menu · hold G replace · right click take back · hold C/X copy/cut · V paste · Ctrl+Z undo
'; } else if (t === 'vacuum') { ensureBlow(); - const list = blowList(), marks = [...GAME.collect]; + const all = blowingAll(), list = all ? blowAllList() : blowList(), marks = [...GAME.collect]; const sws = ms => ms.slice(0, 4).map(m => ``).join('') + (ms.length > 4 ? `+${ms.length - 4}` : ''); const count = list.reduce((n, m) => n + bag[m], 0); h = `
Pick up${sws(marks)}
-
Blow outQ${count ? count.toLocaleString('en-US') : 'none left'}E
+
Blow outQ${count ? count.toLocaleString('en-US') : 'none left'}E
Left picks up, right blows out. F chooses what. R marks what is under the cursor. Shift takes or blows everything. nozzle ${GAME.brush[1]}
`; } else if (t === 'ignite') { h = `
Igniterup to 640°C
Hold on fuel to light it. Coal catches around 420°C, wood around 300°C. Fuel needs open air next to it.
`; @@ -248,14 +248,14 @@ function renderBag() { $('bagbar').firstChild.style.width = pct + '%'; $('bagbar').classList.toggle('full', bagTotal >= cap); const l = BAG_ORDER.filter(m => bag[m] > 0 || GAME.collect.has(m) || GAME.blow.has(m)); - const t = toolId(); - const key = l.map(m => m + ':' + bag[m] + (GAME.collect.has(m) ? 'c' : '') + (GAME.blow.has(m) ? 'b' : '')).join(',') + '|' + GAME.wallMat + '|' + GAME.filterCfg.m.join('.') + '|' + t + '|' + GAME.pieceIdx; + const t = toolId(), all = blowingAll(), blowing = all ? blowAllList() : blowList(); + const key = l.map(m => m + ':' + bag[m] + (GAME.collect.has(m) ? 'c' : '') + (GAME.blow.has(m) ? 'b' : '')).join(',') + '|' + GAME.wallMat + '|' + GAME.filterCfg.m.join('.') + '|' + t + '|' + GAME.pieceIdx + '|' + all; if (key === lastBagKey) return; lastBagKey = key; const box = $('baglist'); box.innerHTML = ''; if (!l.length) { box.appendChild(el('div', { class: 'empty' }, 'Empty. Break rock with the Drill, then use the Vacuum.')); return; } for (const m of l) { - const sel = (t === 'vacuum' && GAME.blow.has(m)) || (t === 'build' && m === GAME.wallMat && pieceUsesWall(piece())) || (t === 'build' && piece().filter && GAME.filterCfg.m.includes(m)); + const sel = (t === 'vacuum' && blowing.includes(m)) || (t === 'build' && m === GAME.wallMat && pieceUsesWall(piece())) || (t === 'build' && piece().filter && GAME.filterCfg.m.includes(m)); const row = el('div', { class: 'bagrow' + (sel ? ' on' : '') }); if (t === 'vacuum' && GAME.collect.has(m)) row.appendChild(el('span', { class: 'tag', title: 'Marked to pick up', 'aria-label': 'Marked to pick up' }, '⬇')); const b = el('button', { class: 'pick', title: WALL_OF[m] && t === 'build' ? `Build with ${MD[m].name}` : `Blow out ${MD[m].name}` }); diff --git a/test/play.py b/test/play.py index 7841980..89b9a21 100644 --- a/test/play.py +++ b/test/play.py @@ -330,6 +330,18 @@ with sync_playwright() as p: # Shift + right click blows an even mix of everything loose in the bag, without changing the choice refill('bag[COAL] = 60; bag[CUORE] = 60') ev('GAME.collect = new Set([COAL, CUORE]); GAME.blow = new Set([COAL]); lastBagKey = ""; hudDirty = true;') + pg.keyboard.down('Shift'); settle(150) + check('Shift previews everything in the blow card', 'Everything · mix of' in pg.inner_text('#selcard')) + check('Shift lights every loose bag row', ev("blowAllList().every(m => [...$('baglist').querySelectorAll('.bagrow.on .nm')].some(e => e.textContent === MD[m].name))")) + pg.screenshot(path=str(out / '03b-blow-all-preview.png')) + pg.keyboard.up('Shift'); settle(150) + check('releasing Shift restores the chosen blow material', ev("$('selcard').querySelector('.vacchoice').textContent.includes(MD[COAL].name) && !$('selcard').querySelector('.vacchoice').textContent.includes('Everything')")) + check('releasing Shift lights only coal', ev("(() => { const rows = [...$('baglist').querySelectorAll('.bagrow.on')]; return rows.length === 1 && rows[0].querySelector('.nm').textContent === MD[COAL].name; })()")) + bot_at = ev('[bot.x, bot.y]') + pg.keyboard.down('d'); pg.keyboard.down('Shift'); settle(150) + check('Shift while flying does not preview everything', 'Everything' not in pg.locator('#selcard .vacchoice').inner_text()) + pg.keyboard.up('Shift'); pg.keyboard.up('d') + ev(f'bot.x = {bot_at[0]}; bot.y = {bot_at[1]}; bot.vx = bot.vy = 0;'); settle(150) 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 blows everything loose in the bag', ev('bag[CUORE]') < 60 and ev('bag[COAL]') < 60 and ev('GAME.blow.size === 1 && GAME.blow.has(COAL)'), ev('[bag[COAL], bag[CUORE]]'))