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:
+19
-8
@@ -244,9 +244,9 @@ Starter furnace designs and the milestone list shown in the notebook.
|
||||
- functions: smelterBlueprint
|
||||
- variables: STARTER_DESIGNS, MILESTONES
|
||||
|
||||
### src/sim/world.js (113 lines)
|
||||
### src/sim/world.js (123 lines)
|
||||
World state: the per-cell typed arrays, alloc(), ambient temperature by depth, waking.
|
||||
- functions: emit, bagCap, discover, alloc, depthU, depthY, meanSurface, computeAmbient, fillNoise, renderDirtyXY, renderDirtyIdx, wakeXY, wakeHeatXY, wakeIdx, wakeHeatIdx
|
||||
- functions: emit, bagCap, discover, sharedArray, alloc, depthU, depthY, meanSurface, computeAmbient, fillNoise, renderDirtyXY, renderDirtyChunk, renderDirtyIdx, wakeXY, wakeHeatXY, wakeIdx, wakeHeatIdx
|
||||
- variables: W, H, N, CW, CH, NCH, mat, temp, life, flags, stamp, part, mach, digp, noiseB, airS, netId, isPow, occ, surfY, dirtY, graniteY, basaltY, magmaY, roofY, AMB, worldSeed, surfBase, underD, shallowD, lapse, BW, BH, NB, blockP, blockV, ents, bgm, flyers, ghostBlocks, ghostMolds, renderDirty, awake, hot, hotLvl, powderArea, roofDirty, loadWakeOrder, loadWakePending, loadWakeIndex, tick, tickStamp, machines, molds, nets, pcells, powerDirty, beltL, beltR, screwLists, brokenMachines, ghostMachines, ghostBlocked, EV, bot, bag, bagTotal, player, seen, ledger, stats
|
||||
|
||||
### src/sim/fog.js (52 lines)
|
||||
@@ -254,12 +254,12 @@ Exploration only: never changes grains or the random stream.
|
||||
- functions: fogAlloc, fogMark, fogSeen, fogInit, fogLook, fogRevealAround, fogRevealAll
|
||||
- variables: FOG, FOG_SENSE, FOG_SENSE_FADE, fog, FW, FH
|
||||
|
||||
### src/sim/cells.js (227 lines)
|
||||
### src/sim/cells.js (223 lines)
|
||||
Cell helpers (setCell, move, enter) and the falling-sand movement of powders, liquids and gases (simMove).
|
||||
- functions: setCell, swapCells, move, enter, enterFree, powder, passable, liquid, gas, wakeBotPowder, simMove
|
||||
- functions: setCell, swapCells, swapCellData, move, enter, enterFree, powder, passable, liquid, liquidSearch, gas, wakeBotPowder, simMove, moveChunk
|
||||
- variables: SORTP, SORTP_FUEL, LSTEP, LFILM, CORNER_SEAL
|
||||
|
||||
### src/sim/heat.js (271 lines)
|
||||
### src/sim/heat.js (266 lines)
|
||||
Heat: conduction, melting and freezing, reactions, fire and air, steel.
|
||||
- functions: curAir, airflowAt, alloyMix, airy, hasO2, condPair, heatPass, heatChunk, neighborDir, react, char, quench, burningNear, burn, steelCheck
|
||||
- variables: POROUS, SOIL, HOTLIQ, REACT_MIN, ALLOY
|
||||
@@ -369,6 +369,11 @@ Weather: clouds from steam, rain and snow, and the world water count.
|
||||
- functions: wrnd, resetWeather, rainGap, worldWater, absorbSteam, spawnRain, rainAt, weatherTick
|
||||
- variables: WBAND, WS, weather
|
||||
|
||||
### src/sim/workers.js (153 lines)
|
||||
The same chunk schedule runs inline or in workers. Workers only own a pass at a time.
|
||||
- functions: threadWorld, threadBind, threadContext, resetChunkWork, inlineChunkWork, chunkEvent, chunkWake, chunkHeat, chunkSeed, runChunks, mergeChunkWork, chunkPasses, threadParams, threadSetParams, threadSlot, threadExecute, threadPass, threadWait, threadFailure, threadMessage
|
||||
- variables: simPool, chunkWork, inlineWork
|
||||
|
||||
### src/sim/tick.js (29 lines)
|
||||
simTick: one simulation step, in order.
|
||||
- functions: simTick
|
||||
@@ -451,7 +456,7 @@ Field mip levels preserve the legacy trilinear AO sample at level 2.2.
|
||||
- functions: gpuSmoothInit, gpuSmoothTextures, gpuSmoothBindings, gpuSmoothUpload, gpuSmoothMips
|
||||
- variables: GPU_FIELD_MIP
|
||||
|
||||
### src/render/gpu/device.js (89 lines)
|
||||
### src/render/gpu/device.js (90 lines)
|
||||
All pipelines compile during startup; rendering never falls back.
|
||||
- functions: gpuLimitsOK, gpuInit, gpuFail, gpuRecover, gpuWarmWorld, worldPixelContext
|
||||
- variables: GPU_LIMITS, GPU
|
||||
@@ -492,6 +497,11 @@ Game shell (src/ui/): browser only. This file: settings, the GAME state object,
|
||||
- functions: loadSettings, saveSettings, el, esc, rgbOf, fmtT, toast, setStatus
|
||||
- variables: SET_DEFAULT, SET, TOOLS, ICONS, BAG_ORDER, GAME, keys, hudDirty, $
|
||||
|
||||
### src/ui/threads.js (39 lines)
|
||||
Browser transport only; the simulation owns the schedule and shared state.
|
||||
- functions: startThreads
|
||||
- variables: threadStarting
|
||||
|
||||
### src/ui/title.js (77 lines)
|
||||
Startup capabilities and title art.
|
||||
- functions: checkCapabilities, renderCapabilityNotice, canStartGame, buildTitleCells, drawTitle
|
||||
@@ -511,7 +521,7 @@ Shared player state; the world save remains a complete standalone fallback.
|
||||
Starting, loading and quitting a game; modal dialogs; the pause menu.
|
||||
- functions: showLoading, enterGame, setupNewWorld, startNewWorld, startFromSave, loadSavedWorld, quitToTitle, refreshContinue, anyModal, openModal, closeModal, closeAllModals, topModal, dialog, openPause, renderWorlds
|
||||
|
||||
### src/ui/settings.js (123 lines)
|
||||
### src/ui/settings.js (124 lines)
|
||||
The settings screen.
|
||||
- functions: openSettings, renderSettings, applySettings, toggleFullscreen, confirmWipe
|
||||
- variables: SET_TABS, setTab
|
||||
@@ -607,7 +617,7 @@ Shared world colours, minimap and full map.
|
||||
Browser-only clocks; simulation code stays usable without a browser or a clock.
|
||||
- variables: PERF_MARKS, PERF_FRAME, perfMarks, perfTimes, perfLast
|
||||
|
||||
### src/ui/loop.js (143 lines)
|
||||
### src/ui/loop.js (149 lines)
|
||||
The frame loop and boot.
|
||||
- functions: scanVisible, frame, boot
|
||||
- variables: lastT, acc, lastRender, frameMs, simMs, fpsSm, ambientT
|
||||
@@ -656,6 +666,7 @@ The frame loop and boot.
|
||||
- `test/t_slopes.js`: Sloped walls: flow, sealed joints, building, refunds and persistence.
|
||||
- `test/t_smooth.py`: The smooth look: the WebGPU pass compiles and draws, and the thermal lens still works.
|
||||
- `test/t_stone.js`: Uses for waste rock and sand: glass bricks from a fan furnace, the Saw, the silo and builders' orders.
|
||||
- `test/t_threads.js`: Shared allocation, checkerboard independence, and real worker-count determinism.
|
||||
- `test/t_water.js`: Water conservation, settling, ledge pouring, boundaries, pipes and phase changes. Tall worlds keep geothermal heat out of the ponds.
|
||||
- `test/t_weather.js`: Weather: no more springs; steam rises into clouds and rains back; showers; snow when it is freezing.
|
||||
- `test/t_world.js`: Trees from acorns, charcoal, brick, density, water/lava/snow/ice, full-bag refunds, drill reach, ballistic blowing, snowy mountains.
|
||||
|
||||
+15
-3
@@ -49,7 +49,7 @@ done(); // prints ALL PASS or N FAILED, sets the
|
||||
- A new suite named `test/t_<topic>.js` is picked up by `run_all.py` automatically.
|
||||
|
||||
**Determinism.** The simulation's randomness comes from seeded generators. The same code and the
|
||||
same test give the same output every run, apart from lines with timings in ms. To prove a
|
||||
same test give outputs identical across thread counts and between runs, apart from lines with timings in ms. To prove a
|
||||
refactor changed nothing, save every suite's output before and after, and diff them:
|
||||
|
||||
```sh
|
||||
@@ -58,6 +58,17 @@ for t in test/t_*.js; do node $t > /tmp/after/$(basename $t).txt; done # afte
|
||||
diff -r /tmp/before /tmp/after
|
||||
```
|
||||
|
||||
`test/t_threads.js` runs the real shared-memory scheduler inline and with 1, 2 and 8
|
||||
Node workers, including repeats, comparing all mutable cell/chunk bytes and JS side effects.
|
||||
The browser creates up to `min(16, hardwareConcurrency - 2, CPU workers setting)` helpers.
|
||||
Small passes run inline; larger passes use batches to amortize dispatch. Wakes and heat flags
|
||||
merge after each pass; long liquid searches are deferred until that merge. Global machine and
|
||||
weather randomness remains separate from the chunk streams. World generation uses the same
|
||||
inline schedule while settling. The t88 re-baseline changes worldgen material hashes and the
|
||||
seeded sand pile bounds in `t_corner_seal.js`; the heat oracle now supports pass outputs.
|
||||
The conveyor fixture uses a wider feed pile, the screen fixture warms longer, and `t_load.js`
|
||||
checks waking within eight ticks separately from the probabilistic consumption of fuel.
|
||||
|
||||
## Browser tests
|
||||
|
||||
- They need the `.venv` from README.md → Develop; plain `python3` switches to it on its own.
|
||||
@@ -73,7 +84,8 @@ diff -r /tmp/before /tmp/after
|
||||
both looks at zoom 12 and 2. ImageMagick decodes RGB; the per-channel tolerance is 8,
|
||||
mean RGB error must stay below 0.5/255 and fewer than 0.3% of pixels may exceed 8
|
||||
(smooth AO/edge rounding). These are frozen pre-removal legacy images, not generated
|
||||
from the new renderer. Inspect the matching `shots/golden-*.png` after visible changes.
|
||||
from the new renderer. The compressed `golden-world-640x*.json.gz` fixtures retain the
|
||||
Phase 2 world bytes that produced them, isolating renderer checks from worldgen RNG changes. Inspect the matching `shots/golden-*.png` after visible changes.
|
||||
- `test/t_gpu.py` checks default limits (four storage textures per stage), dirty-upload
|
||||
readback after 240 busy ticks with scrolling and both looks, exact dirty/full image equality
|
||||
with a frozen clock, world replacement, transparent overlays, drill contours, shader compilation timing, successful device recreation and failed recovery with
|
||||
@@ -98,7 +110,7 @@ diff -r /tmp/before /tmp/after
|
||||
- `python3 tools/caps.py` prints isolation, actual WebGPU adapter info and the GL renderer for
|
||||
Chromium and installed Firefox. Real mode requires a hardware Chromium adapter.
|
||||
- `test/t_caps.py` checks absent, null, rejected and insufficient-limit adapters, notices and
|
||||
the mandatory WebGPU startup gate. Threads remain optional until Phase 3. Firefox smoke
|
||||
the mandatory WebGPU startup gate. Cross-origin isolation is required for threads. Firefox smoke
|
||||
checks that a browser without WebGPU shows the notice and cannot start a world.
|
||||
- Install Firefox with `.venv/bin/python -m playwright install firefox`.
|
||||
`python3 test/firefox_smoke.py` checks isolation and the WebGPU notice, starts a pixel world
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user