diff --git a/NOTES.md b/NOTES.md index 442e482..dbedd9c 100644 --- a/NOTES.md +++ b/NOTES.md @@ -10,10 +10,8 @@ and stay there until the owner has checked them in the game and deleted them. - stacking ingots is a little odd, they dont stack neatly. so putting them all in the same place means it will get full but have lots of air space. what can we do about this? WHat in real life might be the solution for storing ingots? - i have noticed that belts with a big pile on top only move the grains on the top of the stack. the belt should move the whole stack. if this is an issue for performance, maybe a nice solution is to stall the belt when there is too much on it. then the player must build in a way to control the flow to that belt. - (agent) Written after version 24, which already tried to fix this: carryColumn in src/sim/conveyors.js moves the whole stack, up to 96 grains deep. A one-off headless check of a tall heap moving together passed then (it was not kept as a suite test), so the real game must differ from that check somehow. Reproduce it in the game first (tools/shot.py, or play it), then add a suite test that fails, then fix. -- for the build menu, add shift + q/e to move tabs. - instead of full description on to left for build menu, mouse hover reviels a new info popup. organize the popup look so its nice and easy to read the information. - dont need the short description in the build menu if we have a hover info popup. -- in the build menu, having the "g pick" there is confusing. im not sure if it should be there or not. also middle mouse the default for pick is better, g shouldnt even be used at all, its too far from the normal keys people use around wasd. maybe later for something less used. - lets think about how best to design the factory products. basics are normal smelting ore, refining, etc. but the actual export products could be like real construction materials. materials that a real construction site might need. - the world still seems quite small. i dont know if thats because the bot moves so fast, the bot can mine too fast, or the grains should be smaller... i would like to make the worl seem huge to the player and give them loads of resources to mine for a long game. as is, you could mine all the resources from the map pretty quickly and end up having to start a new world and new game as a result. or maybe there is some other solution that i havent thought of yet. need to brainstorm this. @@ -25,4 +23,7 @@ and stay there until the owner has checked them in the game and deleted them. ## Done, please check -(Nothing yet.) +- for the build menu, add shift + q/e to move tabs. + - (agent) Shift+Q and Shift+E switch build-menu tabs, wrapping at the ends; each tab remembers the piece you last chose in it. Plain Q/E still cycle pieces. The keys are in the menu hint and the notebook's controls list. +- in the build menu, having the "g pick" there is confusing. im not sure if it should be there or not. also middle mouse the default for pick is better, g shouldnt even be used at all, its too far from the normal keys people use around wasd. maybe later for something less used. + - (agent) G does nothing now and is free for something later. Middle mouse picks, and the menu hint, build card and notebook controls list say so. diff --git a/docs/DESIGN.md b/docs/DESIGN.md index d77a568..16ad374 100644 --- a/docs/DESIGN.md +++ b/docs/DESIGN.md @@ -79,7 +79,8 @@ Details and numbers live in the code and in `docs/systems/`. - Machines: fan, Refiner, Stone cutter, Silo. - Molds: ingot, bit, tank. - Build actions: drag for lines, Shift-drag for rectangles, Ctrl to replace, hold C / X and - drag to copy / cut, V to paste, Ctrl+Z to undo, middle mouse (or G) to pick. + drag to copy / cut, V to paste, Ctrl+Z to undo, middle mouse to pick, + Shift+Q/E to switch build tabs. - **Economy:** - The trade dock pays for anything dropped through its grate, at the market price. - Prices dip when you sell a lot of one thing and recover over minutes. @@ -135,8 +136,8 @@ above, including: build more lines. - **A heap on a belt moves as one**, up to 96 grains deep, not just its bottom layer. The owner reports this still misbehaves (see NOTES.md). -- **Pick** (middle mouse, also G for now) selects the piece under the cursor with its direction, - wall material and filter settings. The owner wants G dropped (see NOTES.md). +- **Pick** (middle mouse) selects the piece under the cursor with its direction, + wall material and filter settings. G is unbound, as the owner requested. - **The build menu** is a bar of tabs and icon tiles along the bottom: Structure, Moving, Liquids, Machines, Molds. The owner wants more changes (see NOTES.md). - **No length limit when dragging.** Blocks out of the bot's reach become ghosts that it builds diff --git a/docs/PROJECT_MAP.md b/docs/PROJECT_MAP.md index 4f5f537..48c9271 100644 --- a/docs/PROJECT_MAP.md +++ b/docs/PROJECT_MAP.md @@ -392,9 +392,9 @@ The notebook (N): milestones, market, wallet, blueprints, controls. The tool hotbar and what is selected. - functions: renderHotbar, toolId, steering, modShift, grainAt, selectTool, bagMaterials, mixList, ensureDropMat, piece -### src/ui/build-menu.js (72 lines) +### src/ui/build-menu.js (80 lines) The build menu along the bottom: categories, piece tiles and their icons. -- functions: pieceIcon, catOf, renderBuildBar, setupBuildBar +- functions: pieceIcon, catOf, selectBuildCat, cycleBuildCat, renderBuildBar, setupBuildBar - variables: BUILD_CATS, BUILD_ORDER, PIECE_ICONS, lastBuildKey ### src/ui/selection.js (246 lines) @@ -406,7 +406,7 @@ The selected tool and piece: R / Q / E, drill width, filter picker, the selectio Discovery cards, hover info (I) and the cursor label. - functions: showCard, howText, discoveryCard, reactionsOf, hoverInfo, renderInfo, drawCursorLabel -### src/ui/input.js (76 lines) +### src/ui/input.js (75 lines) Keyboard and mouse listeners. - functions: isTyping - variables: canvas diff --git a/docs/agent-log.md b/docs/agent-log.md index ea4165e..1467911 100644 --- a/docs/agent-log.md +++ b/docs/agent-log.md @@ -4,6 +4,23 @@ 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-23: Shift+Q/E build tabs, G pick key removed (Claude Code, with Codex) + +Notes: "add shift + q/e to move tabs" and "having the "g pick" there is confusing". + +- `src/ui/build-menu.js`: `selectBuildCat(c)` (shared with tab clicks) and `cycleBuildCat(d)`; + the menu hint now reads Q/E cycle · Shift+Q/E tab. +- `src/ui/input.js`: Shift+Q/E switch tabs with the build tool; G is unbound. +- Text: build card (`selection.js`), notebook controls (`notebook.js`), `actions.js` comment, + `docs/DESIGN.md`, `docs/PROJECT_MAP.md` (regenerated, `--check` passes). +- `test/play.py`: checks for Shift+Q/E, wrapping, the remembered piece per tab, plain Q/E, + and that G no longer calls pick while middle mouse still does (109 checks). +- Tests: all 11 headless suites and `play.py` pass; a `tools/shot.py` shot of the Moving tab + shows the new hint. `t_smooth.py` passes, but its "half-resolution smooth still draws" check + failed on several runs with the 'slow' pixel fallback while the machine was busy (load ~14). + The previous commit fails the same way under the same load, so this change didn't cause it. +- Open: that check is timing-sensitive on a loaded machine; rerun it when the machine is idle. + ## 2026-10-23: browser tests run locally (Claude Code, with Codex) Request: get the browser tests, Playwright and Chromium working on the owner's Arch machine diff --git a/src/ui/actions.js b/src/ui/actions.js index ee7aabf..3669b66 100644 --- a/src/ui/actions.js +++ b/src/ui/actions.js @@ -45,7 +45,7 @@ function doInteract() { if (msg) { setStatus(msg, 'info'); if (!/^New (drill bit|bag)/.test(msg)) toast(msg); } else { setStatus('Nothing to use there. F works on gates, molds, fans and filters.', 'info'); } } -// Pick (G or middle mouse): the piece under the cursor becomes the one you build, with its +// Pick (middle mouse): the piece under the cursor becomes the one you build, with its // direction, wall material and filter settings. On loose material it picks that to blow out. function pickUnderCursor() { const ax = Math.floor(GAME.aim.x), ay = Math.floor(GAME.aim.y); diff --git a/src/ui/build-menu.js b/src/ui/build-menu.js index 6100ddf..7977cb0 100644 --- a/src/ui/build-menu.js +++ b/src/ui/build-menu.js @@ -29,6 +29,17 @@ const PIECE_ICONS = { function pieceIcon(id) { return ``; } function catOf(id) { const c = BUILD_CATS.findIndex(([, ids]) => ids.includes(id)); return c < 0 ? 0 : c; } let lastBuildKey = ''; +function selectBuildCat(c) { + GAME.buildCat = c; + const ids = BUILD_CATS[c][1], keep = GAME.catPick && GAME.catPick[c]; + GAME.pieceIdx = PIECE_BY_ID[ids.includes(keep) ? keep : ids[0]].index; + GAME.catPick = GAME.catPick || {}; GAME.catPick[c] = piece().id; + GAME.drag = null; hudDirty = true; lastBuildKey = ''; playSfx('ui'); +} +function cycleBuildCat(d) { + const c = GAME.buildCat != null ? GAME.buildCat : catOf(piece().id); + selectBuildCat((c + d + BUILD_CATS.length) % BUILD_CATS.length); +} function renderBuildBar() { const bar = $('buildbar'); if (!bar) return; const show = GAME.running && toolId() === 'build' && !GAME.paste && !GAME.selDrag && !selModeHeld(); @@ -42,7 +53,7 @@ function renderBuildBar() { lastBuildKey = key; const tip = PIECE_BY_ID[hov && ids.includes(hov) ? hov : P.id]; let h = '
' + BUILD_CATS.map(([name], k) => ``).join('') - + 'Q/E cycle · G pick
'; + + 'Q/E cycle · Shift+Q/E tab
'; for (const id of ids) { const Q = PIECE_BY_ID[id], cost = pieceCostNow(Q), ok = canAfford(cost, 1); const sw = Q.variants === 'wall' ? `` : ''; @@ -57,11 +68,8 @@ function setupBuildBar() { const bar = $('buildbar'); if (!bar) return; bar.addEventListener('click', e => { const t = e.target.closest('[data-cat],[data-piece]'); if (!t) return; - if (t.dataset.cat != null) { - const c = +t.dataset.cat; GAME.buildCat = c; - const ids = BUILD_CATS[c][1], keep = GAME.catPick && GAME.catPick[c]; - GAME.pieceIdx = PIECE_BY_ID[ids.includes(keep) ? keep : ids[0]].index; - } else { GAME.pieceIdx = PIECE_BY_ID[t.dataset.piece].index; GAME.buildCat = catOf(t.dataset.piece); } + if (t.dataset.cat != null) { selectBuildCat(+t.dataset.cat); return; } + GAME.pieceIdx = PIECE_BY_ID[t.dataset.piece].index; GAME.buildCat = catOf(t.dataset.piece); GAME.catPick = GAME.catPick || {}; GAME.catPick[catOf(piece().id)] = piece().id; GAME.drag = null; hudDirty = true; lastBuildKey = ''; playSfx('ui'); }); diff --git a/src/ui/input.js b/src/ui/input.js index f87d184..659bc68 100644 --- a/src/ui/input.js +++ b/src/ui/input.js @@ -25,9 +25,8 @@ window.addEventListener('keydown', e => { case '1': case '2': case '3': case '4': selectTool(+k - 1); break; case 'c': case 'x': hudDirty = true; break; case 'v': startPaste(); break; - case 'g': pickUnderCursor(); break; - case 'q': cycleItem(-1); break; - case 'e': cycleItem(1); break; + case 'q': if (e.shiftKey && toolId() === 'build') cycleBuildCat(-1); else cycleItem(-1); break; + case 'e': if (e.shiftKey && toolId() === 'build') cycleBuildCat(1); else cycleItem(1); break; case 'r': rotate(); break; case 't': if (toolId() === 'vacuum') toggleCollectUnderCursor(); else if (toolId() === 'build' && piece().filter) pickFilterUnderCursor(); break; case 'f': doInteract(); break; diff --git a/src/ui/notebook.js b/src/ui/notebook.js index ca4fec8..40b82fb 100644 --- a/src/ui/notebook.js +++ b/src/ui/notebook.js @@ -169,10 +169,10 @@ function copyCode(code) { 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'], ['Cycle what you blow · piece', 'Q / E'], ['Variant: material · direction · flip', 'R'], + ['Tools', '1 – 4'], ['Cycle what you blow · piece', 'Q / E'], ['Switch build tab', 'Shift + Q / E'], ['Variant: material · direction · flip', 'R'], ['Tick/untick what is under the cursor (Vacuum)', 'R or T'], ['Nozzle or brush size', '[ ] or Shift + wheel'], ['Replace what is built (Build)', 'Hold Ctrl'], ['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)', 'G or middle mouse'], ['Notebook', 'N'], ['Clear all blueprint ghosts', 'Delete'], ['Save', 'Ctrl + S'], ['Pause · menu', 'Esc'], ['Mute', 'M'], + ['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'], ]; const pad = [ ['Fly', 'Left stick'], ['Boost', 'Left stick pushed all the way'], ['Aim', 'Right stick'], ['Use tool · suck', 'RT'], ['Second action · blow', 'LT'], ['Tools', 'LB / RB'], diff --git a/src/ui/selection.js b/src/ui/selection.js index beb7c03..8100185 100644 --- a/src/ui/selection.js +++ b/src/ui/selection.js @@ -199,7 +199,7 @@ function renderSelCard() {
${esc(P.desc)}
${P.filter ? `
New ones will ${esc(filterSentence(GAME.filterCfg))}. F sets materials and mode (or click materials in the bag, or T over one); F on a placed filter changes its whole row.
` : ''}
${P.kind === 'block' ? 'Drag for a line (any length: what is out of reach becomes ghosts), Shift-drag for a rectangle.' : 'Click to place.'}${P.dirs && P.kind === 'block' ? ' A dragged line runs the way you drag it; a single click uses R.' : P.dirs ? ' R turns it.' : pieceUsesWall(P) ? ' R picks the material.' : ''}
-
Ctrl replace · Ctrl+Z undo · right click take back · G pick · hold C/X copy/cut · V paste
${ng ? `
${ng} ghost pieces waiting${ghostMissing ? ', some short on materials' : ''}.
` : ''}`; +
Ctrl replace · Ctrl+Z undo · right click take back · middle mouse pick · hold C/X copy/cut · V paste
${ng ? `
${ng} ghost pieces waiting${ghostMissing ? ', some short on materials' : ''}.
` : ''}`; } 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.
`; } diff --git a/test/play.py b/test/play.py index 1d09d37..4bfa6ce 100644 --- a/test/play.py +++ b/test/play.py @@ -208,6 +208,30 @@ with sync_playwright() as p: ok = select('3', "piece().id === 'wall'") ev('GAME.wallMat = RSTONE; hudDirty = true') check('build tool starts on wall blocks', ok) + c0 = ev('GAME.buildCat != null ? GAME.buildCat : catOf(piece().id)') + pg.keyboard.press('Shift+E') + check('Shift+E switches build tabs and selects a piece in the tab', ev('GAME.buildCat') == (c0 + 1) % ev('BUILD_CATS.length') and ev('BUILD_CATS[GAME.buildCat][1].includes(piece().id)')) + pg.keyboard.press('Shift+Q') + check('Shift+Q returns to the tab and restores its wall piece', ev('GAME.buildCat') == c0 and ev("piece().id === 'wall'")) + ev('selectBuildCat(0)') + pg.keyboard.press('Shift+Q') + check('Shift+Q wraps from the first build tab to the last', ev('GAME.buildCat === BUILD_CATS.length - 1')) + pg.keyboard.press('Shift+E') + check('Shift+E wraps back to the first build tab', ev('GAME.buildCat === 0')) + p0 = ev('piece().id') + pg.keyboard.press('e') + check('plain E cycles pieces without switching tabs', ev('piece().id') != p0 and ev('GAME.buildCat') == 0) + pg.keyboard.press('q') + check('plain Q returns to the piece without switching tabs', ev('piece().id') == p0 and ev('GAME.buildCat') == 0) + pg.mouse.move(*cell_to_screen(bx + 6, gy + 8)); settle(100) + ev('window._pickN = 0; window._pickOrig = pickUnderCursor; pickUnderCursor = function () { _pickN++; }; 0') # 0: evaluate would call a returned function + pg.keyboard.press('g'); settle(60) + g_picks = ev('_pickN') + pg.mouse.click(*cell_to_screen(bx + 6, gy + 8), button='middle'); settle(60) + check('G no longer picks; middle mouse still does', g_picks == 0 and ev('_pickN') == 1, f"G {g_picks}, then {ev('_pickN')}") + ev('pickUnderCursor = _pickOrig') + check('G leaves the build piece unchanged over the world', ev('piece().id') == p0) + ev("GAME.pieceIdx = PIECE_BY_ID.wall.index; GAME.wallMat = RSTONE; hudDirty = true") bbx, bby = ev('[Math.floor((bot.x + 2) / 4), Math.floor((bot.y + 2) / 4)]') rb = ev('bag[RSTONE]'); nb0 = ev('blockP.reduce((a, v) => a + (v === BP_WALL), 0)') drag(lambda: blk(bbx + 3, bby - 4), lambda: blk(bbx + 8, bby - 4))