694 lines
46 KiB
Markdown
694 lines
46 KiB
Markdown
# Agent log
|
||
|
||
What agents have done, newest first. Add an entry for every piece of work: the date, which
|
||
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: t3 fix: deep ore burned and melted after a reload (Claude Code)
|
||
|
||
- Owner: a Huge world ran at about 10 fps after travelling between claims, even after a refresh.
|
||
Cause: reloading wakes every area, and t3's deep coal (ignites at 430°C, natural rock reaches
|
||
that at about 70% depth) and gold next to the magma burned and melted forever: about 2,100 of
|
||
12,288 areas stayed busy, 65 ms per tick. A fresh world hid it because nothing deep was awake.
|
||
- `worldgen.js`: an ore is not placed where `AMB[y]` is within 120°C of its ignition or 200°C of
|
||
its melting point; a cooler coal band in stone and granite at 45%+ depth replaces most of the
|
||
lost coal. Huge after a reload: 335 areas awake, about 5 ms per tick; Medium 104 (126 before
|
||
t3). Medium seed 424242 ore is now about 4.2x the original (coal 3.2x, gold 2.8x).
|
||
- `test/t_world.js`: no ore in too-hot rock; a reloaded world settles with no fires and under
|
||
10% of areas moving; ore balance ranges updated. Existing saves made with the bug keep it.
|
||
|
||
## 2026-10-24: t3: a world that lasts (Claude Code, Codex)
|
||
|
||
- `26d17c2` deeper is richer (`worldgen.js` ore thresholds drop with depth below fy 0.25).
|
||
Medium seed 424242 ore grains before -> after: coal 33,493 -> 170,437; copper 26,508 ->
|
||
127,298; iron 77,679 -> 361,683; zinc 11,797 -> 53,527; tin 7,723 -> 38,551; silver 3,638 ->
|
||
12,166; gold 1,681 -> 5,858; total 162,519 -> 769,520 (4.73x). Shallow ore unchanged. Codex's
|
||
first try stretched noise by depth (streaks) and gave gold 26x; redone.
|
||
- `4a88254` waste rock: `REFINE` granite/basalt about 2x; granite and basalt bulk orders; prices.
|
||
- `56c93ae` Huge 6144x2048 and gzip-packed save buffers (`packBuffers`/`unpackBuffers`, old raw
|
||
saves still load). Medium 29.0 MB -> 0.44 MB, Huge 92.8 MB -> 1.36 MB; Huge generation 5.9 s,
|
||
save 0.5 s, 0.39 ms per idle tick.
|
||
- New claims: `src/ui/claims.js` (career record `career:<id>` in IndexedDB, applied over each
|
||
loaded claim; `openNewClaim`, `travel`, Claims tab), `session.js` `setupNewWorld`, `saves.js`.
|
||
I switched claims.js to the repo's single quotes. Screenshot `shots/t3-claims.png`.
|
||
- Tests: play.py 268 checks and all headless suites pass. t_smooth's half-resolution check
|
||
fails under load on this machine; the build before t3 failed it 3 of 3 times under the same
|
||
load, so it isn't caused by this work.
|
||
|
||
## 2026-10-24: t4 follow-ups and t28 Brick press (Claude Code, Codex)
|
||
|
||
- `9739ca0`: Refiner and Stone cutter code removed; `save.js` skips unknown machine types on load
|
||
and refunds a refiner ($120) or cutter ($90). Their old t_pipes/t_stone checks now use blocks.
|
||
- `f5e5591`: `drawProcBlocks` (`render/machines.js`) draws Grinder wheels and the Saw blade over a
|
||
plain housing texture; turns while busy, no `rnd()`.
|
||
- `08d2d65`: `PLANK` item; the Saw cuts `LWOOD` too (`SAW_CUTS`, per-material `cut` counts).
|
||
"Stone brick" is "Stone block" in player text. Saw text explains the pile-up under it.
|
||
- t28: `BP_PRESS`/`PRESSB`, `GREENB` and `BARBR` items, `FIRETO` (items.js fires a hot green
|
||
brick into a Brick). Machines tab, hover, notebook, milestone hint.
|
||
- Tests: `run_all.py --browser` passed after each commit (t_smooth's flake rerun alone).
|
||
Screenshots `shots/t4-wheels{,-smooth}.png`, `shots/t28-press.png`.
|
||
|
||
## 2026-10-24: t4: Grinder and Saw hopper blocks (Claude Code, Codex)
|
||
|
||
- New block pieces `grinder`/`saw` (`BP_GRIND`/`BP_SAW`, materials `GRINDER`/`SAWB`, NMAT 97).
|
||
`procBlocksTick` (`src/sim/processors.js`) works the row above each stack top and passes the
|
||
result down the stack (`procFeed`), reusing `REFINE` and `CUT_PER_BRICK`; per-block state in
|
||
`procState`, rebuilt on place/remove/load. Refiner and Stone cutter are `hidden`; their ticks are
|
||
unchanged. Texture in `render/world.js`, hover text in `info.js`, Machines tab, notebook.
|
||
- Codex hit its output limit before finishing the tests; I fixed the test setup (a leftover brick
|
||
blocked the outlet), kept the old machine icons, stopped "blocked" flickering on idle ticks,
|
||
and added the play.py Machines-tab and drag-a-row checks.
|
||
- Tests: t_stone all pass, play.py 243 pass; t_smooth's half-resolution check fails inside
|
||
run_all at load ~9 and passes alone (known flake). Screenshots `shots/t4-hoppers{,-smooth}.png`.
|
||
|
||
## 2026-10-24: t25: air streaks for the Pneumatic nozzle (Claude Code, Codex)
|
||
|
||
- `drawAirStreaks` in `src/ui/previews.js`: 12 teal streaks spiral into the ring while sucking, 10
|
||
amber streaks run from the nozzle to the cursor in a cone while blowing. `performance.now()`
|
||
only, no `rnd()`; skipped with particles off or in fog. `test/play.py`: drawing both doesn't
|
||
change the sim RNG. Also commits the `docs/PROJECT_MAP.md` refresh missed in t27/t26.
|
||
- Tests: `run_all.py --browser` passed (play.py 240; t_smooth's half-resolution flake at load 9
|
||
passed on rerun). Screenshots `shots/t25-{suck,blow}.png`.
|
||
|
||
## 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
|
||
told to stay quiet (the "New drill bit/bag" F messages, which have their own card). `toast`
|
||
restarts the timer of a showing toast with the same text instead of adding another, so per-tick
|
||
messages make one toast. The card's status line and its CSS are gone; `doInteract` no longer
|
||
toasts twice. Files: `src/ui/{state,selection,actions}.js`, `style.css`, `test/play.py`.
|
||
- Tests: `run_all.py --browser` all passed (play.py 237). Screenshot `shots/t27-toasts.png`.
|
||
|
||
## 2026-10-24: t20 follow-up: bag hint and Clear stay visible (Claude Code)
|
||
|
||
- The hint and Clear were inside the scrolling `#baglist`, so a long Ctrl list scrolled them away.
|
||
They now render into a new `#bagfoot` under the list (`src/shell.html`, `renderBag`); Clear is a
|
||
full-width, highlighted "Clear selection" button (`style.css`).
|
||
- `test/play.py`: a long Ctrl list scrolls while the hint and a full-width Clear stay visible.
|
||
Screenshots `shots/t20c-ctrl{,-900}.png`. Tests: `run_all.py --browser` all passed (play.py 232).
|
||
|
||
## 2026-10-24: t20 follow-ups: deselect when blown out, Ctrl + wheel, Ctrl+click (Claude Code)
|
||
|
||
- Blowing the last of a selected material removes it from the selection (`toolTick`); materials
|
||
selected before you have any stay. Window `wheel` listener: Ctrl + wheel never zooms the page
|
||
and scrolls the scrollable panel under the mouse (the Ctrl bag list). Bag clicks add when
|
||
`GAME.ctrl` is set even if the click has no `ctrlKey` (owner had to hold Ctrl+Shift; not
|
||
reproduced in headless Chromium, which passes ctrlKey).
|
||
- Files: `src/ui/{actions,input,selection,notebook}.js`, `test/play.py` (229 checks).
|
||
- Tests: `run_all.py --browser` all passed.
|
||
|
||
## 2026-10-24: t20 follow-up: one selection, full list while Ctrl is held (Claude Code, Codex)
|
||
|
||
- `GAME.blow` is now the selection for picking up and blowing out (`vacFilter` -2 = the selection).
|
||
`blowList()` returns only selected materials in the bag; no pruning, no auto-select. `GAME.ctrl`
|
||
(input.js) makes `renderBag` add a "Not in bag" section of every `seen` material the vacuum can
|
||
take; selected but empty materials always show there. Clear button, click-the-only-row, gamepad B.
|
||
- Files: `src/ui/{actions,selection,hud,input,state,gamepad,previews,notebook,session}.js`,
|
||
`src/sim/{vacuum,progress}.js`, `src/styles/style.css`, `test/play.py`, `test/t_basics.js`.
|
||
- Tests: `run_all.py --browser` passed (play.py 226) except `t_smooth` half-resolution at load 8.4,
|
||
which passed on rerun. Screenshots `shots/t20b-{noctrl,ctrl,ctrl-900}.png`.
|
||
|
||
## 2026-10-24: t20: pick up what you click, blow out what's in hand (Claude Code, Codex)
|
||
|
||
- After discussing options with the owner: one Vacuum, a drag locks onto the material it starts on
|
||
(Shift: everything), and `GAME.blow` is "the hand" (click/Q/E, Ctrl+click mix). Empty materials
|
||
leave the hand; an empty hand blows nothing; a pickup fills an empty hand. Removed `GAME.collect`,
|
||
R marks, Ctrl+right-click, `openVacuumMenu`, `ensureBlow` and the `.vpick` styles.
|
||
- Files: `src/ui/{actions,selection,hud,state,previews,build-menu,info,notebook,session}.js`,
|
||
`src/sim/{vacuum,progress}.js` (`toolVacuum` returns the first material picked up),
|
||
`src/styles/style.css`, `test/play.py`, `test/t_basics.js`.
|
||
- Tests: `run_all.py --browser` all passed (play.py 214 checks). Screenshots `shots/t20-*.png`
|
||
(empty hand, one item, mix, 900px).
|
||
|
||
## 2026-10-24: t17 follow-up: no jagged rim, unexplored is black (Claude Code)
|
||
|
||
- The owner still saw a jagged grey edge. Causes found by sampling pixels: the fade went to 0
|
||
(black) while unexplored was drawn (12, 10, 10), leaving a dark stepped rim; the ±40 noise lit
|
||
the faintest cells; rays that reached rock already dim dropped to 0 in 2–3 grains.
|
||
- `src/render/world.js`: unexplored and off-map are pure black (`OUT`, smooth-look buffers);
|
||
partial fog eases into black along a smoothstep, with noise scaled by s·(1−s). `src/sim/fog.js`:
|
||
sight into rock fades over 8 grains as a fraction of its brightness.
|
||
- `test/play.py`: the three unexplored-colour checks expect black (owner's decision).
|
||
`docs/systems/fog.md` updated. Screenshots `shots/fog-dig-black-{pixel,smooth,nolight}.png`.
|
||
- Tests: `run_all.py --browser` passed except `t_smooth` once at load 7 (passed on rerun).
|
||
|
||
## 2026-10-24: t17 follow-up: softer fog edges (Claude Code, Codex)
|
||
|
||
- The owner found the edges sharp and stepped. `src/sim/fog.js`: sense ring 24 grains with a
|
||
12-grain fade (`FOG_SENSE_FADE`), rays fade 8 grains into rock and over the last 16 of range,
|
||
surface fade 16, `fogRevealAround` gets a 6-grain soft edge. `src/render/world.js`: partly seen
|
||
grains get ±40 of per-grain noise (`noiseB`), so edges look grainy, not stepped.
|
||
- `test/t_fog.js`: a point in the fade band is between 0 and 128. `docs/systems/fog.md` updated.
|
||
Screenshots `shots/fog-edge-{before,after}{,-nolight}.png` compared.
|
||
- Tests: all pass except `t_smooth` half-resolution, which failed 1 of 3 runs with the change and
|
||
passed 2 of 2 without, at load 6–12; that check runs with fog off, so the drawing change does
|
||
not run there. Fog drawing 0.43 → 0.45 ms.
|
||
|
||
## 2026-10-24: t17 Fog of war reveals the ground around the bot (Claude Code, Codex)
|
||
|
||
- `src/sim/fog.js`: `FOG_SENSE = 20`; `fogLook` also reveals every fog cell within 20 grains,
|
||
through rock (full to 16, fading to 20). Claude rewrote Codex's per-grain `Math.hypot` loop to
|
||
work per fog cell. `fogLook` cost: 0.042 → 0.043 ms (every 4 ticks).
|
||
- `test/t_fog.js`: rock 12 grains away is seen, near the edge partly, 25 away still hidden.
|
||
- `docs/systems/fog.md`, DESIGN.md decision. Also: `.kandown/kandown_work.md` rule to write
|
||
tasks from chat in our own words (no owner labels, dates or quotes); t16/t19 descriptions
|
||
rewritten that way; `docs/kandown.md` notes that web-board descriptions were lost.
|
||
- Tests: `run_all.py --browser` all pass (220 `play.py` checks). `t_smooth` half-resolution failed
|
||
on two runs while the machine was loaded (load 6–10) and passed on the final run; timing
|
||
`fogLook` showed the change is not the cause. Screenshot `shots/fog-sense.png` checked.
|
||
|
||
## 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
|
||
Claude made the edits.
|
||
- `input.js`/`actions.js`: `secondaryPress(shift)` sets `GAME.blowAll`; `hud.js` `blowAllList()`
|
||
(loose bag materials, no bars); `previews.js` shows it. `selection.js`: Ctrl+click a bag name
|
||
toggles blow, Ctrl+right-click toggles pick-up; the vacuum menu is one `.vrow` per material
|
||
(CSS in `style.css`). Text in the card, bag hint and `notebook.js`.
|
||
- `test/play.py`: Shift blow-all (replaces the old "Shift still blows only coal" check, per the
|
||
owner's change), Ctrl+click and Ctrl+right-click, plain right click does nothing, one line per
|
||
menu row at 1280 and 900.
|
||
- Tests: `run_all.py --browser` all pass (215 `play.py` checks). Screenshots checked.
|
||
|
||
## 2026-10-24: t16 Vacuum tool rework, again (Claude Code, Codex)
|
||
|
||
- The owner: too many modes; chose an F menu with two pick-up states.
|
||
- `src/ui/selection.js`: `openVacuumMenu()` (a tile per material with ⬇ Pick up / ⬆ Blow, plus
|
||
Pick up everything and Blow one at a time); `GAME.pickMode`, `setPickMode`, `cyclePickMode`,
|
||
`GAME.vacLocked` and the 'first' locking removed. Bag rows lose ⬇/⬆ buttons (a ⬇ tag shows
|
||
marks). `actions.js`: pick-up is everything unless marked; F with the vacuum opens the menu
|
||
when nothing in reach can be used. Card chips open the menu (`build-menu.js`). T and gamepad B
|
||
no longer change vacuum modes. Text in `notebook.js`, `progress.js`, `session.js` (Welcome),
|
||
`previews.js`; CSS `.vpick`.
|
||
- `test/play.py`: mode checks replaced (no marks takes everything, marks, Shift, menu toggles,
|
||
F order, card chip, no bag buttons, menu screenshots at 1280 and 900); `test/t_basics.js` name.
|
||
- Tests: `run_all.py --browser` all pass (208 `play.py` checks). Codex ran out of credits before
|
||
its final test run; Claude fixed one check (it mutated a mold's kind) and the suction line.
|
||
|
||
## 2026-10-24: t6 Rework the vacuum/blower tool, with t15 (Claude Code, Codex)
|
||
|
||
- The owner folded t15 (a material menu for the vacuum) into t6's chosen design.
|
||
- `src/ui/state.js`, `hud.js`, `actions.js`, `selection.js`: `GAME.pickMode` ('first', 'marked',
|
||
'all', saved in localStorage) and `GAME.blow` (a set) replace `dropMat`, `blowMix`,
|
||
`matChosen` and the Ticked mix. Pick-up marks stay in `GAME.collect` (same storage key).
|
||
- Bag rows with the vacuum: ⬇ pick-up toggle, name (blow only this), ⬆ blow toggle. Tool card:
|
||
a pick-up mode chip (T or click cycles) and the blow choice (Q/E). Gamepad button 1 cycles the
|
||
mode when nothing else uses it. Text updated in `info.js`, `notebook.js`, `progress.js` (the
|
||
copper hint), `src/sim/vacuum.js` (a status line), `docs/DESIGN.md`.
|
||
- `test/play.py`: modes, automatic switching, independent marks, mixes, persistence, toggles only
|
||
with the vacuum; `test/t_basics.js`: the new status. Screenshots checked at 1280 and 900.
|
||
- Tests: `run_all.py --browser` all pass (214 `play.py` checks).
|
||
|
||
## 2026-10-24: decisions for t1, t4, t6 (Claude Code)
|
||
|
||
- Options given in chat; the owner chose: t1 neat bar piles plus a Bar rack; t4 Grinder and Saw
|
||
blocks replace the Refiner and Stone cutter (old ones keep working); t6 the bag as the vacuum's
|
||
control panel with separate pick-up and blow marks. Recorded in `docs/DESIGN.md` and as
|
||
subtasks on each task. Nothing built yet.
|
||
|
||
## 2026-10-24: t8 Fog of war for unexplored areas (Claude Code, Codex)
|
||
|
||
- Decided with the owner: revealed by line of sight; unseen is black with a soft edge.
|
||
- New `src/sim/fog.js` (2×2-grain cells, 0-255, only ever rises): `fogInit` (sky and surface
|
||
seen), `fogLook` (256 rays, 48 grains, through air, gas and liquid, 4 grains into rock), called
|
||
every 4 ticks from `simTick`. Never read by the simulation.
|
||
- `src/sim/save.js`: fog saved; saves without it rebuild it (surface, 12 grains around built
|
||
things, the bot's view). `src/sim/world.js`, `worldgen.js`: allocate and initialize.
|
||
- `src/render/world.js`: grains scaled by a bilinear fog value; fully unseen grains drawn like
|
||
out-of-world grains; no glow from fog; thermal lens fogged too. `overlays.js`: rain and
|
||
particles hidden in fog. `src/ui/info.js`: "Unexplored". Setting `fog` (Game tab).
|
||
- Browser tests and tools turn fog off (`play.py`, `t_smooth.py`, `shot.py`, `perf.py`,
|
||
`playthrough.py`) except the fog checks. New `test/t_fog.js`; `docs/systems/fog.md`.
|
||
- Tests: all 12 headless suites, `play.py` (185 checks), `t_smooth` pass. Screenshots checked in
|
||
both looks. `tools/perf.py`: drawWorld 0.34 → 0.43 ms with fog on (no change with light off).
|
||
|
||
## 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.
|
||
- `src/ui/loop.js`: opening the panel fills it with the next `World N` and selects it; Enter in
|
||
Name or Seed generates. `src/ui/session.js`: `startNewWorld(seed, w, h, name)`.
|
||
- `src/ui/saves.js`: `cleanWorldName` (trim, 40 characters, else a fallback), shared with
|
||
`renameWorld`.
|
||
- `test/play.py`: a typed name reaches the Worlds list and the pause menu; the default and an
|
||
all-spaces name both give `World N`. Screenshot `shots/play/new-world.png` checked.
|
||
- Tests: `run_all.py --browser` all pass.
|
||
|
||
## 2026-10-24: t7 Option to turn off smooth pan following the mouse (Claude Code, Codex)
|
||
|
||
- New setting `cameraLead` (Display: "Camera looks toward the pointer", on by default):
|
||
`src/ui/state.js`, `src/ui/settings.js`; `src/ui/loop.js` gives the camera no look-ahead when
|
||
it is off (mouse and gamepad).
|
||
- `test/play.py`: the toggle saves; with it off the camera stays on the bot with the mouse in a
|
||
corner; with it on the camera moves toward the corner.
|
||
- Tests: headless suites and `play.py` pass; `t_smooth` half-resolution flake as before.
|
||
|
||
## 2026-10-24: t9 Script to run the local server for testing (Claude Code, Codex)
|
||
|
||
- New `tools/serve.py`: builds, serves `dist/` on 127.0.0.1 (port 8000, or the next free one up
|
||
to 8010), sends `Cache-Control: no-store`, and rebuilds when `src/` or `build.py` changes; a
|
||
failed build is printed and the old page keeps being served.
|
||
- `README.md`, `AGENTS.md`, `build.py` docstring, `docs/PROJECT_MAP.md`: point at it.
|
||
- Checked by hand: page served with no-store; touching a source file rebuilt it; with the owner's
|
||
old server on 8000 it moved to 8001; Ctrl+C stops it. Headless suites and map check pass.
|
||
|
||
## 2026-10-24: t11 Welcome and first milestone shown again on an older world (Claude Code, Codex)
|
||
|
||
- Cause: nothing cleared the discovery cards (`#cards`) when a world was entered, so a new
|
||
world's Welcome and milestone cards stayed up after loading an older world.
|
||
- `src/ui/session.js`: `enterGame` clears the cards, floaters, particles and the status line;
|
||
a new world still shows its Welcome card afterwards.
|
||
- `test/play.py`: a new world shows Welcome; loading a world from the list shows no cards.
|
||
- Tests: `run_all.py --browser` passes (153 `play.py` checks). `t_smooth`'s half-resolution
|
||
check flaked (passed and failed on reruns, with and without this change; the machine was busy).
|
||
|
||
## 2026-10-24: the to-do list moved from NOTES.md to a kandown board (Claude Code)
|
||
|
||
Request: initialize kandown (https://kandown.dev, 0.52.0) for the project, move all the notes
|
||
into it, and document how it works.
|
||
|
||
- Practised first in a scratch folder: `init --no-open` starts no daemon and makes no sample tasks
|
||
when `tasks/` exists; `create` prints only the id and names the file `<id>_<slug>.md`; a
|
||
multi-line `report: |` block is read (checked in kandown's parser) and `doctor` accepts it;
|
||
`list --json` doesn't include `report`; project instructions in `.kandown/kandown_work.md`
|
||
appear in `kandown work` (~1,100 tokens).
|
||
- A mistake on the way: `kandown init --help` ran `init` in the vault (it ignores `--help`) and
|
||
started a daemon there. Stopped it, and removed the vault's earlier kandown test files as the
|
||
owner asked. `docs/kandown.md` warns about it.
|
||
- `kandown init --no-open` in the repo. `.kandown/kandown.json`: default columns, Review's
|
||
instructions say only the owner moves work to Done, tags and priority shown. `.gitignore`:
|
||
`.kandown/kandown.html` (6.5 MB, regenerated).
|
||
- 14 notes became tasks t1-t14 (Todo: 6 tasks; Backlog: 5 tagged `discussion`, 3 tagged
|
||
`idea`), each with the owner's note word for word under `## Context` (checked by script: 14 of
|
||
14). `kandown doctor` is clean.
|
||
- `.kandown/kandown_work.md` holds the board rules (from AGENTS.md's old NOTES section);
|
||
`docs/kandown.md` explains how kandown works here. `AGENTS.md` (session start, a new "Working
|
||
from the task board" section linking both), `CLAUDE.md`, `README.md`, `docs/PROJECT_MAP.md`,
|
||
`docs/DESIGN.md` (decision). `NOTES.md` removed.
|
||
- `kandown list` and `kandown doctor` checked inside Codex's sandbox: they work.
|
||
- Open: the web board (`kandown`) wasn't opened here; the owner uses it.
|
||
|
||
## 2026-10-24: several worlds, with a Worlds list (Claude Code, with Codex)
|
||
|
||
Note: "support multiple saves". The owner chose a world list.
|
||
|
||
- `src/ui/saves.js`: each world has an id and its own `<id>:main` / `<id>:auto` saves, plus an
|
||
index record `worlds` (name, seed, size, created, saved, playtime). A save writes the world
|
||
and its index entry in one transaction. `GAME.worldId` is the open world.
|
||
- Migration: an old single `main` / `auto` save is copied to "World 1", every byte compared,
|
||
and only then are the old keys deleted (in the same transaction that writes the index). If
|
||
anything fails the old save is kept and a toast says so.
|
||
- Title screen: Continue loads the most recently saved world; a Worlds button opens a list
|
||
with Load, Rename (trimmed, at most 40 characters, escaped when shown) and Delete (with a
|
||
confirmation). New and imported worlds get new ids. Settings' "Delete saves" became "Delete
|
||
all worlds" with a confirmation. `shell.html`, `session.js`, `loop.js`, `settings.js`,
|
||
`state.js`, `style.css`, `docs/DESIGN.md`, `docs/PROJECT_MAP.md`.
|
||
- `test/play.py` (149 checks): migration of an old save, two worlds listed, load switches,
|
||
rename survives a reload, HTML in a name shows as text, long names cut to 40, delete and
|
||
cancel, Continue picks the newest, import adds a world. All 11 headless suites pass;
|
||
`t_smooth.py` passed in Codex's run and failed its known flaky check in mine.
|
||
- Shots: `shots/worlds-{pixel,smooth}.png` and `-900` versions.
|
||
- Not committed: a line in `AGENTS.md` about running `kandown work` appeared in the working
|
||
tree during this task; it was not part of the task and is left for the owner.
|
||
- Open: nothing.
|
||
|
||
## 2026-10-24: screw tubes carry bars (Claude Code, with Codex)
|
||
|
||
Note: "how do you transport bars? belts work but tubes do not, should they?" The owner chose
|
||
tubes over a separate bar lift.
|
||
|
||
- `src/sim/items.js`: a bar whose eight cells are all tube cells is in the tube (derived from
|
||
its position, so saves need nothing new). On the tube beat it steps along the tube, snaps
|
||
into the next block's lane at corners, and leaves a mouth with a launch like grains. A bar
|
||
pushed by a belt into a tube's side or back, falling into a down tube or sitting on an up
|
||
tube's intake enters when the aligned cells are free. Grains and bars wait for each other.
|
||
Gas and liquid don't block a bar (Claude added this after review: smoke near furnaces stalled
|
||
bars in Codex's version).
|
||
- Text: `pieces.js` tube description, `notebook.js`, `docs/DESIGN.md`, `docs/PROJECT_MAP.md`.
|
||
- Tests: `t_conveyors` (55 checks): belt into a lift and out the top, right tube, up-to-right
|
||
and right-to-down corners, blocked mouth waits without overlap, grains and a bar share a
|
||
lift with sand conserved, save mid-tube, smoke and steam in the tube, melting in a tube,
|
||
bars still ride and stop on belts and rest on top of sideways tubes. All 11 headless suites
|
||
and `play.py` (131) pass; `t_smooth.py`'s half-resolution check failed on the slow fallback
|
||
(known flaky).
|
||
- Shots: `tools/scenes/bar-lift.js`, `shots/bar-lift-{300,900}.png`: mold, belt, lift, corner,
|
||
belt; the bars arrive at the top.
|
||
- Open: nothing.
|
||
|
||
## 2026-10-23: screw tubes use all their lanes (Claude Code, with Codex)
|
||
|
||
Note: "maybe the belts can move faster/carry more than the tubes… the tubes only carry one line of
|
||
materials on either side." The owner chose to fill every lane; belt speed is unchanged.
|
||
|
||
- Measured first (`test/t_conveyors.js`, a belt with a sand heap into the side of a 6-block up
|
||
tube, 3,000 ticks): 1,459 grains out, lanes `[2940, 0, 0, 10]`; the edge lane was full (one
|
||
lane carries at most ~1,500). Spreading only at intake changed little (1,486): belts push
|
||
straight into the edge lane without going through `tubeIntake`.
|
||
- `src/sim/machine-tick.js`: on each tube beat a grain in a vertical tube may step sideways
|
||
within its block to a lane with clearly fewer grains ahead (a strict gap stops shuffling).
|
||
`src/sim/conveyors.js`: `tubeSpread`, shared by side intake (far lanes first) and corners.
|
||
- After: 2,071 out (92% of the 2,243 the same belt delivers to an open drop), lanes
|
||
`[1937, 1116, 486, 872]`, sand conserved. Checks: all four lanes, conservation, at least 90%
|
||
of the belt's delivery, a side-fed sideways tube fills more than one row. All 11 headless
|
||
suites and `play.py` pass. `docs/DESIGN.md`, `docs/PROJECT_MAP.md`.
|
||
- Found on the way: Codex's sandbox (`workspace-write`) blocks network sockets, and headless
|
||
Chrome crashes at startup without one (SIGTRAP core dumps in pairs). Codex runs now pass
|
||
`-c sandbox_workspace_write.network_access=true`.
|
||
- Open: nothing.
|
||
|
||
## 2026-10-23: the build card describes the build tool, not the piece (Claude Code)
|
||
|
||
Note: "instead of the current info box for the build tool showing the current building selected,
|
||
it should just show a description about the build tool in general." The owner kept the R/F chips.
|
||
|
||
- `src/ui/selection.js`: the build card reads "Build", then the R/F chips, Replace · G and
|
||
ghosts, a line on placing, the build keys and the status line. No piece name or cost.
|
||
- `test/play.py`: the card shows "Build" and the placing text, not the piece's name, cost or
|
||
description; the other card checks are unchanged. `play.py` and all 11 headless suites pass.
|
||
- Shots: `shots/build-card-general-{pixel,smooth}.png`. `docs/DESIGN.md`.
|
||
- Open: nothing.
|
||
|
||
## 2026-10-23: world sounds are heard around the bot, not the view (Claude Code)
|
||
|
||
Note: "the sound effect is still heard when the player's zoom level is farther out… sounds
|
||
shouldnt be heard based on the viewport, but rather in a radius from the player."
|
||
|
||
- `src/ui/audio.js` `sfxPlace`: gain from the distance to the bot's centre (full within 24
|
||
grains, silent at 96), pan from the horizontal offset; zoom no longer matters.
|
||
- `test/play.py`: the audio checks use points relative to the bot; a new check zooms out to 2
|
||
and plays a visible sizzle 200 grains away: silent (it played before). `play.py` and all 11
|
||
headless suites pass. `docs/DESIGN.md`.
|
||
- Open: nothing.
|
||
|
||
## 2026-10-23: F on an ingot mold does nothing and says nothing (Claude Code)
|
||
|
||
Note: "now that ingot molds empty themselves, F still makes a message…"
|
||
|
||
- `src/sim/actions.js` `interactAt` passes ingot molds by, so F reaches anything else nearby;
|
||
`src/ui/actions.js` shows no "Nothing to use there." when the cursor is on an ingot mold.
|
||
- Tests: `t_molds` (F on an ingot mold in every state returns nothing, changes nothing, no
|
||
sound) and `play.py` (F over an ingot mold leaves no status and no toast). All 11 headless
|
||
suites and `play.py` pass.
|
||
- Open: nothing.
|
||
|
||
## 2026-10-23: pixel-art silo for the pixel look (Claude Code, with Codex)
|
||
|
||
Note: "the silo art looks the same for both pixelated graphiocs and smooth."
|
||
|
||
- `src/render/machines.js`: `drawSilo` is the smooth (illustrated) version as before; in the
|
||
pixel look `drawSiloPixel` draws it grain by grain: a drum shaded by column with rib rows and
|
||
ladder rungs, a cone to the slide gate, legs, the roof hatch with its lid, and the sight glass
|
||
filling in the contents colour. Codex's first version read poorly (dark side columns made
|
||
the drum look narrow); Claude redrew the grain layout.
|
||
- `tools/scenes/silo.js` (run after `clear-sky.js`): three silos, empty, half sand, nearly full
|
||
copper ore with the gate open. Shots `shots/silo-*.png` at zoom 6 and 12; smooth shots match
|
||
the before shots pixel for pixel. `docs/DESIGN.md`, `docs/PROJECT_MAP.md`.
|
||
- Tests: all 11 headless suites and `play.py` (129) pass. `t_smooth.py`'s half-resolution check
|
||
failed twice on the slow fallback at load ~5 (known flaky; it passed in the previous task).
|
||
- Open: nothing.
|
||
|
||
## 2026-10-23: world sounds are local (Claude Code, with Codex)
|
||
|
||
Note: "there is a sound effect that seems to happen randomly but constantly… maybe the sound
|
||
effects are not local?" The owner then confirmed a new world is quiet.
|
||
|
||
- Measured headlessly over 5 game minutes: a fresh world emits no sounds; a lava and water
|
||
contact 720+ grains from the bot emitted 2,889 sizzles (about 580 a minute), and `events.js`
|
||
played every one at full volume. The sizzle (`heat.js` boiling and quenching) is the only sim
|
||
sound with a position.
|
||
- `src/ui/audio.js`: `sfxPlace(x, y)` gives gain (1 in view, 0 one view width beyond it) and
|
||
pan; `playSfx(name, arg, x, y)` skips silent ones before the rate limit and plays the rest
|
||
through a gain and stereo panner (`AU.bus`). `src/ui/events.js` passes positions.
|
||
`docs/DESIGN.md`, `docs/PROJECT_MAP.md`.
|
||
- `test/play.py`: far off-screen is silent, on screen plays, just off-screen is quieter and
|
||
panned. All 11 headless suites, `play.py` and `t_smooth.py` pass.
|
||
- Checked afterwards with the owner's exported world (seed 622525): over one minute, 48 sizzles
|
||
and no other sounds, all from water boiling on naturally hot deep rock (a pool on 580°C basalt
|
||
and drips in a shaft and a cave, about 500 grains below the bot). Nothing built there. With
|
||
the fix every one of them is silent from the surface.
|
||
|
||
## 2026-10-23: fewer, smaller messages (Claude Code, with Codex)
|
||
|
||
Note: "lets change the engot mold and other status messages. they are way too large and just too
|
||
many messages…". The owner chose molds, machine warnings and shorter status lines.
|
||
|
||
- `src/render/overlays.js`: every mold always shows a small percentage (`moldLabel`), gold
|
||
`100% · F` for a set bit or tank, red when spoiled or when an ingot mold is blocked
|
||
(`mo.blocked`, set in `ejectIngot`, not saved). Details are in the hover panel (`info.js`),
|
||
which also carries the furnace-loading advice that F messages used to append.
|
||
- `src/ui/events.js`: no cards or toasts for bars dropping, blocked molds or set castings (the
|
||
sounds stay). Fan heat is a red ! marker on the fan (`mm.hot`, hover shows °C); melted tubes
|
||
and machines destroyed by heat get a short red floating label (`warn` event, coloured
|
||
`spawnFloater`) instead of a toast.
|
||
- Status lines shortened to one sentence across `src/sim` (molds, actions, blocks, blueprints,
|
||
drill, items, machines, tools, undo, vacuum) and `src/ui` (actions, selection).
|
||
- Tests: `play.py` records every status line and checks none is over 90 characters (longest
|
||
now 55); mold label and no-toast checks. `t_molds` (message lengths, `mo.blocked`), `t_build`
|
||
(hot fan: `mm.hot`, no toast), `t_world`. All 11 headless suites and `play.py` pass.
|
||
- Shots: `shots/messages-pixel.png`, `shots/messages-smooth.png` (filling, blocked, set bit,
|
||
hot fan).
|
||
- Open: the first-ever bar drop no longer shows an explaining card; the notebook still explains.
|
||
|
||
## 2026-10-23: ingot molds need no F; removing a mold returns its metal (Claude Code, with Codex)
|
||
|
||
Note: "now that the ingot molds drop the ingots, what is the purpose of F…". The owner chose no F
|
||
on ingot molds.
|
||
|
||
- `src/sim/molds.js` `ejectIngot`: when nothing over or in the mold is molten, junk and mixed
|
||
metal (as scrap) and plugs over the trough or frozen in the grate are written as loose grains
|
||
into the 4 rows under the mold; what doesn't fit waits. Slag and ash are thrown away. After a
|
||
drop or a clean-out the grate above is raked. Bars and the short stub work as before.
|
||
- `collectMold` on an ingot mold only says "Ingot molds empty themselves." Bit and tank molds
|
||
keep F. `removeMold` (every kind) gives the set metal to the bag: whole bars as bars, the rest
|
||
as scrap; molten metal is left alone.
|
||
- Text: `troughState` flaws, `info.js`, `overlays.js` (no "· F" on ingots), `pieces.js`,
|
||
`progress.js`, `notebook.js`, `docs/systems/stone-and-liquids.md`, `docs/DESIGN.md`.
|
||
- Tests: `t_molds` 60 checks (F does nothing on ingots; plate, slag and rake cases now
|
||
automatic; junk out, mixed metal out as scrap, junk waits when blocked and while molten;
|
||
removal returns bars and scrap, leaves molten metal). `furnace_play.js` adapted. All 11
|
||
headless suites and `play.py` (122) pass.
|
||
- Open: undoing a mold removal brings the mold back empty (its metal stays in the bag).
|
||
|
||
## 2026-10-23: compact build card top-left (Claude Code, with Codex)
|
||
|
||
Note: "build tool has no info box like the other tools." The owner chose a compact card.
|
||
|
||
- `src/ui/selection.js`: the build tool has a card again: Q, piece name, cost (red if short), E;
|
||
variant chips, Replace · G and ghosts waiting; one how-to line; the status line. No long
|
||
description; that stays in the hover popup.
|
||
- `src/ui/build-menu.js`: the `.bb-status` row is gone (its contents are on the card); the
|
||
variant chips are shared through `buildVariantChips(P)`. `src/styles/style.css`,
|
||
`docs/DESIGN.md`, `docs/PROJECT_MAP.md`.
|
||
- `test/play.py`: the card is visible while building with name and cost and without the
|
||
description; status and Replace · G are read from the card; no `.bb-status` (122 checks pass).
|
||
All 11 headless suites pass; `t_smooth.py`'s half-resolution check is still flaky under load.
|
||
- Shots: `shots/build-card-*` (belt and wall, both looks, 1280×800 and 900×700) look right.
|
||
- Open: at 900px wide the bottom-left hover panel still sits partly under the build bar.
|
||
|
||
## 2026-10-23: Replace is held G, not Ctrl (Claude Code, with Codex)
|
||
|
||
Note: "i see the issue was ctrl+w closing the tab… it happens for the bookmark hotkey too. maybe
|
||
change the replace key?" The owner chose holding G.
|
||
|
||
- `src/ui/input.js`: `GAME.ctrl` became `GAME.replace`, set while G is held. Ctrl no longer
|
||
does anything on its own, so WASD works while replacing. Ctrl+S, Ctrl+Z, Ctrl+wheel and the
|
||
leave-page prompt are unchanged.
|
||
- Flag users and text: `previews.js`, `actions.js` ("Hold G to replace it."), `build-menu.js`
|
||
(Replace · G chip, key hints), `loop.js`, `state.js`, `notebook.js` controls, `docs/DESIGN.md`.
|
||
- `test/play.py`: the replace checks hold G; a new check flies right with D while G is held
|
||
(121 checks pass). All 11 headless suites pass. `t_smooth.py`'s half-resolution check failed
|
||
on the slow fallback again (known, flaky under load).
|
||
- Open: nothing.
|
||
|
||
## 2026-10-23: Ctrl+W no longer closes the game without asking (Claude Code)
|
||
|
||
Note: "crash: hoplding crtl and pressing W crashes the whole browser."
|
||
|
||
- Cause: Ctrl+W is the browser's close-tab shortcut (the whole window if it is the last tab);
|
||
holding Ctrl for replace while pressing W to fly hits it. Chrome and Brave reserve it, so the
|
||
page never sees the key and cannot cancel it.
|
||
- `src/ui/input.js`: a `beforeunload` handler makes the browser ask before leaving while
|
||
`GAME.running`. The `visibilitychange` autosave no longer depends on the audio context
|
||
existing (it used to return early before any sound had played).
|
||
- `test/play.py`: 2 checks (leaving asks while a world is open, not on the title screen); 120
|
||
checks pass. All 11 headless suites pass; `t_smooth.py` passed on its rerun (the known flaky
|
||
half-resolution check failed once at load ~5).
|
||
- Open: the real Ctrl+W prompt can only be seen in a headed browser; the owner checks it.
|
||
|
||
## 2026-10-23: build menu hover popup, top-left card hidden while building (Claude Code, with Codex)
|
||
|
||
Notes: "mouse hover reviels a new info popup…" and "dont need the short description in the build
|
||
menu…". The owner chose to hide the top-left card entirely while building.
|
||
|
||
- `src/ui/build-menu.js`: `#bb-pop` above the hovered tile (icon, name, cost, size, power, full
|
||
description, how to place, filter help, build keys), clamped to the window; on a gamepad it
|
||
shows for the selected tile. The `.bb-tip` row became `.bb-status`: variant (material,
|
||
direction, filter) chips, Replace · Ctrl, ghosts waiting, and status messages. The bar now
|
||
updates its parts separately so hovering doesn't rebuild the tiles.
|
||
- `src/ui/selection.js`: `#selcard` is hidden for the build tool unless copying, cutting or
|
||
pasting; the build branch moved to the bar. `src/styles/style.css`: popup and status styles;
|
||
on narrow screens the bottom-right panel moves up while building.
|
||
- `src/ui/notebook.js`, `docs/DESIGN.md`, `docs/PROJECT_MAP.md`.
|
||
- Tests: `play.py` 118 checks (popup on hover, switching tiles, hidden on leave, gamepad,
|
||
no `.bb-tip`, card hidden while building and back for tools 1 and 2, status in the bar; the
|
||
Ctrl replace check now reads the bar). All 11 headless suites pass. `t_smooth.py`'s
|
||
half-resolution check is still flaky under load (~4.8): 1 pass in 3 runs, as before.
|
||
- Screenshots in `shots/` (build-popup-*) at 1280×800 and 900×700 in both looks.
|
||
- Open: at 900px wide the bottom-left hover-info panel sits partly under the build bar.
|
||
|
||
## 2026-10-23: a backed-up heap on a belt no longer shears (Claude Code, with Codex)
|
||
|
||
Note: "belts with a big pile on top only move the grains on the top of the stack…". The owner's
|
||
setup: a belt ending in a screw tube going up; material mixed into the bottom layers never moved.
|
||
|
||
- Cause: `carryColumn` (`src/sim/conveyors.js`) skipped a blocked grain and carried on up the
|
||
column, so with the front backed up the top slid over a stuck bottom. Reproduced headlessly
|
||
first: over 4,000 ticks, 8,815 shear steps at the tube (1,415 against a wall) and only 57 of
|
||
192 bottom sand grains left the belt.
|
||
- Fix: on a belt, the walk stops at the first blocked powder grain, so the load above stands
|
||
still. On a filter the walk goes on only so chosen materials can still drop through; nothing
|
||
above a blocked grain is carried. After: no shear, 119 of 192 sand grains out through the tube.
|
||
- `test/t_conveyors.js`: 3 new checks (no shear into an up tube, none against a wall, bottom
|
||
layer leaves through the tube). `tools/scenes/belt-heap.js` sets up the owner's case for
|
||
`tools/shot.py`; shots at 30 and 900 ticks look right. `docs/DESIGN.md`, `docs/PROJECT_MAP.md`.
|
||
- Tests: all 11 headless suites and `play.py` (109) pass. `t_smooth.py` failed its
|
||
half-resolution check once at load 6.5 and passed on the rerun.
|
||
- Open: `t_smooth.py`'s half-resolution check is flaky on a busy machine (the game drops to
|
||
pixel when frames are slow). It is worth making that test immune to the slow fallback.
|
||
|
||
## 2026-10-23: ingots drop out of the bottom of the mold (Claude Code, with Codex)
|
||
|
||
Note: "copper bars go into the furnace. also they shouldnt require the player to press F…".
|
||
The owner confirmed the first part was a bug: the knock-out threw bars up into the furnace.
|
||
|
||
- `src/sim/molds.js`: `ejectIngot` runs from `checkMolds` (every 15 ticks). Bars spawn directly
|
||
under the mold, only where `entFreeAt` says there is room; the rest wait in the trough. Metal
|
||
is taken from the top so the short stub stays on the floor. `ingot_drop` and `ingot_blocked`
|
||
events (blocked is said once per spell via `mo.blockedSaid`, not saved). F on a ready ingot
|
||
never launches bars upward; F on an empty, short or spoiled mold still cleans and rakes.
|
||
- `src/ui/events.js`: first drop gets a card, later drops a toast; blocked gets a toast. Ingots
|
||
no longer emit `cast_ready`.
|
||
- Text: `pieces.js`, `progress.js`, `notebook.js`, `info.js`, `docs/DESIGN.md` (with the
|
||
decision), `docs/systems/stone-and-liquids.md`, `docs/PROJECT_MAP.md`.
|
||
- Tests: `t_molds` (38 checks: downward drops, stub kept, top-up, blocked and warned once,
|
||
partial drops, full entity pool, slag), `t_basics`, `t_stone`, `furnace_play.js` and
|
||
`play.py` section 6 changed to the new behaviour. All 11 headless suites, `play.py` (109) and
|
||
`t_smooth.py` (8) pass. A shot of a mold with a belt one block below shows the bars riding off.
|
||
- Open: a belt touching the mold's underside counts as blocked; bars need one open block. The
|
||
owner may want a belt directly under the mold to work.
|
||
|
||
## 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
|
||
(Python 3.14 refuses a system-wide pip install).
|
||
|
||
- Playwright 1.64.1 and its Chromium 156 (revision 1248) are in an in-repo `.venv/` made with uv.
|
||
|
||
- Added `tools/use_venv.py` and called it before Playwright imports in `tools/shot.py`,
|
||
`tools/perf.py`, `tools/playthrough.py`, `test/play.py` and `test/t_smooth.py`.
|
||
The `sys.prefix` guard prevents an exec loop if the venv lacks Playwright.
|
||
- Updated `requirements.txt`, `.gitignore`, `README.md`, `AGENTS.md`, `docs/TESTING.md`
|
||
and `docs/PROJECT_MAP.md` for Playwright 1.64.1 and venv setup.
|
||
- Checks: regenerated the project map and ran `tools/map.py --check`.
|
||
- Tests run with plain `python3`: `test/run_all.py --browser` all passed (11 headless suites,
|
||
`play.py` 101 checks, `t_smooth.py` 8 checks) on the newer Chromium; `tools/shot.py` shots
|
||
of the stone-uses scene look right in both looks. With `.venv` moved away, `play.py` exits
|
||
with the setup message instead of a traceback.
|
||
- Open: nothing.
|
||
|
||
## 2026-10-23: export from claude.ai to a local repository (Claude, in claude.ai)
|
||
|
||
Request: split the source into small files, deduplicate and tidy a little, test fully, and
|
||
write the notes, docs and agent instructions for local development.
|
||
|
||
- **Source split.**
|
||
- `core.js` (3,688 lines), `main.js` (2,012) and `render.js` (938) became 52 files in
|
||
`src/sim/`, `src/render/` and `src/ui/`, each with a header line. The build order lives in
|
||
`src/manifest.json`.
|
||
- Checked that the joined code was identical to before, apart from comments and blank lines.
|
||
- Every headless suite gave the same output, line for line, before and after.
|
||
- Screenshots of two scenes, in both looks, were pixel-identical before and after
|
||
(`tools/shot.py --freeze`).
|
||
- **Tidying, behaviour unchanged.**
|
||
- One scan loop in `simMove` instead of two mirrored copies.
|
||
- A `drawHazardRims` helper replaced 3 copies.
|
||
- The belt and filter shader code shares `beltLineSD`, `bandRidge` and `bandColor`.
|
||
- Removed unused `refundCost`, `wakeArea`, `brushFor`, and the `PARTS` / `PART_BY_ID` aliases.
|
||
- The suites share `suite()` (check / done) from the harness, and failures now set the exit
|
||
code.
|
||
- **Tests.**
|
||
- Six broken old scripts were deleted; they used functions removed long ago.
|
||
- Experiments and reports moved to `tools/experiments/`.
|
||
- `t_v2` / `t_v4` / `t_v9` were renamed `t_basics` / `t_world` / `t_conveyors`.
|
||
- Added `test/run_all.py`.
|
||
- **Tools.**
|
||
- `tools/shot.py`: repeatable screenshots of a scene set up in JavaScript.
|
||
- `tools/where.py`: maps built-page line numbers back to source files.
|
||
- `tools/map.py`: the project map's index.
|
||
- `tools/scenes/`: example scenes.
|
||
- The game skips cursor previews when `GAME.hidePreviews` is set, for clean screenshots.
|
||
- **World files outside claude.ai.**
|
||
- Pause → Export world file only worked inside claude.ai, through its downloads hook.
|
||
- It now falls back to an ordinary browser download (`exportWorld` in `src/ui/saves.js`).
|
||
- `test/play.py` checks that an exported file downloads and imports back on the title screen
|
||
as the same world.
|
||
- **Build.** `build.py` writes `dist/index.html` (a full page), `dist/deepgrain-foundry.html`
|
||
(the same without the document wrapper, as the claude.ai artifact used) and
|
||
`dist/bundle-lines.json`.
|
||
- **Docs.** `AGENTS.md`, `CLAUDE.md`, `README.md`, `NOTES.md`, `docs/PROJECT_MAP.md`,
|
||
`docs/DESIGN.md`, `docs/TESTING.md`, `docs/systems/*`, `docs/history/requests.md`.
|
||
- **Tests run.** All 11 headless suites pass, as do `test/play.py` (101 checks) and
|
||
`test/t_smooth.py` (8 checks).
|
||
- **Open.**
|
||
- The owner reports that big piles on belts still only move at the top (NOTES.md); not yet
|
||
reproduced.
|
||
- The game's last claude.ai artifact was version 24.
|