acdream/docs/research/2026-08-07-ap159-s1b-contract.md
Erik 04b794ad7c docs: overnight contracts — #330 headless collision, AP-159 S1B box-admit; morning-gate skeleton
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>
2026-08-07 00:54:13 +02:00

111 lines
6.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 14 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 **310127310257**.
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 **309968310126**) also contains a
box refinement block (@0x0052c76c0x0052c7d2). 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.