Campaign S night shift. #330's contract pins the scoping facts so the implementer inherits measurements instead of re-deriving them: the builder is already presentation-free logic (the only App coupling is one identity-guard parameter), headless has full content (_content.Dats + prepared PhysicsDataCache), shadow-sync already runs in Runtime once a shadow exists, and the no-window inbound route is host-disjoint per AD-64 so registration wired there cannot double-register on the graphical host. The local-player ProvenShapeless pin is explicitly OUT — fixing it blind risks the K-series gates. AP-159's S1B contract maps the pseudo-C line ranges for the part-array find_transit_cells overload, the box-vs-cell BSP traversal, and flags the adjacent overload's Binary Ninja signature artifact for the mandatory pseudocode step to resolve. House rule carried: a new traversal in two representations ships with an exact differential referee, and the direction assertion (membership strictly shrinks) is a test, not an assumption. Also settled at scoping, evidence in the S1A brief: AP-157's CylHeight half is a NON-divergence — CObjCell::find_cell_list's cylsphere overload @0x0052b9f0 copies localtoglobal(low_pt) + radius per cylsphere, capped at 10, and never reads height; acdream's base-point cylinder flood is exactly retail's behaviour. Register correction follows with S1A's measurement numbers. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
111 lines
6.3 KiB
Markdown
111 lines
6.3 KiB
Markdown
# AP-159 / #335 contract (Campaign S slice S1B) — port the INDOOR box-admit half of retail's part-array `find_transit_cells`
|
||
|
||
**Date:** 2026-08-07 (overnight). **Scoped by:** the session lead.
|
||
**Implementer:** one Sonnet agent against THIS contract. **Review:** dual
|
||
Opus after. **Severity honest-check:** LOW — over-inclusive only (extra
|
||
indoor broadphase candidates, never a missed one). It is in scope tonight
|
||
because it is the last half of AP-156's traversal residual and Campaign S's
|
||
S1; if any step below turns out larger than described, STOP and report
|
||
rather than compressing quality.
|
||
|
||
## The divergence
|
||
|
||
acdream's indoor cell-membership walk for multi-part BSP objects
|
||
(`CellTransit.BuildShadowCellSetFromParts`, indoor arm) admits a neighbouring
|
||
EnvCell on a **sphere**-vs-portal-plane test (`FindTransitCellsSphere` over
|
||
`BuildBspPartSpheres` output). Retail's part-array
|
||
`CEnvCell::find_transit_cells` uses the sphere only as a **cheap reject**;
|
||
the ADMITTING test is **box**-vs-plane, followed by a **box-vs-cell BSP**
|
||
gate in the destination cell. Full disassembly-backed spec: the #335 entry in
|
||
`docs/ISSUES.md` (steps 1–4 with addresses) — that entry is the oracle of
|
||
record; this contract adds the pseudo-C line map.
|
||
|
||
## Oracle map (grep-named-first is already done — these are the exact places)
|
||
|
||
`docs/research/named-retail/acclient_2013_pseudo_c.txt`:
|
||
|
||
- **Part-array overload** `CEnvCell::find_transit_cells(this, count,
|
||
CPhysicsPart** parts, CELLARRAY*)` @0x0052cae0 — lines **310127–310257**.
|
||
Per portal x per part: sphere cheap-reject (`physics_sphere` centre through
|
||
`Position::localtolocal`, eps = `F_EPSILON + radius` @0x0052cb65) -> box
|
||
admit (`CPhysicsPart::GetBoundingBox` @0x0050d600 -> `BBox::LocalToLocal`
|
||
@0x005b1e60 -> `Plane::intersect_box` @0x005aa170 @0x0052cc05) -> if
|
||
portal-side differs: `other_cell_id == 0xFFFFFFFF` sets leads-outside,
|
||
else `CCellPortal::GetOtherCell` (threading `do_not_load_cells`) then
|
||
box-vs-destination-BSP `CCellStruct::box_intersects_cell` @0x00533910
|
||
(line 317675: one-liner into `BSPTREE::box_intersects_cell_bsp`
|
||
@0x005398b0 -> `BSPNODE::box_intersects_cell_bsp` @0x0053c880, body at
|
||
line **325993**) gating the add @0x0052cc5a; after all portals,
|
||
leads-outside runs `add_all_outside_cells` @0x0052ccea.
|
||
- **Adjacent overload** @0x0052c820 (lines **309968–310126**) also contains a
|
||
box refinement block (@0x0052c76c–0x0052c7d2). Its Binary Ninja signature
|
||
(`CSphere const* arg4`) is inconsistent with its body indexing parts —
|
||
a known BN artifact class. Your MANDATORY pseudocode step (below)
|
||
disentangles the two overloads BEFORE any C# is written; if your reading
|
||
contradicts the #335 entry's step list, STOP and report.
|
||
- `Plane::intersect_box` @0x005aa170 — line 439037. Note its result contract
|
||
(the caller accepts `== 3 || == side`).
|
||
- `BSPNODE::box_intersects_cell_bsp` @0x0053c880 — line 325993. This is the
|
||
box-vs-cell traversal you must port TWICE (graph + flat, below).
|
||
|
||
## Deliverables, in order
|
||
|
||
**D0 — pseudocode doc FIRST** (`docs/research/2026-08-07-ap159-pseudocode.md`):
|
||
the part-array overload and the box-vs-BSP traversal, translated to readable
|
||
pseudocode with the addresses inline, per the repo's mandatory workflow. The
|
||
overload-signature confusion above must be resolved here. No C# before this
|
||
file exists.
|
||
|
||
**D1 — the box primitives, in BOTH representations + referee.**
|
||
`BSPQuery` (graph) and `FlatBspQuery` (production flat) each gain the
|
||
box-vs-cell-BSP traversal (`BoxIntersectsCellBsp`), and `Plane`-vs-box gains
|
||
`intersect_box` semantics wherever the shared math lives. House rule from
|
||
Slices I4/I5: a NEW traversal in two representations ships with an **exact
|
||
differential referee test** — same inputs through both, assert identical
|
||
verdicts over (a) synthetic cells covering each BSP node type and (b) a sweep
|
||
of installed-DAT EnvCells (follow `FlatBspQueryDifferentialTests` patterns).
|
||
|
||
**D2 — rewire the indoor arm.** `BuildShadowCellSetFromParts`'s indoor arm
|
||
follows retail's order exactly: per portal x per part sphere cheap-reject ->
|
||
box admit -> other-cell resolution with `do_not_load_cells` -> destination
|
||
`box_intersects_cell` gate -> leads-outside flag -> `AddAllOutsideCellsFromParts`
|
||
after the portal loop. The box data ALREADY EXISTS: `ShadowPartBox`
|
||
(`worldParts`) is passed in today and used only by the outdoor rectangle. Do
|
||
NOT touch the outdoor arm, the AD-49 seed-time rectangle, or the static
|
||
prune. The outdoor **building bridge** (`CheckBuildingTransit`, retail
|
||
`check_building_transit` @0x0052c5d0) has the same sphere-as-admit defect:
|
||
port it ONLY if it reuses D1's primitive with a small diff; otherwise leave
|
||
it and report it as the explicit remainder.
|
||
|
||
**D3 — conformance + direction tests.**
|
||
1. A synthetic two-EnvCell fixture where a part's SPHERE overlaps the portal
|
||
plane but its BOX does not: pre-fix the neighbour is admitted, post-fix it
|
||
is not. Sabotage-verify: re-widen the admit to the sphere test and the
|
||
test must redden.
|
||
2. The inverse guard: a part whose box DOES cross admits — unchanged.
|
||
3. Direction assertion over an installed-DAT sweep: the post-fix indoor
|
||
membership set for every swept object is a SUBSET of the pre-fix set
|
||
(over-inclusion strictly shrinks; any ADDED cell is a defect). Report
|
||
counts: objects swept, cells removed, cells added (must be 0).
|
||
|
||
**D4 — measurement in the report:** from D3.3, the installed-DAT population
|
||
whose membership actually changed, with three worst examples (object,
|
||
cells before/after). If the population is ZERO, say so plainly — that is a
|
||
valid outcome and the register row then records "ported, no installed data
|
||
affected".
|
||
|
||
## Scope — OUT
|
||
|
||
- The AP-156 SCALE residual (retail doesn't scale flood spheres; we do) —
|
||
reserved for the user's explicit decision. Do not change scaling anywhere.
|
||
- The outdoor arm, `AddAllOutsideCellsFromParts`, the 3x3 sphere path,
|
||
anything in `ShadowObjectRegistry`.
|
||
- Register/ISSUES edits — the session lead does those.
|
||
|
||
## Acceptance
|
||
|
||
Build green; FULL suite `dotnet test AcDream.slnx -c Release -m:1` green with
|
||
exact totals reported; D3.1 sabotage performed and reported verbatim; nothing
|
||
committed. Work ONLY in `C:\Users\erikn\source\repos\acdream` with absolute
|
||
paths; do NOT spawn subagents. If any oracle reading contradicts the #335
|
||
entry or this contract, STOP and report the contradiction.
|