# P0 — Conformance apparatus notes (characterized topology + goldens) 2026-06-03. Companion to [`docs/superpowers/plans/2026-06-03-p0-conformance-apparatus.md`](../superpowers/plans/2026-06-03-p0-conformance-apparatus.md). Source of truth for the cell ids + golden inputs the P0 conformance tests pin. All values below were **characterized from the real client dats** (not guessed) by `CottageDoorwayCharacterizationTests` against `%USERPROFILE%\Documents\Asheron's Call`. ## Characterized Holtburg (landblock 0xA9B4) indoor neighborhood Scanned `0xA9B40140..0xA9B4017F`. All cells load with a real ContainmentBsp and `seenOutside=1`. Cells share a per-BUILDING world origin (the building's reference frame). The distinct buildings: | Cell range | World origin | Stab | Identity | |---|---|---|---| | `0140..0150` (17 cells) | (130.50, 11.50, 94.00) | 17 | **Cottage-with-cellar** (#98 cellar saga; exit portals at 0145,014C,014E,014F,0150) | | `0151..0169` (25 cells) | (107.50, 36.00, 94.00) | 24 | Larger building (inn) | | `016A..016E` (5 cells) | (79.50, 37.50, 94.00) | 4 | Small building | | **`016F..0175` (7 cells)** | **(161.93, 7.50, 94.00)** | 6 | **THE doorway-threshold building (master-plan 0170/0171)** | | `0176..0178` | (65.21, 156.63, 66.00) | 2 | — | | `0179..017A` | (158.18, 37.71, 94.00) | 1 | — | | `017B..017F` | (161.72, 105.05, 66.00) | 4 | — | ## The pinned threshold topology (master-plan `0031↔0170↔0171`) Building at world origin **(161.93, 7.50, 94.00)**: | Cell | Role | Portal dests | seenOutside | BSP | |---|---|---|---|---| | `0xA9B40170` | **vestibule / doorway** | `[0xFFFF (exit→outdoor), 0x0171]` | 1 | real | | `0xA9B40171` | **room** (behind the door) | `[0x0170, 0x0173, 0x0175]` | 1 | real | Grid math confirms the outdoor side: origin (161.93, 7.50) → gridX = ⌊161.93/24⌋ = 6, gridY = ⌊7.5/24⌋ = 0 → landcell id `6*8 + 0 + 1 = 49 = 0x31` → **outdoor landcell `0xA9B40031`**. So the player crossing the doorway traverses `0031 (outdoor) → 0170 (vestibule, via the 0xFFFF exit portal) → 0171 (room)` — exactly the master-plan ping-pong `0031↔0170↔0171`. **Verified real.** > Naming note: a 2026-05-21 capture dir called `0170/0171` the "inn 2nd floor"; that label was > loose. By geometry it is the 7-cell building at (161.93, 7.50). Identity (cottage vs inn) is > irrelevant to the conformance — what matters is `0170` carries the exit portal (the doorway) and > `0171` is the room behind it. ## Golden interior points (verified `point_in_cell == true`) Cell-LOCAL points whose `EnvCell.PointInCell(world)` returns true (the world form is `Vector3.Transform(local, cellPhysics.WorldTransform)`): | Cell | Interior LOCAL point | Interior WORLD point | Notes | |---|---|---|---| | `0xA9B40170` | (5.865, -8.449, 0.417) | (156.06, 15.95, 94.42) | 115/125 grid-inset points inside (small/irregular vestibule) | | `0xA9B40171` | (6.55, -3.25, 4.60) | (157.01, 13.69, 95.53)* | bounds-center = bsphere origin; 125/125 inside (clean box) | \* world recomputed by the test at runtime via the cell's WorldTransform; the value above is the firstInside probe — the canonical bsphere-origin local (6.55,-3.25,4.60) is also fully interior. These are **retail-faithful by construction**: the ContainmentBsp is loaded from the same dats retail loads, so a geometrically-correct containment answer is the retail answer. ## Golden provenance summary | Golden | Provenance | Status | |---|---|---| | `point_in_cell` (interior true, far-away false) | geometric (real dat BSP) | autonomous (Task 3) | | `find_cell_list` deep-inside picks `0171`/`0170` | geometric (real dat cells) | autonomous (Task 4) | | `find_cell_list` doorway-threshold pick | **retail cdb trace** | Task 6 — USER GATE / mine existing traces | | PVS visible-set | retail cdb `cell_draw_list` trace | deferred to P4 (Task 7 scaffold) | ## Existing retail traces — mined, NOT usable for membership `docs/research/2026-05-21-a6-captures/*/retail.log` + `retail.decoded.log` were grepped for `change_cell` / `curr_cell` / `find_cell_list` / `cell=0x` / `insert_into_cell`. **Zero matches in any retail log** — all 15,492 membership/cell-id matches are in the paired `acdream.log` files (our own probe output). The committed retail traces are collision-only (`find_collisions` hit-counters, `set_neg_poly_hit`, etc.); they carry no membership cell-id or position. So the retail-trace-backed threshold golden **cannot** be built from existing data — a live capture is required (`tools/cdb/find-cell-list-capture.cdb`). ## Retail capture — DONE (2026-06-03). The P0 gate is MET. Captured live from retail (cdb on `CPhysicsObj::change_cell`, symbol-driven, PDB MATCH) at the "Agent of Arcanum" house — which by geometry IS the `0170`/`0171` building. **22 transitions, a perfectly clean monotonic `0031↔0170↔0171` sequence, NO ping-pong** (retail's membership is correct-by-construction, as the master plan asserts). Golden fixture committed at `tests/AcDream.Core.Tests/Conformance/Fixtures/find-cell-list-threshold.log`. Capture+decode tooling: `tools/cdb/find-cell-list-capture.cdb` + `tools/cdb/decode_fcl_capture.py`. ## ★ ROOT-CAUSE FINDING (the central P1 work) ★ The per-transition containment diagnostic (`ThresholdDivergenceDiagnosticTests`) shows **all 22 transitions diverge**, for ONE reason: | retail transition | retail picks | acdream `FindCellList` | foot `in_seed` | `in_0170` | `in_0171` | |---|---|---|---|---|---| | `0170→0171` (enter room) | **0171** | 0170 | 1 | 1 | **0** | | `0171→0170` (leave room) | **0170** | 0171 | 1 | 0 | **1** | | `0170→0031` (exit bldg) | **0031** | 0170 | 1 | 1 | 0 | | `0031→0170` (enter bldg) | **0170** | 0031 | 0 | 0/1 | 0 | **Retail transitions membership at the PORTAL CROSSING** (`CEnvCell::find_transit_cells` — the sphere crosses the doorway polygon plane). **acdream's `FindCellList` re-picks by POINT-IN-CELL containment at the foot.** So retail commits the neighbour cell BEFORE the foot point is geometrically inside it (it enters room `0171` while the foot is still inside vestibule `0170`'s BSP, `in_0171=0`), and acdream lags. This is the master-plan §0 "hysteresis gap" diagnosis confirmed against live retail — and it is NOT a per-cell hysteresis or a building-entry-only split; it is a single criterion mismatch (directed portal crossing vs point-in-cell) that affects EVERY threshold transition. **P1's central job:** port `CEnvCell::find_transit_cells` (`@ 0x52c820 pc:309968`) directed portal-crossing so membership transitions at the doorway plane, not at the BSP-containment boundary. (Plus intrinsic building entry A3 + uniform `find_env_collisions` B1.) The documents-the-bug test `FindCellList_DoorwayThreshold_DivergesFromRetail_PendingP1` PASSES while this diverges and FAILS when P1 lands → rewrite it to assert the full sequence then. **P1 design nuances (from reading `find_transit_cells` @ pc:309968 + the acdream membership map):** 1. `find_transit_cells` (sphere variant) STRUCTURE: per portal — (a) exit portal (`other_cell_id == 0xffffffff`): if a sphere crosses the exit-portal polygon plane toward outside → set `exitOutside`; (b) interior/building portal with a LOADED neighbour: if a sphere `CCellStruct::sphere_intersects_cell(neighbour) != OUTSIDE` → `add_cell(neighbour)`; (c) interior/building portal with an UNLOADED neighbour: if a sphere crosses the portal POLYGON plane (`dist` vs ±(radius+0.0002), honoring `exact_match`) → `add_cell(other_cell_id)`. After all portals, `if (exitOutside) add_all_outside_cells`. The transition's `curr_cell` then advances to the crossed/overlapped neighbour — BEFORE foot-containment. 2. **acdream already has `CellTransit.FindTransitCellsSphere`** (a partial port of exactly this: exit→exitOutside, loaded-neighbour→`SphereIntersectsCellBsp`, unloaded→plane-distance). So P1 is NOT writing find_transit_cells from scratch — it is making `curr_cell` ADVANCEMENT use the portal-crossing result (the candidate the sphere crossed into) as the membership answer, instead of `FindCellSet`/`FindCellList`'s point-in-cell interior-wins pick. Audit `RunCheckOtherCellsAndAdvance` + `SetCheckPos` (the swept-cell advance) — that is where the pick criterion gets chosen. 3. **Test against the PRODUCTION path, not bare `FindCellList`.** The P0 documents-the-bug test calls `FindCellList` directly (unit-level). P1's conformance must replay the golden POSITIONS through `PhysicsEngine.ResolveWithTransition` (a trajectory, like `CellarUpTrajectoryReplay`, seeded incl. the outdoor landcell `0031`) and assert the swept `CellId` sequence matches the captured retail `change_cell` sequence. Bare-`FindCellList` is the unit pin; the trajectory is the integration gate. (Needs the outdoor landcell + building portal loaded — more than the building-only cache P0 uses.) ## ⚠ CORRECTION (same session, after reading `point_in_cell` + an architect pass) The "all 22 diverge → retail uses portal-crossing, acdream uses point-in-cell" framing above is the **probe-level** result and is **likely a test artifact**. Two decomp reads correct it: 1. **Retail's `find_cell_list` PICK is center-only `point_in_cell`, NOT radius-aware.** `CObjCell::find_cell_list` pick @ pc:308810 calls `vtable[0x84]` = `CEnvCell::point_in_cell` (`@ 0x52c300 pc:309677`) → `globaltolocal` → `CCellStruct::point_in_cell` (`@ 0x5338f0 pc:317657`) → **`BSPTREE::point_inside_cell_bsp(cell_bsp, point)`** — a center-only point test. The radius-aware `sphere_intersects_cell_bsp` is a SEPARATE method (`CCellStruct::sphere_intersects_cell` `@ 0x533900 pc:317666`), used by `find_transit_cells`' SET-BUILD, not the pick. So acdream's pick criterion (`PointInsideCellBsp`) ALREADY matches retail. An architect hypothesis that the fix is "use `SphereIntersectsCellBsp` in the pick" is **FALSIFIED** — that would DIVERGE from retail. 2. **The pick point retail tests is `global_sphere[0].center` (the swept sphere center), not the foot origin.** `find_cell_list(&check_pos, num_sphere, global_sphere, …)` (pc:309088); the pick tests `arg3->center` = `global_sphere[0].center`. The P0 `FindCellList_DoorwayThreshold_*` tests + the diagnostic fed `pick.Position` = the captured `m_position.frame.m_fOrigin` (the FOOT origin at the resting position), NOT the sphere center, and NOT through the sweep. The diagnostic's own data shows the tell: retail always commits the cell AHEAD of motion while the foot is still behind (`0170→0171` with `in_0171=0`; `0171→0170` with `in_0170=0`) — the signature of the **swept sphere center crossing the portal**, which a static foot-point probe cannot reproduce. **What this means for P1.** The membership criterion may NOT be broken; acdream's production `ResolveWithTransition` already feeds `sp.GlobalSphere` (the sphere center) through the swept transition. The DECISIVE, evidence-first first step is the **production-path trajectory conformance** (replay the golden positions through `ResolveWithTransition`, assert the swept `CellId` sequence == retail's `change_cell` sequence). Outcomes: (a) production MATCHES → the bare-`FindCellList` divergence was a probe artifact, P1 shrinks to building-entry cleanup (big de-risk); (b) diverges, fixed by the right point/sweep → small + localized; (c) still diverges → a real bug, design from THAT evidence. **Do NOT design or code a P1 membership "fix" before the production-path test exists and its RED/GREEN is read.** The P0 `..._DivergesFromRetail_PendingP1` test is a UNIT-level pin only, NOT evidence of a production divergence. ## ⚠ SUPERSEDED 2026-06-03 — the "production path DIVERGES" conclusion below was a CAPTURE ARTIFACT The section below concluded the production path genuinely diverges (0/11 → "port the swept curr_cell advance"). **That is wrong.** The 0/11 came from a one-frame skew in the cdb golden: `CPhysicsObj::SetPositionInternal` calls `change_cell` (`acclient_2013_pseudo_c.txt:283456`) BEFORE `set_frame` writes `m_position` (`:283458`), so the original capture paired each frame's NEW cell with the PREVIOUS frame's position (`golden_picked[i] == geom(golden_position[i+1])`, all 22 rows). An aligned re-capture (`tools/cdb/find-cell-list-capture-aligned.cdb`, position read from the following `set_frame`) makes the production gate read **9/9 with NO code change** — acdream's center-only `point_in_cell` pick already IS retail's true per-frame membership. Canonical corrected finding: `memory/project_retail_membership_criterion.md` + the RESOLVED banner in `docs/research/2026-06-03-p1-membership-swept-advance-handoff.md`. The text below is retained as the investigation trail. ## ✅ (HISTORICAL) RESOLVED — the production path DIVERGES (the "probe artifact" hypothesis is FALSIFIED) Built `ThresholdPortalCrossingReplayTests.ProductionPath_IndoorCrossings_DivergeFromRetail_PendingP1` (replays the golden indoor `0170↔0171` segments through the REAL `ResolveWithTransition` — engine builds the global sphere + sweeps; cells loaded from dats with real BSP). Result: **0/11 match retail.** Every segment: `restPos == target` (the sweep COMPLETES the move cleanly) but `CellId` stays on the SOURCE cell — acdream moves the body across the doorway yet **never advances `curr_cell`**. So production membership genuinely lags; the P0 finding is REAL, not a probe artifact. **Refined mechanism (supersedes the "portal-crossing vs point-in-cell criterion" framing).** Both retail and acdream PICK with center-only `point_in_cell`. The divergence is that retail's `curr_cell` ADVANCES to the neighbour during the sweep (the swept sphere crossing the doorway polygon, and/or a sphere point that leads the foot into the room), so by the time the foot rests at the captured position the membership has already advanced. acdream's swept advance does NOT promote the neighbour — at the end-position its tested sphere center is still inside the source cell's BSP, so the pick keeps the source cell. **P1's job: port how retail advances `curr_cell` across the portal mid-sweep.** The open decomp questions for P1: (1) how `global_sphere[0]` local origin relates to `m_position` (does retail's sphere point lead the foot?); (2) whether `curr_cell` advances via `find_transit_cells`' swept crossing in `transitional_insert`/`validate_transition` BEFORE the `find_cell_list` pick, vs the pick alone. Anchors: `CTransition::transitional_insert @ 0x50aa70 pc:272547`, `CTransition::validate_transition`, `CPhysicsObj::SetPositionInternal @ 0x515330 pc:283399`. The RED production-path test is the gate the P1 fix must turn GREEN. ## P0 status / P1-entry checklist — COMPLETE **Apparatus: COMPLETE + GREEN.** (Conformance suite 59 pass / 1 skip / 0 fail.) - ✅ Dat-backed fixture loader (`ConformanceDats`). - ✅ Characterized + pinned cottage-doorway topology (`0031↔0170↔0171` verified real). - ✅ `point_in_cell` goldens vs real dat BSP. - ✅ `find_cell_list` unambiguous goldens (interior picks + stale-seed re-pick stability). - ✅ Retail-trace parser + cdb capture script + decoder + README. - ✅ PVS-golden scaffold (skipped; filled in P4). - ✅ **Retail-trace golden captured + the threshold divergence pinned (documents-the-bug, GREEN).** - ✅ **P0 GATE MET: ≥1 retail-trace-backed assertion exists. P1 may begin.** **Re-scope note for P1 (discovered during P0):** this branch already carries the membership "Stage 1" work the master plan's §2 "acdream now" column lists as partial — `CellArray` (ordered CELLARRAY / R1 flap fix), `FindCellSet`'s interior-wins pick (cites pc:308788-308825), `RunCheckOtherCellsAndAdvance` (collide-then-pick), swept `sp.CurCellId` return, player-only `UpdatePlayerCurrCell`. **Per the CORRECTION above, the pick criterion is NOT confirmed wrong** — retail's pick is center-only `point_in_cell`, same as acdream's. P1's TRUE first step is the production-path conformance to find where (if anywhere) production membership actually diverges from the retail `change_cell` golden, THEN the (likely small) fix, THEN delete `CheckBuildingTransit` + unify `find_env_collisions` + demote `ResolveCellId` to seed-only. Re-confirm against the code + the production-path evidence when P1 starts — do not port a "portal-crossing pick" on the probe artifact.