Skip to content

WIP: tier cull distance and a built-once occluder grid (A2.8) - no measured saving yet - #2313

Closed
Noisemaker111 wants to merge 2 commits into
betafrom
agent/lane-a28-lod
Closed

Noisemaker111 wants to merge 2 commits into
betafrom
agent/lane-a28-lod

Conversation

@Noisemaker111

@Noisemaker111 Noisemaker111 commented Oct 24, 2026 •

Copy link
Copy Markdown
Owner

Verdict: no saving - the occlusion and distance stages rejected nothing at four of eight cameras, the two shots that differed differed with matched draw counts and moving NPCs, and the grid finds almost no occluders because it trusts only placed solids >= 6 m while Harbor Heat'"'"'s downtown is generated chunks and Brightway Park'"'"'s props are smaller.

Part of epic #2312 A2.8. Closed unmerged; the measured outcome is recorded on the epic. LOD cross-fade is #2316.

What is here:

  • render/cullPolicy.ts: a tier'"'"'s instance cull distance from GraphicsQuality + the profile drawDistance, per-game scene override wins, ultra resolves to Infinity, hysteresis on the boundary.
  • world/occluderGrid.ts: cell grid built once from authored boxes, queried by sampling the camera ray; a sample counts only when strictly inside a blocker footprint, so it can miss a hidden object but cannot see through a blocking one.
  • scatter/InstancedScatter.tsx: foliage batches re-test instances against the tier distance when the camera moves and set count to survivors.
  • visibility/CullingProvider.tsx: resolves the policy from the terrain-quality tier and exposes it to batched draws.
  • visibility/cullingDriver.ts: builds the grid once from placed solids with radius >= 6 m and hands isOccluded real occluders instead of an empty list.

Verification: bun scripts/verify-fast.ts - 12 files, 19 test files, 11 gates, all passed.

Before/after pair, both sides from a checkout of origin/beta (lane-a28-baseline) and this branch, same pinned camera (--look 0,0 --look-from <dist>,<height>,2.4), default graphics tier on both sides (shoot/drive have no tier flag; #2318 tracks it), debug_report read in the same run as the shot:

shot draws before -> after triangles before -> after
brightway-park 60 m 555 -> 555 95,697 -> 100,441
brightway-park 180 m 445 -> 445 72,939 -> 72,939
brightway-park 400 m 313 -> 313 44,150 -> 44,150
brightway-park 45 m, 3 m high 616 -> 616 93,070 -> 93,070
harbor-heat 60 m 663 -> 954 1,740,221 -> 1,972,374
harbor-heat 180 m 563 -> 563 1,345,337 -> 1,345,337
harbor-heat 400 m 797 -> 604 922,317 -> 707,851
harbor-heat 45 m, 3 m high 481 -> 481 1,486,742 -> 1,486,742

Four of eight shots are identical to the draw, so the occlusion and distance stages rejected nothing there. The two Harbor Heat shots that differ differ in both directions and share their draw counts with the noise of a live scene (traffic and NPCs move between runs), so they are not a culling effect.

Why nothing is rejected: the grid trusts only placed solids with radius >= 6 m. Harbor Heat'"'"'s downtown is generated city geometry, not placed objects, and Brightway Park'"'"'s props are all smaller, so both scenes give the grid almost no occluders.

This withdraws the earlier claim in this PR that Brightway Park dropped from ~1,330 to 628 draws (-52.8%): that compared one run against a recorded budget baseline taken with the game'"'"'s own camera, not a pair.

Pixel difference against the baseline shot, mean absolute difference / share of pixels differing by more than 8/255: brightway-park 60 m 1.06 / 3.03%, 180 m 0.04 / 0.05%, 400 m 0.05 / 0.06%, 45 m low 0.58 / 1.19%; harbor-heat 60 m 2.66 / 5.29%, 180 m 2.72 / 4.85%, 400 m 1.82 / 0.20%, 45 m low 4.55 / 13.89%. Separate browser runs of a live sim, so moving entities dominate: Harbor Heat'"'"'s 13.9% sits beside identical draw counts.

Missing: the grid'"'"'s per-frame testMs against the draws it saves. occluderStats() exists on the driver but nothing surfaces it to debug_report, so there is no in-game number.

Recommendation: either extend the grid to generated building chunks (the geometry that actually occludes in these games) and re-run this pair, or drop the occlusion half and keep the scatter distance half, which is correct but saved nothing at these distances because tier 400 m already covered the visible scatter.

Core primitives for epic #2312 A2.8. The renderer still submits batches by frame; these give it a data-driven distance and a built-once occluder field to submit from.
Scatter batches compact per instance against the tier distance with hysteresis, and the culling driver builds an occluder grid once from building-sized placed solids so isOccluded receives real occluders.
@Noisemaker111 Noisemaker111 changed the title Tier instance cull policy and a built-once occluder grid (A2.8, part 1 of 3) Tier cull distance and a built-once occluder grid (A2.8) Oct 24, 2026
@Noisemaker111 Noisemaker111 changed the title Tier cull distance and a built-once occluder grid (A2.8) WIP: tier cull distance and a built-once occluder grid (A2.8) - no measured saving yet Oct 24, 2026
@Noisemaker111

Copy link
Copy Markdown
Owner Author

No measured saving: unsupported by the pair in this PR and the outcome is recorded on #2312 (comment), with the tier-flag gap in #2318.

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