acdream/docs/research/2026-07-26-prepared-indoor-transit-regression.md

90 lines
3.6 KiB
Markdown

# Prepared indoor transit regression
**Date:** 2026-07-26
**Scope:** Slice-I prepared collision consumer correction
**Behavioral boundary:** No collision formula, portal rule, or renderer
behavior changed. This restores the existing retail traversal over the
production flat representation.
## Symptom and connected evidence
After the Slice-I parsed-graph removal, entering houses or dungeons could:
- collide with empty space at a doorway or room boundary;
- retain the wrong indoor cell on stairs or upper floors;
- expose the world background through the interior portal view.
The connected capture at
`.test-out/indoor-visibility-20260726-220007/stdout.log` placed the player in
Holtburg cottage vestibule `0xA9B40150`, with the chase-camera eye outside the
doorway in outdoor landcell `0xA9B40029`. The original DAT graph advanced the
camera transition from `0xA9B40150` to `0xA9B40029`. The prepared production
path retained `0xA9B40150`, causing collision and portal rendering to share
the same wrong viewer-cell root.
## Retail oracle
Named retail:
- `CEnvCell::find_transit_cells @ 0x0052C820`
- `CObjCell::find_cell_list`
- `CEnvCell::find_visible_child_cell`
Consolidated pseudocode:
[`acclient_indoor_transitions_pseudocode.md`](acclient_indoor_transitions_pseudocode.md).
For each `CCellPortal`, retail reads that portal's polygon plane from the
owning `CCellStruct`. An exterior portal admits outdoor cells when any path
sphere straddles the plane. An interior portal tests the loaded neighbor's
cell-containment BSP, using the plane only as an unloaded-cell hint.
## Root cause
The prepared package was complete:
- `FlatEnvCellTopology.Portals[i].PolygonIndex` retained the direct portal
polygon index;
- `FlatCellStructureCollisionAsset.PortalPolygons` retained the exact plane;
- prepared containment and physics BSPs matched the original DAT graph.
`CellTransit.FindTransitCellsSphere`, however, still began with:
```text
if currentCell.PortalPolygons is null: return
```
`PortalPolygons` is the old parsed-DAT dictionary and is intentionally null in
production after Slice I7. Therefore every prepared indoor cell skipped its
entire portal loop. The I4/I5 differential referee covered BSP queries and
complete resolver results, but did not exercise portal-plane lookup after the
parsed graph had been removed, allowing the representation-boundary defect to
escape.
## Correction
`CellTransit` now resolves the exact same portal plane from:
1. the parsed dictionary in explicit graph-oracle fixtures; or
2. the prepared topology's direct polygon index and flat polygon table in
production.
Prepared topology/list mismatches and invalid polygon indices fail loudly as
corrupt publication rather than silently disabling indoor traversal. Portal
iteration order, comparisons, sphere transforms, neighbor containment, and
outside admission remain unchanged.
## Automated evidence
- Synthetic prepared exterior-portal test with no parsed polygon dictionary.
- Synthetic prepared loaded-neighbor test with no parsed containment tree.
- Exact connected Holtburg cottage camera path:
graph and prepared paths both end in `0xA9B40029` at identical position.
- Installed-DAT graph/flat coverage expanded across the Holtburg cottage and
inn vestibules, connectors, stairwell, upper floor, and inn shell.
- Physics-focused Release tests: 1,778 passed / 1 skipped.
- Interaction/projection focused App tests: 127 passed.
- Client-object ordering tests: 82 passed.
- Complete Release build: passed with zero warnings.
No divergence-register row is required: the correction removes an accidental
production divergence and introduces no replacement behavior.