Skip to content

fix(world): PRD-494 — the main pass fits the draw budget (load stall, sharded record, native bundle draws) - #475

Closed
jonit-dev wants to merge 8 commits into
developfrom
feat/prd-494-main-record
Closed

jonit-dev wants to merge 8 commits into
developfrom
feat/prd-494-main-record

Conversation

@jonit-dev

Copy link
Copy Markdown
Contributor

TL;DR

Finishes PRD-494 (the main pass fits the draw budget). PR #423 merged the PRD's engine code. This PR fixes a load stall that code caused, brings the walking main-pass draw count to p95 48 in the load window (85 at worst) over 3 Machinefall runs, makes native render bundles work with it, and moves the PRD to done/.

One thing to decide (João): this is a second PR for PRD-494. #473 was my first PR for it, but its branch feat/prd-494-finish was overwritten by a fast-forward from the runbook-120 worktree (feat/machinefall-120fps-runbook, head d2bb5f649), which merged PRD-478 runbook work and PRD-538/541/489 docs into it (218 files). I did not force-push or merge that PRD-478 work. This PR carries only PRD-494, on its own branch from develop. Squash-merging #473 would land the PRD-478 work too, which is not mine to merge. #473 includes my first commit (2002139c9).

Why

On develop f7dd08621, Machinefall ?scene=map-walk never finishes loading under the playtest harness. A Chrome trace found one 22.9 s main-thread task. 18 s of it was bundleSafe's material walk (samplesFramebuffer) enumerating every texture's mip bytes through Object.values.

What changed

Change Where Proof
The bundle-safety walk skips ArrayBuffer views world-cells.ts red expected 11 to be +0, green
Main draw record split into 32 BundleGroup shards (FNV-1a of the key name), so a mint re-records its shard and not every main draw world-cells.ts red: one main group; first-window p95 157 → 48
A shard recorded while a compile is in flight is re-recorded once it settles (three encodes a draw only when its pipeline is ready and never re-checks on replay) world-cells.ts red expected 7 to be greater than 7
Guard: a shard whose members change is re-recorded in the same update spec mutation-checked
Native GPURenderBundleEncoder gets drawIndirect / drawIndexedIndirect bindings_commands.cpp red drawIndirect binding missing, green
Census and native coverage regenerated; WorldProbe publishes bundleOn / bundleChildren; desktop scenario asserts them docs, example pnpm budgets exit 0

Evidence (Machinefall ?scene=map-walk, RTX 2080 WebGPU, final core d8e6543fe)

  • AC-1 counts: traversed main draws p95 per 300-frame window, 3 runs. First window after startup (the load interval): 48 / 48 / 48 (157 with one group, 141 / 105 / 105 with 8 shards). Later windows peak at 71 / 73 / 85. At 30-frame windows the single window holding the overlay drop still reads 157.
  • World gate (pnpm visuals:world, 3 fresh blind critics, settled capture): 0 LOSS, pose-start 2 → 3, walk-08/16/24 equal at 4, 0 disallowed popping of 61 reported events. The gate exits 1 on its absolute 4/5 floor alone, which the reference misses too (2 on pose-start). It is not green by exit code. The frame shows the mid-field forest under adaptive LOD bias (TN_LOD_BIAS about 2.3 at 15–40 ms GPU against an 8.33 ms budget), which this PR does not touch.
  • The unsettled pose-start row is a race, not a loss. The first pair gave 1 LOSS there. The same frame flips within one arm: reference lacked the mid-field trees in 1 of 5 captures, candidate in 2 of 6, final core in 1 of 5.
  • Native: --target desktop on the checkout host: streamed-world scenario passes, 440 frames, 0 console errors; TN_WORLD_BUNDLE on reason=default children=7 records=18 in a long-settle log. With the published prebuilt host it threw drawIndexedIndirect is not a function every frame (24,804 errors).
  • AC-2: the quiet-host tick (2026-10-03) stands. A loaded re-measure: draw span p95 13.3 / 7.0 / 1.7 ms on against 67.0 / 8.2 / 21.3 ms off; two of three pairs clear 4 ms.
  • Tests: core 2681 passed / 2 skipped before the compile fix; world-bundles + world-gpu-scene 88 passed after; prose lane 244 passed; pnpm typecheck, pnpm budgets exit 0.

Before / after frames: reference, settled · candidate, settled · race frames: reference, candidate, full forest.

Not run or not claimed

  • Android, iOS and the full CI board were not run locally. CI is the first run of the full board on this diff.
  • The world-capture-settled scenario and the desktop run recipe (nativeEntry: src/world-native.ts, ui.renderer: "native") are local files, not in this PR.

…-494)

samplesFramebuffer walked Object.values of everything a material reaches,
so every texture's mip data was enumerated one byte at a time. On
Machinefall's map-walk that held the main thread for 22.9 s during load,
and the playtest bridge timed out before the world appeared. An
ArrayBuffer view holds numbers, never a node, so the walk now skips it.

Red: world-bundles.spec.ts "asks a textured surface whether it can be
recorded without reading its pixels" read the pixel array 11 times
(expected 11 to be +0). Green after: 15 of 15 in the file.
A BundleGroup re-record walks every draw it holds. With one main group,
each key a streaming walk minted made three walk the whole main pass in
that frame: Machinefall's map-walk read traversed-draw p95 257-291 in
30-frame windows for ~90 frames after the overlay dropped, and the first
300-frame window read 157 against 31-78 settled. The main record is now
eight groups keyed by an FNV-1a hash of the key name, so a mint
re-records its own shard and the others replay what they hold.

Red: world-bundles.spec.ts "re-records a mint's share of the main pass,
not the whole of it" found one main group (expected 1 to be greater
than 1). Green after. The three cases that claimed one group now claim
that every dressed main mesh sits in a shard and the shards hold
nothing else. world-bundles + world-gpu-scene: 86 passed.
The native GPURenderBundleEncoder had draw and drawIndexed but no
drawIndirect or drawIndexedIndirect. A GPU-scene main batch is an
indirect draw, and bundles are on by default, so recording the world on
the desktop host threw "s.drawIndexedIndirect is not a function" every
frame and cascaded into null bind groups: 24,804 console errors on the
streamed-world scenario with the published prebuilt host.

The two handlers mirror the render-pass ones. Red: the public binding
surface check in webgpu_bindings_reentrancy_test.cpp failed with
"GPURenderBundleEncoder.drawIndirect binding missing". Green after.

The desktop streamed-world scenario now runs on the checkout host
(THREENATIVE_RUNTIME_BINARY=build/tn-linux/mystral, nativeEntry
src/world-native.ts, ui.renderer native): exit 0, 440 native frames,
0 console errors. WorldProbe publishes stats().bundle as bundleOn and
bundleChildren, and the scenario asserts both. Its diagnostics block
is web-only (the desktop runner produces no runtimeDiagnostics), and its
residentCells row no longer asks the start and end counts to differ: a
full ring holds 3 cells at both ends of a straight flight, and residency
change is what evictions (0 -> 9) and residenceChanges assert.
Eight shards held the mid-walk record spikes to 145 traversed draws in a
30-frame window, but one asset mints several keys (levels, parts) in one
frame and they land in two or three shards. Over three Machinefall
map-walk runs the first 300-frame window after startup read p95
141 / 105 / 105, so one run still missed the 120 budget. A replay costs
one executeBundles per group, so thirty-two groups cost little next to
the draws they stop re-walking. world-bundles + world-gpu-scene: 86
passed.
…draws (PRD-494)

Generated by pnpm --filter @threenative/runtime-native native:coverage after
the render-bundle indirect-draw handlers landed; pnpm budgets reported the
source digest stale.
…changed (PRD-494)

Guards the sharding invariant: with one group a join without a re-record was
masked by any other key minted in the same load; with a group per shard the
repair needs a bump in the same shard. Over a settle, a camera turn and a
streaming walk on a CPU-path world, every shard whose member meshes changed
in an update has a higher BundleGroup.version after it.

Mutation-checked: with #bumpBundle a no-op for main shards the spec reads
"walk-2: 1 meshes" and fails; green on the real code. It found no stale join
in this fixture.
…during has settled (PRD-494)

three encodes a draw into a bundle only when its pipeline is ready, then marks the
bundle current; the replay never re-checks. A shard recorded while a compileAsync
overlapped therefore left the draws whose pipelines were still compiling out of
its record until something bumped that same group. With one main group any mint
bumped it within a frame, which hid this. With 32 shards the repair needs a bump
in the same shard, so Machinefall's overlay-drop frame lost its mid-field forest
(shadows drawn, trees missing) on 2 of 3 candidate captures and 0 of 2 reference
captures.

WorldCells now remembers the groups bumped while the renderer reports a compile in
flight (compiling, or compileCount moved since the last update) and bumps each once
in the first update that sees it settled. Only those shards pay, so the traversed
draw count AC-1 measures does not grow.

Red: world-bundles.spec.ts "re-records a shard recorded during a compile once the
compile has settled" failed with "expected 7 to be greater than 7". Green after;
world-bundles + world-gpu-scene: 88 passed.
AC-1: traversed main draws p95 48 in the load window and 85 at worst over 3
Machinefall map-walk runs on the final core. The world gate shows 0 LOSS and 0
disallowed popping but exits 1 on its absolute 4/5 floor, which the reference arm
misses too; the PRD records that, the capture race on pose-start, and the
adaptive LOD bias as the likely owner of the mid-field frame.
Native leg: the streamed-world desktop scenario passes on the checkout host with
bundles on, after the native render bundle encoder gained indirect draws.
AC-2 sign re-confirmed under load; the quiet-host tick stands.

The runbook's link to the PRD follows it to done/.
@jonit-dev

Copy link
Copy Markdown
Contributor Author

Squashed into the one PRD-494 PR, #473, at the owner's request (one PR per PRD). Every commit of this branch is in #473 at f238d26 via a merge; nothing is lost. Continue PRD-494 on feat/prd-494-finish.

jonit-dev added a commit that referenced this pull request Oct 9, 2026
The owner wants one PR for PRD-494. #475 carried the sharded main record,
native bundle indirect draws and the PRD close; it lands here instead.
Conflicts: world-cells.ts (kept gpuPlaced and the shard wording) and the
native census (regenerated with pnpm census).
@jonit-dev jonit-dev closed this Oct 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant