acdream/docs/research/2026-06-03-p0-conformance-apparatus-notes.md
Erik 46a86d282e docs(p0): CORRECTION — retail pick is center-only point_in_cell; the bare-FindCellList divergence is a probe artifact
Read CEnvCell::point_in_cell @ 0x52c300 -> CCellStruct::point_in_cell @ 0x5338f0
-> BSPTREE::point_inside_cell_bsp: the find_cell_list PICK (pc:308810) is
CENTER-ONLY, at global_sphere[0].center (the swept sphere center), NOT
radius-aware and NOT the foot origin. So acdream's PointInsideCellBsp pick
criterion ALREADY matches retail. The architect's 'use SphereIntersectsCellBsp
in the pick' hypothesis is FALSIFIED. The P0 FindCellList_DoorwayThreshold probe
fed the foot origin (captured m_position) through no sweep -> its 'all 22
diverge' is a PROBE ARTIFACT, not a confirmed production divergence (the data's
own tell: retail commits the cell AHEAD of motion while the foot is behind = the
swept sphere center crossing the portal).

P1's decisive first step is the PRODUCTION-PATH trajectory conformance (replay
the golden through ResolveWithTransition, which uses sp.GlobalSphere + the sweep)
BEFORE designing any fix. Do not port a portal-crossing/radius pick on the probe.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 15:25:45 +02:00

192 lines
13 KiB
Markdown

# 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.
## 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.