ship(phys): A6.P3 slice 1 — Indoor ContactPlane retention COMPLETE

Slice 1 ships the strip-synthesis + Mechanism B (LKCP restore) fix
addressing A6.P2 Finding 2. Includes:

  - scen3_inn_2nd_floor_postfix paired capture (retail.log + decoded
    + acdream.log) as verification artifact. acdream cp-write count
    dropped from 86,748 to 25,082; per-unit-of-activity rate dropped
    63x (164.61 -> 2.60 cp-writes per cell-cache event).

  - Findings doc (docs/research/2026-05-21-a6-cdb-capture-findings.md)
    appended with slice 1 SHIPPED section: commit map, scen2/scen3
    pre/post comparison tables, user happy-test results, status of
    each A6.P2 finding (Finding 1 CLOSED as side-effect, Finding 2
    PARTIALLY CLOSED with remaining 99.3% from L622 seed flagged
    as #96, Finding 3 + #95 still open), slice 2 recommendation.

  - Issue #96 filed: "Per-tick PhysicsEngine.ResolveWithTransition CP
    seed contributes 99.3% of post-slice-1 CP writes." Slice 2 target.
    Sketch options: remove entirely / gate by change-detection / no-op
    guard inside SetContactPlane.

  - Issue #97 filed: "Phantom collisions + occasional fall-through on
    indoor 2nd floor." User-reported during happy-testing. HYPOTHESIS:
    side-effect of #96; falsifiable by re-testing post-slice-2.

  - CLAUDE.md updated: Currently-working-toward block now points at
    A6.P3 slice 2 (#96) as the active phase. M1.5 building/cellar
    demo half is ACHIEVABLE NOW (slice 1 unblocked the physics).

  - Roadmap updated: A6.P3 broken into 3 slices, slice 1 marked
    SHIPPED with commit history.

KEY USER-VISIBLE OUTCOME: stairs + cellar descent now WORK in acdream
(user happy-test confirmed walking up/down inn stairs multiple times,
walking down to cellar). A6.P2 Finding 1 (dispatcher entry frequency
mismatch) confirmed as secondary effect of Finding 2 — closed without
explicit Finding 1 work, as A6.P2 hypothesized.

REMAINING CONCERNS for slice 2 + future:
  - L622 per-tick seed (#96) still firing 24,906 times in scen3 postfix
  - Phantom collisions + fall-through on 2nd floor (#97)
  - See-through-walls indoors (#95, separate scope)
  - Indoor lighting broken (A7 scope)

Test suite: 1148 pass + 8 pre-existing fail (baseline maintained;
T3 IndoorContactPlaneRetentionTests adds the +1).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Erik 2026-05-22 10:11:44 +02:00
parent 066568a711
commit f04ea90050
7 changed files with 127435 additions and 24 deletions

View file

@ -726,25 +726,31 @@ render identically to pre-O. Spec:
[`docs/superpowers/specs/2026-05-21-phase-o-dat-path-unification-design.md`](docs/superpowers/specs/2026-05-21-phase-o-dat-path-unification-design.md).
**Currently working toward: M1.5 — Indoor world feels right** (resumed
from 2026-05-20 baseline after Phase O ship). **A6.P1 + A6.P2 SHIPPED
2026-05-21** (this session). Current A6 phase: **A6.P3 — Fix BSP
correction paths.** Per A6.P2 sequencing: Finding 2 first (ContactPlane
resynthesis blowup — primary M1.5 root cause; suspect site
`Transition.FindEnvCollisions` indoor branch). Re-run scen1-5 captures
after PR. Then Finding 3 (cell-resolver sling-out from scen4; remove #90
workaround in same PR). Findings doc:
from 2026-05-20 baseline after Phase O ship). **A6.P1 + A6.P2 + A6.P3
slice 1 SHIPPED 2026-05-21** (this session). Slice 1's strip-synthesis
+ Mechanism B (LKCP restore) fix unblocked stairs + cellar descent in
acdream (user happy-test confirmed walking up/down stairs and into
cellar) — confirms A6.P2's hypothesis that Finding 1 (dispatcher
frequency mismatch) was a secondary effect of Finding 2 (CP-write
blowup). Current A6 phase: **A6.P3 slice 2 — gate L622 per-tick CP
seed (issue #96)**. Slice 2 closes the remaining 99.3% of post-fix CP
writes that come from the body-CP seed at
`PhysicsEngine.ResolveWithTransition:622` (retail equivalent fires zero
calls). After slice 2, re-test phantom collisions + occasional
fall-through on 2nd floor (issue #97, hypothesized side-effect of #96).
Findings doc:
[`docs/research/2026-05-21-a6-cdb-capture-findings.md`](docs/research/2026-05-21-a6-cdb-capture-findings.md).
Original demo scenario (Holtburg Sewer end-to-end) is unreachable: sewer
doesn't exist on this server, and **issue #95** (portal-graph visibility
blowup, discovered in A6.P1 scen5) blocks any substitute dungeon. Revised
M1.5 demo split into building/cellar half (achievable after A6.P3) +
blowup) blocks any substitute dungeon. Revised M1.5 demo split into
building/cellar half (ACHIEVABLE NOW — slice 1 unblocked the physics) +
dungeon half (blocked on #95). Issues in scope: #80, #81, #83, #88, #90
(workaround removal), **#95** (visibility blowup; not A6 scope but
blocks dungeon demo), L-indoor, L-spotlight, stairs, 2nd-floor, cellar,
indoor sling-out (new from scen4), and the `TryFindIndoorWalkablePlane`
synthesis removal. **M2 ("Kill a drudge") is deferred until M1.5
lands.** Full M1.5 writeup at the corresponding block in
`docs/plans/2026-05-12-milestones.md`.
(workaround removal), **#95** (visibility blowup; not A6 scope), **#96**
(L622 seed, slice 2), **#97** (phantom collisions + fall-through; #96
side-effect), L-indoor, L-spotlight, indoor sling-out (Finding 3), and
the `TryFindIndoorWalkablePlane` definition deletion (A6.P4). **M2
("Kill a drudge") is deferred until M1.5 lands.** Full M1.5 writeup at
the corresponding block in `docs/plans/2026-05-12-milestones.md`.
**Today's pre-M1.5 baseline (2026-05-20).** Five surgical fixes
shipped to close the user-reported "logged in inside the inn, ran

View file

@ -602,6 +602,68 @@ Retail oracle for cell-id hysteresis: `acclient_2013_pseudo_c.txt:308742-308783`
---
## #96 — Per-tick PhysicsEngine.ResolveWithTransition CP seed contributes 99.3% of post-slice-1 CP writes
**Status:** OPEN — **A6.P3 slice 2 target**
**Severity:** MEDIUM (functional code works; symptom is CP-write-rate mismatch vs retail; suspected cause of #97 phantom collisions)
**Filed:** 2026-05-21
**Component:** physics, ContactPlane retention
**Description:** After A6.P3 slice 1 (commits `5aba071` + `5f7722a` + `39fc037`) stripped the `TryFindIndoorWalkablePlane` synthesis path from `Transition.FindEnvCollisions` indoor branch, scen3 post-fix re-capture showed acdream still writes ContactPlane fields 25,082 times during a flat-floor walk — 24,906 of those (99.3%) come from `PhysicsEngine.ResolveWithTransition` line 622, which seeds `ci.ContactPlane` from `body.ContactPlane` at every transition start when the body is grounded. Retail's equivalent code path fires `set_contact_plane` zero times during the same flat-floor walk (scen3 retail BP7 = 0).
**Root cause / status:** The L622 seed is per-tick infrastructure (every transition tick that's grounded), writing the same plane value repeatedly. Retail does NOT seed `ci.ContactPlane` at transition start — it relies on Mechanism A (Path-6 land write inside `BSPQuery.FindCollisions`) and Mechanism B (LKCP restore inside `validate_transition`, ported in slice 1 commit `5aba071`) to populate `ci.ContactPlane` only when needed.
The L622 seed comment claims it preserves slope-walking continuity. Need to verify whether removing it (relying on Mechanism B for cross-tick CP propagation) breaks slope walking, or whether the seed is redundant infrastructure left over from before Mechanism B existed.
**Fix sketch (slice 2 candidates):**
- Option A: Remove the L622 seed entirely. Rely on Mechanism A + Mechanism B for CP propagation. Verify slope walking still works (uphill/downhill, walking up stairs).
- Option B: Gate the L622 seed to fire only when `body.ContactPlane` has changed since last seed (track last-seeded plane). Most ticks would skip writes.
- Option C: Hybrid — keep the seed but route it through a no-op-if-unchanged guard inside `CollisionInfo.SetContactPlane` itself (also applies to all other call sites).
**Evidence (committed):**
- `docs/research/2026-05-21-a6-captures/scen3_inn_2nd_floor_postfix/acdream.log` — shows 24,906 cp-write entries with `caller=PhysicsEngine.ResolveWithTransition:622`.
- A6.P2 findings doc: per-unit-of-activity CP-write rate dropped 63× from slice 1 strip, but absolute count remains high.
**Files:**
- `src/AcDream.Core/Physics/PhysicsEngine.cs:622` (the seed call site)
- `src/AcDream.Core/Physics/TransitionTypes.cs:251-270` (`CollisionInfo.SetContactPlane` — candidate for no-op-if-unchanged guard, Option C)
**Acceptance:** scen3 re-capture shows acdream cp-write count ≤ 100 (matching retail's BP7 ~0 with small tolerance for legitimate Mechanism A/B fires). Slope walking + stair-climb + cellar descent still work in visual verification.
---
## #97 — Phantom collisions + occasional fall-through on indoor 2nd floor (post-slice-1 happy-testing)
**Status:** OPEN — **investigate after issue #96 lands** (hypothesized to be a side-effect)
**Severity:** MEDIUM (intermittent; doesn't block stair-walking which works post-slice-1)
**Filed:** 2026-05-21
**Component:** physics, ContactPlane stability
**Description:** During user happy-testing post-A6.P3 slice 1 (2026-05-21), walking on the inn 2nd floor in acdream produced:
- Intermittent "phantom collisions" — hitting invisible barriers in open floor space.
- One observed "fall-through the floor" — character dropped through the 2nd floor at a specific spot.
These are NOT the indoor stair-climb or cellar-descent symptoms (those WORK post-slice-1). They appear during normal flat-floor walking.
**Root cause / status:** Hypothesis: caused by issue #96 (L622 per-tick CP seed). The seed writes `ci.ContactPlane` every tick from `body.ContactPlane`, which may carry stale values across cell transitions or after the BSP didn't land a fresh plane. If a transient `ci.ContactPlane` value points to a plane that doesn't match the actual current floor geometry, `ValidateWalkable` (called from the outdoor terrain fallback) or downstream physics may briefly believe the player is below the floor → fall-through; OR may believe a wall is present where there isn't one → phantom collision.
Falsifiable: if #96 fix closes #97 as a side-effect, the hypothesis is confirmed. If #97 persists post-#96, deeper investigation needed (possibly cell-resolver stickiness — Finding 3 family).
**Reproduction (informal — needs sharpening):**
- Launch acdream, teleport to inn 2nd floor.
- Walk back and forth across the floor for ~30 seconds in various patterns.
- Phantom collisions appear intermittently — exact reproduction location unknown.
- Fall-through happened at one specific spot; location not recorded.
**Files:**
- `src/AcDream.Core/Physics/PhysicsEngine.cs` (CP seed + body persist)
- `src/AcDream.Core/Physics/TransitionTypes.cs` (`Transition.FindEnvCollisions` indoor branch + `Transition.ValidateTransition`)
- `src/AcDream.Core/Physics/BSPQuery.cs` (Path-6 land write site)
**Acceptance:** Walking on inn 2nd floor for ≥60 seconds in varied patterns produces zero phantom collisions and zero fall-through events.
---
**Status:** DONE

View file

@ -174,15 +174,38 @@ successfully 2026-04-30 for the steep-roof case. Matching binaries
deferred to optional A6.P1.5 (entry+exit BPs in cdb script);
not blocking A6.P3. M1.5 symptom coverage matrix shows every
in-scope physics symptom mapped to at least one finding.
- **A6.P3 — Fix the BSP correction paths** (~35 days). Sequencing
per A6.P2 recommendation: Finding 2 first (highest-confidence
single-cause; suspect site is `Transition.FindEnvCollisions` indoor
branch + the retention mechanisms that should keep ContactPlane
across frames). Re-run scen1-5 captures after the PR. If CP-write
ratios drop from ~1,000× to ~1×, Finding 2 is closed and Finding 1
may close as side-effect. Then Finding 3 (cell-resolver stickiness
in ResolveCellId + CheckBuildingTransit; same PR also removes #90
workaround). Issue #95 is NOT in A6.P3 scope.
- **A6.P3 — Fix the BSP correction paths** (~35 days). Multi-slice.
- **✓ SHIPPED — A6.P3 slice 1 — Indoor ContactPlane retention**
(2026-05-21, commits `ba9655f` plan + `6b4be7f`/`c6bc2b9` T1
research + `869edd9` T2 counter + `36975ef`/`a32f569` T3 test +
`5aba071` T4 Mechanism B + `5f7722a`/`39fc037`/`bd5fe2e` T5 strip
+ `066568a` scen2 postfix proof + `<this commit>` T8 bookkeeping).
Stripped `TryFindIndoorWalkablePlane` synthesis path from
`Transition.FindEnvCollisions` indoor branch (matches retail's
tiny `CEnvCell::find_env_collisions` shape at acclient_2013_pseudo_c.txt:309573).
Added Mechanism B (LKCP restore) in `Transition.ValidateTransition`
matching retail's pattern at acclient_2013_pseudo_c.txt:272565-272583.
Per-unit-of-activity CP-write rate dropped 63×. **Unexpected win:
stairs + cellar descent now WORK in acdream** (user happy-test
confirmed). A6.P2 Finding 1 (dispatcher entry frequency mismatch)
CLOSED as side-effect (dispatcher shape now retail-like). Finding 2
PARTIALLY CLOSED — 99.3% of remaining cp-writes come from L622
per-tick body-CP seed at `PhysicsEngine.ResolveWithTransition:622`
(filed as issue #96 for slice 2).
- **A6.P3 slice 2 — Gate L622 per-tick CP seed** (issue #96,
~1 day). Either remove the L622 seed entirely (rely on Mechanism
A + Mechanism B for CP propagation) OR gate it to fire only on
`body.ContactPlane` change. Verify slope walking + stair-climb +
cellar still work. Target: scen3 cp-write ≤ 100 (matches retail
BP7 ~0). Re-test phantom collisions + fall-through (issue #97,
hypothesized side-effect of #96) post-slice-2.
- **A6.P3 slice 3 — Finding 3 (cell-resolver sling-out)** (if not
closed as side-effect of slice 1+2). Re-capture scen4 first to
confirm whether sling-out persists. Otherwise: narrow fix in
`ResolveCellId` + `CheckBuildingTransit` cell-stickiness; remove
#90 workaround.
- Issue #95 (visibility blowup) NOT in A6.P3 scope — separate work
surface.
- **A6.P4 — Remove workarounds + visual verification** (~1 day after
P3). Revert #90 sphere-overlap stickiness in
`PhysicsEngine.ResolveCellId`. Delete `Transition.TryFindIndoorWalkablePlane`

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -282,3 +282,75 @@ Per spec §5.1: "highest-confidence single-cause fix first."
- **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.