Multi-core simulation: grain movement and heat across a worker pool (t88 phase 3)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-28 03:23:43 -04:00
co-authored by Claude Opus 5.5
parent 20963c776e
commit 7468d00efb
35 changed files with 578 additions and 113 deletions
+82
View File
@@ -4,6 +4,88 @@ What agents have done, newest first. Add an entry for every piece of work: the d
note or request it was for, what changed, the files touched, the tests run, and anything left
open or not verified. Keep each entry short; the git history has the details.
## 2026-10-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