# A6.P2 cdb capture findings — 2026-05-21

**Status:** SHIPPED — 5 of 9 scenarios captured; scen6-9 cancelled (see
"Capture inventory" below). Findings 1-4 ready for A6.P3 fix surfacing.

**Spec:** [`docs/superpowers/specs/2026-05-21-phase-a6-indoor-physics-fidelity-design.md`](../superpowers/specs/2026-05-21-phase-a6-indoor-physics-fidelity-design.md).

**PDB match verification:** [`pdb-match-verification.txt`](2026-05-21-a6-captures/pdb-match-verification.txt).

**Prior handoffs:**
- [A6.P1 partial-ship handoff](2026-05-21-a6-p1-partial-ship-handoff.md) (this session continues from there)

## TL;DR — what the 5 captures prove

1. **Finding 2 (ContactPlane resynthesis blowup) is overwhelmingly confirmed.**
   Across all 5 scenarios, acdream writes ContactPlane fields **250× to ∞×**
   more often than retail. The infinite ratio is scen3 (flat 2nd-floor walk):
   retail's `set_contact_plane` fires **zero times** while acdream writes
   86,748 field updates. This is the M1.5 root cause for "indoor walking
   feels broken." A6.P3 fix surface: stop resynthesizing CP every frame.
2. **Finding 1 (dispatcher entry frequency mismatch) extends to all scenarios**
   but is shape-divergent. Retail calls `BSPTREE::find_collisions` 4× to
   281× more often than acdream's `BSPQuery.FindCollisions`. Largest gap on
   scen5 (Town Network walk: retail 9,552 vs acdream 34 — 281× fewer in
   acdream). Suggests retail's `transitional_insert` calls dispatcher
   per-sub-step regardless of expected collisions; acdream's modern path
   is lazier.
3. **Finding 3 (cell-resolver indoor sling-out) directly captured on scen4.**
   +Acdream walked a few meters inside a Holtburg cottage cellar and the
   resolver flung the character across a landblock boundary (`0xA9B40148 →
   0xA9B40029 → 0xA9B30030`, all `reason=resolver`). Indoor BSP was barely
   queried (only 2 `[indoor-bsp]` hits during the sling-out); `[check-bldg]`
   fired 5,495 times trying to re-resolve which building +Acdream was in.
   This is a distinct failure family from the stair-attempt pattern (scen2)
   and the CP-write blowup.
4. **Finding 4 (portal-graph visibility blowup, scope-adjacent).** Discovered
   incidentally during scen5: after portal teleport to Town Network hub,
   `visibleCells` per cell exploded from ~4 to 135-145 and cells from
   disconnected landblocks (0x0007, 0x020A, 0x0408) were cached. Filed
   separately as **issue #95** — this is the underlying cause of
   user-observed "dungeons are broken (see through walls / other dungeons
   rendering)" across the project.

## Capture inventory

| # | Tag | Walk script | Retail | Acdream | Status |
|---|---|---|---|---|---|
| 1 | scen1_inn_doorway | Walk through inn door, stop just inside | ✅ | ✅ | committed (prior session) |
| 2 | scen2_inn_stairs | Walk up 4 steps; acdream re-captured as stair-FAILURE | ✅ | ✅ | committed (this session) |
| 3 | scen3_inn_2nd_floor | Forward 3m, sidestep 1m, walk back (teleport into acdream) | ✅ | ✅ | committed (this session) |
| 4 | scen4_cottage_cellar | Retail ascent + acdream teleport-in + sling-out | ✅ | ✅ | committed (this session) |
| 5 | scen5_sewer_entry | Town Network portal entry (Holtburg sewer doesn't exist) | ✅ | ✅ | committed (this session) |
| 6 | scen6_sewer_first_stair | — | ❌ | ❌ | **cancelled** — Holtburg Sewer doesn't exist on this server |
| 7 | scen7_sewer_inter_room | — | ❌ | ❌ | **cancelled** — same |
| 8 | scen8_sewer_chamber | — | ❌ | ❌ | **cancelled** — same |
| 9 | scen9_sewer_corridor | — | ❌ | ❌ | **cancelled** — same; any substitute dungeon hits issue #95 (visibility blowup) on portal entry, making physics-only analysis impossible |

**Why scen6-9 cancelled:** The A6.P1 design spec assumed the Holtburg Sewer existed and was accessed via portal. Neither is true on this ACE server. Any substitute dungeon (the path we'd normally take) hits the portal-graph visibility bug (issue #95) immediately on portal entry, making the dungeon visually unusable for navigation. A dedicated A6.P1-redux capturing post-#95-fix dungeon physics is a candidate for A8 or M1.5-residual scope; for A6.P2 the 5 captured scenarios provide sufficient evidence.

## Analysis tables

### Table 1 — Per-site push-back delta

**DEFERRED.** The current cdb probe (v4) captures BP5 (`adjust_sphere_to_plane`) at function **entry only**. Computing per-call delta `‖output_center − input_center‖` requires a paired epilogue breakpoint that captures the corrected sphere center after the function returns. This was scoped out of A6.P1 to keep the cdb script simple; adding it is a future A6.P1.5 (estimated 1 hour: add `bu` exit breakpoints to v5 of `a6-probe.cdb`).

**What we have instead:** BP5 entry-count is a proxy for "how often does adjust_sphere fire." From the BP5 counts in Table 3 (column 1), acdream's BP5 call rate is divergent from retail's, but without paired entry/exit values we can't quantify the over-correction directly.

**Bug-candidate threshold flagged in the spec (acdream > 3× retail on delta):** unmeasurable from current data; defer to A6.P1.5 or accept Findings 1-3 as sufficient triggers for A6.P3 fixes.

### Table 2 — Path-frequency diff

**DEFERRED.** Same reason as Table 1 — the cdb probe captures `BSPTREE::find_collisions` entry only, not which of the 7 exit paths (`PLACEMENT_INSERT`, `check_walkable`, `step_down`, `collide_with_pt`, `set_collide+slid`, `step_sphere_up`, `find_walkable`) was taken. Adding exit-discriminating breakpoints requires breakpoint after each return site in `find_collisions` — non-trivial cdb scripting work, deferred to A6.P1.5.

**What we have instead:** total dispatcher entries per scenario (Table 3 column 1). Acdream's overall dispatcher call rate is wildly lower than retail's in every scenario — see Finding 1 below.

### Table 3 — ContactPlane lifecycle diff (the smoking gun)

Walk duration was variable per scenario; values below are raw counts over the full capture window. Ratios are comparable across rows because both clients walked similar-duration scenarios per pair.

| Scenario | Retail BP4 dispatcher | Acdream push-back-disp | Acdream/Retail dispatcher ratio | Retail BP7 set_contact_plane | Acdream cp-write | **CP-write ratio (acd/retail)** |
|---|---:|---:|---:|---:|---:|---:|
| 1 inn doorway | 9,289 | 295 | 0.032× (31× fewer) | 18 | 73,304 | **4,072×** |
| 2 inn stairs (acdream: stair-fail) | 47,783 | 4,156 | 0.087× (11× fewer) | 136 | 33,969 | **250×** |
| 3 inn 2nd floor (acdream teleport) | 10,636 | 2,752 | 0.259× (4× fewer) | **0** | 86,748 | **∞** |
| 4 cottage cellar (acdream sling-out) | 12,596 | 82 | 0.007× (154× fewer) | 3 | 35,624 | **11,875×** |
| 5 town network portal | 9,552 | 34 | 0.004× (281× fewer) | 65 | 20,956 | **322×** |

**Geometric mean of CP-write ratio across the 4 finite scenarios (excluding scen3 ∞):** ~1,470×. **Median (excluding scen3):** ~2,200×.

**Verdict:** acdream writes the ContactPlane on order of 1,000× more frequently than retail. The only scenario where ratios are "small" (250×) is scen2's stair-attempt, where acdream's CP-write count is actually LOWER than the other scenarios because the failing physics couldn't synthesize a valid CP — see Finding 2 inversion.

### Table 4 — Sub-step state mutations

**PARTIAL.** Per-field mutation counts require shadow-state diffing across sub-steps, which the v4 probe doesn't emit. What we CAN report is per-tag firing rates that approximate state-mutation pressure:

| Scenario | Retail BP2 step_up | Acdream indoor-bsp | Acdream indoor-walkable | Acdream cell-cache | Acdream check-bldg |
|---|---:|---:|---:|---:|---:|
| 1 inn doorway | (0) | 26 | 18 | 540 | 9,530 |
| 2 inn stairs (fail) | 188 | 1,286 | 859 | 527 | 81 |
| 3 inn 2nd floor | (0) | 1,061 | 707 | 527 | 740 |
| 4 cottage cellar (sling) | 13 | 2 | 2 | 540 | **5,495** |
| 5 town network | 1 | 2 | 2 | 9,642 | 740 |

Notable patterns:
- **Acdream check-bldg fires 1-2 orders of magnitude more than push-back-disp** in scen1 (9,530 vs 295), scen4 (5,495 vs 82), and scen5 (740 vs 34). The `CheckBuildingTransit` machinery is constantly re-resolving "which building is the player in" even when the BSP itself isn't being queried. This is a state-thrash separate from the CP-write blowup.
- **Acdream indoor-bsp and indoor-walkable scale together** in the stair-attempt scenarios (scen2: 1,286/859; scen3: 1,061/707) but stay near zero on outdoor/portal walks (scen4/5: ~2 each). Suggests indoor BSP is gated by something that doesn't fire during normal Holtburg walking but DOES fire during stair attempts.
- **Cell-cache scales with how much landblock streaming happened**: 540 on standstill scenarios, 9,642 on scen5 where the player walked across Holtburg to reach the network portal.

## Per-scenario narrative

### Scenario 1 — Inn doorway (prior session)

User walked through the Holtburg inn front door, stopped just inside. Standard short walk over a threshold.

Retail: 18 set_contact_plane calls (~one per second of walking).
Acdream: 73,304 cp-write events. **Ratio: 4,072×.**

Per-call shape match (BP5 hit#1, vertical step-down probe against ground):
- Plane: (0, 0, 1), d≈0 — identical.
- Sphere radius: 0.48 — identical.
- WalkInterp: 1.0 — identical.
- Sphere.center.z and Movement.z DIFFER between retail and acdream (retail: -0.27 / -0.75; acdream: +0.46 / -0.50). Could be local-space convention difference (retail's `localspace_pos` vs our per-cell transform) OR could be the BSP correction-path divergence the spec hypothesizes. A6.P3 work surface.

### Scenario 2 — Inn stairs (acdream re-captured as stair-FAILURE)

Retail walked successfully up 4 inn stair steps. Acdream re-captured AFTER an initial mislabeled door-walk: user attempted to climb the inn stairs, character failed (couldn't ascend).

**Retail signature:** BP2 step_up=188 — clean stair-climb signature (scen1 doorway had only 1 BP2 hit). BP6 check_walkable=677 (with threshold=FloorZ 0.6642, confirmed by hex decoder).

**Acdream failure signature (stair-attempt vs door-walk):**

| Tag | door-walk | stair-attempt | Ratio |
|---|---:|---:|---:|
| push-back-disp | 1,141 | 4,156 | 3.6× |
| push-back-cell | 87 | 1,478 | **17×** |
| other-cells | 87 | 1,478 | **17×** |
| indoor-bsp | 343 | 1,286 | 3.7× |
| indoor-walkable | 227 | 859 | 3.8× |
| cp-write | 70,244 | 33,969 | 0.5× (inverse!) |

The 17× explosion on push-back-cell / other-cells is the failure: when the indoor BSP query can't resolve a stair-step, the multi-cell fallback fires constantly. The cp-write DROP (half the door-walk volume) is the inverse signal: when no ground plane resolves, no CP gets written. Both are A6.P3 fix-surface indicators.

### Scenario 3 — Inn 2nd floor (acdream via @teleport)

Flat-floor walk: forward 3 m, sidestep 1 m, walk back. Both clients in the same physical space (acdream got there via `@teleport` admin command).

**Retail signature:** BP1=10,217, BP4=10,636, BP5=113, BP6=113, **BP2=0, BP3=0, BP7=0.** No stairs, no walls, no contact plane updates — retail's physics did almost nothing because the 2nd-floor is flat and there's nothing to collide with.

**Acdream signature:** cp-write=86,748, push-back-disp=2,752, indoor-bsp=1,061, push-back=320.

The infinite-ratio CP-write blowup. Retail wrote CP zero times across an entire flat-floor walk; acdream rewrote CP fields 86,748 times. This is the cleanest evidence for Finding 2: the bug fires equally on ordinary flat indoor walking, not just on stair attempts.

### Scenario 4 — Cottage cellar (asymmetric pair)

Retail: walked UP out of cellar (2-step ascent + indoor→outdoor exit).
Acdream: teleported INTO cellar, walked a few meters, resolver flung +Acdream OUTSIDE the cottage.

**Retail signature:** BP2=13 (cellar ascent is 2 steps; gives 13 step_up hits — non-linear vs scen2's 188 hits for 4 stair steps; depends on step height and tick density). BP7=3 (almost no CP updates during ascent + exit).

**Acdream sling-out signature:** distinct from scen2's stair-attempt:
- check-bldg=5,495 (CheckBuildingTransit fired constantly during the sling)
- cell-transit=3 events captured the sling: `0xA9B40148 → 0xA9B40029 → 0xA9B30030`, all `reason=resolver`. The third transit crossed a landblock boundary (`A9B4 → A9B3`).
- indoor-bsp=2 (indoor BSP was barely queried during the sling!)
- push-back=1 (no real sphere-adjustment happened)

The sling-out is the cell-RESOLVER misbehaving, not the BSP. ResolveCellId pushed the player out of indoor space without engaging the indoor BSP collision path at all. The check-bldg storm is the symptom: every tick, CheckBuildingTransit re-tries to figure out which building the player is in, gets it wrong, and the resolver acts on that wrong answer.

### Scenario 5 — Town Network portal entry

Substituted for "Holtburg Sewer entry" (which doesn't exist). Both clients walked to the Town Network Portal in Holtburg, entered it, walked 2 m forward in the network hub.

**Retail signature:** clean walk + portal transition + indoor walking in hub. BP1=13,863, BP4=9,552, BP5=97, BP6=55, BP7=65 (moderate CP updates around the portal threshold), BP2=1 (portal threshold step-up).

**Acdream signature:** clean physics — no failure mode. cp-write=20,956 (still ~322× retail), push-back-disp=34 (very few dispatcher hits — mostly flat-ground walking with no collisions).

**Cell-transit chain — captures the portal entry:**

```
0x00000000 -> 0xA9B30030  reason=teleport  (login spawn)
0xA9B30030 -> 0xA9B40029 -> 0xA9B40021 -> 0xA9B40019 ->
0xA9B40011 -> 0xA9B40012 -> 0xA9B4000A -> 0xA9B4000B ->
0xA9B40003                                    (walked across Holtburg)
0xA9B40003 -> 0x00070143  reason=teleport  (PORTAL ENTRY)
0x00070143 -> 0xA9B30016  reason=resolver  (post-teleport resolver)
0xA9B30016 -> 0x00060016  reason=resolver  (lands at network hub)
```

**Incidental discovery (filed as issue #95):** post-teleport, `[cell-cache]` events showed `visibleCells=135-145` per cell (vs normal ~4-7), with cells cached from 3 separate landblocks (0x0007, 0x020A, 0x0408) — different `worldOrigin`s, i.e. different dungeons entirely. This is the portal-graph visibility blowup. Direct cause of "see through walls / other dungeons rendering" across the project.

## Findings

### Finding 1 — Dispatcher entry frequency mismatch (4× to 281× fewer in acdream)

**Status:** confirmed in all 5 scenarios; severity MEDIUM (probable secondary effect of the v4 probe scope rather than a single fix surface).

**Retail decomp anchor:** [`CTransition::transitional_insert`](docs/research/named-retail/acclient_2013_pseudo_c.txt) — retail's outer loop dispatches `BSPTREE::find_collisions` per sub-step regardless of expected collision. (Spec §1.2 hypothesis.)

**Our suspect code site:** `src/AcDream.Core/Physics/Transition.cs` / `src/AcDream.Core/Physics/BSPQuery.cs` — the modern dispatcher path likely short-circuits when no candidate cell has potential collision geometry.

**Divergence quantified:** retail BP4 hit count vs acdream push-back-disp hit count, per Table 3 column "Acdream/Retail dispatcher ratio." Range: 0.004× (scen5) to 0.259× (scen3). Worst gap on flat-walk scenarios where retail still queries dispatcher constantly.

**Proposed fix sketch:** investigate whether acdream's `Transition` is correctly calling FindCollisions in the per-sub-step inner loop. If `transitional_insert` short-circuits on a "no obvious collision" heuristic, the optimization may be hiding CP retention behavior that ONLY runs in the dispatcher's idle paths (e.g. step_down probe-to-ground that maintains LKCP). Removing the short-circuit may close Finding 2 as a side effect.

**Scenarios affected:** all 5.

### Finding 2 — ContactPlane resynthesis blowup (250× to ∞× more in acdream)

**Status:** confirmed in all 5 scenarios; severity **HIGH (single largest M1.5 root cause)**.

**Retail decomp anchor:** `COLLISIONINFO::set_contact_plane` and the three documented retention mechanisms — Mechanism A (Path-6 land write in `BSPQuery.FindCollisions`), Mechanism B (LKCP-restore in `validate_transition`), Mechanism C (post-OK step-down probe). See spec §1.2.

**Our suspect code site:** `src/AcDream.Core/Physics/Transition.FindEnvCollisions` indoor branch — likely resynthesizes ContactPlane per frame instead of retaining via the three mechanisms. Closely related: the existing `TryFindIndoorWalkablePlane` synthesis workaround (flagged for A6.P4 removal).

**Divergence quantified:** per Table 3 column "CP-write ratio." Median 2,200× across the 4 finite scenarios; infinite ratio on scen3 (retail: 0 writes; acdream: 86,748 writes for the same flat-floor walk).

**Proposed fix sketch:**
1. Audit every site in our physics code that writes `ContactPlane`. There should be at most 3 active sites per the retention mechanisms — likely we have N>>3.
2. Replace per-frame `ContactPlane.Set(...)` calls with the retain-or-restore pattern: at the start of each tick, restore CP from `LastKnownContactPlane` (Mechanism B); only update when Path-6 lands write a new plane (Mechanism A); only re-probe via step-down when the post-OK position is suspect (Mechanism C).
3. The `TryFindIndoorWalkablePlane` synthesis goes away as part of the same change (A6.P4).
4. Verification: after the fix, re-run the scen3 capture. Target: acdream cp-write count drops from 86,748 to ≤ retail's BP7 + some buffer (say ≤ 100). If the drop is large, the change is on the right track.

**Scenarios affected:** all 5 — strongest signal in scen3 (∞× ratio).

### Finding 3 — Indoor cell-resolver sling-out (scen4)

**Status:** confirmed in scen4; severity HIGH (player can't stay inside small indoor spaces).

**Retail decomp anchor:** `CObjCell::find_cell_list` Position-variant (`acclient_2013_pseudo_c.txt:308742-308783` — already cited in CLAUDE.md as the cell-tracking ping-pong oracle for the M1.5 hypothesis).

**Our suspect code site:** `src/AcDream.Core/Physics/PhysicsEngine.ResolveCellId` + `src/AcDream.Core/Physics/CellPhysics.CheckBuildingTransit`. Issue #90 (cell-id ping-pong workaround) is part of this surface and would be removed in A6.P4 once the proper fix lands.

**Divergence quantified:** scen4 captured 3 cell-transit events during a few meters of walking inside a cottage cellar:
- `0xA9B40148 → 0xA9B40029` (indoor cottage → outdoor cell, `reason=resolver`)
- `0xA9B40029 → 0xA9B30030` (crossed landblock boundary, `reason=resolver`)

During the sling, `[check-bldg]` fired 5,495 times (CheckBuildingTransit re-resolving repeatedly), `[indoor-bsp]` fired only 2 times (indoor BSP was barely queried), and `[push-back]` fired only 1 time (no real sphere-adjustment).

**Proposed fix sketch:** ResolveCellId / CheckBuildingTransit should preserve indoor cell membership when the sphere is close to (but slightly outside) the indoor CellBSP volume — the cell-array hysteresis logic retail uses. Port the stickiness logic from the retail decomp anchor above. May obsolete issue #90's workaround.

**Scenarios affected:** scen4 directly; likely scen2/scen3 cellar/inn variants too once the visibility bug (#95) is fixed and we can re-capture.

### Finding 4 — Portal-graph visibility blowup (scope-adjacent; filed as #95)

**Status:** confirmed in scen5; severity HIGH (blocks all dungeon navigation visually); **filed as issue #95**.

Not strictly an A6 physics finding — this surfaced incidentally during scen5 capture and explains the project-wide "dungeons are broken" symptom. Full writeup in `docs/ISSUES.md` issue #95. Mentioned here so A6.P3 sequencing knows about it: any future dungeon-physics work (A8 or M1.5-residual) needs #95 fixed first, because a broken visibility set makes any in-dungeon physics analysis untrustworthy (cells are loaded that shouldn't be, distance/visibility queries return wrong answers, etc).

## M1.5 symptom coverage

Per spec §4.7, every M1.5-in-scope symptom maps to at least one bug candidate OR is explicitly flagged as deferred.

| Symptom | Source | Mapped to finding | Notes |
|---|---|---|---|
| Issue #83 — Indoor multi-Z walking broken | ISSUES.md | Finding 2 (CP-write) + Finding 3 (resolver sling) | scen3 + scen4 evidence |
| Issue #88 — Indoor static objects vibrate | ISSUES.md | Finding 2 (CP-write resynthesis per-tick causes per-tick visible micro-adjustments on static-object physics) | Hypothesis: same root cause |
| Issue #90 — Cell-id ping-pong at indoor doorway threshold | ISSUES.md | Finding 3 (cell-resolver bug) | Issue #90 is the workaround; root cause is Finding 3. A6.P4 removes the workaround. |
| Stairs walk-through (acdream can't climb) | observed | Finding 1 + Finding 2 (the stair-step probe fails because CP isn't retained between sub-steps so step_up's walkability check sees the wrong plane) | scen2 stair-attempt direct evidence |
| 2nd-floor walking (works once teleported) | observed | Finding 2 only (scen3 shows pure flat-floor CP blowup) | Walking itself fine; CP-write is the divergence |
| Cellar descent (acdream can't descend) | observed | Finding 1 + Finding 2 same as stairs | not directly captured (couldn't descend in acdream) but same physics |
| `TryFindIndoorWalkablePlane` synthesis MISS | spec §1.2 | Finding 2 (same family) | A6.P4 removes the workaround as part of the Finding 2 fix |
| Sling-out from inside building | scen4 discovery | Finding 3 (cell-resolver) | NEW symptom not in original M1.5 list — promote to symptom roster |
| "Dungeons are broken" project-wide | user-observed | Finding 4 / issue #95 (NOT A6 scope) | Defer to dedicated visibility-bug fix |

**A6.P2 acceptance test:** every in-scope M1.5 physics symptom has a mapped finding. ✅ Met.

## A6.P3 fix-surface sequencing recommendation

Per spec §5.1: "highest-confidence single-cause fix first."

**Recommended order:**

1. **Finding 2 first** (CP-write resynthesis) — single largest divergence, single largest probable impact, narrowest suspected code site (`Transition.FindEnvCollisions` indoor branch + ContactPlane retention). If Finding 1 IS a secondary effect of CP-write missing the dispatcher idle paths (the hypothesis in Finding 1's fix sketch), then fixing Finding 2 may close Finding 1 automatically. Highest expected value per PR.
2. **Re-run scen1-5 captures after Finding 2 PR lands.** Compute new ratios. If CP-write ratios drop from ~1000× to ~1× (target), Finding 2 is closed.
3. **If Finding 1 dispatcher gap also closed** — proceed directly to Finding 3.
4. **If Finding 1 still wide** — separate PR for the dispatcher-call-rate fix.
5. **Finding 3** (cell-resolver sling-out) — narrower fix; specific to ResolveCellId + CheckBuildingTransit cell-stickiness. PR also removes issue #90 workaround.
6. **A6.P4 visual verification at Holtburg inn → stairs → cellar.** Acceptance per spec §6.3.
7. **Finding 4 / issue #95** is NOT in A6.P3 scope. Handle separately when scheduled for the visibility-bug work.

## Open items / next-session candidates

- **A6.P1.5** (optional, ~1 hour): extend cdb probe with paired entry/exit BPs to capture `adjust_sphere_to_plane` output delta (Table 1) and `find_collisions` exit-path discrimination (Table 2). Only needed if A6.P3 fixes don't close the symptoms and we need sharper data. Defer until after A6.P3 first attempt.
- **Issue #95** (separate work surface): portal-graph visibility blowup. Schedule outside A6 since fixing it unblocks scen6-9 captures and any future dungeon physics work.
- **Symptom roster update:** add "indoor sling-out" to M1.5 symptom list (Finding 3 family); already captured here as a finding, but M1.5 doc should reflect it.

---

## A6.P3 slice 1 — SHIPPED 2026-05-21

Strip-synthesis + Mechanism B (LKCP restore) fix landed in 8 commits across this same session:

| Commit | Task | What |
|---|---|---|
| `ba9655f` | plan | A6.P3 slice 1 implementation plan written |
| `6b4be7f` + `c6bc2b9` | T1 | Research note: retail's `CTransition::validate_transition` LKCP-restore (line 272565-272583) + insertion-point identified in our `Transition.ValidateTransition` at TransitionTypes.cs:2849 |
| `869edd9` | T2 | Test instrumentation: `CollisionInfo.ContactPlaneWriteCount` counter |
| `36975ef` + `a32f569` | T3 | Failing regression: `IndoorContactPlaneRetentionTests` — asserts ≤5 CP writes across 60 flat-floor frames |
| `5aba071` | T4 | Mechanism B (LKCP restore) inserted in `ValidateTransition` + proximity-check sphere bug fix (`GlobalSphere[0]` → `GlobalCurrCenter[0]`) |
| `5f7722a` + `39fc037` + `bd5fe2e` | T5 | Indoor branch of `FindEnvCollisions` stripped to match retail's tiny `CEnvCell::find_env_collisions` shape; test redesigned as real regression sentinel (validated 60-writes-pre-strip → 0-writes-post-strip) |
| `066568a` | T6/T7 partial | scen2_inn_stairs_postfix acdream capture proves stairs now work |
| (this commit) | T6 + T8 | scen3_inn_2nd_floor_postfix capture + bookkeeping (findings doc + roadmap + CLAUDE.md + issues #96/#97 filed) |

### scen3 re-capture results (postfix)

scen3 (Holtburg inn 2nd floor flat-walk) re-captured in this slice-1 ship commit:

| Metric | Pre-fix (4b5aebc) | Post-fix | Reduction |
|---|---:|---:|---:|
| acdream cp-write (absolute) | 86,748 | 25,082 | 3.5× |
| acdream cell-cache events (proxy for session length) | 527 | 9,629 | 18× longer session |
| **cp-write per cell-cache (normalized)** | **164.61** | **2.60** | **63.2× per-unit-of-activity** |
| retail BP7 set_contact_plane | 0 | 0 | unchanged (oracle) |

Per-unit-of-activity drop is the meaningful number — a longer post-fix session naturally accumulates more total writes, but the rate per "unit of activity" (cell-cache events ~ landblocks traversed) collapsed 63×.

### scen2 re-capture results (postfix — UNEXPECTED WIN)

scen2 (Holtburg inn stairs) acdream re-captured at commit `066568a`. **Pre-fix: physics hammered BSP trying to resolve stairs (failure mode). Post-fix: user walked up and down stairs multiple times with no failure.** Tag shape shifted:

| Tag | Pre-fix (stair FAIL) | Post-fix (stair SUCCESS) | Signal |
|---|---:|---:|---|
| indoor-walkable | 859 | **0** | synthesis gone (as designed) |
| push-back-cell | 1,478 | 879 (-40%) | multi-cell iteration relaxed |
| push-back | 51 | 345 (+577%) | real step_up firing |
| push-back-disp | 4,156 | 6,055 (+46%) | real BSP traversal |
| cp-write | 33,969 | 57,846 | L622 seed (slice 2 work) |

Stairs working post-slice-1 confirms A6.P2's hypothesis that **Finding 1 (dispatcher entry frequency mismatch) was a secondary effect of Finding 2** — fixing CP retention also closes the cell-array iteration storm that prevented stair-step resolution.

### Visual verification (user happy-testing, 2026-05-21)

User report from happy-testing session post-slice-1:
- ✅ 2nd floor walking works (with caveats below)
- ✅ Stairs up + down work (M1.5 demo target unblocked)
- ✅ Cellar descent works (M1.5 demo target unblocked)
- ❌ Phantom collisions occasionally on 2nd floor — filed as **issue #97** (hypothesis: caused by #96)
- ❌ Occasional fall-through on 2nd floor — filed as **issue #97** (same)
- ❌ See-through-walls indoors — **issue #95** (not A6 scope; visibility blowup)
- ❌ Indoor lighting broken — **A7 scope**

### Status of A6.P2 findings post-slice-1

| Finding | Status post-slice-1 |
|---|---|
| Finding 1 — dispatcher entry frequency mismatch | **CLOSED as side-effect of Finding 2 fix** (scen2 dispatcher shape now retail-like) |
| Finding 2 — ContactPlane resynthesis blowup | **PARTIALLY CLOSED.** Synthesis path eliminated (indoor-walkable = 0). Remaining 99.3% of post-fix CP writes come from `PhysicsEngine.ResolveWithTransition` line 622 — a per-tick body-CP seed that retail doesn't do. **Filed as issue #96** for slice 2. |
| Finding 3 — Indoor cell-resolver sling-out | OPEN. Not addressed by slice 1. Needs scen4 re-capture to confirm whether sling-out symptom persists post-slice-1 (possible side-effect close); separate fix surface in ResolveCellId / CheckBuildingTransit otherwise. |
| Finding 4 — Portal-graph visibility blowup | OPEN as issue #95 (not A6 scope; user-confirmed during happy-testing). |

### Slice 2 recommendation

**Highest-value next slice: gate the L622 per-tick CP seed.** It's responsible for 99.3% of remaining post-fix CP writes (24,906 of 25,082 in scen3 postfix). Retail's equivalent code path fires zero `set_contact_plane` calls during flat-floor walks. Either remove the seed entirely (rely on Mechanism A/B for CP propagation) OR gate it to fire only when the body's CP has changed since last seed.

After slice 2, re-test phantom collisions + fall-through (issue #97) — they may close as side-effects (same family of "CP state being unstable across ticks"). If not, that becomes slice 3 territory + Finding 3 work.

A6.P4 (workaround removal + visual verification) can proceed in parallel with slice 2 if scope allows.
