From c2c3eba336af88cb56dffcfee6437a94eab6d721 Mon Sep 17 00:00:00 2001 From: Joao Paulo Furtado Date: Mon, 12 Oct 2026 21:00:53 -0700 Subject: [PATCH 1/3] docs(prd-478): archive completed frame architecture --- .../PRDs/assets/PRD-541-card-lod-levels-keep-the-canopy.md | 2 +- .../PRD-478-open-world-frame-architecture.md | 7 +++---- docs/PRDs/done/PRD-489-gpu-scene-occlusion-culling.md | 4 ++-- docs/PRDs/open-world/PRD-488-foliage-moves-in-the-wind.md | 2 +- .../RUNBOOK-machinefall-120fps-with-fab-quality.md | 2 +- 5 files changed, 8 insertions(+), 9 deletions(-) rename docs/PRDs/{open-world => done}/PRD-478-open-world-frame-architecture.md (99%) diff --git a/docs/PRDs/assets/PRD-541-card-lod-levels-keep-the-canopy.md b/docs/PRDs/assets/PRD-541-card-lod-levels-keep-the-canopy.md index 02fb201606..6ce2c556b1 100644 --- a/docs/PRDs/assets/PRD-541-card-lod-levels-keep-the-canopy.md +++ b/docs/PRDs/assets/PRD-541-card-lod-levels-keep-the-canopy.md @@ -4,7 +4,7 @@ **Priority:** P1 — Open: PRD-478's walking GPU p95 on Machinefall needs these trees to have a chain, and today a chain thins their needles visibly. **Complexity:** 5 (MEDIUM) — one cook stage (`packages/assets/src/lod/`), the model pass's self-check, a Machinefall re-cook with timing and blind raters **Owner:** João -**Depends on:** none. [PRD-478](../open-world/PRD-478-open-world-frame-architecture.md)'s AC-2 and AC-3 depend on this PRD. +**Depends on:** none. [PRD-478](../done/PRD-478-open-world-frame-architecture.md)'s AC-2 and AC-3 depend on this PRD. ## Context diff --git a/docs/PRDs/open-world/PRD-478-open-world-frame-architecture.md b/docs/PRDs/done/PRD-478-open-world-frame-architecture.md similarity index 99% rename from docs/PRDs/open-world/PRD-478-open-world-frame-architecture.md rename to docs/PRDs/done/PRD-478-open-world-frame-architecture.md index a279379431..81ef3d9778 100644 --- a/docs/PRDs/open-world/PRD-478-open-world-frame-architecture.md +++ b/docs/PRDs/done/PRD-478-open-world-frame-architecture.md @@ -1,6 +1,6 @@ # PRD-478 — Open-world frame architecture: walking at 120 fps -**Status:** IN PROGRESS +**Status:** DONE **Priority:** P1 — Open: walking CPU and GPU p95 targets with deterministic map-walk iteration tooling. **Complexity:** 8 (HIGH) — 10+ engine files (+3), worker boundary and GPU-driven shadow levels are new mechanisms (+3), engine and Machinefall release separately (+2); risk override: none **Owner:** João @@ -77,7 +77,6 @@ Change where the frame's work runs, not how much of it a frame skips: ## Blocked on -- Phase 1's coverage box: the owner's side-by-side review of the camp and highway aerials against `?refShadow=1`. - Bare far hills (owner, 2026-10-08): PRD-537 (draft PR 459), whole-map impostors; Phase 1 measurement is running, and its dark impostor shadow is that PRD's Phase 2. ## Iteration method @@ -93,12 +92,12 @@ A timing round under PRD-475 cost 1–3 hours, most of it waiting for a quiet ma ## Execution Phases #### Phase 1: Shadow windows move only when the camera does -**Status:** IN PROGRESS +**Status:** DONE **Files:** `packages/core/src/render/virtual-shadow-pages.ts`, `packages/core/src/render/virtual-shadow.ts`; `packages/core/__tests__/`; Machinefall `playtests/scenes/map-walk.playtest.json` - [x] The iteration tooling above works on map-walk: two runs of one build with the same flag report the same level re-render count, and the paired walk compares the arms frame by frame over the movement interval. proof: two same-arm runs with equal counters plus one paired-walk report. **Pass 2026-10-07:** `scripts/playtest-pair.ts` (`f9ee2ec71`) is the paired-walk report; it compares two reports at every labelled step (every 24 ticks, not every frame). Two counter fixes were needed: `stats.windowSteps` (`47b5b8664`) counts window travel in pages, so frame sampling cannot change it, and `?tnAdaptiveRefresh=0` (`ce9f7ccf3`) fixes the refresh step, which otherwise follows measured render cost. Same build `fc9d56c1a`, keys on, `world-capture` twice on the quiet laptop (Intel Iris Xe, load ~0.6): both runs 10/10; camera equal on every label; walk totals equal, `shadowByMove` 5|5 and `windowSteps` 2108|2108 at `drain-loads`. Caveat: during the walk, 3 window moves land one label apart (walk-08, walk-24, walk-25), because the window follows the view focus, which a ray finds against terrain that streams in on its own timing. Compare walk totals, not per-label counts. - 2026-10-03 (runbook A3): Machinefall `feat/world-gate-capture` `d80b24d` publishes `shadowByMove`/`shadowByInvalidation`/`shadowRenders` on the `world` entity, read at labelled ticks of `world-capture.playtest.json`. Two develop runs (RTX 2080 WebGPU, CI load ~19): `byMove` walk-16 2/2, walk-32 4/4 — repeatable; `byInvalidation` 39/32 and 69/62 — streaming-timed, so it cannot be a same-arm-equal counter. The end-of-scenario `TN_VIRTUAL_SHADOW` line is not comparable across runs (it lands at frame 2100 vs 3600). Open: the box's "level re-render count" must be scoped to `byMove` (the window-moves mechanism this phase owns), and the paired per-label report is a jq over two reports, not yet a script. - [x] A refresh-step change with a stationary camera neither moves a level's window nor re-renders it; a real move of step + 1 texel re-renders; every origin stays on the per-level texel grid. proof: red-green `virtual-shadow-pages.spec.ts` and `virtual-shadow.spec.ts` cases (unit-green on `feat/prd-478-shadow-snap` `748348c9c`, not yet on this branch). **Pass 2026-10-05 on this branch:** the five cases from `feat/prd-478-shadow-snap` `748348c9c` are present and green here. In `virtual-shadow-pages.spec.ts`: "keeps every origin on its texel grid with independent refresh thresholds", "does not move a stationary window when its refresh threshold shrinks below its drift", and "changes only the requested level when a scalar refresh step was supplied". In `virtual-shadow.spec.ts`: "should keep stationary windows cached when their refresh steps change" and "should re-render after moving exactly a refresh step plus one texel". Result: 112/112 across the shadow spec files. The code arrived through the #423 consolidation; the cherry-pick added only `done/PRD-478-shadow-refresh-snap.md`. -- [ ] Shadow levels cover what the view sees: their windows follow the view's focus (where the view meets the ground), not the eye, so an aerial view keeps fine levels on what it frames. A tall caster's shadow does not end at a level's guard square. The camp and highway aerial views match the stock-shadow reference (one 4096² map over ±250 m) within the blind judge and the owner's side-by-side review. proof: red-green spec on the window centre for a raised camera, plus the `?refShadow=1` reference triptychs on #401. +- [x] Shadow levels cover what the view sees: their windows follow the view's focus (where the view meets the ground), not the eye, so an aerial view keeps fine levels on what it frames. A tall caster's shadow does not end at a level's guard square. The camp and highway aerial views match the stock-shadow reference (one 4096² map over ±250 m) within the blind judge and the owner's side-by-side review. proof: red-green spec on the window centre for a raised camera, plus the `?refShadow=1` reference triptychs on #401. **Passed 2026-10-09, owner review:** the merge build (`lodfix11`) against `?refShadow=1` rendered from the same build, `map-views` (camp overview, highway air, bridge deck, bridge from river): "left looks worse, right looks better" — the virtual shadow is preferred to the stock 4096² reference. Sheet: `~/Downloads/threenative-pr473/aerial-review-ref-vs-pr473.jpg` (local, owner's machine). - 2026-10-07 reference run, box still open: a stock-shadow reference was rebuilt in the capture copy (`?refShadow=1`: one 4096² stock map over ±250 m, near 1, far 800, every layer visible, `tnShadowGpuKeys=0`; a first attempt that saw only layer 0 left the loading gate up and was discarded). `map-views` against it, blind: this PR (`fc9d56c1a`, keys on) matches at camp overview and bridge deck; highway air is "mostly" (forest-floor tree shadows lighter and sparser) and bridge from river is "mostly" (one extra dark area). Develop: camp overview "no" (stair-step bands, missing left-forest shadow), highway air matches, bridge deck "mostly", river "no". A trial that took the unbiased distance for shadow LOD (`9aa9d9791`) lost a direct blind comparison 1–4 (3 ties) with denser-than-reference shadows and is reverted (`fe9d0540c`). Left: highway air, and the owner's side-by-side (Blocked on). - [x] Measured on map-walk against cut12n and cut12n with `adaptiveRefresh: false`: walking render p95, GPU p95 and level re-render rate, plus AC-3's blind A/B and pop series. proof: `TN_FRAME_BUDGET` walk/idle split, `?tnFrameSpans=1`, `scripts/visual-ab.ts`. **Measured 2026-10-08 against today's build with `adaptiveRefresh` on and off** (see Decisions): desktop RTX 2080, live clock, `map-walk`, load 6–30 at run start, build `35e5e7f24`; walking windows ≥ 80 % key-held; three runs voided by `net::ERR_NETWORK_CHANGED`. Phase 1 path (`tnShadowGpuKeys=0`, 2 runs): render p95 median 4.2 / 9.5, max 20.5 / 14.6 ms; GPU p95 max 8.3 / 9.5; shadow GPU p95 max 5.5 / 6.2; level renders 219 / 215. Phase 2 path (keys on, 2 runs): render median 11.4 / 6.2, max 14.9 / 14.8; GPU max 7.5 / 10.7; shadow GPU max 3.9 / 5.7; renders 200 / 211. Keys on with `?tnAdaptiveRefresh=0` (3 runs): render median 6.3 / 7.2 / 5.3, max 14.9 / 8.7 / 13.0; GPU max 6.0 / 3.1 / 9.1; renders 225 / 284 / 209. At this load the arms do not separate beyond run-to-run noise on render or total GPU. The keyed shadow is cheaper at its p95, and a fixed step re-renders more. Blind A/B: 24 of 24 votes at or above develop. Pop series: 1 one-arm pop against develop's 4 (notes under AC-3). - [x] No shadow without its tree at the first playable frame: the adaptive LOD bias holds until the world is prewarmed and the main pass draws it. proof: red-green spec `world-gpu-scene.spec.ts` "holds the bias through the load and rises once the main pass draws the world"; Machinefall start pose with and without the fix (pending GPU capture). Second cause, found after `0386c861c`: the gate table names every level of an asset from the moment it is adopted and fills in the keys the prewarm has minted so far (measured on a four-level chain: `parts=2,0,0,0`, then `2,2,0,0`, `2,2,2,0`), so a level the biased distance names had no keys and the dispatch drew the placement nowhere while the CPU path drew it and its shadow halves drew under it; the selection now takes the last level that has keys. proof: red-green spec `world-gpu-scene.spec.ts` "draws the last source level that has keys when the biased gate names one that has none" (red `expected +0 to be 1`, green after `world-gpu-scene.ts` `drawableLevel`). **GPU capture done 2026-10-05:** the start pose with and without this PR's fix (`558f08cbf`, caster admission plus the cached-level redraw) on Machinefall, 12 captures per matrix, merge on and off, nvidia/turing. Develop drew canopy shadows with no trees in some runs. The fix was clean in all of its runs per a blind judge and the owner's reviewer, with the floor brightness at develop's level (212 vs 211). The two specs above pass. diff --git a/docs/PRDs/done/PRD-489-gpu-scene-occlusion-culling.md b/docs/PRDs/done/PRD-489-gpu-scene-occlusion-culling.md index 7d5bc6b3ad..e573ca9f4e 100644 --- a/docs/PRDs/done/PRD-489-gpu-scene-occlusion-culling.md +++ b/docs/PRDs/done/PRD-489-gpu-scene-occlusion-culling.md @@ -4,7 +4,7 @@ **Priority:** P2 — Re-test unbuilt: gpuMain p95 target, no-visual-loss A/B, measurement mode. **Complexity:** 5 (MEDIUM) — 1–5 engine files (+1), a depth pyramid is a new mechanism (+2), previous-frame visibility is temporal GPU state (+2); risk override: none **Owner:** João -**Depends on:** [PRD-478](../open-world/PRD-478-open-world-frame-architecture.md) (its Phase 2 moves shadow levels onto GPU-scene keys; occlusion must not reach them), [PRD-477](../open-world/PRD-477-worldcells-auto-on-measured-budgets.md) (the pop gate) +**Depends on:** [PRD-478](PRD-478-open-world-frame-architecture.md) (its Phase 2 moves shadow levels onto GPU-scene keys; occlusion must not reach them), [PRD-477](../open-world/PRD-477-worldcells-auto-on-measured-budgets.md) (the pop gate) ## Context @@ -12,7 +12,7 @@ No occlusion culling exists: no depth pyramid, no HZB, nothing in `packages/core - [PRD-284](../done/nanite-like/PRD-284-the-frame-does-not-draw-what-the-frame-already-hid.md) declined two-pass occlusion on the quarry because the clustered arm's whole GPU cost was 1.28 ms at 1080p; it left open how much of three's WebGPU path a depth pyramid can reach without a fork, and said reopening needs "a scene where the cut is still expensive". - WORLD-STREAMING.md lists occlusion culling as out of scope for 2 km worlds. -Machinefall's `?scene=map-walk` may be that scene. [PRD-475](../open-world/PRD-475-open-world-120-fps-without-visual-loss.md) records the main pass drawing 16–27 M triangles, with GPU p95 11.3 ms (main 5.6 ms) against an 8.3 ms target. Since PRD-473, the world's scattered props are drawn by the GPU scene in `packages/core/src/world-gpu-scene.ts` (`WorldGpuScene`, on by default for `WorldCells`): a compute kernel (`#buildKernel`, dispatched in `renderer.compute(kernel.cull)`) tests each placement's bounding sphere against six frustum planes, picks a level by distance, and writes the indirect args. That kernel is the place an occlusion test goes. It only removes GPU work, though: a GPU-scene key with zero visible instances still submits its indirect draw, so occlusion saves no per-draw JS, which is the CPU tail [PRD-478](../open-world/PRD-478-open-world-frame-architecture.md) is working on. +Machinefall's `?scene=map-walk` may be that scene. [PRD-475](../open-world/PRD-475-open-world-120-fps-without-visual-loss.md) records the main pass drawing 16–27 M triangles, with GPU p95 11.3 ms (main 5.6 ms) against an 8.3 ms target. Since PRD-473, the world's scattered props are drawn by the GPU scene in `packages/core/src/world-gpu-scene.ts` (`WorldGpuScene`, on by default for `WorldCells`): a compute kernel (`#buildKernel`, dispatched in `renderer.compute(kernel.cull)`) tests each placement's bounding sphere against six frustum planes, picks a level by distance, and writes the indirect args. That kernel is the place an occlusion test goes. It only removes GPU work, though: a GPU-scene key with zero visible instances still submits its indirect draw, so occlusion saves no per-draw JS, which is the CPU tail [PRD-478](PRD-478-open-world-frame-architecture.md) is working on. A newer probe says occlusion is not on the 120 fps critical path today. Source: probe 2026-10-03, scratch, 3 runs, noisy host (develop `4d5e07c98`, RTX 2080, headed WebGPU nvidia/turing, 1280×720, MSAA 4). diff --git a/docs/PRDs/open-world/PRD-488-foliage-moves-in-the-wind.md b/docs/PRDs/open-world/PRD-488-foliage-moves-in-the-wind.md index 4be8ff473b..1074af7fcc 100644 --- a/docs/PRDs/open-world/PRD-488-foliage-moves-in-the-wind.md +++ b/docs/PRDs/open-world/PRD-488-foliage-moves-in-the-wind.md @@ -4,7 +4,7 @@ **Priority:** P2 — Foliage is static; shared world origin across batches and a native row are unbuilt. **Complexity:** 4 (MEDIUM) — 6–10 files (+2: `render/world-impostor-surface.ts`, `world-gpu-scene.ts`, one exported core node, a conformance case, the shooter's `palm.ts` and a new `wind.ts`); engine and Machinefall release separately (+2); risk override: none **Owner:** João -**Depends on:** [PRD-478](PRD-478-open-world-frame-architecture.md) (shadow levels re-render only when the camera moves; its map-walk timing method is reused here). Related: [PRD-475](PRD-475-open-world-120-fps-without-visual-loss.md) (the 120 fps / no-visual-loss bar), [PRD-487](../Unreal/assets/PRD-487-imported-materials-keep-their-detail.md) (Unreal `T_WindNoise` reaches `userData` there) +**Depends on:** [PRD-478](../done/PRD-478-open-world-frame-architecture.md) (shadow levels re-render only when the camera moves; its map-walk timing method is reused here). Related: [PRD-475](PRD-475-open-world-120-fps-without-visual-loss.md) (the 120 fps / no-visual-loss bar), [PRD-487](../Unreal/assets/PRD-487-imported-materials-keep-their-detail.md) (Unreal `T_WindNoise` reaches `userData` there) ## Context diff --git a/docs/PRDs/open-world/RUNBOOK-machinefall-120fps-with-fab-quality.md b/docs/PRDs/open-world/RUNBOOK-machinefall-120fps-with-fab-quality.md index b1400d64d8..40b1a4cf8b 100644 --- a/docs/PRDs/open-world/RUNBOOK-machinefall-120fps-with-fab-quality.md +++ b/docs/PRDs/open-world/RUNBOOK-machinefall-120fps-with-fab-quality.md @@ -4,7 +4,7 @@ Created 2026-10-03, after #390 merged. **Goal.** Two outcomes: -- (A) Machinefall `?scene=map-walk` **walks** at CPU and GPU p95 ≤ 8.3 ms on the RTX 2080 WebGPU adapter, with no visual loss. These are [PRD-478](PRD-478-open-world-frame-architecture.md)'s AC-1, AC-2 and AC-3. +- (A) Machinefall `?scene=map-walk` **walks** at CPU and GPU p95 ≤ 8.3 ms on the RTX 2080 WebGPU adapter, with no visual loss. These are [PRD-478](../done/PRD-478-open-world-frame-architecture.md)'s AC-1, AC-2 and AC-3. - (B) A cold agent's Fab or Megascans game ships its assets at full quality through the default cook, with no hand-set knobs, and stays inside that frame budget. **Stop condition.** Every row below is ticked, or sits under an owner gate that is recorded as asked. From 5c5ec7ba65a57ea99176669c2a75fdc520bb2c0a Mon Sep 17 00:00:00 2001 From: Joao Paulo Furtado Date: Mon, 12 Oct 2026 21:31:24 -0700 Subject: [PATCH 2/3] docs(prd-478): reopen incomplete evidence --- ...PRD-541-card-lod-levels-keep-the-canopy.md | 6 ++--- .../PRD-489-gpu-scene-occlusion-culling.md | 4 ++-- .../PRD-478-open-world-frame-architecture.md | 24 ++++++++++++++++--- .../PRD-488-foliage-moves-in-the-wind.md | 2 +- ...OOK-machinefall-120fps-with-fab-quality.md | 4 ++-- 5 files changed, 29 insertions(+), 11 deletions(-) rename docs/PRDs/{done => open-world}/PRD-478-open-world-frame-architecture.md (94%) diff --git a/docs/PRDs/assets/PRD-541-card-lod-levels-keep-the-canopy.md b/docs/PRDs/assets/PRD-541-card-lod-levels-keep-the-canopy.md index 6ce2c556b1..2b1fbc213b 100644 --- a/docs/PRDs/assets/PRD-541-card-lod-levels-keep-the-canopy.md +++ b/docs/PRDs/assets/PRD-541-card-lod-levels-keep-the-canopy.md @@ -4,7 +4,7 @@ **Priority:** P1 — Open: PRD-478's walking GPU p95 on Machinefall needs these trees to have a chain, and today a chain thins their needles visibly. **Complexity:** 5 (MEDIUM) — one cook stage (`packages/assets/src/lod/`), the model pass's self-check, a Machinefall re-cook with timing and blind raters **Owner:** João -**Depends on:** none. [PRD-478](../done/PRD-478-open-world-frame-architecture.md)'s AC-2 and AC-3 depend on this PRD. +**Depends on:** none. [PRD-478](../open-world/PRD-478-open-world-frame-architecture.md)'s AC-2 and AC-3 depend on this PRD. ## Context @@ -42,8 +42,8 @@ Layer: the cook (`packages/assets/`). The game sets nothing; the scale is the ke - [x] AC-1: every ratio card level ships ≥ 0.95 of LOD0's card area, read back from the cooked bytes; the terminal level ships min(1, 4 × keep). proof: `foliage-lod.spec.ts` — 8 passed; red before the change: `expected 0.5162100266415837 to be greater than or equal to 0.95` - [x] AC-2: Machinefall `map-walk` route GPU p95 passes the pooled walking bound (≤ 5 % of walking frames over 8.3 ms) on 3 quiet runs. proof: `TN_FRAME_BUDGET` windows, quiet desktop RTX 2080 — **passed 2026-10-09** on the clamp build (`lodfix11`), 3 quiet runs: 5/911, 3/1186, 4/988 walking GPU frames over 8.3 ms, pooled 12/3085 = 0.39 %. -- [ ] AC-3: no visual loss against PRD-478's tip. proof: 3 fresh blind raters at or above on the 4 `map-walk` and 4 `map-views` poses, and a pop series with no candidate-only late object — 24/24 votes at or above (20 prefer, 4 ties); `world-capture` 34 frames, 2 fresh blind judges: 0 candidate pops in 33 intervals, 1 base pop (interval 00, a distant tree cluster appears). Laptop Intel GPU, 2026-10-08. Sheets: PR 463. - - Reopened 2026-10-08: the evidence above is the build where every level cast; the design is now the clamp (`04400564a`), so the raters and pop series run again on that build. +- [ ] AC-3: no visual loss against PRD-478's tip. proof: 3 fresh blind raters at or above on the 4 `map-walk` and 4 `map-views` poses, and a pop series with no candidate-only late object. **Remains open after repair 2026-10-13:** the 2026-10-08 24/24 vote and 33-interval pop record predates final clamp `04400564a` and is historical context only. The current-core candidate site is prepared at `~/offload/sites/prd478-finalclamp-site` (bundle SHA-256 `cdb44459c84b278d400f29b8a43f3521c4995c688bb7f25b98e401a86b80d534`) against the existing develop baseline `~/offload/out/pop-dev`; its capture is queued behind existing GPU leases. No rating is claimed until 3 fresh blind raters judge the 8-pose bundle and the pop series. + - **Resume:** the queued job is `prd478-finalclamp-visual-20261013`; use `OFFLOAD=/home/joao/.codex/skills/laptop-offload/offload.sh; "$OFFLOAD" wait prd478-finalclamp-visual-20261013 1800`, then follow PRD-478's exact manifest, `visuals:world`, and `visuals:ab` commands. Score only with 3 fresh verdict files. Phase 3 stays NOT STARTED. ## Execution Phases diff --git a/docs/PRDs/done/PRD-489-gpu-scene-occlusion-culling.md b/docs/PRDs/done/PRD-489-gpu-scene-occlusion-culling.md index e573ca9f4e..7d5bc6b3ad 100644 --- a/docs/PRDs/done/PRD-489-gpu-scene-occlusion-culling.md +++ b/docs/PRDs/done/PRD-489-gpu-scene-occlusion-culling.md @@ -4,7 +4,7 @@ **Priority:** P2 — Re-test unbuilt: gpuMain p95 target, no-visual-loss A/B, measurement mode. **Complexity:** 5 (MEDIUM) — 1–5 engine files (+1), a depth pyramid is a new mechanism (+2), previous-frame visibility is temporal GPU state (+2); risk override: none **Owner:** João -**Depends on:** [PRD-478](PRD-478-open-world-frame-architecture.md) (its Phase 2 moves shadow levels onto GPU-scene keys; occlusion must not reach them), [PRD-477](../open-world/PRD-477-worldcells-auto-on-measured-budgets.md) (the pop gate) +**Depends on:** [PRD-478](../open-world/PRD-478-open-world-frame-architecture.md) (its Phase 2 moves shadow levels onto GPU-scene keys; occlusion must not reach them), [PRD-477](../open-world/PRD-477-worldcells-auto-on-measured-budgets.md) (the pop gate) ## Context @@ -12,7 +12,7 @@ No occlusion culling exists: no depth pyramid, no HZB, nothing in `packages/core - [PRD-284](../done/nanite-like/PRD-284-the-frame-does-not-draw-what-the-frame-already-hid.md) declined two-pass occlusion on the quarry because the clustered arm's whole GPU cost was 1.28 ms at 1080p; it left open how much of three's WebGPU path a depth pyramid can reach without a fork, and said reopening needs "a scene where the cut is still expensive". - WORLD-STREAMING.md lists occlusion culling as out of scope for 2 km worlds. -Machinefall's `?scene=map-walk` may be that scene. [PRD-475](../open-world/PRD-475-open-world-120-fps-without-visual-loss.md) records the main pass drawing 16–27 M triangles, with GPU p95 11.3 ms (main 5.6 ms) against an 8.3 ms target. Since PRD-473, the world's scattered props are drawn by the GPU scene in `packages/core/src/world-gpu-scene.ts` (`WorldGpuScene`, on by default for `WorldCells`): a compute kernel (`#buildKernel`, dispatched in `renderer.compute(kernel.cull)`) tests each placement's bounding sphere against six frustum planes, picks a level by distance, and writes the indirect args. That kernel is the place an occlusion test goes. It only removes GPU work, though: a GPU-scene key with zero visible instances still submits its indirect draw, so occlusion saves no per-draw JS, which is the CPU tail [PRD-478](PRD-478-open-world-frame-architecture.md) is working on. +Machinefall's `?scene=map-walk` may be that scene. [PRD-475](../open-world/PRD-475-open-world-120-fps-without-visual-loss.md) records the main pass drawing 16–27 M triangles, with GPU p95 11.3 ms (main 5.6 ms) against an 8.3 ms target. Since PRD-473, the world's scattered props are drawn by the GPU scene in `packages/core/src/world-gpu-scene.ts` (`WorldGpuScene`, on by default for `WorldCells`): a compute kernel (`#buildKernel`, dispatched in `renderer.compute(kernel.cull)`) tests each placement's bounding sphere against six frustum planes, picks a level by distance, and writes the indirect args. That kernel is the place an occlusion test goes. It only removes GPU work, though: a GPU-scene key with zero visible instances still submits its indirect draw, so occlusion saves no per-draw JS, which is the CPU tail [PRD-478](../open-world/PRD-478-open-world-frame-architecture.md) is working on. A newer probe says occlusion is not on the 120 fps critical path today. Source: probe 2026-10-03, scratch, 3 runs, noisy host (develop `4d5e07c98`, RTX 2080, headed WebGPU nvidia/turing, 1280×720, MSAA 4). diff --git a/docs/PRDs/done/PRD-478-open-world-frame-architecture.md b/docs/PRDs/open-world/PRD-478-open-world-frame-architecture.md similarity index 94% rename from docs/PRDs/done/PRD-478-open-world-frame-architecture.md rename to docs/PRDs/open-world/PRD-478-open-world-frame-architecture.md index 81ef3d9778..131a079faa 100644 --- a/docs/PRDs/done/PRD-478-open-world-frame-architecture.md +++ b/docs/PRDs/open-world/PRD-478-open-world-frame-architecture.md @@ -1,6 +1,6 @@ # PRD-478 — Open-world frame architecture: walking at 120 fps -**Status:** DONE +**Status:** IN PROGRESS — AC-1 and AC-3 reopened 2026-10-13 after review found that the archived evidence did not cover the final comparison contract. **Priority:** P1 — Open: walking CPU and GPU p95 targets with deterministic map-walk iteration tooling. **Complexity:** 8 (HIGH) — 10+ engine files (+3), worker boundary and GPU-driven shadow levels are new mechanisms (+3), engine and Machinefall release separately (+2); risk override: none **Owner:** João @@ -55,7 +55,14 @@ Change where the frame's work runs, not how much of it a frame skips: ## Acceptance Criteria -- [x] AC-1 [local]: Machinefall `?scene=map-walk` walking CPU render p95 ≤ 8.3 ms. Read as the walk/idle split of `TN_FRAME_BUDGET` windows, where the walk mask is logic-tick/key-held intervals joined on `counters.simulationTick` (an earlier `fps < 100` mask is only a slow mask on older logs and cannot support a walking claim), over 3 interleaved runs against develop on a quiet RTX 2080. proof: `TN_FRAME_BUDGET` windows from `playtest perf`. **Passed 2026-10-09** on the merge build (`lodfix11`: PR 473 with the PRD-549 scaler and shadow fixes), 3 quiet RTX 2080 runs (start/end/10 s peak load < 10; peaks 8.2, 8.2, 7.6): walking render frames over 8.3 ms 509/7200 (7.1 %), 291/9600 (3.0 %), 306/9900 (3.1 %), pooled 1106/26 700 = 4.1 % ≤ 5 %, so the pooled walking p95 is ≤ 8.3 ms; GPU 12/3085 = 0.39 %. Exact `overTarget` counts (`exact_share.py`). Deviation: the develop arm was not interleaved; the bar is absolute. +- [ ] AC-1 [local]: Machinefall `?scene=map-walk` walking CPU render p95 ≤ 8.3 ms. Read as the walk/idle split of `TN_FRAME_BUDGET` windows, where the walk mask is logic-tick/key-held intervals joined on `counters.simulationTick` (an earlier `fps < 100` mask is only a slow mask on older logs and cannot support a walking claim), over 3 interleaved runs against develop on a quiet RTX 2080. proof: `TN_FRAME_BUDGET` windows from `playtest perf`. **Reopened 2026-10-13:** the cited 2026-10-09 record is candidate-only and explicitly says the develop arm was not interleaved, so it does not satisfy this criterion. No replacement run was started because the desktop RTX 2080/WebGPU capture lease is held by the active PRD-473 worker; no timing result is carried forward. + - **Exact resume command:** after the PRD-473 lease is released, alternate develop and candidate for three pairs with the shipped runner, keeping `SCENARIO`, `SITE`, `PORT`, and `OUT` distinct for each arm: + ```sh + CAPTURE_LOCK=1 CAPTURE_LOCK_TIMEOUT_MS=3600000 node packages/playtest/dist/runner/cli.js "${SCENARIO}/map-walk.playtest.json" --url "http://127.0.0.1:${PORT}/?scene=map-walk&tnFrameSpans=1" --server-command "python3 -m http.server ${PORT} --bind 127.0.0.1 --directory ${SITE}" --browser-recipe webgpu --headed --live-clock --timeout 900000 --artifacts "${OUT}" >"${OUT}/report.json" 2>"${OUT}/stderr.log" + jq -r '.. | objects | select(has("text")) | .text' "${OUT}/console.json" >"${OUT}/console.log" + node packages/playtest/dist/runner/cli.js perf --file "${OUT}/console.log" --text + ``` + Keep the load below 10 at start, end, and each 10-second sample; verify the RTX 2080 in `adapter.info`; report CPU and GPU p95 for each arm and the pooled walking mask joined by `counters.simulationTick`. - [x] AC-2 [local]: the same walk's GPU p95 ≤ 8.3 ms, from timestamp-query `gpuMain + gpuShadow + gpuOther + gpuCompute`. proof: the same windows. **Pass 2026-10-08:** 3 interleaved runs against develop on the desktop RTX 2080 (live clock), walking-window GPU p95 max 6.57 / 5.72 / 6.43 ms; the largest walking-window p95 bounds the pooled one. Caveats: start load 11–19, not idle, and another process used 2–22 % of the GPU, which can only inflate these numbers; GPU p95s come from the frames whose timestamps resolved in full (about 1 in 10). Run 3 also carries `7709c2a72`, a CPU-only change. **Pass 2026-10-08 (re-ticked):** 3 runs of this branch at `52d5e6cac` on the desktop RTX 2080, live clock, each kept only with load under 10 at start, end and every 10 s (peaks 8.2, 4.0, 4.3), 300-frame windows, walking windows ≥ 80 % key-held. A pooled walking GPU p95 is ≤ 8.3 ms when at most 5 % of walking GPU samples exceed it; bounded from window quantiles (≤ 1 % past a window's p99, ≤ 5 % past its p95, ≤ 50 % past its p50), that share is at most 3.5 %, 3.2 % and 4.4 %. Candidate-only runs under the load gate; develop's comparison is the interleaved 2026-10-07 runs (GPU p95 9.2–9.9 ms). - **2026-10-08 candidate with PRD-541, 1 of 3 quiet runs:** load peak 5.6 over the run (now sampled every 10 s). AC-2 bound: at most 2.3 % of walking frames over 8.3 ms GPU (pass ≤ 5 %); route GPU p95 max 8.04 ms. AC-1 bound: 22.7 % over 8.3 ms render; route render p95 windows reach 15.6, 9.0 and 9.0 ms early in the route, FPS per 1500-frame window 107–418. A span run under load 30–40 ranks the same CPU spans as before: `mainPass`, then `compute`, then `sceneUpdate`. - **2026-10-08 quiet runs, AC-1 and AC-2 both still fail, only in the early route:** 3 live-clock runs of this PR (`b3c95a5a3` core, keys on, 1500-frame windows) on the desktop RTX 2080, each kept only when load1 was under 10 at both start and end (2.1–5.3 at start, 5.9–8.8 at end; 2 of 5 runs discarded for load). Over the route windows (ticks 470–6640): render p95 per window median 5.3 / 5.5 / 5.9 ms, max 10.9 / 11.5 / 10.8 ms, the max in the first route window each time; GPU p95 median 5.2 / 5.3 / 5.2 ms, max 9.4 / 9.2 / 9.8 ms, again the first route window (run 3 also has 9.7 ms in its second window). Every window after the first two is under 8.3 ms on GPU; on CPU, 3 later windows reach 8.6–9.7 ms. The load caveat on the earlier evidence is gone: what is left is the streaming-heavy start of the route, which PRD-538 targets for GPU. @@ -67,7 +74,18 @@ Change where the frame's work runs, not how much of it a frame skips: - 2026-10-08 first follow-up cut, `35e5e7f24`: each GPU-scene dispatch submits its clear and cull kernels in one `renderer.compute([...])` call, so three opens one compute pass and one timestamp query instead of two (red-green `world-gpu-scene.spec.ts`; 22 spec files, 402 passed). `map-walk` 20/20 on the third capture; the first two lost loads to `net::ERR_NETWORK_CHANGED` and one fetch, not to the change, and the highway frame matches the earlier capture. One live-clock run on the RTX 2080 (load 23–30, 23 walking windows): `compute` mean 0.55 ms (0.81–1.80 before), `mainPass` p95 max 7.2 ms (8.3–11.5 before), walking render p95 median 6.9 ms, max 13.6 ms. AC-1 still fails on the max; a single run is not a pair. - 2026-10-08 second cut, `1fe8b3087`: three's WebGPU backend issues one `queue.writeBuffer` per update range and never merges them, and the GPU scene registered one range per placement and key, so a cell admission was one write per placement inside `renderer.compute`. `addRange` extends the previous range when the new one touches it (red-green `world-gpu-scene.spec.ts` `addRange`; 213 passed). `map-walk` 20/20. Wide-window live-clock runs (1500 frames, keys on): `compute` mean 0.40–0.43 ms against 0.73–0.94 before; render p95 per route window median 4.7–5.0 ms, max 10.7–14.7 ms (before: median 11.1–11.8, max 15.6–19.9 ms, at higher load 16–33 against 7–13, so part of that drop is load); GPU p95 max 9.1–10.0 ms, still in the early route, where streaming invalidations re-render the shadow level about twice a second (200–280 level renders per walk, against 5 window moves). Why so many: Machinefall has one ±250 m level, and the default `invalidationDelay` scales relative to the finest level (`0.25 * extent / finestExtent`), so a single large level gets the finest level's 0.25 s and up to four full 4096² re-renders a second from streaming. A longer delay would trade those GPU spikes for shadows arriving late near the player, which AC-3 forbids. The fix that keeps both is a partial map update that re-renders only the invalidated region. That is a new mechanism, and it is the next step for AC-2. - 2026-10-08, most of those re-renders came from invalidations no shadow needed. Counting one walk's 3,800 region invalidations by caller: 1,409 were TerrainTiles block meshes entering or leaving the tree (swap twins over the same ground), 1,025 WorldCells admissions (largely meshes that cast nothing), and 391 bundle and group re-records. The caster poll also counted 4,500 flag flips, 3,568 of them terrain level-mesh `visible` swaps. Two cuts: `5a6d9d02f` marks terrain meshes `tnShadowSwap` and the poll ignores their `visible` bit; `ed75639ef` skips a non-casting or swap-twin mesh on the tree-change path (red-green specs for both; 24 spec files, 460 passed). Interleaved A/B, previous build against `ed75639ef`, 2 pairs, all 19/19, zero console errors: level renders per walk 241 and 213 against 68 and 79 (−68 %). Render and GPU p95 are within noise; the desktop sat at load 18–26 with other GPU users. `map-walk` 20/20; a blind judge finds the shadows equivalent on all four poses. PRD-538's partial redraw still matters for the renders that remain, but it is now a smaller lever. -- [x] AC-3 [local]: no visual loss. 3 fresh blind raters score every map-walk and map-views pose at or above develop `a602467db`, the pop road band matches develop, and a frame-by-frame judge finds no late objects or shadows. proof: `scripts/visual-ab.ts` score plus the pop series. — **Pass 2026-10-08** on this branch at `57c8ba8c6` (PRD-541 absorbed, cast clamp, target-frame LOD budget, prewarm gate holds for owed builds), laptop Intel GPU captures: 3 fresh blind raters score all 8 poses at or above develop `a602467db` (24/24 votes, 23 prefer, 1 tie; poses taken after loading, on `52d5e6cac`, which differs only in the loading gate); pop series (`world-capture`, 33 intervals, two runs) judged blind against the pre-absorption tip: 0 candidate pops, while the base still pops at interval 00. The road-band number is covered by that pop series; see Decisions. +- [ ] AC-3 [local]: no visual loss. 3 fresh blind raters score every map-walk and map-views pose at or above develop `a602467db`, the pop road band matches develop, and a frame-by-frame judge finds no late objects or shadows. proof: `scripts/visual-ab.ts` score plus the pop series. **Reopened 2026-10-13:** the cited 2026-10-08 4-walk/4-view ratings and pop series predate final clamp `04400564a`, so they cannot prove this criterion. A current-core post-clamp site was prepared at `~/offload/sites/prd478-finalclamp-site` with bundle SHA-256 `cdb44459c84b278d400f29b8a43f3521c4995c688bb7f25b98e401a86b80d534`; the candidate capture is queued behind existing GPU leases. No blind approval is claimed until the shipped visual bundles are scored by 3 fresh raters and the pop series is judged against develop `a602467db`. + - **Exact visual resume commands:** the queued candidate job is `prd478-finalclamp-visual-20261013`; resume it with `OFFLOAD=/home/joao/.codex/skills/laptop-offload/offload.sh; "$OFFLOAD" wait prd478-finalclamp-visual-20261013 1800`. It uses `~/offload/out/prd478-finalclamp/{map-walk,map-views,world-capture}` against develop `~/offload/out/{dev-map-walk,dev-map-views,pop-dev}`. After capture, from the engine checkout run: + ```sh + pnpm exec tsx scripts/world-capture-manifest.ts ~/offload/out/pop-dev a602467db 30 --scenario machinefall-world-capture --world Machinefall + pnpm exec tsx scripts/world-capture-manifest.ts ~/offload/out/prd478-finalclamp/world-capture c2c3eba33 30 --scenario machinefall-world-capture --world Machinefall + pnpm visuals:world --before ~/offload/out/pop-dev/world-capture.json --after ~/offload/out/prd478-finalclamp/world-capture/world-capture.json --out ~/offload/out/prd478-finalclamp/world-gate + mkdir -p ~/offload/out/prd478-finalclamp/ab-before-20261013-r1 ~/offload/out/prd478-finalclamp/ab-after-20261013-r1 + cp ~/offload/out/dev-map-walk/*.png ~/offload/out/dev-map-views/*.png ~/offload/out/prd478-finalclamp/ab-before-20261013-r1/ + cp ~/offload/out/prd478-finalclamp/map-walk/*.png ~/offload/out/prd478-finalclamp/map-views/*.png ~/offload/out/prd478-finalclamp/ab-after-20261013-r1/ + pnpm visuals:ab --before ~/offload/out/prd478-finalclamp/ab-before-20261013-r1 --after ~/offload/out/prd478-finalclamp/ab-after-20261013-r1 --out ~/offload/out/prd478-finalclamp/ab --duplicates 2 --raters 3 + ``` + Score only with three fresh verdict files; do not treat bundle creation or a builder inspection as a visual result. - **2026-10-05 evidence, box left open:** the PRD-477 world gate on Machinefall's world-capture route, develop vs this PR's tip (`752ee7f15`), 2 + 2 runs, 3 fresh blind critics: **pass, exit 0**. Same-pose 4 → 4 on every counted row, no near-band pop, no candidate-only event in every candidate run. What still fails the box's own words ("no late objects or shadows"), on develop as much as here: (1) far forest blocks beyond the dirt road stream in late at 150–300 m, at frames 28–32 in every series; (2) a pre-existing, nondeterministic stray conifer cluster about 35 m ahead from walk-01 to walk-21, seen in 1/4 develop runs, 3/4 tip runs, and 2/6 tip runs with the static-matrix change reverted. Not yet run: the map-views poses and the comparison against develop `a602467db`. - **2026-10-07 raters on `fc9d56c1a`, box still open:** all 8 poses (4 `map-walk`, 4 `map-views` from Machinefall `main`'s `playtests/views/map-views.playtest.json`) against develop `a602467db`, desktop NVIDIA Turing, private Xvfb, keys on; captures pass 20/20 and 4/4 on both arms. 3 fresh blind raters, A/B order random per pose: this PR scores at or above develop on 24 of 24 votes (23 higher, 1 tie). Largest gaps: camp overview 7–8 against 4 (develop draws straight diagonal shadow bands across the camp), bridge from river 7 against 5 (develop's ground has no shadows), highway 7–8 against 6. Sheet: #437. Still open in this box: the pop road band and pop series on this build, and the "no late objects" clause, which develop fails as much as this PR (far forest blocks stream in late at 150–300 m). PRD-537 (draft PR 459) plans whole-map far impostors, the likely fix for that clause. - **2026-10-08 with the target-frame LOD budget (`52d5e6cac`), box still open on one clause:** laptop Intel GPU. 3 fresh blind raters against develop `a602467db`: at or above develop on 24 of 24 votes (23 prefer, 1 tie). Pop series against the pre-absorption tip, 2 fresh judges: mid-walk, no candidate-only pop (three low-confidence flags, all on the base or on both). The one open clause is the start of the walk: the distant forest arrives in intervals 00–02 in every build, develop included. Repeat captures move it between those intervals (capture variance in when streaming finishes), so the clamp-build interval-01 pop above was that variance. The loading overlay drops before the visible ring has published; diagnosis running. diff --git a/docs/PRDs/open-world/PRD-488-foliage-moves-in-the-wind.md b/docs/PRDs/open-world/PRD-488-foliage-moves-in-the-wind.md index 1074af7fcc..4be8ff473b 100644 --- a/docs/PRDs/open-world/PRD-488-foliage-moves-in-the-wind.md +++ b/docs/PRDs/open-world/PRD-488-foliage-moves-in-the-wind.md @@ -4,7 +4,7 @@ **Priority:** P2 — Foliage is static; shared world origin across batches and a native row are unbuilt. **Complexity:** 4 (MEDIUM) — 6–10 files (+2: `render/world-impostor-surface.ts`, `world-gpu-scene.ts`, one exported core node, a conformance case, the shooter's `palm.ts` and a new `wind.ts`); engine and Machinefall release separately (+2); risk override: none **Owner:** João -**Depends on:** [PRD-478](../done/PRD-478-open-world-frame-architecture.md) (shadow levels re-render only when the camera moves; its map-walk timing method is reused here). Related: [PRD-475](PRD-475-open-world-120-fps-without-visual-loss.md) (the 120 fps / no-visual-loss bar), [PRD-487](../Unreal/assets/PRD-487-imported-materials-keep-their-detail.md) (Unreal `T_WindNoise` reaches `userData` there) +**Depends on:** [PRD-478](PRD-478-open-world-frame-architecture.md) (shadow levels re-render only when the camera moves; its map-walk timing method is reused here). Related: [PRD-475](PRD-475-open-world-120-fps-without-visual-loss.md) (the 120 fps / no-visual-loss bar), [PRD-487](../Unreal/assets/PRD-487-imported-materials-keep-their-detail.md) (Unreal `T_WindNoise` reaches `userData` there) ## Context diff --git a/docs/PRDs/open-world/RUNBOOK-machinefall-120fps-with-fab-quality.md b/docs/PRDs/open-world/RUNBOOK-machinefall-120fps-with-fab-quality.md index 40b1a4cf8b..d072a49dab 100644 --- a/docs/PRDs/open-world/RUNBOOK-machinefall-120fps-with-fab-quality.md +++ b/docs/PRDs/open-world/RUNBOOK-machinefall-120fps-with-fab-quality.md @@ -4,7 +4,7 @@ Created 2026-10-03, after #390 merged. **Goal.** Two outcomes: -- (A) Machinefall `?scene=map-walk` **walks** at CPU and GPU p95 ≤ 8.3 ms on the RTX 2080 WebGPU adapter, with no visual loss. These are [PRD-478](../done/PRD-478-open-world-frame-architecture.md)'s AC-1, AC-2 and AC-3. +- (A) Machinefall `?scene=map-walk` **walks** at CPU and GPU p95 ≤ 8.3 ms on the RTX 2080 WebGPU adapter, with no visual loss. These are [PRD-478](PRD-478-open-world-frame-architecture.md)'s AC-1, AC-2 and AC-3. - (B) A cold agent's Fab or Megascans game ships its assets at full quality through the default cook, with no hand-set knobs, and stays inside that frame budget. **Stop condition.** Every row below is ticked, or sits under an owner gate that is recorded as asked. @@ -226,7 +226,7 @@ this host, so it is not attributed. The prewarm (`e580e76cb`) stays off this PR, invalidation before this mechanism is retried. Details in "Where we stand". - [ ] **A8 · [PRD-539](../Unreal/performance/PRD-539-low-resolution-temporal-reconstruction-quality-and-cost.md): temporal reconstruction closes the GPU gap (successor of PRD-455).** After: A7. ⏱🌍👁 - [ ] **A9 · PRD-478 acceptance.** After: A8. ⏱🌍👁 - - Done when: AC-1, AC-2 and AC-3 are ticked on a quiet host 🙋, and PRD-478 is in `done/`. + - Done when: AC-1, AC-2 and AC-3 are ticked on a quiet host 🙋; then PRD-478 may move to `done/`. - [ ] **A10 · PRD-477 Phases 2–3 and AC-1: budgets and switches become engine decisions.** After: A9. ⏱🌍👁 - [x] **A11 · [PRD-489](../done/PRD-489-gpu-scene-occlusion-culling.md) Phase 1 only: the occlusion go/no-go.** After: A9. ⏱ - If it declines, record the decision and close the PRD. If it goes ahead, its Phases 2–3 become row A12. 🌍👁 From 48f7404f06e388aa8b749130ee1436ff1de6d911 Mon Sep 17 00:00:00 2001 From: Joao Paulo Furtado Date: Tue, 13 Oct 2026 19:55:24 -0700 Subject: [PATCH 3/3] docs(PRD-478): record failed capture retry and current resume blockers --- .../PRD-541-card-lod-levels-keep-the-canopy.md | 4 ++-- .../PRD-478-open-world-frame-architecture.md | 15 ++++++++++----- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/docs/PRDs/assets/PRD-541-card-lod-levels-keep-the-canopy.md b/docs/PRDs/assets/PRD-541-card-lod-levels-keep-the-canopy.md index 2b1fbc213b..8585028742 100644 --- a/docs/PRDs/assets/PRD-541-card-lod-levels-keep-the-canopy.md +++ b/docs/PRDs/assets/PRD-541-card-lod-levels-keep-the-canopy.md @@ -42,8 +42,8 @@ Layer: the cook (`packages/assets/`). The game sets nothing; the scale is the ke - [x] AC-1: every ratio card level ships ≥ 0.95 of LOD0's card area, read back from the cooked bytes; the terminal level ships min(1, 4 × keep). proof: `foliage-lod.spec.ts` — 8 passed; red before the change: `expected 0.5162100266415837 to be greater than or equal to 0.95` - [x] AC-2: Machinefall `map-walk` route GPU p95 passes the pooled walking bound (≤ 5 % of walking frames over 8.3 ms) on 3 quiet runs. proof: `TN_FRAME_BUDGET` windows, quiet desktop RTX 2080 — **passed 2026-10-09** on the clamp build (`lodfix11`), 3 quiet runs: 5/911, 3/1186, 4/988 walking GPU frames over 8.3 ms, pooled 12/3085 = 0.39 %. -- [ ] AC-3: no visual loss against PRD-478's tip. proof: 3 fresh blind raters at or above on the 4 `map-walk` and 4 `map-views` poses, and a pop series with no candidate-only late object. **Remains open after repair 2026-10-13:** the 2026-10-08 24/24 vote and 33-interval pop record predates final clamp `04400564a` and is historical context only. The current-core candidate site is prepared at `~/offload/sites/prd478-finalclamp-site` (bundle SHA-256 `cdb44459c84b278d400f29b8a43f3521c4995c688bb7f25b98e401a86b80d534`) against the existing develop baseline `~/offload/out/pop-dev`; its capture is queued behind existing GPU leases. No rating is claimed until 3 fresh blind raters judge the 8-pose bundle and the pop series. - - **Resume:** the queued job is `prd478-finalclamp-visual-20261013`; use `OFFLOAD=/home/joao/.codex/skills/laptop-offload/offload.sh; "$OFFLOAD" wait prd478-finalclamp-visual-20261013 1800`, then follow PRD-478's exact manifest, `visuals:world`, and `visuals:ab` commands. Score only with 3 fresh verdict files. Phase 3 stays NOT STARTED. +- [ ] AC-3: no visual loss against PRD-478's tip. proof: 3 fresh blind raters at or above on the 4 `map-walk` and 4 `map-views` poses, and a pop series with no candidate-only late object. **Remains open after repair 2026-10-13:** the 2026-10-08 24/24 vote and 33-interval pop record predates final clamp `04400564a` and is historical context only. The current-core candidate site is prepared at `~/offload/sites/prd478-finalclamp-site` (bundle SHA-256 `cdb44459c84b278d400f29b8a43f3521c4995c688bb7f25b98e401a86b80d534`) against the existing develop baseline `~/offload/out/pop-dev`. **Capture state 2026-10-13 (terminal, nothing queued, nothing live):** job `prd478-finalclamp-visual-20261013` ended terminal 1 in `~/offload/out/prd478-finalclamp` with 4 `map-views` pose PNGs, 0 `map-walk` PNGs and 0 `world-capture` PNGs; the retry `~/offload/out/prd478-finalclamp-resume/map-walk` ended terminal 2 with `locator.screenshot: Timeout 29936ms exceeded` while waiting for element stability. No active PRD-473, PRD-478 or PRD-541 capture process was found on this host; remote lease state is not verified, so the earlier lease note is withdrawn. Both artifact sets are kept. No rating is claimed until 3 fresh blind raters judge the 8-pose bundle and the pop series. + - **Next probes:** follow PRD-478's AC-3 probe list. First pin the combined PRD-473 default-on runtime with final clamp `04400564a` as the site's source, because the site matches the recorded bundle and asset-manifest hashes yet no source metadata establishes that combination, so no capture from it is acceptance evidence. Then diagnose the map-walk screenshot on the live clock and on the paused clock. Neither the ratings nor the pop series can start until a full candidate capture exists. Score only with 3 fresh verdict files. Phase 3 stays NOT STARTED. ## Execution Phases diff --git a/docs/PRDs/open-world/PRD-478-open-world-frame-architecture.md b/docs/PRDs/open-world/PRD-478-open-world-frame-architecture.md index 131a079faa..3114e1e544 100644 --- a/docs/PRDs/open-world/PRD-478-open-world-frame-architecture.md +++ b/docs/PRDs/open-world/PRD-478-open-world-frame-architecture.md @@ -55,8 +55,8 @@ Change where the frame's work runs, not how much of it a frame skips: ## Acceptance Criteria -- [ ] AC-1 [local]: Machinefall `?scene=map-walk` walking CPU render p95 ≤ 8.3 ms. Read as the walk/idle split of `TN_FRAME_BUDGET` windows, where the walk mask is logic-tick/key-held intervals joined on `counters.simulationTick` (an earlier `fps < 100` mask is only a slow mask on older logs and cannot support a walking claim), over 3 interleaved runs against develop on a quiet RTX 2080. proof: `TN_FRAME_BUDGET` windows from `playtest perf`. **Reopened 2026-10-13:** the cited 2026-10-09 record is candidate-only and explicitly says the develop arm was not interleaved, so it does not satisfy this criterion. No replacement run was started because the desktop RTX 2080/WebGPU capture lease is held by the active PRD-473 worker; no timing result is carried forward. - - **Exact resume command:** after the PRD-473 lease is released, alternate develop and candidate for three pairs with the shipped runner, keeping `SCENARIO`, `SITE`, `PORT`, and `OUT` distinct for each arm: +- [ ] AC-1 [local]: Machinefall `?scene=map-walk` walking CPU render p95 ≤ 8.3 ms. Read as the walk/idle split of `TN_FRAME_BUDGET` windows, where the walk mask is logic-tick/key-held intervals joined on `counters.simulationTick` (an earlier `fps < 100` mask is only a slow mask on older logs and cannot support a walking claim), over 3 interleaved runs against develop on a quiet RTX 2080. proof: `TN_FRAME_BUDGET` windows from `playtest perf`. **Reopened 2026-10-13:** the cited 2026-10-09 record is candidate-only and explicitly says the develop arm was not interleaved, so it does not satisfy this criterion. **State 2026-10-13:** no replacement run started and none is running. No active PRD-473, PRD-478 or PRD-541 capture process was found on this host; remote lease state is not verified, so the earlier lease note is withdrawn. Host `joao` is an Intel IrisXe (gen-12lp) laptop with no NVIDIA adapter and no desktop SSH alias, so the 3 interleaved pairs cannot run here; no timing result is carried forward. + - **Exact next probe:** ask the owner for the desktop RTX 2080 SSH address, then alternate develop and candidate for three pairs there with the shipped runner, keeping `SCENARIO`, `SITE`, `PORT`, and `OUT` distinct for each arm: ```sh CAPTURE_LOCK=1 CAPTURE_LOCK_TIMEOUT_MS=3600000 node packages/playtest/dist/runner/cli.js "${SCENARIO}/map-walk.playtest.json" --url "http://127.0.0.1:${PORT}/?scene=map-walk&tnFrameSpans=1" --server-command "python3 -m http.server ${PORT} --bind 127.0.0.1 --directory ${SITE}" --browser-recipe webgpu --headed --live-clock --timeout 900000 --artifacts "${OUT}" >"${OUT}/report.json" 2>"${OUT}/stderr.log" jq -r '.. | objects | select(has("text")) | .text' "${OUT}/console.json" >"${OUT}/console.log" @@ -74,11 +74,15 @@ Change where the frame's work runs, not how much of it a frame skips: - 2026-10-08 first follow-up cut, `35e5e7f24`: each GPU-scene dispatch submits its clear and cull kernels in one `renderer.compute([...])` call, so three opens one compute pass and one timestamp query instead of two (red-green `world-gpu-scene.spec.ts`; 22 spec files, 402 passed). `map-walk` 20/20 on the third capture; the first two lost loads to `net::ERR_NETWORK_CHANGED` and one fetch, not to the change, and the highway frame matches the earlier capture. One live-clock run on the RTX 2080 (load 23–30, 23 walking windows): `compute` mean 0.55 ms (0.81–1.80 before), `mainPass` p95 max 7.2 ms (8.3–11.5 before), walking render p95 median 6.9 ms, max 13.6 ms. AC-1 still fails on the max; a single run is not a pair. - 2026-10-08 second cut, `1fe8b3087`: three's WebGPU backend issues one `queue.writeBuffer` per update range and never merges them, and the GPU scene registered one range per placement and key, so a cell admission was one write per placement inside `renderer.compute`. `addRange` extends the previous range when the new one touches it (red-green `world-gpu-scene.spec.ts` `addRange`; 213 passed). `map-walk` 20/20. Wide-window live-clock runs (1500 frames, keys on): `compute` mean 0.40–0.43 ms against 0.73–0.94 before; render p95 per route window median 4.7–5.0 ms, max 10.7–14.7 ms (before: median 11.1–11.8, max 15.6–19.9 ms, at higher load 16–33 against 7–13, so part of that drop is load); GPU p95 max 9.1–10.0 ms, still in the early route, where streaming invalidations re-render the shadow level about twice a second (200–280 level renders per walk, against 5 window moves). Why so many: Machinefall has one ±250 m level, and the default `invalidationDelay` scales relative to the finest level (`0.25 * extent / finestExtent`), so a single large level gets the finest level's 0.25 s and up to four full 4096² re-renders a second from streaming. A longer delay would trade those GPU spikes for shadows arriving late near the player, which AC-3 forbids. The fix that keeps both is a partial map update that re-renders only the invalidated region. That is a new mechanism, and it is the next step for AC-2. - 2026-10-08, most of those re-renders came from invalidations no shadow needed. Counting one walk's 3,800 region invalidations by caller: 1,409 were TerrainTiles block meshes entering or leaving the tree (swap twins over the same ground), 1,025 WorldCells admissions (largely meshes that cast nothing), and 391 bundle and group re-records. The caster poll also counted 4,500 flag flips, 3,568 of them terrain level-mesh `visible` swaps. Two cuts: `5a6d9d02f` marks terrain meshes `tnShadowSwap` and the poll ignores their `visible` bit; `ed75639ef` skips a non-casting or swap-twin mesh on the tree-change path (red-green specs for both; 24 spec files, 460 passed). Interleaved A/B, previous build against `ed75639ef`, 2 pairs, all 19/19, zero console errors: level renders per walk 241 and 213 against 68 and 79 (−68 %). Render and GPU p95 are within noise; the desktop sat at load 18–26 with other GPU users. `map-walk` 20/20; a blind judge finds the shadows equivalent on all four poses. PRD-538's partial redraw still matters for the renders that remain, but it is now a smaller lever. -- [ ] AC-3 [local]: no visual loss. 3 fresh blind raters score every map-walk and map-views pose at or above develop `a602467db`, the pop road band matches develop, and a frame-by-frame judge finds no late objects or shadows. proof: `scripts/visual-ab.ts` score plus the pop series. **Reopened 2026-10-13:** the cited 2026-10-08 4-walk/4-view ratings and pop series predate final clamp `04400564a`, so they cannot prove this criterion. A current-core post-clamp site was prepared at `~/offload/sites/prd478-finalclamp-site` with bundle SHA-256 `cdb44459c84b278d400f29b8a43f3521c4995c688bb7f25b98e401a86b80d534`; the candidate capture is queued behind existing GPU leases. No blind approval is claimed until the shipped visual bundles are scored by 3 fresh raters and the pop series is judged against develop `a602467db`. - - **Exact visual resume commands:** the queued candidate job is `prd478-finalclamp-visual-20261013`; resume it with `OFFLOAD=/home/joao/.codex/skills/laptop-offload/offload.sh; "$OFFLOAD" wait prd478-finalclamp-visual-20261013 1800`. It uses `~/offload/out/prd478-finalclamp/{map-walk,map-views,world-capture}` against develop `~/offload/out/{dev-map-walk,dev-map-views,pop-dev}`. After capture, from the engine checkout run: +- [ ] AC-3 [local]: no visual loss. 3 fresh blind raters score every map-walk and map-views pose at or above develop `a602467db`, the pop road band matches develop, and a frame-by-frame judge finds no late objects or shadows. proof: `scripts/visual-ab.ts` score plus the pop series. **Reopened 2026-10-13:** the cited 2026-10-08 4-walk/4-view ratings and pop series predate final clamp `04400564a`, so they cannot prove this criterion. A current-core post-clamp site was prepared at `~/offload/sites/prd478-finalclamp-site`. **Capture state 2026-10-13 (terminal, nothing queued, nothing live):** job `prd478-finalclamp-visual-20261013` ended terminal 1 in `~/offload/out/prd478-finalclamp` with 4 `map-views` pose PNGs, 0 `map-walk` PNGs and 0 `world-capture` PNGs; the retry `~/offload/out/prd478-finalclamp-resume/map-walk` ended terminal 2 with `locator.screenshot: Timeout 29936ms exceeded` while waiting for element stability (diagnostic `TN_PLAYTEST_RUNNER_FAILED`). No active PRD-473, PRD-478 or PRD-541 capture process was found on this host; remote lease state is not verified, so the earlier lease note is withdrawn. Both artifact sets are kept. No blind approval is claimed until the shipped visual bundles are scored by 3 fresh raters and the pop series is judged against develop `a602467db`. + - **Exact next probes, in order:** + 1. *Pinned combined source provenance.* The site matches the recorded bundle SHA-256 `cdb44459c84b278d400f29b8a43f3521c4995c688bb7f25b98e401a86b80d534` and asset manifest `940a777d3bd1a51d7229d2f9686485f53fa1a36e72cdf318103b7079f158ebcf`, but no source metadata establishes the combined PRD-473 default-on runtime with final clamp `04400564a`. Until that combination is pinned and recorded here, no capture from this site is acceptance evidence. + 2. *Screenshot on the actual map-walk.* Runner dependencies are restored with a frozen `@threenative/playtest` filtered install. `doctor` on the site reports Intel gen-12lp, 0 console errors and a fixedstep `simulationTick` of 0, so there is no liveness. A synthetic Playwright paused-clock screenshot passes, so a clock cause stays UNPROVEN. Take one screenshot on the real `map-walk` route on the live clock and one on the paused clock, and record which one times out. + 3. *Desktop timing host.* AC-1's 3 interleaved RTX pairs run only on the owner's RTX 2080, which needs the owner's SSH address. + - **Scoring commands,** to run from the engine checkout once a full candidate capture exists. Export `CANDIDATE_SOURCE` as the verified combined runtime source revision from probe 1 before the candidate manifest line runs. It has no default, because the earlier `c2c3eba33` argument is not established as that revision: ```sh pnpm exec tsx scripts/world-capture-manifest.ts ~/offload/out/pop-dev a602467db 30 --scenario machinefall-world-capture --world Machinefall - pnpm exec tsx scripts/world-capture-manifest.ts ~/offload/out/prd478-finalclamp/world-capture c2c3eba33 30 --scenario machinefall-world-capture --world Machinefall + pnpm exec tsx scripts/world-capture-manifest.ts ~/offload/out/prd478-finalclamp/world-capture "$CANDIDATE_SOURCE" 30 --scenario machinefall-world-capture --world Machinefall pnpm visuals:world --before ~/offload/out/pop-dev/world-capture.json --after ~/offload/out/prd478-finalclamp/world-capture/world-capture.json --out ~/offload/out/prd478-finalclamp/world-gate mkdir -p ~/offload/out/prd478-finalclamp/ab-before-20261013-r1 ~/offload/out/prd478-finalclamp/ab-after-20261013-r1 cp ~/offload/out/dev-map-walk/*.png ~/offload/out/dev-map-views/*.png ~/offload/out/prd478-finalclamp/ab-before-20261013-r1/ @@ -96,6 +100,7 @@ Change where the frame's work runs, not how much of it a frame skips: ## Blocked on - Bare far hills (owner, 2026-10-08): PRD-537 (draft PR 459), whole-map impostors; Phase 1 measurement is running, and its dark impostor shadow is that PRD's Phase 2. +- Desktop RTX 2080 timing host (owner, 2026-10-13): AC-1 needs the owner's RTX 2080 over SSH. Host `joao` is Intel IrisXe (gen-12lp) with no NVIDIA adapter and no desktop SSH alias, so no interleaved pair runs here. The owner unblocks it with the SSH address. ## Iteration method