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>
192 lines
13 KiB
Markdown
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.
|