docs(render): S2 plan packet - membership ownership map and revised chunks
Read-only trace of every production owner of cell membership (render and physics), the retail transaction they must collapse into (Contracts A/B), and the five S2 chunks re-cut on that evidence: the two static floods are one CellTransit walk with different part inputs; retail uses the whole part array once and registers every part in every array cell; children inherit at registration; topology consolidation moves to S3. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
75873886f3
commit
e7ad25a705
2 changed files with 139 additions and 15 deletions
113
docs/research/2026-09-01-overhaul/s2-membership-ownership-map.md
Normal file
113
docs/research/2026-09-01-overhaul/s2-membership-ownership-map.md
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
# S2 plan packet — cell-membership ownership map and chunk contracts
|
||||
|
||||
Date: 2026-09-02. Campaign OVERHAUL S2 (world graph: one membership owner).
|
||||
Retail contract: `oh1-construction-landscape-contract.md` Contracts A and B.
|
||||
Current-state evidence: read-only code trace (file:line) taken 2026-09-02 after
|
||||
S1 closed at `8c6563ca`.
|
||||
|
||||
## 1. Retail truth this slice ports
|
||||
|
||||
One transaction per object (`CPhysicsObj::calc_cross_cells_static`
|
||||
`0x00515160`, then `remove_shadows_from_cells` `0x00511230` +
|
||||
`add_shadows_to_cells` `0x00514ae0`):
|
||||
|
||||
1. **CELLARRAY** is computed from the whole part array. Cylsphere route when
|
||||
`(state & 0x10000) == 0 && part_array != null && GetNumCylsphere() != 0`;
|
||||
otherwise the bbox route (`find_bbox_cell_list` `0x00510fc0`): seed the
|
||||
object's cell, then for every array cell call
|
||||
`CEnvCell::find_transit_cells` `0x0052cae0`, which per portal and per
|
||||
VISUAL part uses `gfxobj->physics_sphere` else `drawing_sphere` for the
|
||||
plane reject, the part bounding box (`CPhysicsPart::GetBoundingBox`) for
|
||||
`Plane::intersect_box` and `box_intersects_cell`, and
|
||||
`add_all_outside_cells` once for an `0xFFFFFFFF` portal. Insertion order
|
||||
is retained; nothing prunes outside cells afterwards.
|
||||
2. **Two products of that one array, in the same order:** per cell a
|
||||
`CShadowObj` (collision row, `add_shadow_object`) and, when a part array
|
||||
exists, `CPartArray::AddPartsShadow` `0x00517e40`, which adds EVERY part to
|
||||
that cell's `shadow_part_list` with `planes = num_shadow_objects > 1 ?
|
||||
cell->clip_planes : null` and the cell frame. Per-part visibility is NOT a
|
||||
membership subset; it is decided at draw time by the portal view test and
|
||||
the drawn stamp (S3).
|
||||
3. **Children** receive the root's same CELLARRAY recursively in child-list
|
||||
order. **Particles** (`state & 0x1000`) register only in their own cell.
|
||||
4. **Removal** is the inverse transaction at the same owner.
|
||||
|
||||
## 2. What acdream does today (trace, 2026-09-02)
|
||||
|
||||
| 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 |
|
||||
| `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) |
|
||||
| `DirectionalShadowCasterFrame` (`DirectionalShadowCasterFrame.cs:229-233`) | flat outdoor indices, no cell filter | caster list | per frame | shadow pass (S5) |
|
||||
| Topology: `EnvCellLandblockBuildBuilder.BuildVisibilityCell` vs `WalkCellFactory.FromParsed` vs `FlatEnvCellTopology` | same DAT / pak | three distinct portal graphs (`LoadedCell`, `WalkCell`, flat asset) | per landblock build | render, walk, `CellTransit` |
|
||||
|
||||
Findings that shape the chunks:
|
||||
|
||||
- The two static floods are the SAME `CellTransit` walk with DIFFERENT part
|
||||
inputs (BSP-only vs all visual parts). Retail uses all parts once. The
|
||||
render-side flood already carries retail's inputs; the collision side does
|
||||
not.
|
||||
- Render membership is whole-object per cell; retail's is every part per
|
||||
cell (identity retained per part, with cell frame and clip planes).
|
||||
- Children are inherited only on the render side, at bucket time; retail
|
||||
inherits at registration.
|
||||
- `b3b7d922` removed the indoor-seed stab-list prune inside the shared
|
||||
`BuildShadowCellSetFromParts`, affecting every caller. Retail has no such
|
||||
prune in the bbox route (Contract A), so the removal is the retail
|
||||
direction; it is re-verified, not re-litigated, in chunk 3.
|
||||
- Topology triplication is real but the walk is its consumer; consolidating
|
||||
it belongs with S3's `WalkPView` state, not S2. S2 owns membership only.
|
||||
|
||||
## 3. Chunks
|
||||
|
||||
**Chunk 1 — the retail CELLARRAY and the per-part render product, beside
|
||||
the existing owners (zero pixel change).** Core `ShadowObjectRegistry` gains
|
||||
a registration input for the whole part array (per part: GfxObj id, part
|
||||
frame, physics sphere else drawing sphere, part box) and computes retail's
|
||||
CELLARRAY from it as a new retained `RetailCellArray(entityId)`; it also
|
||||
records per-cell part entries `(entityId, partIndex, gfxObjId, cell frame,
|
||||
clipPlanes-required)` in registration order. Existing `_entityToCells`,
|
||||
`_cells`, and every consumer are untouched. App (`LandblockPhysicsPublisher`)
|
||||
and Runtime (`LiveEntityCollisionBuilder`) supply the part array at the same
|
||||
call. A comparator test over installed-DAT fixtures (Facility stair Setup
|
||||
`0x02000623` in `0x8A02015F`, cathedral ramp `0x020009A2`, the #334 Neftet
|
||||
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 3 — collision cut over.** `_entityToCells`/`_cells` are populated
|
||||
from the retail CELLARRAY (collision shapes into every array cell). Physics
|
||||
gates: Core physics suites, the placement/collision replay fixtures, and the
|
||||
connected nine-stop route; `CellTransit`'s prune removal is confirmed
|
||||
against Contract A.
|
||||
|
||||
**Chunk 4 — dynamics and children.** `add_shadows_to_cells` recursion:
|
||||
children register with the root's CELLARRAY at registration and movement;
|
||||
particles own-cell only; the render-side parent-chain walk is deleted.
|
||||
|
||||
**Chunk 5 — consumer cutover and deletion.** Remaining `WalkProductionWorldData`
|
||||
buckets become borrowed views; particle owner-cell union reads the same;
|
||||
duplicate dictionaries and fallbacks are deleted; architecture text updated.
|
||||
Lights and directional shadows stay as they are (S5).
|
||||
|
||||
Owner gate G2 after chunk 5 (plan §9 S2).
|
||||
|
||||
## 4. Tests that pin current behavior (must be consciously replaced, not
|
||||
silently broken)
|
||||
|
||||
`WalkProductionWorldDataTests` (bucketing), `Issue177StairDescentCameraFloodTests.
|
||||
FacilityStairAssembly_RegisterAcross015FTo015EWithoutCollisionRows`, the
|
||||
`CellTransit*` and `BuildShadowCellSetTests` families, `ShadowObjectRegistry*`
|
||||
and `ShadowShapeBuilder*`, `LiveEntityCollisionBuilderTests`,
|
||||
`LandblockPhysicsPublisherTests`, `Issue334*`, the flood replay fixtures, and
|
||||
the Runtime placement/physics suites (full list in the 2026-09-02 trace).
|
||||
Loading…
Add table
Add a link
Reference in a new issue