Phase 4: play-through checked, final profile, dead code removed, next-step recommendation (t88)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-28 06:50:54 -04:00
co-authored by Claude Opus 5.5
parent 7468d00efb
commit fbc3b07be2
11 changed files with 49604 additions and 56 deletions
+2 -2
View File
@@ -207,7 +207,7 @@ flyers and blueprint ghosts. The GPU canvas sits behind the transparent overlay
| World generation | `src/sim/worldgen.js` | | World generation | `src/sim/worldgen.js` |
| Save format and migrations | `src/sim/save.js` | | Save format and migrations | `src/sim/save.js` |
| Machine drawing (silo, refiner...) | `src/render/machines.js` | | Machine drawing (silo, refiner...) | `src/render/machines.js` |
| Smooth-look shader | `src/render/smooth.js` | | Smooth-look shader | `src/render/gpu/smooth-shader.js` |
| Settings screen | `src/ui/settings.js` (settings and defaults: `SET` in `src/ui/state.js`) | | Settings screen | `src/ui/settings.js` (settings and defaults: `SET` in `src/ui/state.js`) |
| Sound and music | `src/ui/audio.js` | | Sound and music | `src/ui/audio.js` |
@@ -397,7 +397,7 @@ Saving and loading a world (serializeWorld / deserializeWorld) and old-save migr
- functions: serializeWorld, deserializeWorld, startLoadWake, wakeLoadArea, wakeLoadNearBot, loadWakeTick - functions: serializeWorld, deserializeWorld, startLoadWake, wakeLoadArea, wakeLoadNearBot, loadWakeTick
- variables: SAVE_VERSION, TOFF - variables: SAVE_VERSION, TOFF
### src/render/setup.js (198 lines) ### src/render/setup.js (190 lines)
Drawing (src/render/): browser only. This file: the renderer state R, colour tables (glow, thermal palettes, pipes, trees, sky), canvas setup and the camera. Drawing (src/render/): browser only. This file: the renderer state R, colour tables (glow, thermal palettes, pipes, trees, sky), canvas setup and the camera.
- functions: glowIndex, thermIdx, pipeCellColor, pipeBodyCell, thinPipeColor, beltLike, buildSky, initRender, resizeRender, viewCells, updateCamera, snapCamera, screenToWorld, worldToScreenPx, renderMonoFont - functions: glowIndex, thermIdx, pipeCellColor, pipeBodyCell, thinPipeColor, beltLike, buildSky, initRender, resizeRender, viewCells, updateCamera, snapCamera, screenToWorld, worldToScreenPx, renderMonoFont
- variables: R, GLOW_W, GLOW_R, GLOW_G, GLOW_B, THERM, PC, TREE_R, TREE_G, TREE_B, BG, SKY_R, SKY_G, SKY_B - variables: R, GLOW_W, GLOW_R, GLOW_G, GLOW_B, THERM, PC, TREE_R, TREE_G, TREE_B, BG, SKY_R, SKY_G, SKY_B
+29 -31
View File
@@ -134,10 +134,8 @@ python3 tools/shot.py shots/silo.png --setup tools/scenes/clear-sky.js \
`tools/scenes/clear-sky.js` clears a flat work area and sets `FLOOR`, `BX`, `BY` and `CAM`. Set `tools/scenes/clear-sky.js` clears a flat work area and sets `FLOOR`, `BX`, `BY` and `CAM`. Set
`CAM = [x, y]` to frame the shot. `CAM = [x, y]` to frame the shot.
- The HUD and the cursor previews are hidden unless you pass `--hud` or `--previews`. - The HUD and the cursor previews are hidden unless you pass `--hud` or `--previews`.
- `--renderer webgpu|legacy` selects the renderer for either look (default WebGPU). WebGPU screenshots - Both looks require WebGPU; there is no renderer switch or legacy fallback.
fail clearly when no device is available; Legacy smooth uses WebGL2; WebGPU smooth uses WGSL. - `test/t_gpu.py` checks GPU uploads, dirty/full equality, contours, resize, timing and device loss.
- `test/t_gpu.py` compares the rendered composite against Legacy for ordinary cells, built pieces,
ghosts, fog and all thermal palettes, and checks world replacement, resize, timing and device loss.
World-pixel tests use `worldPixelContext()` to read both stacked canvases. The helper World-pixel tests use `worldPixelContext()` to read both stacked canvases. The helper
presents the retained GPU result again before copying, because canvas textures expire after presents the retained GPU result again before copying, because canvas textures expire after
presentation ([WebGPU specification](https://gpuweb.github.io/gpuweb/#abstract-opdef-expire-the-current-texture), presentation ([WebGPU specification](https://gpuweb.github.io/gpuweb/#abstract-opdef-expire-the-current-texture),
@@ -202,13 +200,11 @@ python3 tools/shot.py shots/silo.png --setup tools/scenes/clear-sky.js \
another 60 ticks. Each rebuild resets the power-rebuild scheduler, weather random stream and all statistics to another 60 ticks. Each rebuild resets the power-rebuild scheduler, weather random stream and all statistics to
fresh-page values; fixture hashes are checked by `test/t_perf.js`. No save/load snapshot is used: it would quantize heat and land flyers. fresh-page values; fixture hashes are checked by `test/t_perf.js`. No save/load snapshot is used: it would quantize heat and land flyers.
**World** is the whole `drawWorld` CPU call. **Light** is the lighting work inside that call; **World** is the whole `drawWorld` CPU call. **Prep** sets up the view/masks/joins;
**Prep** sets up the view/masks/joins; **Colour** fills CPU grain buffers (including ghosts and **Colour** fills CPU rare-cell overrides (including ghosts and flyers); **Upload** writes
flyers); **Upload** submits the pixel buffer or smooth textures; **Blit** submits drawing, dirty world chunks and override textures; **Blit** submits GPU drawing and composition.
including pixel light/glow composition or the smooth shader and Canvas2D copy. These are CPU These are CPU wall times, including driver stalls; they are not GPU timestamp measurements.
wall times, including driver stalls; they are not GPU timestamp measurements. In pixel look, Bloom is computed on the GPU. GPU light timestamps are recorded separately as `gpuLight`.
the glow upload is part of Blit. The separate `computeLight_ms` times an unconditional light
call even when the row has lighting off. World includes Light: do not add them together.
Overlay totals and each individual draw call are in JSON, with ms/frame and invocation counts; conditional Overlay totals and each individual draw call are in JSON, with ms/frame and invocation counts; conditional
overlays absent from the fixture have zero calls, not a measured zero execution cost. overlays absent from the fixture have zero calls, not a measured zero execution cost.
@@ -225,7 +221,7 @@ python3 tools/shot.py shots/silo.png --setup tools/scenes/clear-sky.js \
retain frame interval (rAF timestamps, including waiting/scheduling), CPU frame work, total retain frame interval (rAF timestamps, including waiting/scheduling), CPU frame work, total
simulation work per frame and number of ticks. p50/p95/p99 use interpolated sorted samples; simulation work per frame and number of ticks. p50/p95/p99 use interpolated sorted samples;
five warm-up frames are excluded. Zero-tick frames correctly have zero simulation time. five warm-up frames are excluded. Zero-tick frames correctly have zero simulation time.
JSON also records the smooth renderer's adaptive resolution scale: compare it as well as time. JSON records a fixed render scale of 1 for both looks.
Comparisons accept subsets of the baseline scenes, looks and zooms, with identical world/viewport dimensions and run duration, print all timing changes, and mark >10% regressions; Comparisons accept subsets of the baseline scenes, looks and zooms, with identical world/viewport dimensions and run duration, print all timing changes, and mark >10% regressions;
tiny timings and tails can be noisy, and a zero baseline has no meaningful percent change. tiny timings and tails can be noisy, and a zero baseline has no meaningful percent change.
@@ -246,30 +242,32 @@ python3 tools/shot.py shots/silo.png --setup tools/scenes/clear-sky.js \
- `tools/experiments/flow.js`: belt and screw conveyor throughput in grains a second. - `tools/experiments/flow.js`: belt and screw conveyor throughput in grains a second.
- `tools/experiments/worldgen.js`: material counts and timings of world generation. - `tools/experiments/worldgen.js`: material counts and timings of world generation.
WebGPU Phase 2a: Colour measures the CPU override fill (including loose objects and ghosts). Both looks use WebGPU. Colour measures the CPU rare-cell override fill, including loose
`frame_ms` is the complete paused-frame CPU call (including overlays and UI). objects and ghosts. `frame_ms` is the complete paused-frame CPU call, with overlays and UI.
Upload measures strided visible-array writes and the override/light uploads, and Blit measures Upload measures dirty world chunks, override buffers and small lighting tables; Blit measures
GPU command submission. `override_cells` records the selected CPU cells; GPU timing is a GPU command submission. `override_cells` counts CPU cells. GPU timestamp samples include
completed timestamp sample for that configuration across colour, bloom and drawing when `timestamp-query` is colour, lighting, bloom and drawing, excluding uploads. CPU lighting is gone. The GPU canvas
available. The timestamp excludes uploads and the CPU lighting pass. Lighting remains on the draws the world; the transparent game canvas holds overlays. Override colours and glow live in
CPU. The GPU canvas draws the world; the transparent game canvas holds the existing overlays. plain typed arrays, without offscreen Canvas2D backing stores.
The comparison fixture is `tools/scenes/t88-renderer.js`. Capture with `--freeze --zoom 12` Use `tools/scenes/t88-renderer.js` with `--freeze --zoom 12` (or 6/2),
(and 6 or 2), `--viewport 1920x1080`, and each `--renderer`. It includes terrain, water, fire, `--viewport 1920x1080` and `--look pixel|smooth`. It includes terrain, water, fire, magma,
magma, fog, a tree, flat/slope belts, screws, pipe, mold, bar and blueprint ghost. fog, a tree, flat/slope belts, screws, pipe, mold, bar and blueprint ghost. Frozen legacy
comparisons are in `test/t_gpu_golden.py`; no legacy renderer remains in the game.
WebGPU Phase 2c: both looks use WebGPU. `test/t_gpu.py` also reads all four smooth input The smooth shader retains the nine-tap glow filter and samples field AO at mip level 2.2;
textures back and compares them against the legacy CPU buffers at zoom 12/6/2 with fog on/off:
classes, sky flags, field bits and geometry tags must match exactly; RGB can differ by one.
The smooth shader retains the legacy nine-tap glow filter and samples field AO at mip level 2.2;
three render passes generate field levels 1-3, including non-power-of-two view sizes. three render passes generate field levels 1-3, including non-power-of-two view sizes.
Only smooth allocates/uploads the back/tree/field overrides and a separate presence mask. Only smooth uploads back/tree/field overrides and a separate presence mask. Profiler scale is
`tools/perf.py` refuses a WebGPU fallback in either look and records WebGPU scale as 1. always 1. The screenshot tool invalidates the parallax horizon after setup changes `surfY`.
The screenshot tool invalidates the shared parallax horizon after scene setup changes `surfY`, Mip pass references: [Chrome's porting guide](https://developer.chrome.com/docs/web-platform/webgpu/from-webgl-to-webgpu#mipmap_generation)
so renderer startup timing cannot change the hills in a comparison. Mip pass references:
[Chrome's porting guide](https://developer.chrome.com/docs/web-platform/webgpu/from-webgl-to-webgpu#mipmap_generation)
and [WebGPU Fundamentals](https://webgpufundamentals.org/webgpu/lessons/webgpu-importing-textures.html#generating-mips-on-the-gpu). and [WebGPU Fundamentals](https://webgpufundamentals.org/webgpu/lessons/webgpu-importing-textures.html#generating-mips-on-the-gpu).
`perfSim` reports synchronous tick wall time (also occupied main-thread time), polling in
`threadWait`, and main compute excluding polling. The latter includes serial simulation,
dispatch/merge and the main thread's own chunk batch; it is not pure scheduling overhead.
Final t88 full-matrix results are saved in `tools/perf-after-t88.json`; keep
`tools/perf-baseline.json` unchanged as the original comparison.
The required pipeline limits are checked in `gpuLimitsOK` before requesting the device: four The required pipeline limits are checked in `gpuLimitsOK` before requesting the device: four
storage textures, sixteen sampled textures and eight storage buffers per stage, plus 8192 for storage textures, sixteen sampled textures and eight storage buffers per stage, plus 8192 for
2D textures; no non-default limit is requested. References: [WebGPU limits](https://www.w3.org/TR/2026/CRD-webgpu-20260109/#limits) 2D textures; no non-default limit is requested. References: [WebGPU limits](https://www.w3.org/TR/2026/CRD-webgpu-20260109/#limits)
+354
View File
@@ -4,6 +4,360 @@ 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 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. 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) ## 2026-10-28: t88 phase 3 resume, final review and verification (Codex)
- Preserved the interrupted uncommitted implementation and reviewed the schedule, shared - Preserved the interrupted uncommitted implementation and reviewed the schedule, shared
+1 -1
View File
@@ -16,7 +16,7 @@ function cellPixel(x, y) {
} }
const useFog = SET.fog, grain = SET.grain, incand = SET.incandescence; const useFog = SET.fog, grain = SET.grain, incand = SET.incandescence;
const anim = R.frame, lens = R.lens, tl = THERM[SET.thermalPalette] || THERM.inferno; const anim = R.frame, lens = R.lens, tl = THERM[SET.thermalPalette] || THERM.inferno;
const glowOn = SET.glow !== 'off' && !lens, gpx = R.glowPx, gw = R.glowC.width; const glowOn = SET.glow !== 'off' && !lens, gpx = R.glowPx, gw = R.glowW;
const fy = y / FOG, fj = Math.floor(fy), ft = fy - fj, fr = fj * FW, fn = Math.min(FH - 1, fj + 1) * FW; const fy = y / FOG, fj = Math.floor(fy), ft = fy - fj, fr = fj * FW, fn = Math.min(FH - 1, fj + 1) * FW;
const yw = y * W; const yw = y * W;
let skr = SKY_R[y], skg = SKY_G[y], skb = SKY_B[y]; let skr = SKY_R[y], skg = SKY_G[y], skb = SKY_B[y];
+1 -1
View File
@@ -58,7 +58,7 @@ function gpuDraw(lighting, glowOn, lightUpdate) {
compute.dispatchWorkgroups(Math.ceil(R.canvas.width / 8), Math.ceil(R.canvas.height / 8)); compute.end(); compute.dispatchWorkgroups(Math.ceil(R.canvas.width / 8), Math.ceil(R.canvas.height / 8)); compute.end();
if (glowOn && !R.smoothOn) for (let i = 0; i < 2; i++) { if (glowOn && !R.smoothOn) for (let i = 0; i < 2; i++) {
const pass = encoder.beginComputePass(); pass.setPipeline(GPU.blur); pass.setBindGroup(0, GPU.bindBlur[i]); const pass = encoder.beginComputePass(); pass.setPipeline(GPU.blur); pass.setBindGroup(0, GPU.bindBlur[i]);
pass.dispatchWorkgroups(Math.ceil(R.glowC.width / 8), Math.ceil(R.glowC.height / 8)); pass.end(); pass.dispatchWorkgroups(Math.ceil(R.glowW / 8), Math.ceil(R.glowH / 8)); pass.end();
} }
const lightTimed = lighting && (lightUpdate || GPU.lightFrame === undefined); const lightTimed = lighting && (lightUpdate || GPU.lightFrame === undefined);
if (lightTimed) { gpuLight(encoder, timed); GPU.lightFrame = R.frame; } if (lightTimed) { gpuLight(encoder, timed); GPU.lightFrame = R.frame; }
+2 -2
View File
@@ -41,7 +41,7 @@ function gpuViewTextures() {
if (GPU.viewKey === key) return; if (GPU.viewKey === key) return;
GPU.viewKey = key; GPU.lightFrame = undefined; GPU.viewKey = key; GPU.lightFrame = undefined;
if (GPU.viewTextures) for (const t of GPU.viewTextures) t.destroy(); if (GPU.viewTextures) for (const t of GPU.viewTextures) t.destroy();
const bw = R.bw, bh = R.bh, gw = R.glowC.width, gh = R.glowC.height; const bw = R.bw, bh = R.bh, gw = R.glowW, gh = R.glowH;
GPU.crackTex = gpuTexture(R.canvas.width, R.canvas.height, 'rgba8unorm', true); GPU.crackTex = gpuTexture(R.canvas.width, R.canvas.height, 'rgba8unorm', true);
GPU.override = gpuTexture(bw, bh); GPU.overrideGlow = gpuTexture(gw, gh); GPU.override = gpuTexture(bw, bh); GPU.overrideGlow = gpuTexture(gw, gh);
GPU.colourTex = gpuTexture(bw, bh, 'rgba8unorm', true); GPU.colourTex = gpuTexture(bw, bh, 'rgba8unorm', true);
@@ -82,7 +82,7 @@ function gpuUpload(lighting, glowOn) {
const q = GPU.device.queue; const q = GPU.device.queue;
q.writeBuffer(GPU.land, 0, land); q.writeBuffer(GPU.land, 0, land);
gpuWrite(GPU.override, R.px, R.bw, 0, 0, R.bw, R.bh); gpuWrite(GPU.override, R.px, R.bw, 0, 0, R.bw, R.bh);
gpuWrite(GPU.overrideGlow, R.glowPx, R.glowC.width, 0, 0, R.glowC.width, R.glowC.height); gpuWrite(GPU.overrideGlow, R.glowPx, R.glowW, 0, 0, R.glowW, R.glowH);
for(let i=0;i<W;i++) { GPU.columnData[i*4]=surfY[i]; GPU.columnData[i*4+1]=roofY[i]; } for(let i=0;i<W;i++) { GPU.columnData[i*4]=surfY[i]; GPU.columnData[i*4+1]=roofY[i]; }
q.writeBuffer(GPU.columns,0,GPU.columnData); q.writeBuffer(GPU.columns,0,GPU.columnData);
if (R.smoothOn) gpuSmoothUpload(); if (R.smoothOn) gpuSmoothUpload();
+5 -13
View File
@@ -2,8 +2,8 @@
thermal palettes, pipes, trees, sky), canvas setup and the camera. */ thermal palettes, pipes, trees, sky), canvas setup and the camera. */
const R = { const R = {
canvas: null, ctx: null, buf: null, bctx: null, img: null, px: null, canvas: null, ctx: null, px: null,
glowC: null, glowX: null, glowImg: null, glowPx: null, glowW: 0, glowH: 0, glowPx: null,
bw: 0, bh: 0, zp: 3, camX: 0, camY: 0, vx0: 0, vy0: 0, bw: 0, bh: 0, zp: 3, camX: 0, camY: 0, vx0: 0, vy0: 0,
lw: 0, lh: 0, lx0: 0, ly0: 0, lres: 1, lw: 0, lh: 0, lx0: 0, ly0: 0, lres: 1,
frame: 0, lens: false, particles: [], frame: 0, lens: false, particles: [],
@@ -139,10 +139,6 @@ function buildSky() {
function initRender(canvas) { function initRender(canvas) {
R.canvas = canvas; R.canvas = canvas;
R.ctx = canvas.getContext('2d'); R.ctx = canvas.getContext('2d');
R.buf = document.createElement('canvas');
R.bctx = R.buf.getContext('2d');
R.glowC = document.createElement('canvas');
R.glowX = R.glowC.getContext('2d');
} }
function resizeRender() { function resizeRender() {
@@ -155,15 +151,11 @@ function resizeRender() {
const bw = Math.ceil(cw / zp) + 2, bh = Math.ceil(ch / zp) + 2; const bw = Math.ceil(cw / zp) + 2, bh = Math.ceil(ch / zp) + 2;
if (bw !== R.bw || bh !== R.bh) { if (bw !== R.bw || bh !== R.bh) {
R.bw = bw; R.bh = bh; R.bw = bw; R.bh = bh;
R.buf.width = bw; R.buf.height = bh; R.px = new Uint32Array(bw * bh);
R.img = R.bctx.createImageData(bw, bh);
R.px = new Uint32Array(R.img.data.buffer);
// the smooth look also needs, per grain: the colour behind it, any tree, and occupancy fields // the smooth look also needs, per grain: the colour behind it, any tree, and occupancy fields
R.backPx = new Uint32Array(bw * bh); R.treePx = new Uint32Array(bw * bh); R.fieldPx = new Uint32Array(bw * bh); R.moldMask = new Uint8Array(bw * bh); R.backPx = new Uint32Array(bw * bh); R.treePx = new Uint32Array(bw * bh); R.fieldPx = new Uint32Array(bw * bh); R.moldMask = new Uint8Array(bw * bh);
const gw = Math.ceil(bw / 2), gh = Math.ceil(bh / 2); R.glowW = Math.ceil(bw / 2); R.glowH = Math.ceil(bh / 2);
R.glowC.width = gw; R.glowC.height = gh; R.glowPx = new Uint32Array(R.glowW * R.glowH);
R.glowImg = R.glowX.createImageData(gw, gh);
R.glowPx = new Uint32Array(R.glowImg.data.buffer);
R.lw = 0; R.lw = 0;
} }
} }
+2 -2
View File
@@ -4,7 +4,7 @@ function fogColor(c, k) { return (c & 0xff000000) | (((c >>> 16 & 255) * k) << 1
// ---------- World pixels ---------- // ---------- World pixels ----------
function drawWorld() { function drawWorld() {
const perfStart = (PERF_MARKS || SET.showPerf) ? performance.now() : 0; const perfStart = (PERF_MARKS || SET.showPerf) ? performance.now() : 0;
if (PERF_MARKS || SET.showPerf) { perfMarks.light = 0; perfMarks.override = 0; delete perfMarks.gpu; } if (PERF_MARKS || SET.showPerf) { perfMarks.override = 0; delete perfMarks.gpu; }
if (!GPU.ok) return; if (!GPU.ok) return;
R.gpuOn = true; GPU.canvas.hidden = false; R.gpuOn = true; GPU.canvas.hidden = false;
R.ctx.clearRect(0, 0, R.canvas.width, R.canvas.height); R.ctx.clearRect(0, 0, R.canvas.width, R.canvas.height);
@@ -40,7 +40,7 @@ function drawWorld() {
worldObjects(); worldObjects();
if (PERF_MARKS || SET.showPerf) { if (PERF_MARKS || SET.showPerf) {
perfMarks.color = performance.now() - colorStart; perfMarks.color = performance.now() - colorStart;
perfMarks.prepare = colorStart - perfStart - perfMarks.light; perfMarks.prepare = colorStart - perfStart;
} }
gpuDraw(lighting, glowOn, lightUpdate); gpuDraw(lighting, glowOn, lightUpdate);
} }
File diff suppressed because it is too large Load Diff
+1 -2
View File
@@ -46,7 +46,6 @@ async function perfRender(lighting, overlays) {
draw += perfLast.drawWorld / n; draw += perfLast.drawWorld / n;
for (const [key, value] of Object.entries(perfMarks)) marks[key] = (marks[key] || 0) + value / n; for (const [key, value] of Object.entries(perfMarks)) marks[key] = (marks[key] || 0) + value / n;
} }
const light = 0;
// Exercise the actual overlay calls and their arguments/conditions in frame(). // Exercise the actual overlay calls and their arguments/conditions in frame().
PERF_MARKS = false; PERF_MARKS = false;
for (let k = 0; k < 5; k++) frame(lastT + 1000 / 60); for (let k = 0; k < 5; k++) frame(lastT + 1000 / 60);
@@ -62,7 +61,7 @@ async function perfRender(lighting, overlays) {
await GPU.timingPromise; await GPU.timingPromise;
PERF_MARKS = false; gpu_ms = GPU.gpuMs; marks.gpu = gpu_ms; marks.gpuLight = GPU.lightMs; PERF_MARKS = false; gpu_ms = GPU.gpuMs; marks.gpu = gpu_ms; marks.gpuLight = GPU.lightMs;
} }
return { frame_ms, drawWorld_ms: draw, computeLight_ms: light, marks_ms: marks, return { frame_ms, drawWorld_ms: draw, marks_ms: marks,
overlays: Object.fromEntries(Object.entries(data).map(([key, d]) => [key, { ms_per_frame: d.ms / n, calls: d.calls }])), overlays: Object.fromEntries(Object.entries(data).map(([key, d]) => [key, { ms_per_frame: d.ms / n, calls: d.calls }])),
overlay_ms: Object.values(data).reduce((sum, d) => sum + d.ms / n, 0), overlay_ms: Object.values(data).reduce((sum, d) => sum + d.ms / n, 0),
scale: 1, actual_look: SET.look, renderer: 'webgpu', override_cells: GPU.overrideCount || 0, gpu_ms }; scale: 1, actual_look: SET.look, renderer: 'webgpu', override_cells: GPU.overrideCount || 0, gpu_ms };
+2 -2
View File
@@ -54,14 +54,14 @@ def report(result, baseline=None):
m = result['machine'] m = result['machine']
print(f"GPU: {m['renderer']}\nCores: {m['cores']} | isolated: {m['crossOriginIsolated']} | navigator.gpu: {m['webgpu']}") print(f"GPU: {m['renderer']}\nCores: {m['cores']} | isolated: {m['crossOriginIsolated']} | navigator.gpu: {m['webgpu']}")
print(f"Viewport {result['options']['viewport']} | world {result['options']['world']} | seed 424242 | ms (CPU wall time)") print(f"Viewport {result['options']['viewport']} | world {result['options']['world']} | seed 424242 | ms (CPU wall time)")
print('\nScene Look Zoom Light Grains World Frame Light Prep Colour Upload Blit Overlays Scale') print('\nScene Look Zoom Light Grains World Frame Prep Colour Upload Blit Overlays Scale')
for scene, data in result['scenes'].items(): for scene, data in result['scenes'].items():
for key, view in data['views'].items(): for key, view in data['views'].items():
look, zoom = key.split('/') look, zoom = key.split('/')
for light, r in view['lighting'].items(): for light, r in view['lighting'].items():
p = r['marks_ms'] p = r['marks_ms']
print(f"{scene:8} {look:6} {zoom:>4} {light:>5} {view['grains']:6d} " print(f"{scene:8} {look:6} {zoom:>4} {light:>5} {view['grains']:6d} "
f"{r['drawWorld_ms']:7.2f} {r.get('frame_ms', 0):6.2f} {p['light']:6.2f} {p['prepare']:5.2f} {p['color']:6.2f} " f"{r['drawWorld_ms']:7.2f} {r.get('frame_ms', 0):6.2f} {p['prepare']:5.2f} {p['color']:6.2f} "
f"{p['upload']:6.2f} {p['blit']:6.2f} {r['overlay_ms']:8.3f} {r['scale']:5.2f}") f"{p['upload']:6.2f} {p['blit']:6.2f} {r['overlay_ms']:8.3f} {r['scale']:5.2f}")
print('\nSimulation: average ms/tick (periodic work amortized over 240 ticks)') print('\nSimulation: average ms/tick (periodic work amortized over 240 ticks)')
names = list(next(iter(result['scenes'].values()))['sim']['steps']) names = list(next(iter(result['scenes'].values()))['sim']['steps'])