# AP-159 / #335 — pseudocode: the INDOOR box-admit half of retail's part-array `find_transit_cells` **Date:** 2026-08-07. Written per the mandatory grep-named → decompile → pseudocode → port workflow, BEFORE any C# is touched, per the pinned contract `docs/research/2026-08-07-ap159-s1b-contract.md`. ## 0. Resolving the contract's flagged overload-signature ambiguity The contract's Oracle map says: > **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. Direct disassembly reading resolves this. The box-refinement block at addresses **0x0052c76c–0x0052c7d2** does **not** belong to the function declared at 0x0052c820 — its addresses are numerically *lower* than that declaration and it is textually printed *before* it in the dump. It belongs to the **preceding** function, which starts at line 309867: ``` 0052c680 void __thiscall CEnvCell::check_building_transit( class CEnvCell const* this, int32_t arg2, uint32_t const arg3, class CPhysicsPart** arg4, class CELLARRAY* arg5) ``` This is a **second, part-array overload of `check_building_transit`** (distinct from the sphere overload at 0x0052c5d0, lines 309827–309863, which is what `CellTransit.CheckBuildingTransit` already ports). Its signature genuinely takes `CPhysicsPart** arg4` — fully consistent with a body that indexes parts (`arg4[eax_1]`, `arg4[var_4c_1]`). There is no BN artifact here; the confusion was address-adjacency between two *different* functions that happen to sit back-to-back in the binary and share a demangled base name with their sibling overloads. The function actually declared at **0x0052c820** (lines 309968–310122) is the **sphere overload of `find_transit_cells`** — signature `CSphere const* arg4` is correct, and its body is pure sphere math (`Frame::globaltolocal`, `CCellStruct::sphere_intersects_cell`, the ±eps straddle test for exterior portals). This is **already ported** as `CellTransit.FindTransitCellsSphere` and needs no changes. It contains no box-refinement block at all. The function the contract's D1/D2 actually needs — the **part-array overload of `find_transit_cells`**, cited correctly by address (**0x0052cae0**, lines 310127–310257) — is a **third, separate function** from either of the above, and its disassembly matches the #335 ISSUES.md entry (the true oracle of record) line-for-line: `Position::localtolocal` sphere cheap-reject → `CPhysicsPart::GetBoundingBox` + `BBox::LocalToLocal` + `Plane::intersect_box` box admit → `other_cell_id==0xFFFFFFFF` leads-outside check → `CCellPortal::GetOtherCell` (threading `do_not_load_cells`) → `CCellStruct::box_intersects_cell` destination gate → `add_all_outside_cells` after the portal loop. **No contradiction with the #335 entry exists; only the contract's supplementary "Adjacent overload" note was misattributed.** This section performs the disentanglement the contract's mandatory D0 step asked for. **Consequence for D2's building-bridge decision:** the TRUE box-refinement counterpart for the outdoor→indoor building bridge is the part-array `check_building_transit` @0x0052c680 (the function this section just identified), not the sphere overload @0x0052c5d0 the contract names. Its control flow operates on a **single fixed portal per call** (index `ebp` supplied by the caller) rather than looping `this->num_portals`, and its cheap-reject branch structure (`if (ebp_1==1) {...} else if (ebp_1!=0) {label:...} else {...}`) has its own three-way-looking shape that does not cleanly decompose into the same two-branch (`PortalSide ? : `) form the part-array `find_transit_cells` uses — porting it would mean building a new, differently-shaped traversal, not "reusing D1's primitive with a small diff." Per the contract's explicit D2 allowance, this is left unported and reported as the explicit remainder (see the implementation report). ## 1. `CEnvCell::find_transit_cells` (part-array overload, @0x0052cae0, pc:310127–310257) Verbatim shape, addresses inline: ``` find_transit_cells(this, numParts, parts[], cellArray): exitOutside = false for each portal in this.Portals: # do-while, pc:310140–310252 portalPlane = ResolvePlane(this, portal) # this->portals[i].portal.plane for each part in parts: # do-while, pc:310147–310246 if part == null: continue sphere = part.GfxObj.PhysicsSphere ?? part.GfxObj.DrawingSphere # 0x0052cb34-0052cb45 if sphere == null: continue # --- cheap reject (sphere) -------------------------------- center = this.Pos.LocalToLocal(part.Pos, sphere.Center) # Position::localtolocal @0x0052cb5a rad = sphere.Radius + F_EPSILON # 0x0052cb65, F_EPSILON=0.000199999995f dist = Dot(center, portalPlane.N) + portalPlane.D # 0x0052cba4 portalSideRaw = portal.portal_side # this->portals[i].portal_side (int, retail live field) if portalSideRaw == 1: # 0x0052cba7 — matches our PortalInfo.PortalSide == true if dist < -rad: continue # skip this part — 0x0052cbb2/0x0052cbc8 (BN artifact # collapsed the two-branch shape into a spurious # 3-way if/elseif/else; the real shape is two branches # both jumping to the SAME box-test label) else: # portalSideRaw == 0, PortalSide == false if dist > rad: continue # --- box admit --------------------------------------------- box = part.GfxObj.PhysicsBox # CPhysicsPart::GetBoundingBox @0x0050d600, 0x0052cbdd localBox = BBox.LocalToLocal(box, part.Pos, this.Pos) # @0x005b1e60, 0x0052cbf9 sidedness = portalPlane.intersect_box(localBox) # Plane::intersect_box @0x005aa170, 0x0052cc05 crossingSide = portal.PortalSide ? Positive : Negative if sidedness != Straddle and sidedness != crossingSide: continue # box fully on the "still inside this cell" side — 0x0052cc0c falls through # --- destination resolution (only when box crosses) -------- if portal.OtherCellId == 0xFFFFFFFF: # 0x0052cc21 exitOutside = true break # out of the PART loop; proceed to next portal — 0x0052cc7e/0x0052ccaa otherCell = CCellPortal.GetOtherCell(portal, do_not_load_cells) # 0x0052cc2b, threads cellArray+4 if otherCell == null: cellArray.add_cell(portal.OtherCellId, null) # unconditional load hint — 0x0052cca5 break # next portal — 0x0052ccaa destBox = BBox.LocalToLocal(box, part.Pos, otherCell.Pos) # 0x0052cc4a if CCellStruct.box_intersects_cell(otherCell.structure, destBox): # 0x0052cc61 → BSPTREE @0x0053c880 cellArray.add_cell(otherCell.ID, otherCell) break # next portal — 0x0052cc8d # else: this part's box didn't actually reach the other cell's # geometry — continue the PART loop (retest remaining parts # against the SAME portal/destination) — 0x0052cc63, no break if exitOutside: CLandCell.add_all_outside_cells(numParts, parts, cellArray) # 0x0052ccea — already ported (#334) ``` Cross-checked against `docs/ISSUES.md` #335 entry (oracle of record) — every step (1–4) and every cited address matches. Also cross-checked against the **pre-existing** ACE-sourced pseudocode at `docs/research/acclient_indoor_transitions_pseudocode.md` §"EnvCell.find_transit_cells (parts/AABB variant)" (written 2026-04/05, predates named-retail), which independently derives the identical shape from ACE's C# port (`EnvCell.cs:245-309`) — including the exact `Positive`/`Negative` sidedness naming this doc uses below. Two independent sources (raw PDB-paired disassembly and an ACE C#-port cross-reference) agree; this doc treats the agreement as confirmation, not as a new independent fact. ### Structural difference from the already-ported sphere overload `CellTransit.FindTransitCellsSphere` special-cases exterior portals (`OtherCellId == 0xFFFF`) with a dedicated symmetric straddle test **before** touching the loaded/unloaded-neighbour logic (matches the sphere overload's own `if (*ecx != 0xffffffff)` branch at the very top of its per-portal body). The part-array overload does **not** do this: cheap-reject and box-admit run uniformly for every portal (interior or exterior); only **after** the box passes admit does it check `other_cell_id==0xFFFFFFFF` to decide interior-vs-exterior handling. This is why D2 needs a genuinely new method (`FindTransitCellsBox`) rather than a small patch to the existing `FindTransitCellsSphere`. ## 2. `Plane::intersect_box` (@0x005aa170, pc:439037–439122) Classifies a box against a plane using **all 8 corners**, short-circuiting per retail's fixed enumeration order (`min`, then all 7 combinations of {min,max}³ in the specific order the compiler emitted — order does not affect the boolean-classification result since it is an AND of per-corner agreement, only which corner short-circuits first). Epsilon is `F_EPSILON = 0.000199999995f` throughout, matching `Plane::which_side` @0x00444720 (PDB-recovered `Sidedness` enum: `Positive` when `dist >= +eps`, an early-return "clearly negative" case the disassembly calls out at the very first corner test, and a third `Straddle`/on-plane case when `-eps <= dist < +eps`). ``` ClassifyBox(plane, box): # shared math for D1 corners = the box's 8 corners (any fixed enumeration; result is order-independent) side0 = WhichSide(plane, corners[0], F_EPSILON) if side0 == Straddle: return Straddle for corner in corners[1..7]: if WhichSide(plane, corner, F_EPSILON) != side0: return Straddle return side0 # Positive or Negative — box is uniformly on one side WhichSide(plane, point, eps): dist = Dot(plane.N, point) + plane.D if dist >= eps: return Positive if dist < -eps: return Negative return Straddle ``` **Result contract** (matches the contract's note, re-derived independently from the raw disassembly and cross-checked): the caller at `find_transit_cells`'s 0x0052cc0c tests `classification != portal_side_raw`; the caller at `check_building_transit`'s 0x0052c7a0 tests `classification == 3 || classification == side`. Both are consistent with `ClassifyBox` returning `Straddle` whenever corners disagree (never a bare 0/1 in that case) and `Positive`/`Negative` only when **all 8 corners agree**. `Straddle` is by construction never equal to a 0/1 `portal_side_raw`, so "!= portal_side" the two callers' admit rules are NOT equivalent — dual review finding F9/R6. For the pure-side case they are OPPOSITE: `find_transit_cells` (`eax != side`) admits {Positive, Straddle} when side==1, while `check_building_transit` (`eax == 3 || eax == side`) admits {Straddle, Negative} — physically expected (reach-beyond-the-portal vs inside-this-building-cell are opposite questions), and the trap the future bridge porter must not fall into. **PortalSide mapping** (verified two ways — direct disassembly branch structure of the part-array `find_transit_cells`'s cheap-reject, AND the pre-existing `acclient_indoor_transitions_pseudocode.md` §"PortalSide flag semantics" cross-reference against ACE): retail's raw `portal_side` field is `1` exactly when our `PortalInfo.PortalSide == true`. The admit rule, restated without needing that raw integer at all: ``` crossingSide = portal.PortalSide ? Positive : Negative ADMIT box-crosses-this-portal iff sidedness == Straddle OR sidedness == crossingSide ``` ## 3. `BSPNODE::box_intersects_cell_bsp` (@0x0053c880, pc:325993–326087) Structurally the box-shaped sibling of the already-ported `BSPNODE::point_inside_cell_bsp` (:325508, `BSPQuery.PointInsideCellBsp`) and `BSPNODE::sphere_intersects_cell_bsp` (:325546, `BSPQuery.SphereIntersectsCellBsp`): an iterative walk down `pos_node` only, rejecting (returning "outside") the instant the box is found to lie entirely on the **negative** side of a splitting plane, and treating a null `pos_node` (or a leaf) as "inside." Epsilon is again `F_EPSILON` (line 326001: `0.000199999995f`), reusing the SAME 8-corner box-vs-plane classification as `Plane::intersect_box` — the disassembly literally duplicates the corner-enumeration idiom. ``` BoxIntersectsCellBsp(node, boxMin, boxMax): while node is not a leaf: if ClassifyBox(node.SplittingPlane, boxMin, boxMax) == Negative: return false # box entirely behind this splitting plane if node.PosNode is null: return true # solid interior — matches point/sphere siblings node = node.PosNode return true # reached a leaf ``` This is the **one new traversal shipped in two representations** per the Slice I4/I5 house rule: `BSPQuery.BoxIntersectsCellBsp` over `CellBSPNode` (the graph) and `FlatBspQuery.BoxIntersectsCellBsp` over `FlatCellContainmentBsp`/`FlatCellBspNode` (the flat production shadow), sharing the `ClassifyBox`/`WhichSide` math (added to `BSPQuery` as internal statics, exactly the existing pattern `FlatBspQuery` already uses for polygon math like `PolygonHitsSpherePrecise`). `CCellStruct::box_intersects_cell` (@0x00533910, pc:317675) is a bare tailcall into `BSPTREE::box_intersects_cell_bsp` (@0x005398b0, pc:323249), itself a bare tailcall into the function above — no additional logic, same shape as `sphere_intersects_cell` → `sphere_intersects_cell_bsp` already established in this codebase. ## 4. Box transform: retail `BBox::LocalToLocal` (@0x005b1e60) Needed at two call sites in §1 (`localBox` into `this` cell's frame, `destBox` into `otherCell`'s frame). Same shape already ported for the OUTDOOR extent walk's `BBox::LocalToGlobal` (`ShadowPartBox.RefitTo`, #334): an eight-corner transform-and-refit, not a min/max-only transform (a rotated box grows, conservatively — retail's own deliberate direction). `LocalToLocal` differs from `LocalToGlobal` only in that the **destination** frame carries its own rotation (a cell's `WorldTransform`/ `InverseWorldTransform`), not just a translation offset. D2 adds a sibling `ShadowPartBox.RefitToLocal(Matrix4x4 worldToLocal, ...)` that composes the part's world placement (`WorldPosition`/`WorldRotation`, identical to `RefitTo`) and then applies the destination's full `Matrix4x4` transform per corner before taking min/max — i.e. `RefitTo` is the degenerate case of `RefitToLocal` where the destination frame has no rotation (world axes). ## 5. Deliverable mapping | Contract deliverable | Implementation | |---|---| | D1 box primitives, graph | `BSPQuery.ClassifyBox`/`WhichSide` (shared), `BSPQuery.BoxIntersectsCellBsp` | | D1 box primitives, flat | `FlatBspQuery.BoxIntersectsCellBsp` (delegates classification math to `BSPQuery`) | | D1 dispatcher (matches existing `SphereIntersectsCell`/`PointInsideCell` shape) | `CollisionTraversal.BoxIntersectsCell` | | D1 referee | `tests/AcDream.Core.Tests/Physics/BoxIntersectsCellBspDifferentialTests.cs` | | D2 box transform | `ShadowPartBox.RefitToLocal` | | D2 indoor-arm rewire | `CellTransit.FindTransitCellsBox` (new), called from `BuildShadowCellSetFromParts`'s indoor branch in place of `FindTransitCellsSphere` | | D2 building-bridge remainder | Left unported (see §0); reported as the explicit remainder | | D3 | `tests/AcDream.Core.Tests/Physics/CellTransitFindTransitCellsBoxTests.cs` (synthetic sabotage + inverse guard) + a direction-assertion sweep test | --- ## Byte confirmations (added at the dual review, 2026-08-07 — PDB-paired binary) - **`Plane::which_side` @0x00444720, full decode:** `fcom dist, eps; test ah,0x41; jnz` — POSITIVE (0) iff `dist > eps` STRICTLY; then `fcompp dist, -eps; test ah,0x05; jnp` — NEGATIVE (1) iff `dist < -eps` strictly; IN_PLANE (2) otherwise (unordered also lands IN_PLANE). The port's `>= eps` boundary tie is a one-ULP divergence, noted in AP-159's landing record. - **`Plane::intersect_box` @0x005aa170, first-corner-in-plane early exit:** the `jp` at 0x005aa1bc targets 0x005aa2e2 = `mov eax, 3` — the early exit returns **CROSSING (3)**, not IN_PLANE (2). Both live call sites admit 3, so the S1B port is unaffected; the future `check_building_transit` port inherits this as a settled fact instead of review item b2. - `acclient.h:2527`: `Sidedness { POSITIVE=0, NEGATIVE=1, IN_PLANE=2, CROSSING=3 }` — the header answer the PlaneSide doc previously called unobservable. `which_side` returns at most 2; `intersect_box` is the only producer of 3.