2017 lines
142 KiB
Markdown
2017 lines
142 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-28: t88 phase 4 hardware verification and handoff (Codex)
|
||
|
||
- Reviewed the existing uncommitted Phase 4 changes rather than duplicating the implementation.
|
||
Grepped src/test/tools/docs for retired R canvas fields, CPU-light profiler fields, renderer
|
||
switches, smoothPace and thread callers. Retained cell.js/objects.js and test light oracles.
|
||
Cleanup against committed Phase 3: 23 code lines removed, 14 added (net -9). No further
|
||
source deletion or simulation change. TESTING now also correctly describes fixed render scale 1.
|
||
- Fresh NVIDIA WebGPU play-through passed: 122.779 s, 7,355 ticks (59.91/s), 16 helpers,
|
||
peak active 4. Flew, drilled 63 grains, vacuumed 27 sand, mouse-built 11 belts and 8 screws,
|
||
fed sand repeatedly and ran water/magma. No page/console errors, pause or stopped simulation.
|
||
Opened drill/flow/final screenshots: bot/terrain/UI intact, sand reaches the outlet pile,
|
||
water remains above cooled magma. Evidence: shots/t88-phase4/result.json and PNGs;
|
||
/tmp/t88-live.py and /tmp/t88-phase4-live-current.txt. Sandbox adapter assertion failed
|
||
before gameplay; host-GPU retry passed.
|
||
- Fresh full default profile exited 0 in 215.99 s: tools/perf.py --compare
|
||
tools/perf-baseline.json --json tools/perf-after-t88.json. NVIDIA Blackwell, Chromium 156,
|
||
24 cores, 1920x1080 / 2048x1024, 30 views with full/off lighting and 12 live runs.
|
||
Source fingerprint verified; original baseline byte-equal to HEAD. Settled/factory/heat
|
||
1.806/2.393/2.623 ms/tick (2.06x/1.53x/1.88x baseline); polling 0.299/0.393/0.433,
|
||
main compute excluding polling 1.507/2.000/2.190 ms/tick. All live runs hold 60 fps;
|
||
zero CPU work durations and zero frame intervals over 16.7 ms in 7,137 samples.
|
||
CPU frame p50/p95, ms (zoom 2 / zoom 6):
|
||
|
||
| Scene | Pixel | Smooth |
|
||
|---|---|---|
|
||
| settled | 7.09/8.31 / 2.78/3.93 | 8.43/9.75 / 3.00/4.19 |
|
||
| factory | 8.43/9.80 / 4.92/6.14 | 10.16/11.45 / 4.90/5.92 |
|
||
| heat | 8.52/9.80 / 4.92/5.95 | 9.99/11.08 / 5.25/6.48 |
|
||
|
||
- Per-phase gains, factory/pixel/zoom 2, ms; history from the original t88 entries below:
|
||
|
||
| Metric | Baseline | After 2a | After 2b | After 2d | After 3 | Final 4 |
|
||
|---|---:|---:|---:|---:|---:|---:|
|
||
| drawWorld | 24.55 | 9.98 | 6.79 | 2.53 | 2.44 | 2.48 |
|
||
| CPU frame p50 | 31.70 | 15.40 | 10.25 | 9.28 | 8.48 | 8.43 |
|
||
| sim/tick | 3.657 | unrecorded | unrecorded | 4.200 | 2.495 | 2.393 |
|
||
|
||
Final factory drawWorld 9.90x, CPU frame p50 3.76x faster than baseline. Smooth factory
|
||
zoom-2 CPU p50: 34.71 baseline -> 14.5 after 2c -> 10.60 after 2d -> 9.59 after 3 ->
|
||
10.16 final; earlier 2a/2b smooth numbers unrecorded. Phase 4 differences from Phase 3
|
||
are run variation; no Phase 4 speedup claimed.
|
||
- Polling retained: threadPass already overlaps its local batch with helpers; following passes
|
||
and serial work require completed outputs. waitAsync is available, but safe yielding reaches
|
||
chunkPasses, simMove/heatPass, simTick/frame and input/save/render access to shared state.
|
||
No cheap safe overlap or measured gain established. Semantics rechecked against
|
||
[MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Atomics/waitAsync)
|
||
and [ECMAScript](https://tc39.es/ecma262/multipage/structured-data.html#sec-atomics.waitasync).
|
||
- Recommendation: YES to a separate whole-simulation-worker project; not implemented.
|
||
Fresh auxiliary /tmp/t88-orchestration.py runs (factory/pixel/2 and heat/smooth/2,
|
||
599 ticks each) measure dispatch/decode/merge 0.564/0.584 ms/tick, 0.571/0.589 ms/frame;
|
||
local batch 1.140/1.307 and polling 0.492/0.561 ms/tick. These wrapped measurements are
|
||
separate from the unwrapped matrix. Live simulation p50 occupies 2.10-2.66 ms/frame;
|
||
expected main-thread recovery about 2-3 ms before messaging/snapshot costs. No expected fps
|
||
gain in already-60-fps scenes. Worst zoom-2 CPU p95 11.448 leaves 5.218 ms of 16.667;
|
||
worst zoom-2 p99 12.384 leaves 4.283 ms. Benefit is responsiveness and larger-factory headroom.
|
||
Cost: messages for flight/drill/vacuum/build/interact, undo/blueprints/dev console; worker
|
||
ownership of generation/load/save/export/pause/step; coherent published state for GPU and
|
||
rare-cell rendering, HUD/bag/economy, hover/minimap; helper-pool integration. Rendering must
|
||
never read in-flight writes. Deterministic commands, input latency and save consistency
|
||
need separate tests.
|
||
- PROJECT_MAP regenerated. tools/test_perf.py exited 0 (headers, quantiles/comparison,
|
||
deterministic state and both-look pixel parity); both readout screenshots inspected.
|
||
Final python3 test/run_all.py --browser exited 0, ALL PASSED: all 32 headless suites;
|
||
gameplay 361, smooth 28, console 18, capabilities 7, GPU 12, golden 28, light 71,
|
||
Firefox 2 checks. Evidence: /tmp/t88-phase4-final-current.txt. Suite summaries match the
|
||
prior gate apart from timings; no source changes during verification. Final source
|
||
fingerprint/baseline checks, diff check and kandown doctor passed; task remains Review.
|
||
Files: retained render/{setup,cell,world,gpu/draw,gpu/textures}, tools/{perf.py,
|
||
perf-measure.js,perf-after-t88.json}, TESTING/PROJECT_MAP/agent-log and t88 tracking.
|
||
No commit; DESIGN and owner's Context untouched. Owner visual review remains.
|
||
|
||
## 2026-10-28: t88 phase 4 requested verification refresh (Codex)
|
||
|
||
- Reviewed existing uncommitted Phase 4 work. Whole-tree src/test/tools/docs searches confirm
|
||
removed Canvas2D backing stores and CPU-light profiler fields have no consumers. Kept
|
||
cell.js/objects.js, GPU lighting fields, live settings and test helpers. No further safe
|
||
deletion found. Cleanup versus committed Phase 3: 23 code lines removed, 14 added, net -9.
|
||
No additional source or simulation behaviour changes in this refresh.
|
||
- Fresh NVIDIA WebGPU live check: 122.800 seconds, 7,356 ticks (59.90/s), 16 helpers,
|
||
peak active 4. Flew, drilled 63 grains, vacuumed 27 sand, mouse-built 11 belts and 8 screws,
|
||
repeatedly fed sand and ran water/magma. No page/console errors, pause or stopped simulation.
|
||
Inspected drill, flow and final screenshots: intact bot/terrain/UI, sand piles beyond the
|
||
outlet and water/magma cooling. Evidence: shots/t88-phase4/result.json and PNGs;
|
||
/tmp/t88-live.py and /tmp/t88-phase4-live-refresh.txt. Sandbox GPU assertion failed before
|
||
gameplay; host-GPU retry passed.
|
||
- Fresh full default profile: `python3 tools/perf.py --compare tools/perf-baseline.json
|
||
--json tools/perf-after-t88.json`, exit 0, 216.5 s. NVIDIA Blackwell, Chromium 156,
|
||
24 cores, 1920x1080 / 2048x1024, all 30 views with full/off light and 12 live runs.
|
||
Saved source fingerprint verified; original baseline byte-equal to HEAD.
|
||
Settled/factory/heat: 2.064/2.439/2.565 ms/tick (1.80x/1.50x/1.92x baseline).
|
||
Polling 0.368/0.431/0.440; compute excluding polling 1.696/2.008/2.125 ms/tick.
|
||
All live runs hold 60 fps; 0/7,138 CPU frames or intervals exceed 16.7 ms.
|
||
CPU frame p50/p95, ms (zoom 2 / zoom 6):
|
||
|
||
| Scene | Pixel | Smooth |
|
||
|---|---|---|
|
||
| settled | 7.11/8.43 / 2.77/3.93 | 8.51/9.95 / 3.10/4.26 |
|
||
| factory | 8.33/9.70 / 5.07/5.93 | 10.26/11.49 / 5.02/6.17 |
|
||
| heat | 8.57/9.82 / 4.91/5.87 | 10.05/11.17 / 5.02/6.02 |
|
||
|
||
- Phase gains, factory/pixel/zoom 2, ms; historical columns from earlier t88 entries:
|
||
|
||
| Metric | Baseline | After 2a | After 2b | After 2d | After 3 | Final 4 |
|
||
|---|---:|---:|---:|---:|---:|---:|
|
||
| drawWorld | 24.55 | 9.98 | 6.79 | 2.53 | 2.44 | 2.30 |
|
||
| CPU frame p50 | 31.70 | 15.40 | 10.25 | 9.28 | 8.48 | 8.33 |
|
||
| sim/tick | 3.657 | unrecorded | unrecorded | 4.200 | 2.495 | 2.439 |
|
||
|
||
Final factory drawWorld 10.68x and CPU frame p50 3.81x faster than baseline. Smooth factory
|
||
zoom-2 CPU p50: 34.71 baseline -> 14.5 after 2c -> 10.60 after 2d -> 9.59 after 3 ->
|
||
10.26 final; earlier smooth 2a/2b figures unrecorded. Phase 4 timing differences are run
|
||
variation; no Phase 4 speedup over 3 claimed.
|
||
- Polling retained: main-thread chunk work already overlaps helpers; later passes and serial
|
||
work need completed outputs. waitAsync is present, but safely yielding requires async
|
||
propagation through chunkPasses, simMove/heatPass, simTick/frame and guards for input,
|
||
saves and rendering during in-flight shared writes. No cheap safe overlap or measured gain
|
||
established. Semantics checked against [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Atomics/waitAsync)
|
||
and [ECMAScript](https://tc39.es/ecma262/multipage/structured-data.html#sec-atomics.waitasync).
|
||
- Recommendation: YES to a separate whole-simulation-worker project; not implemented.
|
||
Fresh auxiliary /tmp/t88-orchestration.py runs (factory/pixel/2 and heat/smooth/2, 599 ticks
|
||
each) measured dispatch/decode/merge 0.558/0.561 ms/tick, local batch 1.086/1.188,
|
||
polling 0.489/0.488. Orchestration is 0.565 ms per sampled frame in both runs. These wrapped
|
||
measurements are separate from the unwrapped matrix. Final live simulation occupies
|
||
2.09-2.64 ms/frame at p50. Expected main-thread recovery about 2-3 ms before messaging and
|
||
snapshot costs; no expected fps gain in these already-60-fps scenes. Worst zoom-2 CPU p95
|
||
11.495 leaves 5.172 ms of 16.667; p99 12.692 leaves 3.975 ms. Benefit: responsiveness
|
||
and room for larger factories. Cost: command messages for flight/drill/vacuum/build/interact,
|
||
undo/blueprints/dev console; worker ownership of generation/load/save/export/pause/step;
|
||
coherent published state for GPU/rare-cell rendering, HUD/bag/economy, hover and minimap;
|
||
helper-pool integration. Rendering must never read a world mid-write. Deterministic command
|
||
order, input latency and save consistency need separate tests.
|
||
- PROJECT_MAP regenerated; existing TESTING updates cover retired renderer/tool fields.
|
||
tools/test_perf.py exited 0: headers, quantiles/comparison, deterministic state and both-look
|
||
pixel parity. Both readout screenshots inspected. Final `python3 test/run_all.py --browser`
|
||
exited 0, ALL PASSED: all 32 headless suites, gameplay 361, smooth 28, console 18, capabilities
|
||
7, GPU 12, golden 28, light 71 and Firefox 2 checks. Output:
|
||
/tmp/t88-phase4-requested-tests.txt. Source unchanged during verification; original baseline
|
||
and DESIGN verified unchanged. Final map/diff/board checks complete; t88 remains Review.
|
||
Files: retained render/{setup,cell,world,gpu/draw,gpu/textures}, tools/{perf.py,
|
||
perf-measure.js,perf-after-t88.json}, TESTING/PROJECT_MAP/agent-log and t88 tracking.
|
||
No commit; DESIGN and owner's Context untouched. Owner review remains.
|
||
|
||
## 2026-10-28: t88 phase 4 current-tree completion check (Codex)
|
||
|
||
- Reviewed the existing uncommitted Phase 4 implementation. Whole-tree src/test/tools/docs
|
||
searches cover removed Canvas2D fields, CPU light/colour names, legacy switches, light-grid
|
||
fields and settings; no further unused renderer functions found. Kept cell.js/objects.js,
|
||
GPU lighting fields and test oracles. Cleanup versus committed Phase 3: 23 code lines
|
||
removed, 14 added, net -9. No additional source change or simulation behaviour change.
|
||
- Fresh real NVIDIA WebGPU live check: /tmp/t88-live.py, 122.827 seconds / 7,357 ticks
|
||
(59.90/s), 16 helpers, peak active 4. Flew, drilled 63 grains, vacuumed 27 sand,
|
||
mouse-built 11 belts and 8 screws, repeatedly fed sand and ran water/magma. No page/console
|
||
errors, paused/stopped simulation or abnormal stuck behaviour found. Opened drill, flow and
|
||
final screenshots: bot/terrain/UI intact, sand crosses the line and piles beyond its outlet,
|
||
water/magma reacts and cools. Evidence: shots/t88-phase4/result.json and PNGs. Sandbox
|
||
adapter assertion failed before gameplay; host-GPU retry passed.
|
||
- Fresh full default `python3 tools/perf.py --compare tools/perf-baseline.json --json
|
||
tools/perf-after-t88.json` exited 0 in 217.0 s. NVIDIA Blackwell (owner's RTX 5070),
|
||
Chromium 156, 24 cores, 1920x1080 / 2048x1024; all 30 views with full/off light,
|
||
12 live runs. Saved source fingerprint matches; original baseline byte-equal to HEAD.
|
||
Settled/factory/heat: 1.828/2.433/2.667 ms/tick (2.03x/1.50x/1.85x baseline).
|
||
Polling 0.318/0.417/0.430; main compute excluding polling 1.510/2.016/2.237 ms/tick.
|
||
All live runs hold 60 fps; 0/7,140 CPU frames or intervals exceed 16.7 ms.
|
||
CPU frame p50/p95, ms (zoom 2 / zoom 6):
|
||
|
||
| Scene | Pixel | Smooth |
|
||
|---|---|---|
|
||
| settled | 7.11/8.49 / 2.84/4.06 | 8.45/9.98 / 2.94/4.07 |
|
||
| factory | 8.46/9.71 / 4.91/6.00 | 9.54/11.04 / 4.88/5.98 |
|
||
| heat | 8.55/10.14 / 4.95/5.97 | 9.93/11.13 / 5.41/6.65 |
|
||
|
||
- Phase history, factory/pixel/zoom 2, ms; historical numbers from earlier t88 log entries:
|
||
|
||
| Metric | Baseline | After 2a | After 2b | After 2d | After 3 | Final 4 |
|
||
|---|---:|---:|---:|---:|---:|---:|
|
||
| drawWorld | 24.55 | 9.98 | 6.79 | 2.53 | 2.44 | 2.29 |
|
||
| CPU frame p50 | 31.70 | 15.40 | 10.25 | 9.28 | 8.48 | 8.46 |
|
||
| sim/tick | 3.657 | unrecorded | unrecorded | 4.200 | 2.495 | 2.433 |
|
||
|
||
Final factory drawWorld 10.72x and CPU p50 3.75x faster than baseline. Smooth factory
|
||
zoom-2 CPU p50: 34.71 baseline -> 14.5 after 2c -> 10.60 after 2d -> 9.59 after 3 ->
|
||
9.54 final (earlier smooth 2a/2b figures unrecorded). Phase 4 timing differences are run
|
||
variation; no claimed Phase 4 speedup over 3.
|
||
- Polling retained. The main thread already overlaps its batch with helpers; the next pass
|
||
and serial work need completed outputs. waitAsync exists in this Chrome, but a safe use
|
||
propagates through chunkPasses, simMove/heatPass, simTick/frame and guards input/save/render
|
||
from in-flight shared writes. No cheap safe overlap or measured gain established.
|
||
API behaviour verified against [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Atomics/waitAsync)
|
||
and [ECMAScript](https://tc39.es/ecma262/multipage/structured-data.html#sec-atomics.waitasync).
|
||
- Recommendation: YES to a separate whole-simulation-worker project; not implemented.
|
||
Fresh auxiliary /tmp/t88-orchestration.py runs (factory/pixel/2 and heat/smooth/2, 599 ticks
|
||
each) measured dispatch/decode/merge at 0.565/0.587 ms/tick, local batch 1.099/1.316,
|
||
polling 0.485/0.543. Dispatch/decode/merge is 0.573/0.592 ms per sampled frame.
|
||
These wrapped measurements are separate from the unwrapped matrix.
|
||
Final live simulation occupies 2.09-2.74 ms/frame at p50; moving ownership could free
|
||
about 2-3 ms of main-thread time before message/snapshot costs. No fps gain expected on
|
||
these already-60-fps scenes. Worst zoom-2 CPU p95 11.127 leaves 5.540 ms of 16.667;
|
||
p99 12.614 leaves 4.052 ms. Benefit: responsiveness and headroom as factories grow.
|
||
Cost: command messages for flight/drill/vacuum/build/interact, undo/blueprints/dev console;
|
||
worker ownership of generation/load/save/export/pause/step; coherent published state for
|
||
GPU/rare-cell rendering, HUD/bag/economy, hover/minimap; integration with the helper pool.
|
||
Rendering must never read a world mid-write. Command determinism, input latency and save
|
||
consistency need separate tests.
|
||
- Map regenerated; TESTING already documents the tool cleanup. Profiler checks passed (headers, quantiles, comparison, deterministic simulation,
|
||
pixel/smooth image parity); both readout screenshots inspected. Final
|
||
`python3 test/run_all.py --browser` exited 0, ALL PASSED: 32 headless suites, gameplay
|
||
361, smooth 28, console 18, capabilities 7, GPU 12, golden 28, light 71, Firefox 2 checks.
|
||
Output: /tmp/t88-phase4-tests-current.txt. Suite summaries match the prior refresh except
|
||
timings. Map, diff check and kandown doctor passed. Phase 4 complete; t88 remains Review.
|
||
Files: retained render/{setup,cell,world,gpu/draw,gpu/textures}, tools/{perf.py,
|
||
perf-measure.js,perf-after-t88.json}, TESTING/PROJECT_MAP/agent-log and t88 tracking.
|
||
No commit; DESIGN and owner's Context untouched. Owner review remains.
|
||
|
||
## 2026-10-28: t88 phase 4 fresh live run, profile and review (Codex)
|
||
|
||
- Found the earlier uncommitted Phase 4 work and reviewed it rather than adding another
|
||
implementation. Whole-tree src/test/tools/docs searches confirm removed Canvas2D backing
|
||
stores and CPU-light profiler fields have no remaining live consumers. Retained cell.js,
|
||
objects.js, GPU lighting grid, settings and test light oracle. Cleanup versus 7468d00 is
|
||
23 code lines removed, 14 added, net -9; no simulation changes or further source edits.
|
||
- Rebuilt and repeated the real-GPU live check with /tmp/t88-live.py: 122.807 seconds,
|
||
7,356 ticks (59.90/s), 16 helpers, active peak 4, no page/console errors, pause or stopped
|
||
simulation. Flew with keys, drilled 63 grains, vacuumed 27 sand, mouse-built 11 belts and
|
||
8 screws, repeatedly fed sand alongside water/magma. Inspected all four screenshots under
|
||
shots/t88-phase4/: intact bot/terrain/UI, sand crosses the line and piles beyond the outlet,
|
||
water/magma reacts and cools. No abnormal stuck behavior found. Sandbox GPU assertion
|
||
failed before play began; host-GPU retry passed.
|
||
- Fresh full default `python3 tools/perf.py --compare tools/perf-baseline.json --json
|
||
tools/perf-after-t88.json` exited 0 in 216.0 seconds. NVIDIA Blackwell / RTX 5070 setup,
|
||
Chromium 156, 24 cores, 1920x1080 / 2048x1024. All 30 views, full/off lighting and 12 live
|
||
runs; source fingerprint verified and original baseline byte-equal to HEAD.
|
||
Settled/factory/heat sim: 1.983/2.442/2.506 ms/tick, 1.87x/1.50x/1.97x baseline.
|
||
Polling: 0.376/0.404/0.396; compute excluding polling: 1.607/2.038/2.110 ms/tick.
|
||
Every live run holds 60 fps; 0/7,139 sampled CPU work durations or frame intervals >16.7 ms.
|
||
CPU frame p50/p95, ms (zoom 2 / zoom 6):
|
||
|
||
| Scene | Pixel | Smooth |
|
||
|---|---|---|
|
||
| settled | 7.04/8.27 / 2.93/4.12 | 8.42/9.79 / 2.99/4.09 |
|
||
| factory | 8.50/9.86 / 4.88/6.13 | 9.58/10.88 / 4.91/6.05 |
|
||
| heat | 8.46/9.81 / 4.80/5.92 | 9.90/11.01 / 5.06/6.18 |
|
||
|
||
- Phase history, factory/pixel/zoom 2, ms; historical columns from the entries below:
|
||
|
||
| Metric | Baseline | After 2a | After 2b | After 2d | After 3 | Final 4 |
|
||
|---|---:|---:|---:|---:|---:|---:|
|
||
| drawWorld | 24.55 | 9.98 | 6.79 | 2.53 | 2.44 | 2.48 |
|
||
| CPU frame p50 | 31.70 | 15.40 | 10.25 | 9.28 | 8.48 | 8.50 |
|
||
| sim/tick | 3.657 | unrecorded | unrecorded | 4.200 | 2.495 | 2.442 |
|
||
|
||
Final factory drawWorld 9.91x and CPU p50 3.73x faster than baseline. Smooth factory
|
||
zoom-2 CPU p50: 34.71 baseline -> 14.5 after 2c -> 10.60 after 2d -> 9.59 after 3 ->
|
||
9.58 final (2a/2b smooth not recorded). No claimed Phase 4 speedup; variation from Phase 3.
|
||
- Polling retained after tracing threadPass/threadWait and their callers. Main-thread chunk
|
||
work already overlaps helpers; later passes/serial work need completed outputs. A safe
|
||
waitAsync change reaches chunkPasses, simMove/heatPass, simTick/frame and UI/save/render
|
||
access to in-flight arrays. No cheap safe overlap or measured gain established. API semantics
|
||
rechecked against [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Atomics/waitAsync)
|
||
and [ECMAScript](https://tc39.es/ecma262/multipage/structured-data.html#sec-atomics.waitasync).
|
||
- Recommendation: YES, as a separate whole-simulation-worker project; not implemented.
|
||
Fresh auxiliary /tmp/t88-orchestration.py runs (factory/pixel/2 and heat/smooth/2, 599 ticks
|
||
each) measure dispatch/decode/merge at 0.56-0.58 ms/tick, local batch 1.15-1.30, polling
|
||
0.49-0.55. These wrapped runs are separate from the unwrapped final profile above.
|
||
Final live sim p50 occupies 2.13-2.63 ms/frame. Expected main-thread recovery about 2-3 ms
|
||
before message/snapshot costs; no expected fps increase in these already-60-fps scenes.
|
||
Worst zoom-2 CPU p95 11.014 leaves 5.653 ms of 16.667; p99 12.103 leaves 4.564 ms.
|
||
Cost: command messages for flight/drill/vacuum/build/interact, undo/blueprints/dev console;
|
||
worker ownership of generation/load/save/export/pause/step; coherent GPU/rare-cell rendering,
|
||
HUD/bag/economy, hover and minimap snapshots; integration with the helper pool. Deterministic
|
||
command order, input latency and save/snapshot consistency need their own verification.
|
||
- Verification refresh complete: tools/test_perf.py exited 0 (headers, summaries, determinism
|
||
and both-look pixel parity); both readout screenshots inspected. Final
|
||
`python3 test/run_all.py --browser` exited 0, ALL PASSED: 32 headless suites; gameplay 361,
|
||
smooth 28, console 18, capabilities 7, GPU 12, golden 28, light 71, Firefox 2 checks.
|
||
/tmp/t88-phase4-refresh-tests.txt matches the prior full-suite summary except timings.
|
||
Current src matches the refresh-start snapshot and saved profile fingerprint. Map, diff
|
||
check and kandown doctor passed. Phase 4 checklist complete; t88 remains in Review.
|
||
tools/map.py regenerated PROJECT_MAP; existing TESTING changes cover the retired fields and
|
||
renderer/tool options. Files retained: render/{setup,cell,world,gpu/draw,gpu/textures},
|
||
tools/{perf.py,perf-measure.js,perf-after-t88.json}, TESTING/PROJECT_MAP/agent-log and t88.
|
||
No commit; DESIGN and owner's Context untouched. Owner visual review remains.
|
||
|
||
## 2026-10-28: t88 phase 4 final verification (Codex)
|
||
|
||
- Resumed the existing Phase 4 changes and checked their diff, live-run driver/results and
|
||
screenshots. The completed 122.8-second real-GPU play-through recorded 7,355 ticks
|
||
(59.90/s), flying, 63 grains drilled, 23 sand vacuumed, 11 belts and 8 screws built with
|
||
mouse drags, sand feeding and water/magma. No errors, pause or stopped simulation.
|
||
Opened 01-drill.png and 03-flow.png again: bot, terrain and UI intact; sand crosses the
|
||
line and piles beyond its outlet. This evidence was retained, not rerun during resume.
|
||
Driver /tmp/t88-live.py; screenshots/raw results in shots/t88-phase4/.
|
||
- Fresh full default profile on the host GPU: `python3 tools/perf.py --compare
|
||
tools/perf-baseline.json --json tools/perf-after-t88.json`, exit 0, 215.5 seconds.
|
||
The JSON source fingerprint matches the current src/tools; baseline bytes match HEAD.
|
||
NVIDIA Blackwell, 24 cores, Chromium 156, 1920x1080 / 2048x1024, all 30 views and
|
||
12 live runs. This fresh run replaces the initial Phase 4 profile.
|
||
settled: 1.913 ms/tick (1.94x baseline), polling 0.392, main compute 1.520; 16 helpers, peak active 4.
|
||
factory: 2.386 ms/tick (1.53x baseline), polling 0.406, main compute 1.980; 16 helpers, peak active 5.
|
||
heat: 2.695 ms/tick (1.83x baseline), polling 0.420, main compute 2.275; 16 helpers, peak active 5.
|
||
All 7,137 sampled frames: zero CPU work or intervals >16.7 ms; every live run 60.00 fps.
|
||
CPU frame p50/p95, ms (zoom 2 / zoom 6):
|
||
|
||
| Scene | Pixel | Smooth |
|
||
|---|---|---|
|
||
| settled | 7.03/8.50 / 2.76/3.89 | 8.47/9.87 / 2.93/4.08 |
|
||
| factory | 8.38/9.82 / 5.00/6.13 | 9.54/10.77 / 4.98/6.08 |
|
||
| heat | 8.80/10.17 / 4.84/5.84 | 9.92/11.27 / 5.07/6.07 |
|
||
|
||
- Phase history, factory/pixel/zoom 2 (ms; earlier columns from the entries below):
|
||
|
||
| Metric | Baseline | After 2a | After 2b | After 2d | After 3 | Final 4 |
|
||
|---|---:|---:|---:|---:|---:|---:|
|
||
| drawWorld | 24.55 | 9.98 | 6.79 | 2.53 | 2.44 | 2.41 |
|
||
| CPU frame p50 | 31.70 | 15.40 | 10.25 | 9.28 | 8.48 | 8.38 |
|
||
| sim/tick | 3.657 | unrecorded | unrecorded | 4.200 | 2.495 | 2.386 |
|
||
|
||
Final factory drawWorld 10.20x and CPU frame p50 3.78x faster than baseline.
|
||
Smooth factory zoom-2 CPU p50: baseline 34.71 -> 2c 14.5 -> 2d 10.60 -> 3 9.59
|
||
-> final 9.54 ms (2a/2b smooth figures unrecorded).
|
||
Phase 4 itself has no claimed speedup over 3; differences are run variation.
|
||
- Cleanup reviewed with whole-tree src/test/tools/docs references: 23 code lines removed,
|
||
14 added (net -9). Unused Canvas2D backing objects and CPU-light profiler fields removed;
|
||
cell.js/objects.js, GPU light grid, live flags and test helpers retained. Updated TESTING's
|
||
tool/renderer descriptions, regenerated PROJECT_MAP. No simulation code changed.
|
||
- Polling retained: no cheap safe overlap found. The existing main-thread batch already
|
||
overlaps helper work; subsequent serial work needs its result. Async wait would require
|
||
changes through chunkPasses/simMove/heatPass/simTick/frame and shared-state guards for
|
||
input, save and render. Non-blocking semantics confirmed against
|
||
[MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Atomics/waitAsync)
|
||
and [ECMAScript](https://tc39.es/ecma262/multipage/structured-data.html#sec-atomics.waitasync).
|
||
- Recommendation: YES to a separate whole-simulation-worker project. Auxiliary instrumentation
|
||
(/tmp/t88-orchestration.py and .json, factory/pixel/2 and heat/smooth/2, 599 ticks each)
|
||
measured dispatch/decode/merge at 0.55-0.60 ms/tick, local batch 1.11-1.35 and polling
|
||
0.50-0.60. These are wrapped auxiliary runs, not the unwrapped final real-loop measurements.
|
||
Current final live sim p50 is 2.09-2.69 ms/frame; estimate 2-3 ms main-thread budget recovered
|
||
before message/snapshot costs, not an fps increase in these already-60-fps scenes.
|
||
No sampled frames miss 16.7 ms. Worst zoom-2 CPU p95 11.27 leaves 5.40 ms;
|
||
p99 12.64 leaves 4.02 ms against 16.667. Expected benefit is headroom and responsiveness.
|
||
Cost: command messages for flight/drill/vacuum/build/interact, undo/blueprints/dev console;
|
||
worldgen/load/save/export/pause/step ownership; coherent render/rare-cell/HUD/bag/economy/
|
||
hover/minimap snapshots while the next tick writes elsewhere; integrate the helper pool.
|
||
Deterministic command order, save consistency and input latency need verification.
|
||
No whole-simulation worker implemented.
|
||
- tools/test_perf.py exited 0: headers, summaries/comparison, deterministic sim and both-look
|
||
image parity; inspected both new perf-readout screenshots. Initial sandboxed profiler failed
|
||
to acquire WebGPU; host-GPU retry passed. Final `python3 test/run_all.py --browser`
|
||
exited 0, ALL PASSED: 32 headless suites; gameplay 361, smooth 28, console 18,
|
||
capabilities 7, GPU 12, golden 28, light 71, Firefox 2 checks. Full output:
|
||
/tmp/t88-phase4-verified-tests.txt. Map, diff check and kandown doctor passed.
|
||
t88 moved to Review; owner visual review and Claude commit remain.
|
||
- Files: existing Phase 4 render/setup/cell/world and GPU draw/textures, perf.py/perf-measure.js/
|
||
perf-after-t88.json, TESTING/PROJECT_MAP/agent-log and t88 tracking. No commit, no DESIGN edit;
|
||
owner's Context unchanged. Owner visual review remains.
|
||
|
||
## 2026-10-28: t88 phase 3 resume, final review and verification (Codex)
|
||
|
||
- Preserved the interrupted uncommitted implementation and reviewed the schedule, shared
|
||
writes, deferred effects, startup gate, test changes and profiler against the Phase 3 brief.
|
||
- Reproduced a deferred-liquid bug before fixing it: replaying liquid() rolled the movement
|
||
chance twice. Extracted liquidSearch() so the deferred search resumes at its exact direction
|
||
and distance without another random draw; removed the now-unneeded RNG replay records.
|
||
Added a failing-then-passing regression and a long water shelf to the repeated 320-tick
|
||
inline/1/2/8-worker and reversed-order fixture.
|
||
- Chunk passes now skip per-cell renderDirty stores in wakeXY/wakeHeatXY; the scanned chunk
|
||
and write halo provide their invalidation. Outside passes, explicit invalidation remains.
|
||
- All headless suites passed. Initial sandboxed browser gate failed (no GPU device); an
|
||
elevated capability check confirms RTX 5070 WebGPU, isolation and 24 cores. Firefox still
|
||
has no adapter and correctly requires WebGPU. Final host-GPU browser gate in progress.
|
||
- Files additionally changed on resume: sim/{cells,workers,world}, test/t_threads.js,
|
||
PROJECT_MAP and tracking. Full fresh profile, two-minute live run and screenshot inspection
|
||
pending. No commit; DESIGN and owner's Context untouched.
|
||
|
||
## 2026-10-27: t88 phase 3, multi-core simulation (Codex; verification in progress)
|
||
|
||
- SharedArrayBuffer allocation for world arrays (ArrayBuffer only when unavailable in Node).
|
||
Four (chunkX&1, chunkY&1) movement passes then four heat passes. Each chunk scopes RS to
|
||
hash(world seed,tick,chunk,pass), restoring the global machine/worldgen RNG afterwards.
|
||
Long liquid reads outside a 24-cell horizontal halo are deferred and replayed in chunk order;
|
||
movement writes reach at most 8 cells, so parallel read/write regions cannot overlap.
|
||
- Pass-local wake/heat flags merge by maximum; per-chunk heat outputs merge first. Discoveries,
|
||
events, fuel ignition counts, wheel flow, cloud moisture, broken machines and partLost merge
|
||
in chunk order. Flyers/launchGrain remain reachable only from main-thread transport, not these
|
||
cell passes; their existing state is included in the determinism hash. Direct shared writes
|
||
are disjoint cell/powder outputs or idempotent render/roof bits.
|
||
- Browser Blob pool uses min(16,hardwareConcurrency-2,setting), at least one. Main thread owns
|
||
machines, transport and game logic, processes a batch in each pass, then polls Atomics.load.
|
||
No Atomics.wait on the document: it is prohibited by
|
||
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Atomics/wait
|
||
and https://tc39.es/ecma262/2024/multipage/structured-data.html. Polling is synchronous and
|
||
occupies the main core; it preserves atomic ticks so input/save/render cannot race workers.
|
||
No whole-simulation worker or asynchronous rendering overlap. Worker timeouts/errors stop
|
||
frame simulation. Small jobs run inline and larger batches use only enough helpers to
|
||
amortize dispatch (32 movement or 8 heat chunks per helper). Pool setting applies on load.
|
||
- Invalidation: remove swap's duplicate hot marks, gas marks and repeated coordinate-to-chunk
|
||
function calls. Each scanned movement chunk marks its halo once; heat marks conduction edges.
|
||
wake functions reuse their already computed chunk index for one direct render bit store;
|
||
rare swaps/building/fog/dig/background writes retain explicit marks. Paused clean draws upload
|
||
zero world cells. GPU equality passes without changing any tolerance.
|
||
- Measurements so far: fresh Phase 2 settled/factory/heat 3.537/4.077/5.824 ms/tick vs frozen
|
||
baseline 3.715/3.657/4.938. Isolated removal of hot dirty calls in a temporary Phase 2 copy
|
||
gave 3.530/3.921/5.936: it did NOT recover the full 15% on its own. Do not claim it did.
|
||
Initial unbatched pool factory 4.21; batched trial 3.04 ms/tick. Full final matrix pending.
|
||
- Final full matrix (`/tmp/t88-phase3-final.json`, `/tmp/t88-phase3-final-perf.txt`, 218.0 s),
|
||
RTX 5070, 24 logical cores, 1920x1080, 2048x1024 world. Pool 16 helpers, peak active 4
|
||
(settled) / 5 (factory, heat), plus main-thread batch. All values below are ms.
|
||
settled: baseline 3.715 -> wall/main occupied 1.897 (1.96x); main compute excluding polling 1.624, polling 0.272.
|
||
factory: baseline 3.657 -> wall/main occupied 2.495 (1.47x); main compute excluding polling 2.037, polling 0.458.
|
||
heat: baseline 4.938 -> wall/main occupied 2.649 (1.86x); main compute excluding polling 2.236, polling 0.412.
|
||
Real-loop CPU work p50/p95, zoom 2 / zoom 6; pixel then smooth:
|
||
settled: pixel 7.08/8.58 / 2.77/3.83; smooth 8.46/9.98 / 2.98/4.13.
|
||
factory: pixel 8.48/10.05 / 4.96/5.95; smooth 9.59/10.80 / 5.13/6.12.
|
||
heat: pixel 8.53/10.56 / 4.88/6.03; smooth 10.05/11.51 / 5.17/6.21.
|
||
Frame intervals p50/p95 are 16.67/16.67 for all these runs (60 fps). Tick wall time and
|
||
occupied main-thread time are equal: polling blocks this core; rendering does not overlap.
|
||
The final threaded sim meets the baseline target in every scene; standalone dirty-only
|
||
recovery remains unproven/unmet (numbers above). Recommend a future whole-simulation worker
|
||
to remove the remaining 1.90-2.65 ms from the main thread; it would require an explicit
|
||
ownership/input/save boundary and is outside this phase.
|
||
- Re-baseline: three worldgen material hashes and seeded pile bounds (57x17 -> 46x19 grains).
|
||
The heat oracle retains its unoptimized per-cell computation, updated for buffered pass
|
||
outputs, and is compared against all four new passes. Same throughput assertions retained:
|
||
conveyor feed heap widened, downhill-screen warm-up lengthened; load-wake deadline kept at
|
||
eight ticks, fuel consumption checked later instead of demanding a random burn on that tick.
|
||
Frozen renderer PNGs and tolerances unchanged: original Phase 2 world bytes captured from the
|
||
Phase 2 simulation (only invalidation edits) in the temporary copy, compressed as golden-world fixtures, so changed
|
||
terrain doesn't get counted as changed renderer pixels.
|
||
- Headless suites pass; repeated inline/1/2/8 real-worker hashes pass (world arrays + JS effects).
|
||
Extended test passes 320 busy ticks including stamp wrap, reversed chunk order, Node fallback.
|
||
First full browser gate: gameplay 361, smooth 28, console 18, capabilities 7, GPU equality 12,
|
||
light 71 and Firefox 2 pass; golden sky failed due changed worldgen terrain. Frozen-input
|
||
golden rerun, final browser gate, full profile and 2-minute play-through pending.
|
||
- Files: sim/{world,cells,heat,workers,fog,machines,weather}, ui/{threads,title,state,settings,loop},
|
||
render/gpu/device, build.py/manifest; harness, threads/heat/load/corner/conveyor/screen/capability/
|
||
golden tests and fixtures; profiler, README, TESTING, PROJECT_MAP and t88 tracking.
|
||
No commit; DESIGN and owner's task context untouched. Phase 3 remains in progress.
|
||
|
||
## 2026-10-27: t88 phase 2d, required WebGPU and dirty uploads (Codex)
|
||
|
||
- Per-32x32-chunk `renderDirty` marks cell writes, heat with its conduction halo, stationary
|
||
fire life, fog, drill damage, tree growth, eviction and block edits. `gpu/uploads.js` merges
|
||
horizontal dirty chunks into strips; clean resident chunks survive scrolling. GPU byte
|
||
readback matches the full CPU data after 240 busy ticks; an unchanged frame uploads no world cells.
|
||
- Both looks stay within default WebGPU limits: four storage textures, sixteen sampled textures,
|
||
eight storage buffers per shader stage, 8192 texture dimension; timestamp queries optional.
|
||
Smooth colour/geometry and glow use two passes. All pipelines compile asynchronously at startup;
|
||
both looks submit behind the loading screen. No fallback: missing/insufficient adapters cannot
|
||
start. Loss tries one device recreation; failure stops frames, autosaves, offers Reload (or
|
||
Export if saving fails). Firefox 157 on Linux shows the required-WebGPU notice.
|
||
- Removed `render/light.js`, `smooth.js`, `smooth-shaders.js`, `slope-belt-shader.js`, the CPU
|
||
plain-colour loop, renderer setting/tool switch and Canvas2D `drawCracks`. The rare CPU shade
|
||
classifier remains in `cell.js`; no CPU light sweep. Drill contours now use GPU colour shading
|
||
from `digp` and the same three seeded Perlin fields at screen resolution.
|
||
- Before removal captured 28 legacy reference crops (renderer, joins, sky, lamp/opening/magma/fire;
|
||
both looks, zoom 12/2), 384x288 PNGs under `test/fixtures/golden/` (1.7 MB total). All pass:
|
||
mean RGB error 0.012-0.287/255; <=0.15% pixels over 8. Golden test allows mean <0.5 and <0.3%
|
||
pixels over 8 for smooth AO rounding. Both contact sheets and pixel/smooth drill screenshots
|
||
inspected. The zoom-12 joins crop was recentered on actual geometry using the original renderer
|
||
built in a temporary checkout; the four joins crops were recaptured there.
|
||
- Final full matrix (1920x1080, RTX 5070), drawWorld zoom 2/6 (ms; baseline -> final):
|
||
settled pixel 21.78/2.71 -> 2.64/0.44, smooth 25.64/3.08 -> 5.16/0.71;
|
||
factory pixel 24.55/2.89 -> 2.53/0.47, smooth 28.20/3.34 -> 3.93/0.85;
|
||
heat pixel 24.35/3.41 -> 2.19/0.34, smooth 27.43/3.54 -> 3.51/0.85.
|
||
Factory uploads zoom 2/6: pixel 6.55/1.76 -> 0.53/0.09 ms, smooth 8.05/1.92 -> 1.88/0.44 ms
|
||
(fresh before/after runs). Real-loop CPU p50/p95 zoom 2/6: pixel 9.28/10.56 and 6.63/7.74 ms;
|
||
smooth 10.60/12.03 and 6.66/7.65 ms. Zoom 2 CPU p50 baseline pixel/smooth 31.70/34.71 ms.
|
||
Standalone sim is a remaining regression vs the old baseline: factory 3.66 -> 4.20 ms/tick,
|
||
heat 4.94 -> 5.65; settled 3.71 -> 3.49. No simulation behaviour changed (output diff exact).
|
||
Full default matrix and comparison: `/tmp/t88-final.json`, `/tmp/t88-final.log` (222.5 s).
|
||
- Isolated 960x600 warm-up check: async pipeline compilation 125.55 ms at startup; world warm-up
|
||
23.73 ms while loading; first visible frame 4.87 ms, following frames 2.10-12.47 ms (including
|
||
normal scheduling/GC variation). Look/zoom timings are in `/tmp/t88-gpu-final.log`; no pipeline
|
||
creation occurs on switches. First switch to smooth at zoom 12: 3.42 ms (normal max 3.51);
|
||
first zoom 2 smooth: 6.01 ms (1.85 ms above the normal max of 4.16); zoom 6 smooth
|
||
3.74 ms (normal max 3.38). All below a 16.67 ms frame budget; no first-use shader compilation.
|
||
These timings include waiting for submitted GPU work.
|
||
- Files: `sim/{world,cells,heat,fog,drill,tools,nature,machines,blocks,actions,conveyors}.js`,
|
||
`render/{world,cell,setup}.js`, `render/gpu/{uploads,cracks,surface,colour,device,draw,smooth,textures}.js`,
|
||
`ui/{title,session,claims,state,settings,loop,perf,preview-drill}.js`, manifest; browser tests,
|
||
`test/{light-cases,light-readback,t_render_dirty}.js`, golden fixtures; profiler/shot tools,
|
||
`tools/browser.py`, README, TESTING, PROJECT_MAP, t88 record. Old `test/t_light.js` moved to actual GPU checks,
|
||
`test/gpu-readback.js` retained for the play test. DESIGN and other tasks untouched.
|
||
- Verification: final `python3 test/run_all.py --browser` exited 0, ALL PASSED (361 play,
|
||
28 smooth, 18 console, 6 capabilities, 12 GPU, 28 golden, 71 light, 2 Firefox checks;
|
||
all headless suites, including nine render-dirty checks). Retained headless outputs match
|
||
before/after exactly (timing lines ignored). `tools/test_perf.py`, map generation, diff check
|
||
and kandown doctor passed. Early gate caught premature GAME.running during warm-up; fixed by
|
||
warming before enterGame. No commit. Phase 2 implementation complete; Phase 3 next. Owner
|
||
visual review pending; other GPU hardware and Firefox with an actual adapter not tested.
|
||
|
||
## 2026-10-27: t88 phase 2c, smooth look on WebGPU (Codex + Claude Code)
|
||
|
||
- WGSL port of the smooth look (`src/render/gpu/smooth.js`, `smooth-shader.js`, `smooth-slope.js`):
|
||
the GPU colour pass writes all four smooth inputs for plain cells; the CPU rare-cell overrides
|
||
(`cell.js`, now also mold walls) fill them for built pieces and objects (12.73/3.41/0.38% of
|
||
factory cells at zoom 12/6/2). Field AO via three GPU mip passes; nine-tap glow kept; no
|
||
WebGPU `smoothPace` (max GPU time 1.24 ms). Legacy still selectable.
|
||
- Checked by Codex: 21 screenshot pairs legacy vs WebGPU, mean RGB error 0.01-0.22/255, small
|
||
counts off by more than 8 (thin AO and edge rounding), no missing shapes or open joins; input
|
||
readbacks equal legacy. Smooth factory drawWorld zoom 2 28.2 -> 10.0 ms; real loop zoom 2 CPU
|
||
p50/p95 34.7/39.0 -> 14.5/15.7 ms. Claude did not inspect the images (token budget).
|
||
- Open for 2d: adapters with fewer than five storage textures per stage fall back to legacy.
|
||
- Tests: `python3 test/run_all.py --browser` passed.
|
||
|
||
## 2026-10-27: t88 phase 2b, lighting on the GPU (Codex + Claude Code)
|
||
|
||
- `src/render/gpu/light.js`: light sources per light cell (transmittance, sky from `roofY`, heat,
|
||
bot lamp), propagation as a left/up then right/down relaxation on float textures (the same
|
||
light paths as the CPU sweep), then packing. The WebGPU path no longer calls `computeLight`;
|
||
the upload rectangle widens by the light margin. Fixed CPU light buffers keeping stale sizes
|
||
after a renderer switch. Scene `tools/scenes/t88-light.js` (lamp, opening, magma, fire).
|
||
- Checked: light scenes legacy vs WebGPU, ImageMagick MAE 16-27/65535 (<0.1/255), identical by
|
||
eye; `test/t_gpu.py` light parity in full and fast modes. Profile (factory, pixel): drawWorld at
|
||
zoom 2 24.55 (baseline) -> 9.98 (2a) -> 6.79 ms, CPU light 0, uploads now the biggest part
|
||
(4.87 ms); real loop zoom 2 CPU p50/p95 31.7/35.3 -> 10.25/11.48 ms, a steady 60 fps.
|
||
- Commit 31e3005 went in after a full-suite run where `t_gpu.py` timed out once (a 30 s page wait
|
||
under load): the commit sat after a heredoc, outside the test gate. The suite was re-run and
|
||
passed in full, and `t_gpu.py` passes alone. Codex hit its usage limit before the final
|
||
checks; Claude Code ran them.
|
||
|
||
## 2026-10-27: t88 phase 2a, WebGPU pixel look (Claude Code + Codex)
|
||
|
||
- New `src/render/gpu/` (device, textures, colour compute, glow blur, draw) renders the pixel look
|
||
on a `#gpu` canvas under `#game`, which becomes a transparent overlay in WebGPU mode. The GPU
|
||
colours plain cells (background, materials with noise and grain, loose shade, fire, water,
|
||
ALPHA, digp shake, incandescence, lens, fog); the CPU colours only rare cells (built blocks,
|
||
screw/slope neighbours, bars, flyers, ghosts) through the extracted per-cell code
|
||
(`src/render/cell.js`, `src/render/objects.js`). World arrays upload per visible rectangle with
|
||
`queue.writeTexture`; light is still `computeLight` on the CPU, multiplied on the GPU.
|
||
`SET.renderer` = webgpu | legacy (Settings). The smooth look still uses the WebGL2 path.
|
||
- Screenshots legacy vs WebGPU: mean abs RGB error 0.112 / 0.062 / 0.034 at zoom 12/6/2, no pixel
|
||
off by more than 8. Factory scene drawWorld at zoom 2: 24.55 -> 9.98 ms (overrides 1.81, uploads
|
||
2.39, CPU light 5.59; GPU time 0.06 ms). Real loop zoom 2 CPU p50/p95: 31.7/35.3 -> 15.4/16.7 ms.
|
||
- Tests: `python3 test/run_all.py --browser` all passed (new `test/t_gpu.py`). Not checked:
|
||
Firefox WebGPU (no adapter in Playwright Firefox on Linux).
|
||
|
||
## 2026-10-27: t88 phase 1, serving and capability checks (Claude Code + Codex)
|
||
|
||
- The game must be served with COOP/COEP (README has nginx and Caddy lines; opening from disk is
|
||
dropped). The title screen checks `crossOriginIsolated` and a WebGPU adapter (`CAPS`,
|
||
`checkCapabilities` in `src/ui/title.js`) and shows a notice; warnings only until
|
||
`REQUIRE_GPU_AND_THREADS` is flipped in Phases 2-3.
|
||
- Browser tests and tools load through `tools/browser.py` + `tools/server.py`, on the real GPU by
|
||
default (`--gpu soft` / `DEEPGRAIN_GPU=soft` for SwiftShader). `tools/caps.py` prints isolation
|
||
and WebGPU per browser; `test/firefox_smoke.py` runs in `run_all.py --browser` when Firefox is
|
||
installed. Chromium 156: isolated, WebGPU on the RTX 5070. Playwright Firefox 157: isolated, no
|
||
WebGPU (Linux).
|
||
- Tests: `python3 test/run_all.py --browser` all passed on the real GPU.
|
||
|
||
## 2026-10-27: t88 phase 0, profiler and baseline (Claude Code + Codex)
|
||
|
||
- `tools/perf.py` rewritten: real GPU (ANGLE Vulkan flags), served by the new `tools/server.py`
|
||
with COOP/COEP (`tools/serve.py` sends them too), zooms 12/6/4/3/2 x pixel/smooth x scenes
|
||
settled/factory/heat (`tools/perf-scenes.js`), drawWorld split by `PERF_MARKS`, per-step sim
|
||
timings, 10 s real runs with p50/p95/p99, `--json`/`--compare`. Baseline `tools/perf-baseline.json`.
|
||
In-game perf readout shows world, light, overlays, move, heat, rest (`src/ui/perf.js`).
|
||
- Baseline (1920x1080, RTX 5070): zoom 2 smooth heat scene drawWorld 27.4 ms (colour loop 21.1,
|
||
light 5.5, upload 0.6); sim 3.7-4.9 ms/tick (simMove 2.0-2.3, heatPass 1.5-2.6); real loop at
|
||
zoom 2 runs at 30 fps (CPU work p50 30-37 ms), at zoom 6 at 60 fps (7-9 ms).
|
||
- Files: `tools/perf.py`, `tools/server.py`, `tools/serve.py`, `tools/perf-measure.js`,
|
||
`tools/perf-scenes.js`, `tools/test_perf.py`, `tools/perf-baseline.json`, `src/ui/perf.js`,
|
||
`src/render/world.js`, `src/render/smooth.js`, `src/ui/loop.js`, `src/styles/style.css`,
|
||
`src/manifest.json`, `test/t_perf.js`, `docs/TESTING.md`, `docs/PROJECT_MAP.md`.
|
||
- Tests: `python3 test/run_all.py --browser` all passed. Not measured yet: GPU time (timestamp
|
||
queries come with WebGPU), Firefox.
|
||
|
||
## 2026-10-27: OS pointer hidden over the game (Claude Code)
|
||
|
||
- The canvas used `cursor: crosshair`, so the system pointer showed on top of the game's own aim;
|
||
it was hidden only while a gamepad was in use. Now `cursor: none` on the canvas always; the
|
||
gamepad's `nocursor` toggling is gone. Files: `src/styles/style.css`, `src/ui/input.js`,
|
||
`src/ui/gamepad.js`.
|
||
|
||
## 2026-10-27: t77 identify ores with the cursor (Claude Code + Codex)
|
||
|
||
- New `src/ui/cursor.js`: `oreLike`, `bitNeeded`, `pingVisible` (a ping's cells, while showing),
|
||
`hoveredVein` (cached capped flood fill and edges), `cursorLabelAlpha` (2 s then fade).
|
||
`hoverInfo` identifies pinged cells under fog and says "needs <bit>"; `drawCursorLabel` honours
|
||
the new Off / Ores only / Everything setting (`SET.cursorLabel`, booleans migrated);
|
||
`drawHoveredVein` in `src/render/overlays.js` draws the outline.
|
||
- Tests: `test/t_cursor.js`, `test/t_smooth.py`, `test/play.py`; `python3 test/run_all.py --browser`
|
||
all passed. Open: the outline keeps its old shape if the vein is drilled while hovered, until
|
||
the cursor moves to another cell.
|
||
|
||
## 2026-10-27: t75 ping recharge, natural ore colour, glints (Claude Code)
|
||
|
||
- 10 s recharge (`PING_RECHARGE`, `pingWait`) with a drill panel chip. `pingImage` draws veins in
|
||
their lit colour (base + per-grain noise) with a lighter rim; the ring is dimmer; up to 60 glints
|
||
per ping flash as the ring passes. Follow-up: veins fade by distance (lit 1.2 s, fade 0.8 s,
|
||
drawn in 6 alpha bands) and `drawPingRecharge` shows an arc around the bot with the drill. Files: `src/ui/actions.js`, `src/ui/selection-panels.js`,
|
||
`src/render/overlays.js`, `src/ui/loop.js`. Tests: full suite with --browser passed. The ring and glints were not
|
||
caught in a screenshot (they finish before the frame is taken).
|
||
|
||
## 2026-10-27: bag counted things its list didn't show (Claude Code)
|
||
|
||
- Owner's exported save: bag total 1,522 but the list showed nothing for 1,000 basalt (from the
|
||
dev console's give), 200 clay (source not traced) and 322 molten slag (vacuumed with the Flask;
|
||
not in BAG_ORDER). `bagAdd` now stores solids as their drill yield; `deserializeWorld` converts
|
||
solids already in a saved bag; `bagMaterials` appends anything outside BAG_ORDER; dev `give`
|
||
reports what it added and refuses gases and solids with no yield.
|
||
- Files: `src/sim/items.js`, `src/sim/save.js`, `src/ui/hud.js`, `src/sim/devconsole.js`,
|
||
`test/t_basics.js`, `test/t_dev_console.js`. Tests: `python3 test/run_all.py --browser` all passed.
|
||
|
||
## 2026-10-27: t50 resonance ping (Claude Code)
|
||
|
||
- Right click with the drill: `firePing` (`src/ui/actions.js`) calls `resonancePing` (`src/sim/drill.js`),
|
||
which lists natural vein cells (coal seam and the six ore veins) within 3x the bit's reach.
|
||
`drawPing` (`src/render/overlays.js`) draws an echo ring and the veins inside it over fog, from
|
||
a one-time offscreen image (one clipped drawImage per frame), fading over 4.5 s. 3 s recharge.
|
||
- Files: `src/sim/drill.js`, `src/ui/actions.js`, `src/render/overlays.js`, `src/ui/loop.js`,
|
||
`src/ui/notebook.js`, `src/ui/selection-panels.js`, `test/t_drill.js`, `docs/PROJECT_MAP.md`.
|
||
- Tests: 4 checks in `test/t_drill.js`; `python3 test/run_all.py --browser` all passed.
|
||
|
||
## 2026-10-27: t64 build range about the screen; out-of-range replace ghosts (Claude Code)
|
||
|
||
- `setBuildRange`/`inBuildRange` in `src/sim/tools.js`: the UI sets the view box each frame
|
||
(`src/ui/loop.js`); half-sizes clamped to REACH..BUILD_MAX (64..200 grains); with no view (tests)
|
||
a REACH box around the bot. Used by `blockInReach`, machine/mold placement and previews,
|
||
right-click take-back, hover reach, and ghost building for blocks, machines and molds.
|
||
- A replace (G) placed beyond range stores a replace flag on the ghost (`[code, v, extra, 1]`);
|
||
`ghostTick0` removes the old piece (refunded) and places the new one (charged), or re-sets a
|
||
filter's settings, when the bot comes in range.
|
||
- Files: `src/sim/tools.js`, `src/sim/blocks.js`, `src/sim/undo.js`, `src/ui/actions.js`,
|
||
`src/ui/previews.js`, `src/ui/info.js`, `src/ui/loop.js`, `test/t_build.js`, `docs/DESIGN.md`,
|
||
`docs/PROJECT_MAP.md`. Tests: `python3 test/run_all.py --browser` all passed. Not played by hand.
|
||
|
||
## 2026-10-27: t49 throughput overlay (Claude Code)
|
||
|
||
- The I overlay also shows grains per second at each line's open outlet, an orange glow on
|
||
transport blocks that are loaded but not moving, and the rate in the hover. `src/sim/flow.js`
|
||
counts block-edge crossings per carrier block while `flowOn` (set by the I toggle) and folds
|
||
them into a smoothed per-second rate once a second. Hooks: `carryColumn`, `machinesTick` screws,
|
||
`screenNudge`, uphill cleat runs (only runs that commit), and the uphill face drag.
|
||
- Not counted: bars. Not built: money per minute.
|
||
- Files: `src/sim/flow.js`, `src/manifest.json`, `src/sim/conveyors.js`, `src/sim/machine-tick.js`,
|
||
`src/sim/slope-conveyors.js`, `src/sim/tick.js`, `src/render/overlays.js`, `src/ui/loop.js`,
|
||
`src/ui/input.js`, `src/ui/info.js`, `src/ui/notebook.js`, `test/t_flow.js`, `docs/PROJECT_MAP.md`.
|
||
- Tests: `test/t_flow.js` (including a check that counting changes nothing in the simulation);
|
||
`python3 test/run_all.py --browser` all passed.
|
||
|
||
## 2026-10-27: cooled magma visible and drillable (Claude Code)
|
||
|
||
- Owner report: bag magma in a brick box left invisible basalt that couldn't be picked up,
|
||
drilled or built on; melted screws left similar bits. Cause: melts froze into non-natural
|
||
rock unless the liquid was natural, the drill only breaks natural rock, and the smooth look's
|
||
terrain field skipped non-natural solids.
|
||
- `src/sim/heat.js`: non-metal, non-glass melts freeze into natural rock; bag water into natural
|
||
ice. `src/sim/drill.js`: `crumbly` no longer needs F_NAT; `builtCell` protects slope corner cells.
|
||
`src/render/world.js`: the terrain field covers any terrain-class solid without a block or
|
||
machine. Note: the bot still passes through old stuck basalt until it is drilled (it only
|
||
collides with natural solids).
|
||
- Tests: 7 checks in `test/t_drill.js`; `python3 test/run_all.py --browser` all passed.
|
||
|
||
## 2026-10-27: t67 phase 4, "doesn't connect" marks (Claude Code)
|
||
|
||
- `nearMiss(bx, by, end, planned)` in `src/sim/joins.js`: an end with no join that would join
|
||
seamlessly, with a diagonal piece involved, one block up or down in empty space. Drop joins
|
||
count as connected; straight beside straight is never marked. It checks the moved block
|
||
through a small override view of `planned`, so ghost maps are never copied.
|
||
- Marks: a red ring with a slash at the end, on built pieces and ghosts (`drawJoinMarks` in
|
||
`src/render/overlays.js`, called every frame from `src/ui/loop.js`) and at the ends of a drag
|
||
preview (`src/ui/previews.js`). Hover says "doesn't connect: place it one block higher/lower"
|
||
(`src/ui/info.js`). This covers t58's mark for belts and the same cases for screws.
|
||
- Tests: 7 nearMiss checks in `test/t_joins.js`; `python3 test/run_all.py --browser` all passed.
|
||
`docs/PROJECT_MAP.md` regenerated.
|
||
|
||
## 2026-10-27: t67 phase 3 shared drag placement, and snapping (Claude Code)
|
||
|
||
- `dragPlan(P, a, b, shift)` in `src/ui/selection.js` gives code, direction, variant, blocks and
|
||
a planned map for every block piece. `primaryRelease` and the drag preview both use it (the
|
||
duplicated `blockLine(...)` expression and the screw/slope-only planned map are gone).
|
||
- Snapping: `joinSnap` in `src/sim/joins.js` moves a line one block up or down when its first
|
||
block would not join but would join seamlessly there with a diagonal piece involved, and
|
||
every block there is free. Fixes the owner's report that an uphill belt started beside a flat
|
||
belt on the same level did not join like screws do: it now snaps up to the seamless join. Also
|
||
covers t58's flat belt level with a downhill low end (snaps down) and a diagonal screw started
|
||
one block off. Straight beside straight never snaps. `joinIsDrop` (same-row feed into a slope's
|
||
low end) is now the one source for the drop, used by snapping and belt drawing.
|
||
- Files: `src/sim/joins.js`, `src/ui/selection.js`, `src/ui/actions.js`, `src/ui/previews.js`,
|
||
`src/render/slope-belts.js`, `test/t_joins.js`, `test/t_slope_belts.js`.
|
||
- Tests: `python3 test/run_all.py --browser` all passed; 9 snap checks plus a real
|
||
`primaryRelease` check. Open: phase 4's "doesn't connect" mark is not built yet.
|
||
|
||
## 2026-10-27: t66 dev console (Claude Code + Codex)
|
||
|
||
- Backtick opens a dev console in any build. The first time in a world it warns that the save
|
||
will be marked; accepting saves `dev: true` and a `devLog` (last 200 commands) in the world,
|
||
and the worlds list shows a "Dev console" badge. Commands: help, money, debt, give (display or
|
||
internal name, past the bag limit), bit, bag, unlock, tp (cursor or x y), free on|off, speed,
|
||
pause, step, weather clear|rain. Command logic is sim-side (`devCommand` in
|
||
`src/sim/devconsole.js`); the panel is `src/ui/devconsole.js`.
|
||
- Not built: `time` (no clean way to set the time of day) and undo (commands are logged instead).
|
||
Free building also skips `payCost`, so ghosts built later are free too; removing a piece built
|
||
for free still refunds its normal cost.
|
||
- Also fixed a flaky build-card check: the wall-material chip markup (`data-wall-picker=""`)
|
||
made the selection card rebuild its button every refresh.
|
||
- Files: `src/sim/devconsole.js`, `src/ui/devconsole.js`, `src/styles/devconsole.css`,
|
||
`src/shell.html`, `src/manifest.json`, `src/sim/items.js`, `src/sim/save.js`, `src/sim/world.js`,
|
||
`src/ui/input.js`, `src/ui/loop.js`, `src/ui/saves.js`, `src/ui/session.js`,
|
||
`src/ui/selection.js`, `src/ui/notebook.js`, `src/ui/build-menu.js`, tests
|
||
`t_dev_console.js`, `t_dev_console.py`, `test/run_all.py`, `docs/PROJECT_MAP.md`.
|
||
- Tests: `python3 test/run_all.py --browser` all passed. Built in a parallel worktree; Codex
|
||
hit its usage limit near the end and Claude finished it (the payCost fix).
|
||
|
||
## 2026-10-27: t67 belt drop drawn as two capped ends (Claude Code)
|
||
|
||
- A flat belt feeding an uphill belt's low end on the same row (and two uphill slopes side by
|
||
side) stays joined, but grains really drop about 3 grains onto the face there, so it now
|
||
draws as two capped ends instead of the bridge phase 2b drew (it overlapped and stepped
|
||
badly). `beltDrop` in `src/render/slope-belts.js` decides it for `slopeBeltConnections` and
|
||
`flatBeltCaps`; the shader and `world.js` packing went back to their pre-2b versions.
|
||
- Tests: `t_joins.js` and `t_smooth.py` drop checks updated; the existing sand test confirms
|
||
the same-row feed carries sand up. `python3 test/run_all.py --browser` all passed.
|
||
|
||
## 2026-10-27: t67 phase 2b, joins follow the travel direction (Claude Code + Codex)
|
||
|
||
- The owner tested phase 2: horizontal → uphill and vertical-up → diagonal didn't join like the
|
||
rest. New rule (DESIGN.md): a piece's outlet points at its block plus its travel vector, and
|
||
the next piece joins when it sits there travelling the same general way. Belts bend it where
|
||
grains can't follow: an uphill belt's top feeds a flat belt on the same row, and a flat belt
|
||
one block below an uphill low end still joins.
|
||
- `src/sim/joins.js` reworked around inlets and outlets; screw drawing, belt drawing, screw
|
||
grain flow and bars follow it. Straight-screw mouths no longer reach a diagonal across a
|
||
corner; straight-to-straight transfers (lift side-feeding) unchanged.
|
||
- Files: `src/sim/joins.js`, `src/sim/conveyors.js`, `src/sim/items.js`,
|
||
`src/render/diagonal-screws.js`, `src/render/slope-belts.js`, `src/render/slope-belt-shader.js`,
|
||
`src/render/world.js`, `src/ui/previews.js`, `tools/scenes/joins.js`, tests `t_joins.js`,
|
||
`t_diag_screws.js`, `t_slope_belt_art.js`, `t_smooth.py`, `docs/PROJECT_MAP.md`.
|
||
- Tests: `python3 test/run_all.py --browser` all passed.
|
||
- Open: Codex hit its usage limit before finishing; the belt drop drawing was finished in the
|
||
next entry. Not playtested.
|
||
|
||
## 2026-10-27: t67 phase 2, screw conveyors follow the join rule (Claude Code + Codex)
|
||
|
||
- Screw drawing, grain flow and bars now all go through `endJoin`. New joins: a horizontal in
|
||
E at a diagonal's low end, and a vertical in E at either end; both draw an elbow that bends
|
||
inside the straight piece's end block. Same-row high-end elbows are unchanged.
|
||
- No longer joined (moves nothing): a same-row horizontal at a low end, a vertical directly
|
||
above or below a diagonal's last block, and any other side contact. Joined belts and filters
|
||
take discharge on their carrying face at either end. A horizontal screw feeding the side of a
|
||
vertical lift still works (`straightScrewNext` keeps straight-to-straight transfers).
|
||
- The opposite-direction check is one helper, `screwAccepts`.
|
||
- Files: `src/render/diagonal-screws.js`, `src/render/smooth-shaders.js`, `src/render/world.js`,
|
||
`src/ui/previews.js`, `src/sim/conveyors.js`, `src/sim/machine-tick.js`, `src/sim/items.js`,
|
||
`src/sim/cells.js`, `tools/scenes/joins.js` (replaces `t62-joins.js`), `test/t_diag_screws.js`,
|
||
`test/t_smooth.py`, `docs/PROJECT_MAP.md`.
|
||
- Tests: `python3 test/run_all.py --browser` all passed; determinism diff changed only screw
|
||
checks. Screenshots: `shots/joins-*.png`.
|
||
- Open: inside the new elbows the flight stripes of both pieces overlap, which looks a little
|
||
busy in the smooth look. Layouts built on the removed joins need moving.
|
||
|
||
## 2026-10-27: t63 vacuum picks up grains in mid-air (Claude Code + Codex)
|
||
|
||
- `toolVacuum` now takes flyers (grains a screw launches or that were blown out) inside its
|
||
brush and reach before loose cells, with the same pick filter, flask rule, bag-full stop and
|
||
48-grain budget. `vacuumTargetAt` also considers them. Rain is skipped: it belongs to the
|
||
clouds, and taking it would make water.
|
||
- Files: `src/sim/vacuum.js`, `test/t_basics.js`. Built on a separate worktree branch in
|
||
parallel with t67 and fast-forwarded.
|
||
- Tests: `python3 test/run_all.py --browser` all passed; new checks only in the determinism diff.
|
||
|
||
## 2026-10-27: t67 phase 1, shared join module (Claude Code + Codex)
|
||
|
||
- Owner decision (DESIGN.md, replaces the t62 same-row rule): one join rule for every transport
|
||
piece. New DOM-free `src/sim/joins.js`: `pieceEnds`, `endJoin`, `pieceJoins`; rule tables are
|
||
compiled once and joins must be mutual. Belt drawing now uses it: `slopeBeltConnections`,
|
||
`flatBeltCaps` (now also for ghosts), and the pixel filter frame (`beltLike(b, end)`). Sloped
|
||
belt drag previews now pass a planned map.
|
||
- Deliberate drawing changes where the old belt rules disagreed: flat belt caps no longer
|
||
treat any same-row diagonal screw as joined, and filter frames open only at a real join.
|
||
- Files: `src/sim/joins.js`, `src/manifest.json`, `src/render/slope-belts.js`,
|
||
`src/render/setup.js`, `src/render/world.js`, `src/ui/previews.js`, `test/t_joins.js`,
|
||
`test/t_slope_belt_art.js`, `docs/PROJECT_MAP.md`, `docs/DESIGN.md`.
|
||
- Tests: `python3 test/run_all.py --browser` all passed; t_joins checks 4,232 placements,
|
||
symmetry, planned maps, and that belt sand flow agrees with the rule. Sim output unchanged.
|
||
- Open: screws still use their old rules until phase 2.
|
||
|
||
## 2026-10-27: t62 corner placements stay separate (Claude Code + Codex)
|
||
|
||
- Owner tried the corner join and found the jog odd; decision (recorded in DESIGN.md): only
|
||
same-row joins connect. Removed join kinds 5/6 and `straightScrewNext`; `diagonalScrewNext`
|
||
and `entDiagonalScrewTick` now continue across a corner only into a diagonal running the same
|
||
way, so a corner straight piece no longer receives grains or bars. Kept the shared geometry
|
||
file and the smooth-look elbow seam fix.
|
||
- Files: `src/render/diagonal-screws.js`, `src/render/smooth-shaders.js`, `src/sim/conveyors.js`,
|
||
`src/sim/items.js`, `src/sim/machine-tick.js`, `test/t_diag_screws.js`, `test/t_smooth.py`,
|
||
`tools/scenes/t62-joins.js`, `docs/PROJECT_MAP.md`, `docs/DESIGN.md`.
|
||
- Tests: `python3 test/run_all.py --browser` all passed; determinism diff changed only the
|
||
replaced corner checks.
|
||
|
||
## 2026-10-27: t62 screw conveyor joins (Claude Code + Codex)
|
||
|
||
- Straight screws touching a diagonal's end only at the corner now join, in both looks and in
|
||
ghosts/previews, and grains and bars cross the joint. Same-row joins were already elbows.
|
||
Also removed the seam across the top of connected elbows in the smooth look.
|
||
- Files: new `src/render/diagonal-screws.js` (join and casing geometry moved out of
|
||
`src/render/world.js`), `src/render/smooth-shaders.js`, `src/ui/previews.js`,
|
||
`src/sim/conveyors.js` (`straightScrewNext`), `src/sim/machine-tick.js`, `src/sim/items.js`,
|
||
`src/manifest.json`, `docs/PROJECT_MAP.md`, `tools/scenes/t62-joins.js`, tests
|
||
`test/t_diag_screws.js`, `test/t_smooth.py`.
|
||
- Tests: `python3 test/run_all.py --browser` all passed; determinism diff showed only timing
|
||
lines and the new checks.
|
||
- Open: the corner join doesn't check that the straight piece ends there, so a straight run
|
||
passing over the diagonal's corner also draws a jog. Not playtested by hand.
|
||
|
||
## 2026-10-27: t66 dev console in every build (Claude Code)
|
||
|
||
- Owner decision: the console ships in production too, with a warning when it's turned on that
|
||
the save gets marked (and future achievements won't unlock). Recorded in `docs/DESIGN.md`;
|
||
t66 gained subtasks for the warning, the save mark, and game speed and fun controls. No code
|
||
changed.
|
||
|
||
## 2026-10-27: t66 dev console added to Todo (Claude Code)
|
||
|
||
- Owner request: a dev console to add money, items and upgrades quickly for testing. Subtasks
|
||
cover how it opens (and hiding it from real players), money and debt, bag items, bit and bag
|
||
tiers, other shortcuts, and marking changed worlds. No code changed.
|
||
|
||
## 2026-10-27: Save compatibility rule dropped from AGENTS.md (Claude Code)
|
||
|
||
- Owner: old saves don't matter; only they play, for testing, and can start a new game.
|
||
`AGENTS.md` still said to keep saves loading (never renumber ids, add migrations), which
|
||
contradicted the DESIGN decision "No backward compatibility for now". It now matches. The t65
|
||
subtask no longer asks for saves to load. `AGENTS.md` is local (gitignored). No code changed.
|
||
|
||
## 2026-10-27: t65 Rust conversion added to Todo (Claude Code)
|
||
|
||
- Owner request: a task to convert the game to Rust for performance, after the base game is more
|
||
playable (sooner if performance becomes a problem). Subtasks: settle the scope (whole game or the
|
||
hot sim code; WebAssembly or native), profile for a baseline, keep tests and saves working. No
|
||
code changed.
|
||
|
||
## 2026-10-27: Shift-drag draws a line for transport pieces (Claude Code)
|
||
|
||
- Owner request: Shift shouldn't fill a rectangle for belts, conveyors and similar. New
|
||
`lineOnly(code)` in `src/sim/pieces.js` (belts, filters, screw conveyors, shafts, pipe pieces);
|
||
`primaryRelease` (`src/ui/actions.js`) and the drag preview (`src/ui/previews.js`) ignore Shift
|
||
for them, and `pieceCode` (`src/ui/selection.js`) no longer flattens a diagonal belt drag on
|
||
Shift. Build-menu "How to place" and the notebook say so; the notebook's stale "cleats up or
|
||
down" line now says uphill only.
|
||
- Tests: play.py checks a Shift-drag with a belt lays a 3-piece sloped line. Full
|
||
`run_all --browser` passes.
|
||
|
||
## 2026-10-27: t61 sticky materials slide freely on built surfaces (Claude Code)
|
||
|
||
- The owner's video showed clay lumps creeping down a sloped wall; removing a piece was
|
||
unrelated. Cause: STICK gates every diagonal step, so cohesive materials crawl on 45°. In
|
||
`powder()` (`src/sim/cells.js`) a grain resting on a built solid (not F_NAT) now always tries
|
||
the diagonal; the random draw is unchanged, so generated worlds are identical.
|
||
- Measured: a clay grain down a 20-piece built slope took 77 ticks, same as sand (before: never
|
||
arrived in 2000 ticks). Heaps on a built floor and on natural ground are unchanged.
|
||
- Test: `t_slopes.js` clay vs sand transit. `python3 test/run_all.py --browser`: ALL PASSED.
|
||
|
||
## 2026-10-27: Unremovable one-cell belt left after cutting slopes (Claude Code)
|
||
|
||
- The owner's exported save had a lone belt cell no block owned, so nothing could build there or
|
||
remove it. It was a sloped belt's seal: a seal may sit in a cell its neighbour leaves open (a
|
||
slope's open corner, or under a grate, screw or screen), but removal only let it go when the
|
||
neighbour was a sloped wall. Now `blockOwnsCell` decides (`src/sim/blocks.js`), and loading a
|
||
world clears belt and screen cells no block or seal owns (`clearStraySeals`, `save.js`).
|
||
- Found with a fuzz of random slope/belt/filter/wall/grate builds removed in random orders and by
|
||
cut (0 strays in 4000 runs after the fix). The owner's save loads clean and the spot is
|
||
buildable. Tests in `t_slope_belts.js` fail on the old code. `run_all --browser`: ALL PASSED.
|
||
|
||
## 2026-10-27: t42 feedback: downhill belts drop the sidewall too; new tasks (Claude Code)
|
||
|
||
- Downhill sloped belts now draw as plain belts: no far wall and no ribs, placed or as ghosts or
|
||
drag previews (`slopeWallAt`, ghost drawing in `render/world.js`, `previewBlock` now gets the
|
||
belt direction from `previews.js` and `preview-selection.js`). `slopeUphill` takes the
|
||
direction bits instead of a block.
|
||
- Test: `t_slope_belt_art.js` checks a downhill run draws no wall or ribs. Drag preview checked
|
||
headlessly with a counting canvas (downhill draws the band only); placed belts and ghosts
|
||
checked in screenshots. `python3 test/run_all.py --browser`: ALL PASSED. Rebuilt dist.
|
||
- Filed t59 (world longevity audit), t60 (renewable resources, discussion), t61 (sloped walls
|
||
sticky after removing a piece mid-flow: not reproduced headless, see its report), t62 (screw
|
||
conveyor horizontal/diagonal joins), t63 (vacuum falling grains).
|
||
|
||
## 2026-10-26: t42 feedback: uphill stall at a seam, no cleats downhill, slope pick (Claude Code)
|
||
|
||
- The owner's long uphill run stalled with a few grains, with a flashing seam between pieces 9
|
||
and 10. Cause: each block kept its own cleat phase, so a piece added to a running belt was out
|
||
of step; at the seam a rib started one or two cells behind another and pushed its pocket into
|
||
that rib's back, stalling the run for good. Every block of a run now takes the phase of the
|
||
run's lower-left block (`slopeCleatsTick`, memoised `slopeRunKey`).
|
||
- Owner decision: downhill belts have no cleats. They carry like a flat belt (`carryColumn`) and
|
||
draw only the sidewall; ribs, friction drag and pushes are uphill only (`slopeUphill`).
|
||
`pushRow`'s downhill levelling is gone. Downhill now 90 grains/s (was 38).
|
||
- Middle click on a sloped belt's band, where it is drawn into the empty block below, now picks
|
||
the belt (`pickUnderCursor` falls back to `slopeBeltAt`).
|
||
- Tests: `t_slope_belts.js` adds pieces added 3/7/11/13 ticks later (13 stalled before) and the
|
||
band pick (fails without the fix); downhill floor back to 60; cleat checks uphill only.
|
||
`t_slope_belt_art.js` draws ribs on uphill runs. `python3 test/run_all.py --browser`: ALL
|
||
PASSED. Screenshots of an up-and-down run in both looks opened. Rebuilt dist.
|
||
|
||
## 2026-10-26: t42 feedback: cleats that really hold and push their load (Claude Code)
|
||
|
||
- The owner saw ribs pass through heavy loads and the load climb as before. Rewrote the slope
|
||
physics in `src/sim/slope-conveyors.js`: no column carry on slopes; each step every rib moves
|
||
one face cell and pushes the rows of grains in its way (`pushRow`, front first, never between
|
||
two rib cells or into a machine); a contact-layer friction drag that gravity beats on 45°;
|
||
a boot-section scoop at the foot of uphill runs; a run stalls (all its moves undone) if a rib
|
||
can't push; grains under a newly placed rib are lifted out. Ribs stop short of built cells.
|
||
Slopes now step before flat belts (`machine-tick.js`), so a feeding belt finds the room.
|
||
- Drawing: ribs drawn by the smooth wall shader at the simulation phase (rubber bars with a
|
||
light cap); pixel ribs lighter with a cap; far wall a little lighter (`render/world.js`,
|
||
`slope-belt-shader.js`, `slope-belts.js`).
|
||
- Rates: uphill 78.7 (suite) to 86 grains/s (heavy probe), downhill 38, flat 90. Perf: 576
|
||
loaded slopes 1.03 ms/tick; 50 slopes in a generated world 2.19 vs 1.83 ms/tick without.
|
||
- Tests: `t_slope_belts.js` adds "no grain ever inside a rib under heavy feed", "a grain on the
|
||
bare face slides back to a rib, then rides up with it"; downhill rate floor lowered to 30.
|
||
`python3 test/run_all.py --browser`: ALL PASSED. Open: downhill rate; t58 (low-end joins).
|
||
|
||
## 2026-10-26: t42/t43 drawing and performance follow-up (Codex)
|
||
|
||
- Filled the far sidewall down to the belt, behind grains, with two-tone pixel corrugation
|
||
and a continuous smooth 45-degree band. Rubber ribs use the physics mask in all eight
|
||
phases. Fixed smooth wall seams; matched ghosts, cursor previews and menu icons.
|
||
Smooth screens retain tinted mesh, now between solid rails without the pixel overlay;
|
||
loaded-only shake and transport are unchanged.
|
||
- Empty cleats only wake nearby load; per-chunk flags bypass mask reads for distant powder.
|
||
Empty eight-block slope: 3 -> 0 moving areas after 120 ticks. Uphill sand/gravel/copper ore
|
||
71.3 grains/s, coal 71.2; exact sorting retained. Isolated 1f8a89d -> current, median five
|
||
generated falling-world samples: no slopes 2.006 -> 2.063 ms/tick; 50 slopes 1.952 -> 2.076.
|
||
Powder probe with cached zero flag: 45.02 -> 50.56 ms (2.14M calls); residual 12 percent
|
||
microbenchmark overhead, about 3 percent in the generated no-slope world. Existing loaded
|
||
576-block stress: 0.515 -> 1.064 ms/tick. slope-cost.js records the reproducible methodology;
|
||
raw results /tmp/t42-perf-{pre,after}-isolated.txt. Pre-t42 loaded slopes use old grip physics.
|
||
- Files: sim cells/slope-conveyors/world; render slope-belts/slope-belt-shader/world;
|
||
UI previews/build-menu; t_slope_belt_art.js, t_smooth.py, slope-cost.js; PROJECT_MAP,
|
||
DESIGN and t42/t43 reports. Reused enterFree for the ordinary entry rules.
|
||
- Final python3 test/run_all.py --browser: ALL PASSED (26 headless suites, 360 play,
|
||
16 smooth checks; art suite 57). Before/after suite outputs differ only by five new
|
||
checks (/tmp/t42-output.diff). Opened shots/t{42,43}-follow-{belt,screen}-{pixel,smooth}-z{4,8}.png;
|
||
rechecked smooth belt after fixing seams; opened t42-follow-ghosts-{pixel,smooth}-z8.png
|
||
with both updated menu icons visible. Added sleep, border/load wake, wall geometry,
|
||
grain colour and smooth grain-class checks. Rebuilt dist, regenerated map; both tasks Review.
|
||
Owner playtest and hardware GPU appearance remain unverified. No commit.
|
||
|
||
## 2026-10-26: t42: sidewall belts with moving cleats (Codex)
|
||
|
||
- Continued the uncommitted prototype. `sim/slope-conveyors.js` supplies moving barrier
|
||
lines (8 face cells apart, 6 grains tall), finite periodic pocket capacity and saved phase.
|
||
Removed slope grip; natural powder movement runs between steps and surplus rolls back.
|
||
Fixed the shared slope-to-screw mouth boundary so grains cannot fall under its casing.
|
||
Pixel/smooth drawing uses the actual mask, continuous dark far walls, matching previews/icons.
|
||
- Files: manifest; sim cells/conveyors/slope-conveyors/machine-tick/items/pieces/blocks/world/save;
|
||
render slope-belts/slope-belt-shader/world; UI actions/selection/selection-pickers/previews/
|
||
build-menu/notebook; slope test suites and play.py; slope-flow/slope-cost experiments,
|
||
slope-conveyors scene, PROJECT_MAP, DESIGN and t42/t43. Removed both requested scratch files.
|
||
- Reproducible `node tools/experiments/slope-flow.js`: sand, gravel, copper ore 71.3 and
|
||
coal 71.2 grains/s uphill, each 90.0 flat; 20-second window after 220 warm-up ticks.
|
||
Measured settled pile angles 33.7 degrees for all four. Final HEAD -> current stress measurement: 0.486 -> 1.038 ms/tick for 576 loaded
|
||
slope blocks (1024x512); no-slope powder microbenchmark, 2.14M calls: 45.07 -> 65.55 ms.
|
||
Median of five samples after warm-up; scripts use DG_HARNESS for the HEAD snapshot in
|
||
/tmp/t42-baseline. Barriers skip mask reads when none exist, but still have a CPU cost.
|
||
- Opened shots/t42-belt-{pixel,smooth}-z{4,8}.png. Headless checks pass, including pocket
|
||
overflow, collision masks, unstamped bare-face sliding, joins, screw mouths, water, bars,
|
||
save phase, all eight drawing phases and unchanged seed hashes 777/77/4242.
|
||
Full python3 test/run_all.py --browser: ALL PASSED (26 headless suites, 360 play
|
||
checks, 15 smooth checks). Rebuilt dist and regenerated PROJECT_MAP. Both tasks moved to
|
||
Review. The owner still decides whether the lower rate feels
|
||
right. Pocket movement is a discrete envelope, not a calculation of individual forces.
|
||
No commit.
|
||
|
||
## 2026-10-26: t43: inclined vibrating screens (Codex)
|
||
|
||
- Diagonal filters always run downhill through all placement/settings/undo/paste paths;
|
||
R leaves a picked slope unchanged. The picker shows fixed downhill travel rather than two
|
||
misleading direction buttons. Flat filters retain their original carry. Slope grains get
|
||
gravity plus one mesh-contact nudge every 4 ticks (15 steps/s); unselected bars use the
|
||
same cadence. No whole-column carry or between-step grip on slopes.
|
||
- Pixel and smooth mesh frames shake a little while loaded, with stable idle drawing.
|
||
Updated ghosts, icons, build text, notebook and the relevant sim/UI/render files listed
|
||
in t42 above. Added real browser drag/R/picker checks and art shake checks.
|
||
- Both shapes and both sorting modes deliver the complete 64 sand + 64 gravel mix exactly.
|
||
Generous-feed discharge is 60.0 grains/s down-left and 59.3 down-right; rest passes through.
|
||
Opened shots/t43-screen-{pixel,smooth}-z{4,8}.png. Headless and smooth suites pass; the
|
||
first browser run found a hover fixture above the mesh and a later timeout. Corrected the
|
||
coordinate and reset picked slope state between fixtures. All new browser checks pass.
|
||
A subsequent run caught an existing 200 ms silo HUD timing race; wait for the expected
|
||
HUD text before the same assertion. Final full browser run: ALL PASSED, 360 play
|
||
checks plus 15 smooth checks; all 26 headless suites pass. t43 moved to Review.
|
||
No uphill-save migration. No commit; owner playtest remains.
|
||
|
||
## 2026-10-26: t44: rename screw tubes to screw conveyors (Codex)
|
||
|
||
- Player text uses Screw conveyor; code uses screw/SCREW, including the piece id, part
|
||
constants, direction tables, transport helpers, shader variable, build icons and settings
|
||
access. Numeric ids and save/blueprint versions unchanged; no aliases or migrations added.
|
||
- Updated affected files across src/sim, src/render, src/ui, test and tools, plus README,
|
||
AGENTS and current DESIGN/TESTING/system docs. Regenerated PROJECT_MAP and built dist.
|
||
Renamed test/t_diag_tubes.js to test/t_diag_screws.js with git mv; no scene filename needed
|
||
renaming. Past log entries, docs/history and task title/Context preserved.
|
||
- All 26 suite outputs saved in /tmp/t44-before and /tmp/t44-after. Raw diff:
|
||
/tmp/t44-output.diff; name/timing-normalized diff: /tmp/t44-output-normalized.diff (empty).
|
||
No numeric output differences except ms timings; numeric literals unchanged in all 39
|
||
edited JavaScript/Python files. Only tube matches in scoped source/docs are the physical
|
||
casing description in pieces.js and casing/flight rendering comment in world.js.
|
||
- python3 test/run_all.py --browser: ALL PASSED (26 headless suites, 352 play checks,
|
||
15 smooth checks). Focused menu, tooltip, hover, notebook, screw blueprint-code round-trip
|
||
and both starter blueprint-code checks pass in pixel and smooth; no page errors.
|
||
Opened shots/t44-{menu,hover,notebook}-{pixel,smooth}.png and the play diagonal screenshot.
|
||
Temporary UI fixture initially lacked SX/SY; corrected to match shot.py before these checks.
|
||
- Task t44 moved to Review. Owner playtest remains. No commit.
|
||
|
||
## 2026-10-26: t38: show silo contents at the cursor and in a wide window (Codex)
|
||
|
||
- `src/ui/info.js`: contents were confined to the bottom info plate (hidden below 720 px),
|
||
omitted from cursor labels, and cone gaps lacked mach ownership. Hover now resolves the
|
||
full 12x24 outline and shows contents beside the cursor; physical footprints stay unchanged.
|
||
- `src/render/machines.js`: shared framed three-grain window in both looks, fixed pixel grain
|
||
noise, visible near-empty row, lifted dark fuel colours, and stencils fitted beside the glass.
|
||
`tools/scenes/silo.js` adds snow and one grain of coal. All 31 accepted powders checked.
|
||
- `test/t_silo_contents.js`: 5 checks confirm snow intake, identity, water accounting, warm
|
||
storage and release. `test/play.py` checks hover/label at five outline points empty/filled,
|
||
narrow HUD, fill pixels at zooms 2-12 and stable drawing for all powders in both looks.
|
||
- Opened and inspected `shots/t38-silos-{pixel,smooth}-z{2,4,8}.png`. Updated t38 and DESIGN;
|
||
regenerated PROJECT_MAP. Final `python3 test/run_all.py --browser`: ALL PASSED (26 headless
|
||
suites, 352 play checks, 15 smooth checks). Task moved to Review; owner playtest remains.
|
||
|
||
## 2026-10-26: t41: distant resonance-drill aim is normal (Codex)
|
||
|
||
- `src/ui/preview-drill.js`, `previews.js`: keep tuned fan and light crosshair, add a solid far
|
||
edge in pulse colour, remove the drill-only red reach circle. `info.js` omits drill distance
|
||
warnings; other tools retain their warning, including beside silo contents.
|
||
- `src/sim/drill.js`: removed only the distance status; too-hard/cannot-be-drilled wording and
|
||
`toolDig` are unchanged. Notebook drill text remains accurate; gamepad code untouched.
|
||
- `test/t_drill.js`: new distance regression failed before fix, now passes; two former warning
|
||
expectations updated intentionally. `test/play.py`: held mouse aim makes no toast/warning,
|
||
hard granite still toasts, no red reach circle, gamepad aim stays within reach.
|
||
- Opened and inspected `shots/t41-drill-beyond-reach.png`. Focused browser checks pass.
|
||
Initial full browser run exposed test timing/paused-input fixture mistakes, corrected without
|
||
changing gameplay. Final `python3 test/run_all.py --browser`: ALL PASSED (26 headless
|
||
suites, 352 play checks, 15 smooth checks). Updated t41 and DESIGN; moved to Review. No commit.
|
||
|
||
## 2026-10-26: t39: seal touching square-block corners (Codex)
|
||
|
||
- `cells.js`: six inline checks reject diagonal moves between two PH_SOLID neighbours, with
|
||
no new random calls. `worldgen.js` saves/disables/restores CORNER_SEAL alongside LSTEP/LFILM
|
||
while settling. No material ids, save fields or piece geometry changed.
|
||
- Reproduced two further leaks: free flyers (`vacuum.js`) and diagonal tube mouths pulling sand
|
||
between external walls (`conveyors.js`); blocked those steps only. Horizontal liquid scans
|
||
already check each intervening cell; vacuum collection removes grains into the bag. Bars
|
||
resolve ordinary movement by axis. Screw lane transport remains explicit and unchanged.
|
||
- `test/t_corner_seal.js`: 23 checks, including both staircase orientations for sand/water/steam,
|
||
all four diagonal intakes, a flyer, world hashes/flag restoration, and an open pile that remains
|
||
57x17 with identical material layout and random state when sealing is disabled.
|
||
- All 25 headless suites pass. Before/after outputs: `t_slopes` square-funnel outflow 379 -> 339
|
||
(more sand retained on steps); `t_drill` ore outside the wall 56 -> 55 (solid corners hold ore);
|
||
`t_world` awake chunks after 900 gameplay ticks following reload 148 -> 149 (different settling
|
||
after corner blocking). Disabling the checks restores drill/world baseline output exactly.
|
||
Other existing output is identical except timings. Evidence: /tmp/t39-before, /tmp/t39-after.
|
||
- Full mat/temp/flags hashes match original code for seeds 777/77/4242 at 512x384 and 2048x1024,
|
||
plus 424242 at 1024x640 (/tmp/t39-world-hashes-{before,after}.txt).
|
||
- Factory benchmark at 1024x640: idle 0.69 -> 0.68 ms/tick; 1/4/8 modules
|
||
2.05/2.36/2.65 -> 2.13/2.44/2.70 ms/tick. Timing is indicative: settling changes, and this
|
||
fixture reported zero machine-moved grains, so it does not prove loaded transport performance.
|
||
- Rebuilt dist and regenerated PROJECT_MAP. Inspected `shots/t39-corners.png` in pixel look:
|
||
sand rests along the upper steps and pours through the central outlet.
|
||
- Final `python3 test/run_all.py --browser`: ALL PASSED (25 headless suites, 323 play checks,
|
||
15 smooth checks). Updated t39 subtasks/report for Review and recorded the design decision.
|
||
No commit made; owner playtest remains.
|
||
|
||
## 2026-10-25: t40 art pass: sloped belts and filters drawn as one belt (Claude Code, Codex)
|
||
|
||
- Sloped runs draw as the flat belt (rollers, studs) or filter (tinted mesh) band turned to 45°,
|
||
continuous, with elbows into flat belts, filters and tubes and rounded free ends, in both looks
|
||
(`src/render/slope-belts.js`, new `slope-belt-shader.js`, `world.js`, `smooth-shaders.js`);
|
||
ghosts and icons match. Drawing only.
|
||
- `test/t_slope_belt_art.js`; `run_all.py --browser` ALL PASSED.
|
||
|
||
## 2026-10-25: t40: sloped belts and filters (Claude Code, Codex)
|
||
|
||
- Block codes 19-22 (`BP_BELT_UP/DOWN`, `BP_FILTER_UP/DOWN`, `src/sim/pieces.js`): slope-shaped
|
||
belts and filters whose 45° face runs, laid by a diagonal Belt or Filter drag. `conveyorStep`,
|
||
`holdSlopeColumn` and a shared front-first order for belts and filters in `src/sim/conveyors.js`;
|
||
bars in `items.js`; saves, blueprints, undo, hover, previews; `src/render/slope-belts.js`.
|
||
- 90 grains/s flat, up and down for belts and filters; a sloped filter sorted 64/64 with no loss.
|
||
Seeds 777, 77 and 4242 generate identically.
|
||
- `test/t_slope_belts.js` (32 checks); `run_all.py --browser` ALL PASSED. Art pass follows.
|
||
|
||
## 2026-10-25: t35 follow-up: diagonal tube elbows (Claude Code, Codex)
|
||
|
||
- Diagonal tubes are as thick as straight ones, and an end block joining a straight tube, belt or
|
||
filter draws an elbow at its centre, so the pipes meet centred and edge to edge; free ends get a
|
||
collar; ghosts match (`src/render/world.js`, `smooth-shaders.js`, `src/ui/previews.js`,
|
||
`preview-selection.js`). Scene `tools/scenes/t35-joins.js`.
|
||
- `test/t_diag_tubes.js`; `run_all.py --browser` ALL PASSED.
|
||
|
||
## 2026-10-25: t35/t36 follow-ups: wall drags make slopes, diagonal tube art (Claude Code, Codex)
|
||
|
||
- Wall piece: a diagonal drag lays slopes (`pieceCode`/`turnSlope` in `src/ui/selection.js`), T flips
|
||
a drag or cycles square and four corners for one block; the Sloped wall piece is gone from the
|
||
menu, slope codes map to the wall piece (`src/sim/pieces.js`); old slope saves load
|
||
(`test/fixtures/slopes-v2.json.gz`).
|
||
- Diagonal tubes draw as one continuous 45° pipe in both looks, overhanging empty cells at joints
|
||
(`src/render/world.js`, `smooth-shaders.js`). Drag ghosts lost the long side arrow; each ghost
|
||
block keeps its own chevron (`src/ui/previews.js`). The owner found the diagonal still meets
|
||
straight tubes off-centre; an elbow fix follows.
|
||
- `test/t_slopes.js`, `t_diag_tubes.js`, `play.py`; `run_all.py --browser` ALL PASSED.
|
||
|
||
## 2026-10-25: t37 follow-up: grains on built things cast no shadows (Claude Code, Codex)
|
||
|
||
- The owner saw a flickering shadow under a grinder fed by a stream: grains resting on its
|
||
housing for a moment shaded. `looseForLight` (`src/render/light.js`) now treats a loose stack
|
||
that rests on any `builtForLight` cell, or ends in air or gas, as see-through. Stacks on natural
|
||
ground or deeper than 12 cells still shade.
|
||
- `test/t_light.js` (a grinder stream checked for 180 ticks in both lighting modes);
|
||
`run_all.py --browser` ALL PASSED.
|
||
|
||
## 2026-10-25: t37 follow-up: falling grains cast no shadows (Claude Code, Codex)
|
||
|
||
- The owner saw a single falling grain darken a column of sky. `onBeltForLight` became
|
||
`looseForLight` (`src/render/light.js`): a loose stack that ends in air or gas within 12 cells
|
||
(falling) is see-through, as are belt and filter loads; stacks resting on any solid or a tube
|
||
part still shade.
|
||
- `test/t_light.js`; `run_all.py --browser` ALL PASSED.
|
||
|
||
## 2026-10-25: t37 follow-up: belt loads cast no shadows (Claude Code, Codex)
|
||
|
||
- The owner saw grains riding a belt overhead still shading the ground. `onBeltForLight(i)` in
|
||
`src/render/light.js` walks down through up to 12 loose cells to a belt or filter; such cells
|
||
are skipped by the light roof and pass light like air. `builtForLight` is unchanged, so the t33
|
||
loose-grain shading still applies to belt loads. Heaps on rock, silos and wall floors still shade.
|
||
- `test/t_light.js` (56 checks); `run_all.py --browser` ALL PASSED.
|
||
|
||
## 2026-10-25: t35: diagonal screw tubes (Claude Code, Codex)
|
||
|
||
- Parts `P_TUR`..`P_TUL` (11-14), `TUBE_PARTS`, and an `IS_TUBE` table replacing every
|
||
`P_TU..P_TR` range check (`src/sim/material-tables.js` and its users); tube variants 4-7;
|
||
diagonal drags for tubes; grains in `src/sim/conveyors.js`/`cells.js`, bars in `items.js`;
|
||
both looks, previews, hover, copy/paste, undo, blueprints, saves.
|
||
- `test/t_diag_tubes.js` (85 checks): 120 grains/s straight, up and down over ten blocks; joins,
|
||
no spills, water passes, bars both ways, controls, persistence. Existing suites unchanged and
|
||
passing; seeds unchanged. `run_all.py --browser` ALL PASSED. Screenshots
|
||
`shots/t35-diagonal-*.png`, scene `tools/scenes/t35-diagonal.js`.
|
||
|
||
## 2026-10-25: t36: sloped walls and diagonal wall lines (Claude Code, Codex)
|
||
|
||
- `BP_SLOPE_BL`..`BP_SLOPE_TR` (15-18) in `src/sim/pieces.js` (`isSlope`, `isWall`, `slopeCell`,
|
||
`slopeCode`); `src/sim/blocks.js`: half cost and refund, `slopeSeal` corner cell and
|
||
`slopeSealOwner`, `diagonalDrag` and `blockLine(.., diagonal)`. Build menu, T key
|
||
(`turnSlope`), previews, copy/paste, undo, blueprints, hover, both looks.
|
||
- `test/t_slopes.js`: funnel 400/400 sand (square steps 379/400), water 400/400, no leaks in all
|
||
four orientations from both sides, bars slide, every wall material; play.py menu and diagonal
|
||
drag. Seeds unchanged. `run_all.py --browser` ALL PASSED. Screenshots
|
||
`shots/t36-funnel-*.png`, scene `tools/scenes/t36-funnel.js`.
|
||
|
||
## 2026-10-25: t33: loose grains and solid ground look different (Claude Code, Codex)
|
||
|
||
- Smooth look (`src/render/smooth-shaders.js`): natural ground gets a soft seam where it meets
|
||
loose grains as well as air; packed grains keep the pebble shading and are slightly lighter.
|
||
Pixel look and thermal lens (`drawWorld()` in `src/render/world.js`): loose grains a touch
|
||
lighter and speckled, solid cells touching loose grains darker.
|
||
- Smooth frame 1.08 -> 1.10 ms. `t_smooth.py` checks the seam and the lighter grains;
|
||
`tools/scenes/t33-ground.js` for screenshots (`shots/t33/`).
|
||
- play.py: 'F on an automatic ingot mold stays quiet' counted toasts on screen, and failed
|
||
now and then when an older toast expired or a new one arrived in the same 150 ms; it now
|
||
records the toasts the key press makes (none). `run_all.py --browser` ALL PASSED apart from
|
||
that check, which passes in the new form.
|
||
|
||
## 2026-10-25: t37: buildings cast no shadows (Claude Code, Codex)
|
||
|
||
- `computeLight()` (`src/render/light.js`): a per-view-column light roof that skips player-built
|
||
cells (`builtForLight`: unnatural solids, block, machine or part cells), and built cells pass
|
||
light like air. `drawWorld()` stops marking built cells as shading terrain for the smooth look.
|
||
The simulation's `roofY` is untouched (built roofs still condense steam).
|
||
- `test/t_light.js`; screenshots `shots/t37-before-*.png`, `shots/t37-after-*.png`.
|
||
`run_all.py --browser` ALL PASSED.
|
||
|
||
## 2026-10-25: t34: the nozzle blows no further than it sucks (Claude Code, Codex)
|
||
|
||
- `toolBlow()` (`src/sim/vacuum.js`) aims at the capped point from `nozzle()` when the cursor is
|
||
out of reach (thrown bars already did); `drawPreviews()` draws the blow brush there, in the blow
|
||
colour rather than the out-of-reach red.
|
||
- `test/t_nozzle.js`: aiming at 3x and 5x the reach lands sand within reach + 16 grains (brush and
|
||
settling, measured 15); inside reach it still lands around the cursor. play.py: the preview
|
||
sits at the capped point. `run_all.py --browser` ALL PASSED. Screenshot
|
||
`shots/t34-blow-out-of-reach.png`.
|
||
|
||
## 2026-10-25: t21: a loaded world starts around the player first (Claude Code, Codex)
|
||
|
||
- Owner: "lets speed up the first load speed. load in areas around the player first".
|
||
- `src/sim/save.js`: `deserializeWorld` no longer wakes every area; `startLoadWake()` keeps the
|
||
load-time heat scan's result per area, wakes those within 150 grains of the bot, and queues the
|
||
rest by distance; `loadWakeTick()` (from `simTick`) wakes 12 a tick and, every 8 ticks, any still
|
||
waiting within 150 grains of the bot. The queue is not saved.
|
||
- Huge world, ms/tick before -> after: ticks 0-600 average 15.2 -> 5.5, max 79.7 -> 16.2, 95th
|
||
percentile 21.0 -> 11.0; ticks 600-1800 average 7.3 -> 10.0 (the same work, later). Every area
|
||
has started by tick 1024 (17 s).
|
||
- `test/t_load.js` (19 checks). `run_all.py --browser` ALL PASSED.
|
||
|
||
## 2026-10-24: t21 phase 4: duplicates merged, largest files split (Claude Code, Codex)
|
||
|
||
- Merged seven real duplicates: wall refunds (`removeWallCell`, walls and molds), tube edges and
|
||
lanes (`entTubeEdge`, `entTubeLane`), pipe text, piece dimensions, font lookup, reach circles,
|
||
surface averaging (`meanSurface`). Look-alikes left alone: data tables, sprite rows, GLSL, and
|
||
the hot movement and heat code.
|
||
- Split by topic: `ui/previews.js` -> `preview-drill.js`, `preview-selection.js`;
|
||
`render/smooth.js` -> `smooth-shaders.js`; `ui/notebook.js` -> `notebook-market.js`,
|
||
`notebook-blueprints.js`; `sim/materials.js` -> `material-tables.js`; `sim/worldgen.js` ->
|
||
`worldgen-helpers.js`; `ui/selection.js` -> `selection-pickers.js`, `selection-panels.js`.
|
||
`render/world.js` left whole (one large function).
|
||
- Every suite's output identical apart from timings; seeds generate the same worlds.
|
||
`run_all.py --browser` ALL PASSED.
|
||
|
||
## 2026-10-24: t21 phase 3: speed-ups that change nothing (Claude Code, Codex)
|
||
|
||
- Profiled a Huge world (6144x2048) headless and in the browser. After a load every area is
|
||
awake: 8-15 ms/tick for the first 600 ticks, settling to about 0.7 ms/tick after 3 minutes. In
|
||
the browser, drawing is cheap; the full roof scan every 20 ticks was the largest single item.
|
||
- Kept: `updateDirtyRoof()` (`src/sim/ledger.js`) rescans only area columns marked by
|
||
`wakeXY`/`wakeHeatXY`/`simMove`/`heatChunk` (`roofDirty`); `updateRoof()` is still the full scan
|
||
for setup code. Quiet Huge world 4.60 -> 1.88 ms per scan, digging 4.67 -> 3.48.
|
||
`heatChunk` skips `react()` for solids and powders below their first reaction threshold
|
||
(`REACT_MIN`). Huge, ms/tick: digging 2.54 -> 2.34, quiet 1.39 -> 1.18.
|
||
- Reverted (no consistent gain): inlining `condPair`, one shared part rule for `enter`/`passable`.
|
||
- `test/t_sim_optimizations.js`: the dirty roof matches a full scan; `heatChunk` matches the
|
||
reference loop in `test/fixtures/heat-chunk.js` byte for byte, random state included (catches a
|
||
reaction added to `react()` but not to `REACT_MIN`).
|
||
- Every suite's output identical before and after apart from timings; seeds 777 and 4242
|
||
generate the same worlds. `run_all.py --browser` ALL PASSED.
|
||
|
||
## 2026-10-24: t21: piles near the bot sort quickly (Claude Code, Codex)
|
||
|
||
- Owner: "a pile should sort when the player is nearby. the sorting shouldnt take long anyway."
|
||
- `src/sim/cells.js`: `SORTP` 0.003 -> 0.06, `SORTP_FUEL` 0.003 for sinking through burning fuel
|
||
(at 0.06 dirt sank through a buried burning wood pile and `t_world`'s charcoal check failed);
|
||
`wakeBotPowder()` every 8 ticks from `simTick` wakes areas with loose grains (`powderArea`
|
||
hint, kept by `simMove`) within 100 grains of the bot. `worldSteps` settles at 0.003: seeds
|
||
unchanged.
|
||
- Cost on a Medium dig site: 0.108 -> 0.134 ms/tick. `t_physics` sorting checks: near the bot ore
|
||
sorts in 432 ticks then sleeps; far away it stays; sand on dirt never sorts.
|
||
- Tests: `run_all.py --browser` ALL PASSED.
|
||
|
||
## 2026-10-24: t21 phase 2: physics test pass for sand, molten metal and gases (Claude Code, Codex)
|
||
|
||
- `test/t_physics.js` (41 checks): heaps, digging under a heap, ore sorting through ash, sand
|
||
sinking in a pond, world edges; molten copper levelling (slower than water), setting, sinking in
|
||
a pond, slag over copper, filling a cavity; smoke rising and fading (longer under a roof), steam
|
||
condensing, fire going out, gas in sealed pockets, gas leaving at the top row. Every scenario
|
||
checks that its areas go back to sleep.
|
||
- Found and fixed: gas stuck in a pocket never finished fading or condensing because its area
|
||
slept (`gas()` keeps its area awake: steady cost 1.34 -> 1.37 ms/tick); a rain drop or blown
|
||
grain landing on steam deleted that steam's water (`landFlyer`/`depositNear` in
|
||
`src/sim/vacuum.js` now send it to the clouds).
|
||
- Found, owner decided: a resting pile never sorts. To follow: piles near the bot keep sorting,
|
||
and sorting gets quick.
|
||
- Tests: `run_all.py --browser` ALL PASSED; seeds 777 and 77 generate the same worlds.
|
||
|
||
## 2026-10-24: t21 phase 1: water test pass, faster pouring, rain spreads (Claude Code, Codex)
|
||
|
||
- `test/t_water.js` (20 checks): conservation, levelling and sleep, pools pouring off ledges,
|
||
world edges and corners, pumps, freezing and thawing, boiling and condensing, a pool draining
|
||
into a hot shaft.
|
||
- `liquid()`/`move()` in `src/sim/cells.js`: multi-cell steps (`LSTEP`, scaled by `FLUID`),
|
||
a longer reach for top-layer grains (`LFILM`), and waking the row ahead only when a top-layer
|
||
grain steps sideways (waking on every liquid move kept 207 of 2048 areas busy after a reload).
|
||
`worldSteps` settles with both at 1: seeds 777, 77 and 4242 hash the same as before.
|
||
- Found: cooled steam drifting under a roof could stay steam for ever, because its heat area
|
||
slept. `gas()` now keeps the heat pass on it.
|
||
- `weatherTick()`: raining clouds drift at half pace; rain falls over the band and one either
|
||
side. `t_weather`: 'close to where it gathered' now samples drops in the air (landed water
|
||
spreads); a new wind check.
|
||
- Test fixes, not weakening: `t_conveyors` pit check counts steam on its way up (the water total
|
||
was exact); `t_molds` burning-bed check seeds `RS` (its result hung on earlier tests' draws:
|
||
0.28-1.04 depending on the sequence, identical on the old and new code).
|
||
- Steady cost after a reload (2048x1024): 1.26 -> 1.24 ms/tick. Tests: `run_all.py --browser`
|
||
ALL PASSED. Screenshots `shots/t21-pour-120.png`, `shots/t21-pour-900.png`.
|
||
|
||
## 2026-10-24: t1: bars stack neatly, Rack blocks store them (Claude Code, Codex)
|
||
|
||
- `232f5af`: `entTick` snap-and-tip (`entSupportCount`, `src/sim/items.js`);
|
||
`test/t_bar_stacking.js` (12 bars fill a pit's bottom six rows; fails on the old code).
|
||
- Rack blocks: `src/sim/racks.js` (`rackState` per block, FIFO runs, `rackIntake` from `entTick`,
|
||
`rackTick` release, `rackRefill`), `BP_RACK`/`RACKB`, F toggle in `interactAt`, removal returns
|
||
bars with `giveOrDrop`, saved in meta `racks` and validated on load, shelves overlay, hover,
|
||
Machines tab, notebook. `test/t_racks.js` (29 checks), play.py menu check.
|
||
- Tests: `run_all.py --browser` ALL PASSED. Screenshot `shots/t1-racks.png`.
|
||
|
||
## 2026-10-24: Backlog combined and rewritten (Claude Code)
|
||
|
||
- On the owner's request: t12 (exotic ores) and t31 (choosing claims) merged into t13, now
|
||
"Distinct worlds: generation options, planet presets, exotic ores and a choice of claims";
|
||
t24 (water fixes) merged into t21, now "Code health: refactor, speed up and test the physics".
|
||
t2 and t14 rewritten with subtasks. t12, t24 and t31 archived (`tasks/archive/`), not deleted.
|
||
|
||
## 2026-10-24: t32: minimap and full map (Claude Code, Codex)
|
||
|
||
- `src/ui/map.js`: colour cache (`resetMap`, `mapArea`, `updateMap` from the frame loop),
|
||
minimap (`#minimap` in `#hud .br`, `SET.minimap`, `SET.minimapZoom`), full map modal `m-map`
|
||
(`openMap`, `zoomMap`, drag pan). `input.js`: M map, Shift+M mute. `#cards` moved to `.bc`.
|
||
- Frame time on Medium: 2.18 ms with the minimap off, 2.29 ms on.
|
||
- Tests: play.py map checks (buttons, zoom setting, cache updates after digging, fog black, M/Esc,
|
||
Shift+M, cards in .bc, setting hides it, frame time). `run_all.py --browser` ALL PASSED.
|
||
Screenshots `shots/t32-hud.png`, `shots/t32-map.png`, `shots/t32-map-fog.png`.
|
||
|
||
## 2026-10-24: New worlds get camp names too (Claude Code, Codex)
|
||
|
||
- The new-world dialog prefills `claimName(seed, existing names)` instead of "World N", and
|
||
follows seed edits until the player types their own name; a blank name falls back to it too
|
||
(`src/ui/loop.js`, `session.js`). play.py checks updated. All tests pass.
|
||
|
||
## 2026-10-24: t30: progress while generating, loading and saving (Claude Code, Codex)
|
||
|
||
- `worldgen.js`: `worldSteps` generator yields `[fraction, label]`; `generateWorld` (sync, tests)
|
||
and `generateWorldAsync` (yields every 30 ms) run it. Small and Medium mat/temp/flags hashes
|
||
match the old generator exactly.
|
||
- `session.js` `showLoading(on, p, label, title)` with `#loadstep`; `GAME.loading` stops the
|
||
frame loop's sim and drawing while worlds swap. `loadSavedWorld` for Continue and the world
|
||
list; `worldNewest`/`unpackBuffers`/`startFromSave` report progress; travel and new claims too.
|
||
`#saving` chip after 150 ms of saving.
|
||
- Tests: t_world progress labels and identical worlds; play.py overlay, responsiveness during
|
||
async generation, Continue labels, saving chip. All pass except t_smooth half-resolution
|
||
(fails under load, as before). Screenshots `shots/t30-loading.png`, `shots/t30-saving.png`.
|
||
|
||
## 2026-10-24: t3 follow-up: claim names; t30 added (Claude Code, Codex)
|
||
|
||
- New claims get a camp name from their seed ("Cinder Gulch"; `claimName` in `src/ui/claims.js`,
|
||
skipping names already in the world list) instead of "Claim N". The Claims tab shows "Claim N"
|
||
beside the name and has a Rename button on every row (reuses `renameWorld`).
|
||
- Owner's request added to Todo as t30: progress meters while generating, loading and saving.
|
||
- Tests: play.py claims checks extended; t_smooth half-resolution still fails under load.
|
||
|
||
## 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.
|