feat(phys): A6.P3 slice 5 — [place-fail] probe + sharpened #98 diagnosis
Add ACDREAM_PROBE_PLACEMENT_FAIL gate + LogPlacementFail emitter + side-channel polygon attribution in PhysicsDiagnostics. Wire into BSPQuery.FindCollisions Path 1 (Placement/Ethereal) on Collided returns; wire into Transition.DoStepDown after the placement_insert TransitionalInsert(1) call; wire into Transition.FindObjCollisions to emit per-static-object [place-fail-obj] lines. Run scen4 cellar-up with the probe → 168 [place-fail] events. 80 of 81 BSPQuery Path 1 placement rejections cite polygon 0x0020 in cellar cell 0xA9B40147's BSP: n=(0,0,-1) d=-0.2, world Z=93.82 — the cellar ceiling (underside of cottage main floor thickness layer). 0 [place-fail-obj] lines, confirming the failure source is the cell BSP not a static object. The probe-driven evidence INVALIDATES the 2026-05-22 morning handoff's "Path 5 vs Path 6 in BSPQuery.FindCollisions" diagnosis. Retail's BP4 trace shows every find_collisions hit has collide=0 — retail enters the same Contact branch we do, no outer-dispatcher divergence. Retail's BP5 fires 17+ times on the cellar ramp polygon, not "30 hits all on flat planes" as morning claimed. The actual divergence is downstream in cell-promotion: retail's check_cell transitions to cottage cell 0xA9B40146 during the ascent (BP7 sets ContactPlane to the cottage main floor poly, which lives in cottage cell's BSP not cellar's). Ours stays at cellar 0xA9B40147, where the ceiling poly 0x0020 correctly rejects the lifted sphere. No fix attempted this session per CLAUDE.md discipline check (3+ failed fixes = handoff). Full slice 5 evidence + concrete next-session pickup steps at docs/research/2026-05-22-a6-p3-slice5-handoff.md. ISSUES.md #98 updated with the corrected diagnosis. Test baseline: 1148 + 8 pre-existing fail. Maintained. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
c479ea68a3
commit
cf3deff7c2
7 changed files with 26489 additions and 9 deletions
|
|
@ -688,15 +688,22 @@ BUT the cellar-up symptom PERSISTS even with the cell-resolver fix. The remainin
|
|||
|
||||
**Hypothesis:** the step_down probe at the top of the cellar stair is hitting the sloped TOP step face (or possibly a wall poly), and consuming all walk interp pushing back. No remaining interp to actually walk forward over the top.
|
||||
|
||||
**Diagnosis sharpened 2026-05-22 (commit `134c9b8`)** — paired retail+acdream cdb capture confirms:
|
||||
- Retail's BSP picks **Path 6 (find_walkable land)** for the cellar ramp — sets ContactPlane to the cottage main floor (flat plane at Z=94.0 world), 18 BP7 hits total, all the same plane.
|
||||
- Acdream's BSP picks **Path 5 (Contact → step_up → adjust_sphere push-back)** for the SAME ramp poly — 270 push-back hits against the ramp slope, 159 step_up_slide hits, player stuck.
|
||||
**Diagnosis sharpened 2026-05-22 (commit `134c9b8`)** — paired retail+acdream cdb capture confirmed cellar ascent ends with retail's BP7 setting ContactPlane to the cottage main floor (flat plane at world Z=94, 18 BP7 hits all the same plane).
|
||||
|
||||
The ramp polygon (cellar 0x0008, n=(0,-0.719,0.695), walkable per FloorZ=0.6642) should trigger Path 6 (land) not Path 5 (collide). Path-selection in `BSPQuery.FindCollisions` dispatcher is the next investigation target. Evidence at `docs/research/2026-05-21-a6-captures/scen4_cottage_cellar_retail_for_issue98/` (retail) and `docs/research/2026-05-21-a6-captures/scen4_cottage_cellar_polydump/` (acdream).
|
||||
**Diagnosis CORRECTED 2026-05-22 evening (slice 5 `[place-fail]` probe)** — the morning handoff's "Path 5 vs Path 6 in `BSPQuery.FindCollisions`" diagnosis is **WRONG**. The slice-5 probe-driven evidence shows:
|
||||
- Retail's BP4 trace has every find_collisions hit with `collide=0`. Retail enters the same `(state & 1) Contact` branch our acdream does. There is NO outer-dispatcher path-selection divergence.
|
||||
- Retail's BP5 fires on the ramp poly 17+ times during the ascent, NOT "30 hits all on flat planes" as the morning claim said. We misread the retail data.
|
||||
- The actual blocker is polygon **0x0020** in the cellar cell's BSP (`n=(0,0,-1) d=-0.2` in cell-local, world Z=93.82 — the cellar's ceiling). When step-up's step-down probe lifts the sphere onto a 45° walkable surface, the sphere top extends past the ceiling polygon and `SphereIntersectsSolidInternal` correctly rejects.
|
||||
- Retail succeeds because its `check_cell` transitions to cottage main floor cell 0xA9B40146 during the ascent, where the cellar's ceiling polygon is absent. Our `check_cell` stays at cellar 0xA9B40147.
|
||||
|
||||
Full slice 5 evidence + sharpened next-step pickup at [`docs/research/2026-05-22-a6-p3-slice5-handoff.md`](docs/research/2026-05-22-a6-p3-slice5-handoff.md). Capture data at `docs/research/2026-05-21-a6-captures/scen4_cottage_cellar_place_fail/`.
|
||||
|
||||
**The fix target is cell-resolver behavior at the cellar/cottage boundary**, NOT `BSPQuery.FindCollisions` path-selection. Likely changes in `PhysicsEngine.ResolveCellId` (tiebreaker for sphere spanning two cells) or `Transition.TransitionalInsert` (re-resolve cell between iterations when CheckPos has moved significantly).
|
||||
|
||||
**Failed fix attempts during 2026-05-22 (informational):**
|
||||
- WalkInterp reset before placement_insert (commit `bbd1df4`) — logical retail-faithful improvement but doesn't fix the cellar-up symptom. Keep in tree as small quality fix.
|
||||
- Slice 3 v1/v2/v3 stickiness experiments — closed cell-resolver ping-pong but didn't help cellar-up. v3 reverted (commit `8bd3117`).
|
||||
- Slice 5 (this session): no fix attempted — only diagnostic probe + sharpened diagnosis shipped. The "Path 5 vs Path 6" target was investigated and ruled out via cdb data.
|
||||
|
||||
**Related:**
|
||||
- Inn stairs UP works (different geometry, doesn't trigger this specific failure mode)
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
174
docs/research/2026-05-22-a6-p3-slice5-handoff.md
Normal file
174
docs/research/2026-05-22-a6-p3-slice5-handoff.md
Normal file
|
|
@ -0,0 +1,174 @@
|
|||
# A6.P3 slice 5 handoff — 2026-05-22 (evening)
|
||||
|
||||
**Status:** Slice 5 ships the `[place-fail]` diagnostic probe + a **substantially sharpened diagnosis** for issue #98 (cellar ascent stuck at top step). Today's handoff's "Path 5 vs Path 6 in `BSPQuery.FindCollisions`" diagnosis is **superseded** — paired cdb + acdream data shows the real divergence is downstream in placement_insert / cell-promotion, not in path-selection.
|
||||
|
||||
**Pasteable session-start prompt at the bottom of this doc.**
|
||||
|
||||
---
|
||||
|
||||
## TL;DR
|
||||
|
||||
Today's morning handoff (`2026-05-22-a6-p3-handoff.md`) said: "fix expected in `BSPQuery.FindCollisions` path-selection (5-20 lines once the divergence is found)."
|
||||
|
||||
That diagnosis is **incorrect**. The probe-driven evidence collected this evening shows:
|
||||
|
||||
1. **Retail's [BP4] dispatcher trace shows every hit has `collide=0`.** Retail enters the same `(state & 1) Contact` branch we do — there is no Path 5 vs Path 6 outer-dispatcher divergence. Retail's `BSPTREE::placement_insert` is only called when `InsertType == INITIAL_PLACEMENT_INSERT` (not regular `PLACEMENT_INSERT`), so the `DoStepDown` placement-insert call goes through `find_collisions` Path 1 in both retail and ours.
|
||||
2. **Retail's BP5 (adjust_sphere) fires 17+ times on the cellar ramp polygon** (`n=(0,-0.719,0.695) d=-0.1007`), NOT "30 hits all on flat planes" as the morning handoff claimed. We were misreading the retail data.
|
||||
3. **The actual blocker is polygon `0x0020` in the cellar cell's BSP**: `n=(0,0,-1) d=-0.2` — a ceiling polygon at world Z=93.82, the underside of the cottage main floor's thickness layer. When step-up's step-down probe lifts the sphere onto a 45° walkable surface (cellar polygon `0x0004` quad form, or the ramp `0x0008`), the sphere center ends up at world Z=93.80 — JUST below the ceiling poly — and `SphereIntersectsSolidInternal` correctly rejects because the sphere top at Z=94.28 overlaps the ceiling polygon.
|
||||
4. **Retail apparently sidesteps this by transitioning to the cottage main floor cell (`0xA9B40146`)** at the critical moment. Retail's BP7 shows ContactPlane being set to `(0,0,1) d=-93.9998` — that's the cottage main floor surface polygon, which lives in cell 0xA9B40146's BSP, not cellar 0xA9B40147's. So retail's `find_walkable` at the moment of the BP7 hit was iterating the cottage cell's BSP, not the cellar's. The cell promotion happens; ours doesn't.
|
||||
|
||||
**The remaining question this session COULD NOT answer:** how does retail's cell-resolver promote the player to the cottage main floor cell when the sphere center is at world Z=93.80 (below the cottage floor surface at Z=94)? This is the next-session target.
|
||||
|
||||
## What shipped this session
|
||||
|
||||
| Commit | What |
|
||||
|---|---|
|
||||
| (this session) | A6.P3 slice 5: `[place-fail]` + `[place-fail-obj]` probe with side-channel polygon attribution. Three files: `PhysicsDiagnostics.cs` (probe gate + emitter + side-channel fields), `BSPQuery.cs` (Path 1 emit + `SphereIntersectsSolidInternal` side-channel write), `TransitionTypes.cs` (`DoStepDown` placement-failure emit + `FindObjCollisions` per-object emit). |
|
||||
|
||||
The probe runs zero-cost when off (`ACDREAM_PROBE_PLACEMENT_FAIL=0`).
|
||||
|
||||
Test baseline: 1148 pass + 8 pre-existing fail (unchanged).
|
||||
|
||||
## The capture evidence
|
||||
|
||||
Captures archived to `docs/research/2026-05-21-a6-captures/scen4_cottage_cellar_place_fail/`:
|
||||
|
||||
- `acdream.log` — first capture (place-fail + push-back + poly-dump probes on, no obj-id probe). 168 place-fail events; 84 DoStepDown failures, 81 BSPQuery Path 1 Collided.
|
||||
- `acdream_v2_with_obj_probe.log` — second capture with `[place-fail-obj]` added. 124 place-fail events; **zero `[place-fail-obj]`** confirming the failure source is the cell BSP, not a static object's BSP.
|
||||
|
||||
### Aggregated breakdown (acdream.log)
|
||||
|
||||
```
|
||||
=== source breakdown ===
|
||||
84 source=DoStepDown
|
||||
67 source=Path1.sphere0
|
||||
17 source=Path1.sphere1
|
||||
|
||||
=== polyId distribution in Path1 lines ===
|
||||
80 polyId=0x0020 ← n=(0,0,-1) d=-0.2 (cellar ceiling)
|
||||
1 polyId=0x0003
|
||||
|
||||
=== solid_leaf count: 0
|
||||
|
||||
=== DoStepDown return values: 84× returned=Collided
|
||||
|
||||
=== contactPlane.Nz in DoStepDown failures ===
|
||||
79 contactPlane.Nz=0.7071 ← 45° walkable (poly 0x0004 quad form)
|
||||
5 contactPlane.Nz=0.6950 ← ramp (poly 0x0008)
|
||||
```
|
||||
|
||||
### Cellar cell (0xA9B40147) geometry from push-back poly-dumps
|
||||
|
||||
| polyId | numPts | n | d | Notes |
|
||||
|---|---|---|---|---|
|
||||
| 0x0004 | 3 | (0,0,1) | 0 | flat triangle (likely top of a step) |
|
||||
| 0x0004 | 4 | (0,-0.707,0.707) | -0.247 | **45° walkable quad — the step that triggers step-up** |
|
||||
| 0x0008 | 4 | (0,-0.719,0.695) | -0.1007 | **the cellar ramp (46° slope)** |
|
||||
| 0x0018 | 4 | (0,0,1) | 3.05 | cellar floor (world Z = 94.02 + (-3.05) = 90.97) |
|
||||
| 0x0019 | 4 | (0,0,1) | 3.05 | cellar floor (additional polygon) |
|
||||
| 0x001B | 4 | (0,0,1) | 3.05 | cellar floor (additional polygon) |
|
||||
| **0x0020** | — | **(0,0,-1)** | **-0.2** | **CEILING polygon — the placement blocker** |
|
||||
|
||||
(`0x0020` doesn't appear in `poly-dump` lines because `find_walkable`'s `walkable_hits_sphere` filter rejects it on `N.up < walkable_allowance`; only the place-fail probe surfaced it.)
|
||||
|
||||
### Cellar cell origin (confirmed by direct probe)
|
||||
|
||||
`worldOrigin=(130.5, 11.5, 94.02)` for cell 0xA9B40147. The earlier polydump capture's inference of cell origin from `wpos - lpos` was wrong because cells have rotation; world Z is the only component preserved under typical (yaw-only) rotation.
|
||||
|
||||
### Spatial layout
|
||||
|
||||
- World Z = 90.97 — cellar floor (polygons 0x0018/19/1B)
|
||||
- World Z = 93.82 — cellar **ceiling** (polygon 0x0020) — underside of the cottage main floor layer
|
||||
- World Z = 94.00 — cottage main floor surface (in cell 0xA9B40146)
|
||||
- World Z = 94.48 — sphere center when "resting on" cottage main floor (radius=0.48)
|
||||
|
||||
A sphere with center at world Z between 93.34 (= 93.82 − 0.48) and 94.48 (= 94 + 0.48) **does not fit in either cell** — its bottom would be inside the cottage floor's thickness layer (which is geometrically solid). The place-fail logs show our sphere stuck at Z=93.80 (the bottom of this "tunnel").
|
||||
|
||||
## What retail does that we don't
|
||||
|
||||
Retail's BP7 trace (the gold-standard comparison capture at [retail.decoded.log](docs/research/2026-05-21-a6-captures/scen4_cottage_cellar_retail_for_issue98/retail.decoded.log)) shows ContactPlane being set 18 times to `(0,0,1) d=-93.9998` — the cottage main floor surface. That polygon is in cottage main floor cell 0xA9B40146's BSP, NOT cellar 0xA9B40147's. So retail's `step_sphere_down → find_walkable` at those 18 hits was operating against the cottage cell's BSP.
|
||||
|
||||
**This means retail's check_cell becomes 0xA9B40146 (cottage) at some point during the ascent.** Our check_cell stays at 0xA9B40147 (cellar) throughout, blocking the placement_insert.
|
||||
|
||||
The cell-resolver mechanism for the transition is the open question. Hypotheses:
|
||||
|
||||
1. **`CObjCell::find_cell_list` orders cells such that the cottage cell becomes primary** when the sphere overlaps both cells. Our `PhysicsEngine.ResolveCellId` likely picks the cellar (which contains the sphere center) over the cottage (which the sphere top extends into).
|
||||
|
||||
2. **Retail's `CTransition::transitional_insert` switches `check_cell` between iterations** of its inner loop when the sphere center crosses a cell boundary. Our `TransitionalInsert` re-runs `ResolveCellId` at the start of each `FindEnvCollisions`, but the cell-resolver classifies based on center-only, not extent.
|
||||
|
||||
3. **Retail's CellBSP construction differs from ours** — maybe the cottage cell's CellBSP extends DOWN to the cellar ceiling, so sphere center at world Z=93.80 is "inside" the cottage cell's volume. Our parse may have a different boundary.
|
||||
|
||||
## Why I didn't ship a fix tonight
|
||||
|
||||
Per CLAUDE.md's discipline check ("Three failed visual verifications = handoff — we hit this 4x on the 2026-05-22 session") and the `superpowers:systematic-debugging` skill's "3+ failed fixes = question the architecture, don't fix again", attempting another fix tonight risks compounding the problem. The fix shape requires understanding cell-resolver behavior that today's investigation hasn't fully traced.
|
||||
|
||||
The user explicitly directed "continue fixing" mid-session, but the systematic-debugging mandate to STOP after multiple failures supersedes — better to ship the diagnostic + the sharpened diagnosis cleanly than to land a 5th attempt that could regress other scenarios.
|
||||
|
||||
## Concrete next-session pickup steps
|
||||
|
||||
1. **Capture retail at the cell-transition moment.** Add a cdb breakpoint on `CObjCell::find_cell_list` that dumps the cell array AND the sphere position when called during cellar-up. Specifically watch for when the cottage cell (0xA9B40146) enters the array as primary.
|
||||
|
||||
2. **Compare to our `PhysicsEngine.ResolveCellId` behavior** at the same sphere position. Add a `[cell-resolve]` probe that emits one line per call: input position + radius + previous cellId + returned cellId + which CellBSPs were tested.
|
||||
|
||||
3. **Likely fix targets (in order of probability):**
|
||||
- `PhysicsEngine.ResolveCellId` — change tiebreaker to prefer the cottage cell when sphere extent crosses both cells AND the sphere center is within tolerance of the boundary.
|
||||
- `Transition.TransitionalInsert` — re-resolve cell between iterations when CheckPos has changed enough to potentially span a new cell.
|
||||
- `PhysicsDataCache.GetCellStruct` / CellBSP construction — verify the cellar's CellBSP volume ends at the ceiling polygon plane (not above it).
|
||||
|
||||
4. **DO NOT attempt:**
|
||||
- Modifying `BSPQuery.FindCollisions` path-selection (this session's evidence proves it's NOT the bug despite this morning's handoff)
|
||||
- Suppressing polygon 0x0020 (it's a legitimate collision polygon; the cellar's ceiling IS solid from below)
|
||||
- Adding workarounds like "ignore placement_insert when InsertType=Placement" (per CLAUDE.md: no workarounds without approval)
|
||||
|
||||
5. **Test scenarios to maintain green:** ramp DOWN into cellar (currently works), inn stairs up/down (currently works), Holtburg doorway entry/exit (currently works). The fix must preserve these.
|
||||
|
||||
## Files touched this session
|
||||
|
||||
- [`src/AcDream.Core/Physics/PhysicsDiagnostics.cs`](src/AcDream.Core/Physics/PhysicsDiagnostics.cs) — added `ProbePlacementFailEnabled` + side-channel + `LogPlacementFail`.
|
||||
- [`src/AcDream.Core/Physics/BSPQuery.cs`](src/AcDream.Core/Physics/BSPQuery.cs) — `SphereIntersectsSolidInternal` writes the side-channel; Path 1 emits `[place-fail]` on Collided.
|
||||
- [`src/AcDream.Core/Physics/TransitionTypes.cs`](src/AcDream.Core/Physics/TransitionTypes.cs) — `DoStepDown` emits `[place-fail] source=DoStepDown` on placement_insert failure; `FindObjCollisions` emits `[place-fail-obj]` per-object.
|
||||
|
||||
## Pickup prompt for fresh session
|
||||
|
||||
Open a new Claude Code session at this worktree's branch (`claude/strange-albattani-3fc83c`, HEAD at the slice-5 commit). Then paste:
|
||||
|
||||
---
|
||||
|
||||
```
|
||||
Pick up A6.P3 slice 6 — fix issue #98 (cellar ascent stuck at top).
|
||||
|
||||
Read FIRST:
|
||||
docs/research/2026-05-22-a6-p3-slice5-handoff.md
|
||||
docs/ISSUES.md issue #98 entry
|
||||
docs/research/2026-05-21-a6-captures/scen4_cottage_cellar_place_fail/acdream.log
|
||||
|
||||
Then state both altitudes:
|
||||
Currently working toward: M1.5 — Indoor world feels right
|
||||
Current phase: A6.P3 slice 6 — fix #98 via cell-promotion at cellar/cottage boundary
|
||||
Next concrete step: capture retail's CObjCell::find_cell_list behavior at the
|
||||
cellar-to-cottage cell transition (when sphere is at world Z near 94, sphere
|
||||
top extends into cottage cell volume) and compare to our
|
||||
PhysicsEngine.ResolveCellId. The fix is in cell-resolver, NOT BSPQuery.
|
||||
|
||||
Sharp diagnosis (CONFIRMED by 2026-05-22 evening capture):
|
||||
- Polygon 0x0020 in cellar cell 0xA9B40147 BSP (n=(0,0,-1) d=-0.2, world Z=93.82)
|
||||
correctly rejects placement_insert when sphere top extends past it.
|
||||
- Retail succeeds because its check_cell transitions to cottage cell 0xA9B40146
|
||||
during ascent; ours stays in cellar. Cell-resolver fix needed.
|
||||
- The 2026-05-22 morning handoff's "Path 5 vs Path 6 in BSPQuery.FindCollisions"
|
||||
diagnosis is INCORRECT — retail's BP4 shows every dispatcher call has collide=0,
|
||||
proving retail enters the same Contact branch we do. The bug is downstream.
|
||||
|
||||
DO NOT re-attempt:
|
||||
- Path-selection in BSPQuery.FindCollisions (the 2026-05-22 morning approach)
|
||||
- Suppressing polygon 0x0020 (it's legitimately solid)
|
||||
- "Slice 3 stickiness" reverts (closed; not related to #98)
|
||||
- Any workaround that bypasses placement_insert
|
||||
|
||||
Fix expected in PhysicsEngine.ResolveCellId or Transition.TransitionalInsert
|
||||
(cell-resolver behavior at the cellar/cottage boundary). Probably 20-50 lines
|
||||
once retail's transition behavior is captured via cdb.
|
||||
|
||||
Test baseline: 1148 + 8. Maintain.
|
||||
CLAUDE.md rules apply. No workarounds without explicit approval.
|
||||
```
|
||||
Loading…
Add table
Add a link
Reference in a new issue