Material menu for walls and molds, opened with F (t5)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -205,6 +205,12 @@ above, including:
|
||||
- **The to-do list is a kandown board.** `NOTES.md` became one task file per note in
|
||||
`tasks/` (Todo for tasks, Backlog for discussions and ideas, Review for "done, please check").
|
||||
The owner wanted a task tool built for AI agents.
|
||||
- **Wall and mold materials have a menu (F).** It lists every wall material with what you have,
|
||||
the cost and the heat it stands; R still cycles. The owner chose F to match the filter menu.
|
||||
- **The camera's drift toward the pointer is a setting** ("Camera looks toward the pointer", on
|
||||
by default). The owner doesn't like it.
|
||||
- **Fog of war reveals by line of sight.** Unexplored ground is black with a soft edge; the sky
|
||||
and the surface start seen. The owner chose line of sight over a plain circle around the bot.
|
||||
|
||||
## Ideas not built yet
|
||||
|
||||
|
||||
+4
-4
@@ -393,14 +393,14 @@ 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 (101 lines)
|
||||
### src/ui/build-menu.js (102 lines)
|
||||
The build menu along the bottom: categories, piece tiles and their icons.
|
||||
- functions: pieceIcon, catOf, selectBuildCat, cycleBuildCat, buildVariantChips, renderBuildBar, setupBuildBar
|
||||
- variables: BUILD_CATS, BUILD_ORDER, PIECE_ICONS, lastBuildKey, lastBuildMenu
|
||||
|
||||
### src/ui/selection.js (236 lines)
|
||||
### src/ui/selection.js (252 lines)
|
||||
The selected tool and piece: R / Q / E, drill width, filter picker, the selection card, the bag panel.
|
||||
- functions: pieceUsesWall, setDrillWidth, tuneUnderCursor, cycleItem, cycleWall, rotate, toggleCollect, pickFilterUnderCursor, toggleFilterMat, toggleCollectUnderCursor, setBrush, pieceV, pieceExtra, filterChoices, matList, filterParts, filterSentence, applyFilterRun, openFilterPicker, filterBlockAt, chipCost, pieceCostNow, renderSelCard, renderBag
|
||||
- functions: pieceUsesWall, setDrillWidth, tuneUnderCursor, cycleItem, cycleWall, rotate, toggleCollect, pickFilterUnderCursor, toggleFilterMat, toggleCollectUnderCursor, setBrush, pieceV, pieceExtra, filterChoices, matList, filterParts, filterSentence, applyFilterRun, openFilterPicker, openWallPicker, filterBlockAt, chipCost, pieceCostNow, renderSelCard, renderBag
|
||||
- variables: lastBagKey
|
||||
|
||||
### src/ui/info.js (170 lines)
|
||||
@@ -412,7 +412,7 @@ Keyboard and mouse listeners.
|
||||
- functions: isTyping
|
||||
- variables: canvas
|
||||
|
||||
### src/ui/actions.js (242 lines)
|
||||
### src/ui/actions.js (243 lines)
|
||||
What input does: overlays, F, pick, copy / cut / paste, undo, building and tool use each frame. Which overlays are on, shown as a coloured frame round the screen and a named badge
|
||||
- functions: updateOverlayUI, toggleLens, doInteract, pickUnderCursor, aimBlock, stampOrigin, selModeHeld, startPaste, stopSelectAndPaste, plural, piecesIn, doUndo, endRmbRecord, dragDirFor, blockLabel, primaryPress, primaryRelease, secondaryPress, toolTick
|
||||
- variables: ovShown
|
||||
|
||||
@@ -4,6 +4,19 @@ 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: t5 Material selection menu (Claude Code, Codex)
|
||||
|
||||
- Decided with the owner: F opens it for walls and molds, R keeps cycling.
|
||||
- `src/ui/selection.js`: `openWallPicker` (a dialog of every wall material: what you have, the
|
||||
cost for this piece, the heat it stands; owned first, unaffordable dimmed; glass says whether
|
||||
it is from shards or bars); `pieceCostNow(P, m)` takes a material.
|
||||
- `src/ui/actions.js`: F with a wall or mold piece opens it. `src/ui/build-menu.js`: the material
|
||||
chip reads "· R · F" and opens it on click; placement help mentions F. `src/ui/notebook.js`:
|
||||
Controls and Blueprints text. `src/styles/style.css`: `.wpick`.
|
||||
- `test/play.py`: F and the chip open it, Brick picks RBRICK, R still cycles, molds have their own
|
||||
menu and cost, it fits at 1280×800 and 900×700 (screenshots checked).
|
||||
- Tests: `run_all.py --browser` pass, apart from the `t_smooth` half-resolution flake.
|
||||
|
||||
## 2026-10-24: t10 World name box for a new world (Claude Code, Codex)
|
||||
|
||||
- `src/shell.html`, `src/styles/style.css`: a Name box across the top of the New world panel.
|
||||
|
||||
@@ -315,6 +315,16 @@ input.text { background: var(--soot-0); border: 1px solid var(--soot-4); border-
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.toast { animation: none; }
|
||||
}
|
||||
/* wall material picker */
|
||||
.wpick { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 6px; max-height: min(60vh, 490px); overflow-y: auto; padding: 2px; }
|
||||
.wpick .wp { display: flex; align-items: flex-start; gap: 8px; text-align: left; padding: 8px; border-radius: 6px; border: 1px solid var(--soot-4); background: var(--soot-2); color: var(--bone); font: inherit; font-size: 12px; cursor: pointer; min-width: 0; }
|
||||
.wpick .wp > span:last-child { min-width: 0; overflow-wrap: anywhere; }
|
||||
.wpick .wp span span { display: block; color: var(--ash); margin-top: 3px; }
|
||||
.wpick .wp.short { opacity: 0.55; }
|
||||
.wpick .wp:hover { background: #2d2724; }
|
||||
.wpick .wp.on { border-color: #e59a52; background: #33261c; }
|
||||
.wpick .wp:focus-visible, [data-wall-picker]:focus-visible { outline: 2px solid #e59a52; outline-offset: 1px; }
|
||||
[data-wall-picker] { cursor: pointer; }
|
||||
/* filter picker */
|
||||
.fpick { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 6px; max-height: min(52vh, 420px); overflow-y: auto; padding: 2px; }
|
||||
.fpick .fp { display: flex; align-items: center; gap: 8px; text-align: left; padding: 7px 9px; border-radius: 6px; border: 1px solid var(--soot-4); background: var(--soot-2); color: var(--bone); font: inherit; font-size: 13px; cursor: pointer; }
|
||||
|
||||
@@ -35,6 +35,7 @@ function doInteract() {
|
||||
GAME.editRun = run;
|
||||
return;
|
||||
}
|
||||
if (toolId() === 'build' && pieceUsesWall(piece())) { openWallPicker(); return; }
|
||||
if (toolId() === 'build' && piece().filter) { openFilterPicker(Object.assign({}, GAME.filterCfg, { dir: GAME.pieceDir.filter || 0 }), f => { GAME.filterCfg = { m: f.m, inv: f.inv }; GAME.pieceDir.filter = f.dir; }, 'New filters'); return; }
|
||||
let msg = '';
|
||||
outer: for (let r = 0; r <= 2; r++) for (let dy = -r; dy <= r; dy++) for (let dx = -r; dx <= r; dx++) {
|
||||
|
||||
@@ -48,7 +48,7 @@ function buildVariantChips(P) {
|
||||
const dirChip = !P.dirs ? '' : dragging ? `<span class="chip ok">${glyph(P.dirs[GAME.dragDir])} ${esc(P.dirs[GAME.dragDir])} · from your drag</span>` : `<span class="chip hot">${glyph(dir)} ${esc(dir)} · R</span>`;
|
||||
const fc = GAME.filterCfg;
|
||||
const sws = ms => ms.slice(0, 6).map(m => `<span class="sw" style="display:inline-block;vertical-align:-2px;width:9px;height:9px;margin-right:2px;background:${rgbOf(m)}"></span>`).join('') + (ms.length > 6 ? `+${ms.length - 6}` : '');
|
||||
return pieceUsesWall(P) ? `<span class="chip hot"><span class="sw" style="display:inline-block;vertical-align:-2px;width:9px;height:9px;margin-right:4px;background:${rgbOf(WALL_OF[GAME.wallMat])}"></span>${esc(MD[WALL_OF[GAME.wallMat]].name.toLowerCase())} · R</span>`
|
||||
return pieceUsesWall(P) ? `<button class="chip hot" data-wall-picker><span class="sw" style="display:inline-block;vertical-align:-2px;width:9px;height:9px;margin-right:4px;background:${rgbOf(WALL_OF[GAME.wallMat])}"></span>${esc(MD[WALL_OF[GAME.wallMat]].name.toLowerCase())} · R · F</button>`
|
||||
: P.filter ? `<span class="chip hot">${fc.inv ? 'carries' : 'drops'} ${fc.m.length ? sws(fc.m) : 'nothing'} · F</span>${dirChip}`
|
||||
: dir ? dirChip : '';
|
||||
}
|
||||
@@ -78,7 +78,7 @@ function renderBuildBar() {
|
||||
if (!tip) return;
|
||||
const size = tip.kind === 'machine' ? `${tip.bw}×${tip.bh} blocks` : tip.kind === 'mold' ? `${MOLDS[tip.mold].bw}×${MOLDS[tip.mold].bh} blocks` : '1 block';
|
||||
pop.innerHTML = `<div class="bb-title">${pieceIcon(tip.id)}<b>${esc(tip.name)}</b></div><div class="bb-chips">${chipCost(pieceCostNow(tip))}<span class="chip">${size}</span>${tip.power ? `<span class="chip">${tip.power} power</span>` : ''}</div>
|
||||
<div class="bb-desc">${esc(tip.desc)}</div><div class="bb-place"><b>How to place</b> ${tip.kind === 'block' ? 'Drag for a line, Shift-drag for a rectangle; out of reach becomes ghosts.' : 'Click to place.'}${tip.dirs && tip.kind === 'block' ? ' A dragged line runs the way you drag it; a single click uses <kbd>R</kbd>.' : tip.dirs ? ' <kbd>R</kbd> turns it.' : pieceUsesWall(tip) ? ' <kbd>R</kbd> picks the material.' : ''}</div>
|
||||
<div class="bb-desc">${esc(tip.desc)}</div><div class="bb-place"><b>How to place</b> ${tip.kind === 'block' ? 'Drag for a line, Shift-drag for a rectangle; out of reach becomes ghosts.' : 'Click to place.'}${tip.dirs && tip.kind === 'block' ? ' A dragged line runs the way you drag it; a single click uses <kbd>R</kbd>.' : tip.dirs ? ' <kbd>R</kbd> turns it.' : pieceUsesWall(tip) ? ' <kbd>R</kbd> cycles materials; <kbd>F</kbd> opens the material menu.' : ''}</div>
|
||||
${tip.filter ? `<div class="bb-place">New ones will ${esc(filterSentence(GAME.filterCfg))}. <kbd>F</kbd> sets materials and mode (or click materials in the bag, or <kbd>T</kbd> over one); <kbd>F</kbd> on a placed filter changes its whole row.</div>` : ''}
|
||||
<div class="bb-place">hold <kbd>G</kbd> replace · <kbd>Ctrl</kbd>+<kbd>Z</kbd> undo · right click take back · middle mouse pick · hold <kbd>C</kbd>/<kbd>X</kbd> copy/cut · <kbd>V</kbd> paste</div>`;
|
||||
const tile = bar.querySelector(`[data-piece="${tip.id}"]`); if (!tile) { pop.hidden = true; return; }
|
||||
@@ -86,6 +86,7 @@ function renderBuildBar() {
|
||||
pop.style.left = `${Math.max(8, Math.min(innerWidth - w - 8, r.left + r.width / 2 - w / 2)) - br.left}px`;
|
||||
}
|
||||
function setupBuildBar() {
|
||||
$('selcard').addEventListener('click', e => { if (e.target.closest('[data-wall-picker]')) openWallPicker(); });
|
||||
const bar = $('buildbar'); if (!bar) return;
|
||||
bar.addEventListener('click', e => {
|
||||
const t = e.target.closest('[data-cat],[data-piece]'); if (!t) return;
|
||||
|
||||
+2
-2
@@ -41,7 +41,7 @@ function renderNotebook() {
|
||||
let h = '<p class="note">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.</p><div class="cards">';
|
||||
for (const P of PIECES) {
|
||||
if (P.hidden) continue;
|
||||
const cost = P.kind === 'mold' ? `${MOLDS[P.mold].walls} grains of wall material (R picks it)` : P.code === BP_WALL ? '16 grains of rubble, lumps or chunks, or 2 bars' : costText(P.cost, 1);
|
||||
const cost = P.kind === 'mold' ? `${MOLDS[P.mold].walls} grains of wall material (R cycles, F opens the menu)` : P.code === BP_WALL ? '16 grains of rubble, lumps or chunks, or 2 bars' : costText(P.cost, 1);
|
||||
const afford = P.kind === 'mold' || P.code === BP_WALL ? true : canAfford(P.cost, 1);
|
||||
const size = P.kind === 'machine' ? `${P.bw}×${P.bh}` : P.kind === 'mold' ? `${MOLDS[P.mold].bw}×${MOLDS[P.mold].bh}` : '1×1';
|
||||
h += `<div class="card"><div class="h"><span>${esc(P.name)}</span><span class="chip ${afford ? 'ok' : ''}">${P.power ? esc(P.power + ' power') : P.id === 'wheel' || P.id === 'engine' ? 'makes power' : size + (size === '1×1' ? ' block' : ' blocks')}</span></div><div class="p">${esc(P.desc)}</div><div class="p">Cost: ${esc(cost)}</div></div>`;
|
||||
@@ -169,7 +169,7 @@ 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'], ['Switch build tab', 'Shift + 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'], ['Wall or mold material menu (Build)', 'F'],
|
||||
['Tick/untick what is under the cursor (Vacuum)', 'R or T'], ['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'],
|
||||
|
||||
+20
-4
@@ -138,11 +138,27 @@ function openFilterPicker(cur, onDone, title) {
|
||||
box.querySelectorAll('[data-m]').forEach(b => b.onclick = () => { const m = +b.dataset.m; if (sel.has(m)) sel.delete(m); else sel.add(m); playSfx('ui'); sync(); });
|
||||
sync();
|
||||
}
|
||||
function openWallPicker() {
|
||||
const P = piece(), title = P.kind === 'mold' ? `${P.name} material` : 'Wall material';
|
||||
const list = WALL_LIST.filter(m => bag[m] > 0).concat(WALL_LIST.filter(m => !bag[m]));
|
||||
const body = `<div class="wpick" role="radiogroup" aria-label="${esc(title)}">${list.map(m => {
|
||||
const d = MD[WALL_OF[m]], cost = pieceCostNow(P, m), on = m === GAME.wallMat;
|
||||
// two sources build the same wall (glass from shards or from bars): say which
|
||||
const name = WALL_LIST.filter(k => WALL_OF[k] === WALL_OF[m]).length > 1 ? `${d.name} from ${MD[m].name.toLowerCase()}` : d.name;
|
||||
const heat = d.ign ? `burns at ${fmtT(d.ign)}` : d.melt ? `melts at ${fmtT(d.melt)}` : "won't melt";
|
||||
return `<button class="wp${on ? ' on' : ''}${canAfford(cost, 1) ? '' : ' short'}" role="radio" aria-checked="${on}" data-wall="${m}"><span class="sw" style="background:${rgbOf(WALL_OF[m])}"></span><span><b>${esc(name)}</b><span>you have ${bag[m].toLocaleString('en-US')}${PH[m] === PH_ITEM ? ` ${esc(MD[m].name.toLowerCase())}${bag[m] === 1 ? '' : 's'}` : ''}</span><span>cost: ${esc(costText(cost, 1))}</span><span>${heat}</span></span></button>`;
|
||||
}).join('')}</div>`;
|
||||
dialog(title, body, [{ label: 'Cancel' }]);
|
||||
$('dlg-body').querySelectorAll('[data-wall]').forEach(b => b.onclick = () => {
|
||||
GAME.wallMat = +b.dataset.wall; closeModal('m-dialog'); playSfx('ui'); hudDirty = true; lastBagKey = '';
|
||||
setStatus(`${P.kind === 'mold' ? P.name : 'Walls'}: ${MD[WALL_OF[GAME.wallMat]].name.toLowerCase()}.`, 'info');
|
||||
});
|
||||
}
|
||||
function filterBlockAt(x, y) { if (x < 0 || y < 0 || x >= W || y >= H) return -1; const b = ((y / BS) | 0) * BW + ((x / BS) | 0); return blockP[b] === BP_FILTER ? b : -1; }
|
||||
function chipCost(cost) { const ok = canAfford(cost, 1); return `<span class="chip ${ok ? 'ok' : 'bad'}">${esc(costText(cost, 1))}</span>`; }
|
||||
function pieceCostNow(P) {
|
||||
if (P.kind === 'mold') return wallCost(GAME.wallMat, MOLDS[P.mold].walls);
|
||||
if (P.kind === 'block' && P.code === BP_WALL) return wallCost(GAME.wallMat, BS * BS);
|
||||
function pieceCostNow(P, m = GAME.wallMat) {
|
||||
if (P.kind === 'mold') return wallCost(m, MOLDS[P.mold].walls);
|
||||
if (P.kind === 'block' && P.code === BP_WALL) return wallCost(m, BS * BS);
|
||||
return P.cost;
|
||||
}
|
||||
function renderSelCard() {
|
||||
@@ -176,7 +192,7 @@ function renderSelCard() {
|
||||
h = '<div class="row"><span class="name grow">Build</span></div>'
|
||||
+ (chips ? `<div class="row" style="flex-wrap:wrap;gap:4px">${chips}</div>` : '')
|
||||
+ '<div class="sub">Place pieces from the menu below. Drag for a line, <kbd>Shift</kbd>-drag for a box; out of reach becomes ghosts.</div>'
|
||||
+ '<div class="sub"><kbd>Q</kbd>/<kbd>E</kbd> piece · <kbd>Shift</kbd>+<kbd>Q</kbd>/<kbd>E</kbd> tab · <kbd>R</kbd> turn or material · hold <kbd>G</kbd> replace · right click take back · hold <kbd>C</kbd>/<kbd>X</kbd> copy/cut · <kbd>V</kbd> paste · <kbd>Ctrl</kbd>+<kbd>Z</kbd> undo</div>';
|
||||
+ '<div class="sub"><kbd>Q</kbd>/<kbd>E</kbd> piece · <kbd>Shift</kbd>+<kbd>Q</kbd>/<kbd>E</kbd> tab · <kbd>R</kbd> turn or material · <kbd>F</kbd> material menu · hold <kbd>G</kbd> replace · right click take back · hold <kbd>C</kbd>/<kbd>X</kbd> copy/cut · <kbd>V</kbd> paste · <kbd>Ctrl</kbd>+<kbd>Z</kbd> undo</div>';
|
||||
} else if (t === 'vacuum') {
|
||||
ensureDropMat();
|
||||
const m = GAME.dropMat, f = GAME.lmb ? GAME.vacFilter : 0;
|
||||
|
||||
@@ -1,9 +1,16 @@
|
||||
---
|
||||
id: t5
|
||||
title: Material selection menu
|
||||
status: Todo
|
||||
status: Review
|
||||
created: 2026-10-24
|
||||
updated: 2026-10-24T04:41:45Z
|
||||
updated: 2026-10-24T05:26:29Z
|
||||
order: 0
|
||||
report: |
|
||||
(agent) New material menu for walls and molds. With the Build tool on a wall block or a mold,
|
||||
press F (or click the material chip on the Build card) to open it. Each material shows how
|
||||
much you have, what this piece costs in it and the heat it stands (melts at / burns at /
|
||||
won't melt). Ones you have come first; ones you can't afford are dimmed. R still cycles.
|
||||
To check: Build (3), Wall block, F, pick Clay. Then a mold piece, F again.
|
||||
---
|
||||
|
||||
## Context
|
||||
|
||||
@@ -342,6 +342,23 @@ with sync_playwright() as p:
|
||||
check('the build tool card describes the build tool, not the piece', pg.inner_text('#selcard .name') == 'Build' and 'menu below' in card and ev('piece().name') not in card and ev("costText(pieceCostNow(piece()), 1) || 'free'") not in card and ev('piece().desc') not in card, card[:160])
|
||||
check('the build menu has no status row', pg.locator('#buildbar .bb-status').count() == 0)
|
||||
check('the build menu has no short description row', pg.locator('.bb-tip').count() == 0)
|
||||
pg.keyboard.press('f'); settle(120)
|
||||
check('F opens all wall materials', pg.is_visible('#m-dialog') and 'material' in pg.inner_text('#dlg-title') and pg.locator('.wp').count() == ev('WALL_LIST.length'))
|
||||
check('wall material selection and costs match the build card', ev("[...document.querySelectorAll('.wp')].every(b => b.getAttribute('aria-checked') === String(+b.dataset.wall === GAME.wallMat) && b.classList.contains('short') === !canAfford(pieceCostNow(piece(), +b.dataset.wall), 1) && b.textContent.includes(costText(pieceCostNow(piece(), +b.dataset.wall), 1)))"))
|
||||
check('owned wall materials come first', ev("[...document.querySelectorAll('.wp')].map(b => +b.dataset.wall).join() === WALL_LIST.filter(m => bag[m] > 0).concat(WALL_LIST.filter(m => !bag[m])).join()"))
|
||||
for width, height, suffix in [(1280, 800, ''), (900, 700, '-900')]:
|
||||
pg.set_viewport_size({'width': width, 'height': height}); settle(120)
|
||||
check(f'wall picker fits at {width}', ev("$('dlg-body').scrollWidth <= $('dlg-body').clientWidth && document.querySelector('.wpick').scrollWidth <= document.querySelector('.wpick').clientWidth"))
|
||||
pg.screenshot(path=str(out / f'wall-picker{suffix}.png'))
|
||||
pg.set_viewport_size({'width': 1280, 'height': 800})
|
||||
pg.click(f'.wp[data-wall="{ev("RBRICK")}"]'); settle(120)
|
||||
check('choosing Brick closes the material menu', ev("GAME.wallMat === RBRICK && $('m-dialog').hidden"))
|
||||
pg.keyboard.press('r'); settle(120)
|
||||
check('R still cycles after picking a material', ev('GAME.wallMat !== RBRICK'))
|
||||
pg.click('#selcard [data-wall-picker]'); settle(120)
|
||||
check('the material chip opens the menu', pg.is_visible('#m-dialog'))
|
||||
pg.click('#dlg-foot button'); settle(120)
|
||||
ev('GAME.wallMat = RSTONE; hudDirty = true')
|
||||
for id in ['wall', 'grate']:
|
||||
pg.hover(f'#buildbar [data-piece="{id}"]'); settle(120)
|
||||
check(f'hover shows the full {id} description and name', pg.is_visible('#bb-pop') and pg.inner_text('#bb-pop .bb-title') == ev(f'PIECE_BY_ID.{id}.name') and pg.inner_text('#bb-pop .bb-desc') == ev(f'PIECE_BY_ID.{id}.desc'))
|
||||
@@ -405,6 +422,9 @@ with sync_playwright() as p:
|
||||
# 6. ingot mold: click to place, pour, bars drop below, vacuum them
|
||||
ok = select('3', "piece().id === 'ingotmold'")
|
||||
check('E cycles to the ingot mold', ok)
|
||||
pg.keyboard.press('f'); settle(120)
|
||||
check('molds have a named material menu and their own cost', pg.inner_text('#dlg-title') == ev("piece().name + ' material'") and ev("[...document.querySelectorAll('.wp')].every(b => b.textContent.includes(costText(wallCost(+b.dataset.wall, MOLDS[piece().mold].walls), 1)))"))
|
||||
pg.click('#dlg-foot button'); settle(120)
|
||||
spot = ev(f'''(() => {{ for (let dx = -2; dx <= 10; dx++) for (let dy = 6; dy < 14; dy++) {{ const ox = {bbx} + dx, oy = {bby} - dy; if (!canPlaceMold('ingot', ox - 1, oy) && [0, 1, 2].every(k => entFreeAt((ox - 1) * 4 + 1 + k * 4, oy * 4 + MOLDS.ingot.h)) && inReach(ox * 4 + 8, oy * 4 + 2)) return [ox, oy]; }} return null; }})()''')
|
||||
check('found a spot for the mold', spot is not None)
|
||||
mx, my = blk(spot[0], spot[1])
|
||||
@@ -417,6 +437,7 @@ with sync_playwright() as p:
|
||||
check('bars set and drop on their own', ev("ents.length === 3 && molds[0].state === 'short' && molds[0].ready.length === 0"), ev('[molds[0].state, ents.length]'))
|
||||
pg.screenshot(path=str(out / '05-mold-ready.png'))
|
||||
check('three bars drop below the mold without F', ev('ents.every(e => e.m === BARCU && e.iy >= molds[0].y + molds[0].h)'), ev('ents.map(e => [e.ix, e.iy])'))
|
||||
pg.keyboard.press('2'); settle(60)
|
||||
ev("GAME.status = ''; GAME.statusT = 0"); n_toasts = ev("$('toasts').children.length")
|
||||
pg.mouse.move(*cell_to_screen(ev('molds[0].x') + 3, ev('molds[0].y') + 3)); settle(150); pg.keyboard.press('f'); settle(150)
|
||||
check('F on an ingot mold says nothing', ev('GAME.status') == '' and ev("$('toasts').children.length") == n_toasts, ev('GAME.status'))
|
||||
|
||||
Reference in New Issue
Block a user