feat(render): S2 chunk 2 - render statics borrow the registry's retail cell array

WalkProductionWorldData no longer floods; its indoor and outdoor static
sweeps read ShadowObjectRegistry.TryGetRetailCellArray (retail's
calc_cross_cells_static @0x00515160 -> AddPartsShadow @0x00517e40
CELLARRAY, computed once at registration). The App-owned render flood
(ResolveStaticRenderCells, its fingerprint cache, the primitive-Setup
special case) and Core's ComputeStaticRenderCells are deleted. The one
remaining fallback, an entity the physics publisher has not registered yet
while the projection journal already published it, buckets to the authored
parent cell and is counted per frame (UnregisteredStaticRenderFallbackCount)
for chunk 5 to judge on the connected route.

The Facility stair pin now registers at the projection's own entity id
(the old pure-function test never carried identity) and reads the retail
array; the installed-DAT comparator compares retail against collision.

Gates (run in the implementer's isolated worktree at identical content):
Release build 0/0; Core Physics 2,202/2,202; App hermetic 6,760/6,760 (the
two added WalkProductionWorldData tests); installed-DAT walk/flood/stair
family 24/24; Runtime 1,884/1,884.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-09-02 22:31:46 +02:00
parent 2b40c0bb38
commit 707d2803a4
10 changed files with 392 additions and 227 deletions

View file

@ -37,7 +37,7 @@ One transaction per object (`CPhysicsObj::calc_cross_cells_static`
| Owner | Inputs | Output | Runs | Consumers |
|---|---|---|---|---|
| `LandblockPhysicsPublisher.PublishStaticEntity``ShadowObjectRegistry.RegisterMultiPart` (`LandblockPhysicsPublisher.cs:930-1079`, `ShadowObjectRegistry.cs:493-584`) | BSP parts only (`ShadowShapeBuilder.FromLandblockBspParts`) or Setup cyl/sphere fallback; `CellTransit.BuildShadowCellSetFromParts` | `_entityToCells[id]` (ordered, the CELLARRAY analog) + `_cells[cell]` `ShadowEntry` rows | once per static at publication | physics broadphase `TransitionTypes.FindObjCollisionsInCell` (`TransitionTypes.cs:3755-3779`); outdoor static render lookup reads it verbatim |
| `WalkProductionWorldData.ResolveStaticRenderCells``ShadowObjectRegistry.ComputeStaticRenderCells` (`WalkProductionWorldData.cs:285-366`, `ShadowObjectRegistry.cs:371-394`) | ALL visual parts (`ShadowShapeBuilder.FromStaticRenderParts`: physics sphere else visual-AABB circumsphere; exact visual box); same `BuildShadowCellSetFromParts` | per-object cell list, cached by fingerprint; whole `RenderProjectionRecord` bucketed into every cell | every `BeginFrame` sweep of `RenderSceneIndex.IndoorCellStatic` | `WalkFrameDriver.GetCellStatics`; `RetailPViewRenderer` particle owner union |
| **DELETED (chunk 2).** Was `WalkProductionWorldData.ResolveStaticRenderCells``ShadowObjectRegistry.ComputeStaticRenderCells`. Now: `WalkProductionWorldData.ResolveIndoorStaticRenderCells`/`ResolveOutdoorStaticRenderCells``ShadowObjectRegistry.TryGetRetailCellArray` (`WalkProductionWorldData.cs`, `ShadowObjectRegistry.cs:634-663`) | none (borrowed) | chunk 1's retained per-entity retail CELLARRAY, computed once at registration | every `BeginFrame` sweep of `RenderSceneIndex.IndoorCellStatic`/`OutdoorStatic` (a borrow, not a recompute) | `WalkFrameDriver.GetCellStatics`; `RetailPViewRenderer` particle owner union; unregistered fallback (indoor: authored `ParentCellId`; outdoor: `GetOwnerCells`) counted by `WalkProductionWorldData.UnregisteredStaticRenderFallbackCount` |
| `LiveEntityCollisionBuilder.Register` / `ShadowObjectRegistry.UpdatePosition` / `CommitSetPosition` (`LiveEntityCollisionBuilder.cs:149-280`, `ShadowObjectRegistry.cs:881-982`) | `ShadowShapeBuilder.FromSetup` (BSP parts, else cylspheres, else spheres) | `_entityToCells` + `_cells` | spawn and every accepted move/appearance change | physics broadphase; dynamic render lookup reads it verbatim |
| `WalkProductionWorldData.ResolveDynamicRenderCells` (`WalkProductionWorldData.cs:247-283`) | `GetOwnerCells`; for an `EquippedChild` with no cells, a 64-hop parent-chain walk | whole-record buckets | every `BeginFrame` | `WalkFrameDriver.GetCellDynamics` |
| `LightManager.SelectForCell` (`LightManager.cs:594-650`) | flat `_all` list, sphere overlap | per-cell light subset | per cell drawn | lighting (S5 owns; retail selects from the current EnvCell + stab list) |
@ -78,12 +78,25 @@ formation, an outdoor landblock-edge static, a multi-part Setup) reports the
three answers side by side: retail CELLARRAY, current collision cells,
current render cells. Differences are evidence for chunks 2/3, not failures.
**Chunk 2 — render statics cut over.** `WalkProductionWorldData` indoor and
outdoor static buckets are filled from the registry's per-cell part entries
(grouped back to records for the existing draw path; per-part draw
admission is S3). `ResolveStaticRenderCells`, `ComputeStaticRenderCells`,
the fingerprint cache, and App's use of `FromStaticRenderParts` are deleted.
The Facility stair pin moves to the registry.
**Chunk 2 — render statics cut over (landed 2026-09-02).** `WalkProductionWorldData`
indoor and outdoor static buckets are filled whole-record-per-cell from the
registry's retail CELLARRAY (`TryGetRetailCellArray`), not from its per-cell
part entries: per-part draw admission is S3's job, and today's draw path
still consumes whole `RenderProjectionRecord`s, so grouping
`GetRetailPartEntriesInCell` back into records here would be a no-op
detour. `ResolveStaticRenderCells`, `ComputeStaticRenderCells`, the
fingerprint cache (`_staticRenderCellCache` + its revision/generation
invalidation), the `primitiveSetup` special case, and
`WalkProductionWorldData`'s use of `ShadowShapeBuilder.FromStaticRenderParts`
are deleted. An entity with no retail array yet (the streaming-window race
between the static-projection journal and the physics publisher — AD-40's
residency reasoning) falls back to a conservative single-cell membership
(indoor: authored `ParentCellId`; outdoor: today's `GetOwnerCells`), counted
by the new `WalkProductionWorldData.UnregisteredStaticRenderFallbackCount`
for chunk 5 to weigh against the connected-route count. The Facility stair
pin (`Issue177StairDescentCameraFloodTests.FacilityStairAssembly_*`) moved to
registering through the production `RegisterMultiPart(..., partArray:)` +
`TryGetRetailCellArray` recipe.
**Chunk 3 — collision cut over.** `_entityToCells`/`_cells` are populated
from the retail CELLARRAY (collision shapes into every array cell). Physics