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/tasks/t19.md b/tasks/t19.md new file mode 100644 index 0000000..ce5916d --- /dev/null +++ b/tasks/t19.md @@ -0,0 +1,25 @@ +--- +id: t19 +status: Review +order: 4 +priority: P3 +created: 2026-10-24 +updated: 2026-10-24T15:59:05Z +title: vacuum blow all visual +report: | + (agent) With the Vacuum, holding Shift (while not flying) now shows what Shift + right click + will blow before you click: the card's Blow out line reads "Everything · mix of N", every bag + row that will be blown lights up, and the swatches by the cursor show the whole mix. Let go of + Shift and it goes back to your chosen material. It also shows while you're blowing everything. + To check: with a few things in the bag and one chosen to blow, hold Shift and watch the card and + bag; then hold Shift with WASD and see it doesn't change (Shift is boost then). +--- + +## Context + +- (owner's answers, 2026-10-24) The description typed in kandown was lost. Chosen: "Bag rows light up" (while Shift is held, or while blowing everything, every row that will be blown lights up in the bag) and "Card shows it while Shift is held" (the Blow out line reads "Everything · mix of N" and the swatches by the cursor show the whole mix, before you click). + +## Subtasks + +- [x] Card shows "Everything · mix of N" while Shift is held +- [x] Bag rows that will be blown light up 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]]'))