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>
6.3 KiB
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_spherecentre throughPosition::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 == 0xFFFFFFFFsets leads-outside, elseCCellPortal::GetOtherCell(threadingdo_not_load_cells) then box-vs-destination-BSPCCellStruct::box_intersects_cell@0x00533910 (line 317675: one-liner intoBSPTREE::box_intersects_cell_bsp@0x005398b0 ->BSPNODE::box_intersects_cell_bsp@0x0053c880, body at line 325993) gating the add @0x0052cc5a; after all portals, leads-outside runsadd_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.
- 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.
- The inverse guard: a part whose box DOES cross admits — unchanged.
- 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 inShadowObjectRegistry. - 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.