diff --git a/docs/DESIGN.md b/docs/DESIGN.md
index 1aa0d02..f359630 100644
--- a/docs/DESIGN.md
+++ b/docs/DESIGN.md
@@ -235,6 +235,11 @@ above, including:
the bag; Ctrl+click a bag name adds it to the blow mix and Ctrl+right-click to pick-up; the menu
is one line per material because the first version was too large.
+- **The Vacuum is the Pneumatic nozzle (t26, 2026-10-24).** The owner's choice of name, since the
+ tool blows as much as it sucks. It shows as the tool card's title row, with the nozzle width as a
+ chip, and in the hotbar tooltip and all player text ("the nozzle" after the first mention). Code
+ ids and names stay `vacuum`.
+
- **Tool messages are toasts (t27, 2026-10-24).** Every tool's messages ("Nothing selected", "Out
of reach.", "Nothing to use there.") show as toasts at the top of the screen, and the red line in
the tool card is gone. The owner asked for it for the vacuum and chose every tool. Errors have a
diff --git a/docs/agent-log.md b/docs/agent-log.md
index 475c430..f053e69 100644
--- a/docs/agent-log.md
+++ b/docs/agent-log.md
@@ -4,6 +4,13 @@ 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: t26: the Vacuum is renamed the Pneumatic nozzle (Claude Code, Codex)
+
+- `TOOLS[1].name`, a title row in the tool card ("Pneumatic nozzle" plus a `width N` chip, moved
+ from the sub line), and player text in `session.js`, `selection.js`, `notebook.js` and
+ `progress.js`. Ids, function names and comments still say vacuum.
+- Tests: `run_all.py --browser` all passed (play.py 239). Screenshots `shots/t26-card{,-900}.png`.
+
## 2026-10-24: t27: tool messages as toasts (Claude Code, Codex)
- `setStatus` still records `GAME.status` and now also shows a toast (`err` or `info` class), unless
diff --git a/src/sim/progress.js b/src/sim/progress.js
index bda4e2a..07d2744 100644
--- a/src/sim/progress.js
+++ b/src/sim/progress.js
@@ -22,16 +22,16 @@ const STARTER_DESIGNS = [
// ---------- Milestones ----------
const MILESTONES = [
- { id: 'dig', title: 'Break ground', msg: 'Broken material falls loose. The Vacuum (2) picks up and blows the selection. Click or Q/E in the bag selects; Ctrl+click mixes. Hold Ctrl to see everything found. Clear returns to picking what you click. Shift: everything.', hint: 'Hold the left mouse button with the Drill (1): it sends pulses at the cursor that shake rock until it shatters. It falls as rubble, ore and coal.' },
- { id: 'fire', title: 'First fire', hint: 'Vacuum up coal or wood chunks, blow a pile out with right click, then hold the Igniter (4) on it. Fuel only burns where it touches open air.', reward: 'Thermal lens unlocked. Press H to see heat.' },
- { id: 'brick', title: 'Fired brick', hint: 'Clay turns into brick after a while above about 900°C, hotter than an open wood fire gets. Blow clay lumps into a coal fire, best walled in like a furnace, and vacuum up the brick rubble. Brick rubble builds brick blocks.', reward: 'Brick insulates far better than stone and survives 1750°C.' },
+ { id: 'dig', title: 'Break ground', msg: 'Broken material falls loose. The Pneumatic nozzle (2) picks up and blows the selection. Click or Q/E in the bag selects; Ctrl+click mixes. Hold Ctrl to see everything found. Clear returns to picking what you click. Shift: everything.', hint: 'Hold the left mouse button with the Drill (1): it sends pulses at the cursor that shake rock until it shatters. It falls as rubble, ore and coal.' },
+ { id: 'fire', title: 'First fire', hint: 'Pick up coal or wood chunks with the nozzle, blow a pile out with right click, then hold the Igniter (4) on it. Fuel only burns where it touches open air.', reward: 'Thermal lens unlocked. Press H to see heat.' },
+ { id: 'brick', title: 'Fired brick', hint: 'Clay turns into brick after a while above about 900°C, hotter than an open wood fire gets. Blow clay lumps into a coal fire, best walled in like a furnace, and pick up the brick rubble with the Pneumatic nozzle. Brick rubble builds brick blocks.', reward: 'Brick insulates far better than stone and survives 1750°C.' },
{ id: 'charcoal', title: 'Charcoal', hint: 'Wood that gets hot without air turns into charcoal instead of burning. Bury a burning pile of wood chunks under dirt, or heat a sealed box of wood chunks from outside.', reward: 'A fuel you can grow: nearly as hot as coal.' },
{ id: 'grow', title: 'Plant a tree', msg: 'Wood is renewable. Plant rows of acorns on dirt to farm it.', hint: 'Cutting down a tree drops acorns. Leave one on dirt or grass under open sky and it sprouts, then grows into a tree in about a minute and a half.' },
{ id: 'copper', title: 'Molten copper', msg: 'Molten copper sinks to the bottom of the charge. Get it into a mold before it sets.', hint: 'Copper ore melts at 1085°C. An open fire loses too much heat, so wall coal and ore in with blocks and leave a small vent at the top for smoke. Mix ore and coal about half and half: Ctrl+click coal and copper ore in the bag to blow a mix. Right click blows an even mix.' },
- { id: 'bars', title: 'Copper bars', msg: 'Bars sell for far more than the ore in them, and make metal wall blocks.', hint: 'Build an Ingot mold (Build, 3) right under the furnace with a floor of clay Grates between them: molten copper drips through, while the charge and the thick slag stay up. When the bars set, they drop out of the bottom of the mold onto whatever is under it; a belt carries them off. Leave space below, or vacuum up the bars. Ingot molds empty themselves: bars, junk and scrap drop below, slag is thrown away and the grate above is raked. Removing any mold returns its set metal to your bag: whole bars as bars, the rest as scrap. Copper that sets in the furnace melts again next firing.' },
+ { id: 'bars', title: 'Copper bars', msg: 'Bars sell for far more than the ore in them, and make metal wall blocks.', hint: 'Build an Ingot mold (Build, 3) right under the furnace with a floor of clay Grates between them: molten copper drips through, while the charge and the thick slag stay up. When the bars set, they drop out of the bottom of the mold onto whatever is under it; a belt carries them off. Leave space below, or pick up the bars with the Pneumatic nozzle. Ingot molds empty themselves: bars, junk and scrap drop below, slag is thrown away and the grate above is raked. Removing any mold returns its set metal to your bag: whole bars as bars, the rest as scrap. Copper that sets in the furnace melts again next firing.' },
{ id: 'bit1', title: 'Copper drill bit', hint: 'Cast copper in a Bit mold. Building the furnace right on top works. A Grate over the funnel keeps the charge out. Let it set, then press F.', reward: 'Copper bit. You can now dig iron ore.' },
{ id: 'auto', title: 'Moving parts', msg: 'Machines can now haul for you. Feed furnaces automatically, and lift ore out of your mines.', hint: 'Belts carry grains and bars resting on them. Screw tubes push loose material along (never liquids); stacked pointing up they lift it out of a mine. A Filter lets one material fall through and carries the rest along, so you can sort ore from rubble. Move 300 grains with machines.' },
- { id: 'glass', title: 'Glass', hint: 'Sand melts near 1300°C. Still air will not get a fire that hot: point a Fan into the fuel. Molten glass runs through a grate like metal, so cast it in an Ingot mold for glass bricks, which sell well.', reward: 'Flask unlocked. The Vacuum can suck up and blow out liquids.' },
+ { id: 'glass', title: 'Glass', hint: 'Sand melts near 1300°C. Still air will not get a fire that hot: point a Fan into the fuel. Molten glass runs through a grate like metal, so cast it in an Ingot mold for glass bricks, which sell well.', reward: 'Flask unlocked. The nozzle can suck up and blow out liquids.' },
{ id: 'iron', title: 'Molten iron', msg: 'Iron is hard to keep molten. A brick mold right under the hot zone works best.', hint: 'Iron ore melts at 1538°C. That needs forced air, and stone slumps at 1250°C, so build the furnace from brick.' },
{ id: 'bit2', title: 'Iron drill bit', hint: 'Cast iron in a Bit mold. A stone mold melts at iron temperatures, so build it from brick.', reward: 'Iron bit. Granite is now diggable.' },
{ id: 'steel', title: 'Steel', msg: 'Cast it in a brick Bit mold for the best drill bit there is.', hint: 'Blow air through molten iron. Aim a Fan at a pool of it and the excess carbon burns off, turning it into steel.' },
diff --git a/src/ui/notebook.js b/src/ui/notebook.js
index 1a9a09b..d88422a 100644
--- a/src/ui/notebook.js
+++ b/src/ui/notebook.js
@@ -36,7 +36,7 @@ function renderNotebook() {
const change = reactionsOf(m, true).map((t, k) => k ? t : t[0].toUpperCase() + t.slice(1)).join(' · ');
rows += `
| ${esc(d.name)} | ${ph} | ${d.hard && d.ph === PH_SOLID ? d.hard : '–'} | ${esc(change || '–')} | ${esc(d.desc || '')} |
`;
}
- body.innerHTML = `What you have found so far. Hardness is what your drill bit has to match. Solid rock breaks into loose rubble that you vacuum up and build with.${unknown ? ` ${unknown} more to discover.` : ''}
| Material | State | Hard | What it does | Notes |
${rows}
`;
+ body.innerHTML = `What you have found so far. Hardness is what your drill bit has to match. Solid rock breaks into loose rubble that you pick up with the Pneumatic nozzle and build with.${unknown ? ` ${unknown} more to discover.` : ''}
| Material | State | Hard | What it does | Notes |
${rows}
`;
} else if (nbTab === 'Pieces') {
let h = 'Everything you build snaps to a grid of 4×4-grain blocks. Pieces only move, hold, blow or sense material; every change to the material itself comes from heat. Select pieces with the Build tool (3). Hover a tile for its cost, full description and placement help. Your bot flies straight through anything you build.
';
for (const P of PIECES) {
@@ -170,7 +170,7 @@ function controlsHtml() {
const kb = [
['Fly', 'W A S D'], ['Boost while flying', 'Shift'], ['Tool modifier (while not flying)', 'Shift'], ['Use tool', 'Left mouse'], ['Second action', 'Right mouse'],
['Tools', '1 – 4'], ['Select one to blow · piece', 'Q / E'], ['Switch build tab', 'Shift + Q / E'], ['Variant: material · direction · flip', 'R'], ['Wall or mold material menu (Build)', 'F'],
- ['Pick up selection or what you click (Vacuum)', 'Left drag · Shift: everything'], ['Select (Vacuum)', 'Click a bag name · Q/E'], ['Add to selection (Vacuum)', 'Ctrl+click a bag name'], ['Show everything found (Vacuum)', 'Hold Ctrl in the bag'], ['Clear selection (Vacuum)', 'Clear in the bag'], ['Blow everything loose in the bag', 'Shift + right click'], ['Nozzle or brush size', '[ ] or Shift + wheel'],
+ ['Pick up selection or what you click (Pneumatic nozzle)', 'Left drag · Shift: everything'], ['Select (Pneumatic nozzle)', 'Click a bag name · Q/E'], ['Add to selection (Pneumatic nozzle)', 'Ctrl+click a bag name'], ['Show everything found (Pneumatic nozzle)', 'Hold Ctrl in the bag'], ['Clear selection (Pneumatic nozzle)', 'Clear in the bag'], ['Blow everything loose in the bag', 'Shift + right click'], ['Nozzle or brush size', '[ ] or Shift + wheel'],
['Replace what is built (Build)', 'Hold G'], ['Copy a box of what you built', 'Hold C, drag'], ['Cut a box (refunded, paste it back)', 'Hold X, drag'], ['Paste again', 'V'], ['Undo building', 'Ctrl + Z'], ['Blueprint library', 'B'], ['Zoom', 'Mouse wheel'], ['Interact (gates, molds, fans, filters)', 'F'], ['Thermal lens', 'H'], ['Info overlay: filters, belt and tube directions', 'I'], ['Bot lamp', 'L'],
['Pick the piece under the cursor (with its direction and settings)', 'Middle mouse'], ['Notebook', 'N'], ['Clear all blueprint ghosts', 'Delete'], ['Save', 'Ctrl + S'], ['Pause · menu', 'Esc'], ['Mute', 'M'],
];
@@ -181,7 +181,7 @@ function controlsHtml() {
];
const tools = [
['1 Resonance drill', 'Hold left click to send pulses from the bot toward the cursor: rock they shake hard enough shatters and falls as rubble, ore, coal or scrap, and trees come apart into wood chunks and acorns. Q and E set the pulse width: narrow bores a tunnel fast, wide clears a big cone slowly, medium is in between. R over a material tunes the drill to it, so only that shatters (great for pulling a vein out of the rock); R again untunes. Built blocks are never harmed. Better bits dig harder rock, faster and further.'],
- ['2 Vacuum', "Left drag picks up the selection, or what you click if nothing is selected. Right click blows the selection. Click a bag name or Q/E selects one; Ctrl+click mixes. Hold Ctrl to see everything found. Clear removes the selection. Bars blow alone. Blowing out the last of a material deselects it. Shift while not flying takes everything; Shift + right click blows everything loose in the bag."],
+ ['2 Pneumatic nozzle', "Left drag picks up the selection, or what you click if nothing is selected. Right click blows the selection. Click a bag name or Q/E selects one; Ctrl+click mixes. Hold Ctrl to see everything found. Clear removes the selection. Bars blow alone. Blowing out the last of a material deselects it. Shift while not flying takes everything; Shift + right click blows everything loose in the bag."],
['3 Build', 'Places pieces on a grid of 4×4 blocks: wall blocks, belts, tubes, filters, grates, gates, pipes, pumps and outlets, machines (fans, the Refiner) and molds. Drag for a line, hold Shift while dragging for a filled rectangle. Right click takes a piece back. Your bot flies through anything you build.'],
['4 Igniter', 'Heats a small spot to 640°C, and its flame reaches a few grains into a pile. Enough to light coal and wood that touch air.'],
['Copy, cut and paste', 'Hold C (copy) or X (cut) and drag a box over what you built; let go and it is picked up, ready to paste. Cut takes it down and refunds it to your bag. Click to place ghosts: your bot builds them from your bag when it is close. R flips, V pastes again, right click or Esc stops. Ctrl+Z undoes building. The notebook (B) keeps saved blueprints and a few starter designs.'],
@@ -191,10 +191,10 @@ function controlsHtml() {
Keyboard and mouse
${tbl(kb)}
Gamepad
${tbl(pad)}
First steps
1. Drill down a little. Stone falls as rubble; coal (dark seams), copper ore (green speckles) and clay (reddish) come loose too.
- 2. With the Vacuum, left drag to pick up the selection, or what you click if none. Shift takes everything.
+ 2. With the Pneumatic nozzle, left drag to pick up the selection, or what you click if none. Shift takes everything.
3. Build (3) a chamber of stone wall blocks with a one-block vent in the roof, a floor of clay Grates, and an Ingot mold right under the grates. The notebook's Blueprints tab has this as a starter design.
4. Ctrl+click coal and copper ore in the bag to blow a mix. Fly inside (the bot passes through what you build) and right click to blow an even mix onto the grates: about 50 of each. (Ore first and coal on top works too; coal buried under ore will not light.) Light a coal grain on top with the Igniter.
- 5. When the ingot sets, bars drop out of the bottom of the mold onto whatever is under it. Leave one open block under it: a belt below that carries them off, or vacuum them up. Leftover metal stays for the next firing. Ingot molds empty themselves: bars, junk and scrap drop below, slag is thrown away and the grate above is raked. Removing any mold returns its set metal to your bag: whole bars as bars, the rest as scrap.
+ 5. When the ingot sets, bars drop out of the bottom of the mold onto whatever is under it. Leave one open block under it: a belt below that carries them off, or pick them up with the nozzle. Leftover metal stays for the next firing. Ingot molds empty themselves: bars, junk and scrap drop below, slag is thrown away and the grate above is raked. Removing any mold returns its set metal to your bag: whole bars as bars, the rest as scrap.
Screw tubes carry grains and whole bars: feed bars in from a belt, turn corners and unload at the mouth.
6. Blow the bars into the trade dock: they sell the moment they drop through its grate, and bars pay far more than ore. Watch the Market page for orders that pay double. Money buys belts, screw tubes and fans; pay down the claim there too.
7. From there: deeper ores (tin, zinc, silver, gold), alloys, fans for hotter fires, iron and steel.
diff --git a/src/ui/selection.js b/src/ui/selection.js
index 9ad0a9e..ec00934 100644
--- a/src/ui/selection.js
+++ b/src/ui/selection.js
@@ -186,9 +186,10 @@ function renderSelCard() {
const all = blowingAll(), list = all ? blowAllList() : blowList();
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${GAME.blow.size ? 'Selected' : 'What you click'} · Shift: everything${sws(GAME.blow.size ? [...GAME.blow] : GAME.lmb && GAME.vacFilter > 0 ? [GAME.vacFilter] : [])}
+ h = `
Pneumatic nozzlewidth ${GAME.brush[1]}
+
Pick up${GAME.blow.size ? 'Selected' : 'What you click'} · Shift: everything${sws(GAME.blow.size ? [...GAME.blow] : GAME.lmb && GAME.vacFilter > 0 ? [GAME.vacFilter] : [])}
Blow outQ${all ? '' : sws(list)}${all ? list.length ? `Everything · mix of ${list.length}` : 'Everything · nothing loose' : list.length > 1 ? `Mix of ${list.length}` : list.length ? esc(MD[list[0]].name) : GAME.blow.size ? 'none in bag' : 'nothing selected'}${count ? count.toLocaleString('en-US') : 'none'}E
-
Left picks up the selection (or what you click if nothing is selected), right blows it out. Hold Ctrl in the bag to pick from everything you've found. Shift: everything. nozzle ${GAME.brush[1]}
`;
+
Left picks up the selection (or what you click if nothing is selected), right blows it out. Hold Ctrl in the bag to pick from everything you've found. Shift: everything.
`;
} 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.
`;
}
@@ -218,7 +219,7 @@ function renderBag() {
foot.appendChild(clear);
}
}
- if (!l.length) { box.appendChild(el('div', { class: 'empty' }, 'Empty. Break rock with the Drill, then use the Vacuum.')); return; }
+ if (!l.length) { box.appendChild(el('div', { class: 'empty' }, 'Empty. Break rock with the Drill, then use the Pneumatic nozzle.')); return; }
for (const m of l) {
if (m === missing[0]) box.appendChild(el('div', { class: 'bagsep' }, 'Not in bag'));
const sel = (t === 'vacuum' && blowing.includes(m)) || (t === 'build' && m === GAME.wallMat && pieceUsesWall(piece())) || (t === 'build' && piece().filter && GAME.filterCfg.m.includes(m));
diff --git a/src/ui/session.js b/src/ui/session.js
index 532b12e..1ea86ef 100644
--- a/src/ui/session.js
+++ b/src/ui/session.js
@@ -27,7 +27,7 @@ function startNewWorld(seed, w, h, name = '') {
GAME.playTime = 0; GAME.clip = null; GAME.paste = false; GAME.selDrag = null; GAME.tool = 0; GAME.blow.clear(); GAME.wallMat = RSTONE; GAME.filterCfg = { m: [], inv: 0 }; GAME.pieceIdx = 0;
showLoading(false);
enterGame();
- showCard({ kind: 'Welcome', title: 'The dig site', body: ['Fly with WASD. The Drill (1) breaks rock loose. The Vacuum (2) picks up and blows the selection. Click or Q/E in the bag selects; Ctrl+click mixes. Hold Ctrl to see everything found. Clear returns to picking what you click. Shift: everything.', 'You owe $20,000 on this claim. Sell what you dig and make at the trade dock next to you: anything that drops through its grate is paid for on the spot, and buyers post orders that pay far more. Every tip lives in the notebook (N).'], hold: 1.8 });
+ showCard({ kind: 'Welcome', title: 'The dig site', body: ['Fly with WASD. The Drill (1) breaks rock loose. The Pneumatic nozzle (2) picks up and blows the selection. Click or Q/E in the bag selects; Ctrl+click mixes. Hold Ctrl to see everything found. Clear returns to picking what you click. Shift: everything.', 'You owe $20,000 on this claim. Sell what you dig and make at the trade dock next to you: anything that drops through its grate is paid for on the spot, and buyers post orders that pay far more. Every tip lives in the notebook (N).'], hold: 1.8 });
await saveWorld('auto');
} catch (e) { showLoading(false); toast('Could not create a world. Browser storage is unavailable.'); }
}, 30);
diff --git a/src/ui/state.js b/src/ui/state.js
index 15926de..4394693 100644
--- a/src/ui/state.js
+++ b/src/ui/state.js
@@ -23,7 +23,7 @@ loadSettings();
// ---------- Game state ----------
const TOOLS = [
{ id: 'drill', name: 'Drill', key: '1' },
- { id: 'vacuum', name: 'Vacuum', key: '2' },
+ { id: 'vacuum', name: 'Pneumatic nozzle', key: '2' },
{ id: 'build', name: 'Build', key: '3' },
{ id: 'ignite', name: 'Igniter', key: '4' },
];
diff --git a/tasks/t26.md b/tasks/t26.md
new file mode 100644
index 0000000..b71efbf
--- /dev/null
+++ b/tasks/t26.md
@@ -0,0 +1,18 @@
+---
+id: t26
+status: Review
+order: 3
+priority: P3
+created: 2026-10-24
+updated: 2026-10-24T19:18:52Z
+title: rename and make name visible somewhere the vac/blow tool
+report: |
+ (agent) The tool is now the Pneumatic nozzle. Its card starts with a title row (the name and the
+ nozzle width), and the hotbar tooltip, Welcome, milestones, notebook and empty-bag hint all use
+ the new name. Please check the wording reads well in the notebook and the Welcome card.
+---
+
+## Subtasks
+
+- [x] The tool is called the Pneumatic nozzle in the hotbar tooltip and in all player text
+- [x] Its tool card starts with a title row showing the name and the nozzle width
diff --git a/test/play.py b/test/play.py
index df88649..367f764 100644
--- a/test/play.py
+++ b/test/play.py
@@ -217,6 +217,8 @@ with sync_playwright() as p:
# 2. vacuum, left button: lock onto what you click
pg.keyboard.press('2'); settle(100)
+ check('Pneumatic nozzle card title and width match the selected tool', ev("GAME.tool === 1 && $('selcard').querySelector('.name').textContent === 'Pneumatic nozzle' && $('selcard').firstElementChild.querySelector('.chip').textContent === `width ${GAME.brush[1]}` && !$('selcard').querySelector('.sub .chip')"))
+ check('Pneumatic nozzle name appears in the tool list and hotbar title', ev("TOOLS[1].name === 'Pneumatic nozzle' && $('hotbar').children[1].title.startsWith('Pneumatic nozzle')"))
tgt = ev(f'''(() => {{ for (let r = 0; r < 30; 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('loose material lies around the hole', tgt is not None)
@@ -257,7 +259,7 @@ with sync_playwright() as p:
for mode in ['dirt', 'air']:
r = ev(f"vacCase('{mode}', [COAL, CUORE])")
check(f'selected coal and ore are picked starting on {mode}, dirt stays', r['coal'] > 0 and r['ore'] > 0 and r['dirt'] == 0 and r['dirtStays'] and r['filter'] == -2 and r['selection'] == ev('[COAL, CUORE]'))
- check('selecting Vacuum leaves an empty selection empty', ev("(() => { const old = GAME.blow; GAME.blow = new Set(); selectTool(0); selectTool(1); renderSelCard(); const ok = !GAME.blow.size && $('selcard').querySelector('.vacchoice').textContent === 'nothing selected' && $('selcard').querySelector('.num.chip').textContent === 'none'; GAME.blow = old; return ok; })()"))
+ check('selecting Pneumatic nozzle leaves an empty selection empty', ev("(() => { const old = GAME.blow; GAME.blow = new Set(); selectTool(0); selectTool(1); renderSelCard(); const ok = !GAME.blow.size && $('selcard').querySelector('.vacchoice').textContent === 'nothing selected' && $('selcard').querySelector('.num.chip').textContent === 'none'; GAME.blow = old; return ok; })()"))
check('Q/E when selection is absent from the bag chooses first or last', ev("(() => { const old = GAME.blow, n = bag[SAND]; bag[SAND] = 0; GAME.blow = new Set([SAND]); cycleItem(-1); const last = blowList()[0] === bagMaterials().at(-1); GAME.blow = new Set([SAND]); cycleItem(1); const first = blowList()[0] === bagMaterials()[0]; bag[SAND] = n; GAME.blow = old; return first && last; })()"))
check('Q/E from an empty selection chooses the first or last bag material', ev("(() => { const old = GAME.blow; GAME.blow = new Set(); cycleItem(1); const first = blowList()[0] === bagMaterials()[0]; GAME.blow.clear(); cycleItem(-1); const last = blowList()[0] === bagMaterials().at(-1); GAME.blow = old; return first && last; })()"))
check('middle click chooses a loose material even with none in the bag', ev("""(() => {
@@ -790,7 +792,7 @@ with sync_playwright() as p:
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.blow = new Set([COAL]); __pad.buttons[1].pressed = true; pollPad(); __pad.buttons[1].pressed = false; pollPad()")
- check('gamepad B clears the Vacuum selection', ev('!GAME.blow.size'))
+ check('gamepad B clears the nozzle selection', ev('!GAME.blow.size'))
ev("selectTool(2); GAME.pieceIdx = PIECE_BY_ID.belt.index; GAME.pieceDir.belt = 0; __pad.buttons[2].pressed = true; pollPad(); __pad.buttons[2].pressed = false; pollPad()")
check('gamepad X rotates with the Build tool', ev('GAME.pieceDir.belt === 1'))
ev('selectTool(1)')