docs(physics): spec + plan + findings for ISSUES #83 walk-miss probe spike
Three docs from the indoor walk-miss probe spike landed in commits 27c7284..a2e7a87: - Spec: design of the [walk-miss] + [floor-polys] diagnostic emissions with the H1/H2/H3 disambiguation matrix. - Plan: 3-task TDD implementation plan (flag, aggregator, emissions). - Findings: live-capture analysis showing H3 (walkable_hits_sphere / adjust_sphere_to_plane synthesis rejection) is the dominant defect. 817 of 876 ground-contact misses (93%) cluster at dz~0.48 m, while the 7 HITs all sit at dz~0.46 m — a 2 cm boundary between working and broken that points at the sphere-overlap math, not the probe distance. H1 (multi-cell iteration missing) is real but only 3% of misses, secondary. H2 (probe distance) ruled out. Next step: line-by-line decomp comparison of FindWalkableInternal / walkable_hits_sphere / adjust_sphere_to_plane against retail at acclient_2013_pseudo_c.txt:322032 / :323006 / :326793, then design the fix in a follow-up session. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
a2e7a87c25
commit
bb1e919ef2
3 changed files with 1157 additions and 0 deletions
|
|
@ -0,0 +1,218 @@
|
|||
# Indoor walk-miss probe (ISSUES #83 H-disambiguation spike)
|
||||
|
||||
**Date:** 2026-05-21
|
||||
**Status:** Spec — awaiting user review before plan-writing.
|
||||
**Phase:** Indoor walking, ISSUES #83 next step. Spike-only — no behavior changes.
|
||||
**Author:** Claude Opus 4.7.
|
||||
|
||||
## Summary
|
||||
|
||||
Indoor walking glitches at floor-poly edges and doorway thresholds
|
||||
(stuck-falling animation). The previous session's investigation
|
||||
(`docs/research/2026-05-20-indoor-walking-bug-a-handoff.md`) narrowed
|
||||
the open question to: **when `TryFindIndoorWalkablePlane` returns MISS,
|
||||
why?** Three live hypotheses (H1 multi-cell iteration missing, H2 probe
|
||||
distance too short, H3 floor poly absent / `walkable_hits_sphere`
|
||||
rejection). A single composite probe answers all three in one capture
|
||||
session. **No physics behavior changes.** The fix is designed in a
|
||||
follow-up session after the probe data points at the right hypothesis.
|
||||
|
||||
## Goal
|
||||
|
||||
Add a diagnostic probe set that, at a single Holtburg test run covering
|
||||
doorway crossing + 2nd-floor edge + cellar descent, produces enough
|
||||
per-MISS evidence to pick between H1/H2/H3 without attaching cdb to
|
||||
retail.
|
||||
|
||||
## What the probe must capture
|
||||
|
||||
For each `[indoor-walkable] ... result=MISS` event, emit a paired
|
||||
`[walk-miss]` line with:
|
||||
|
||||
| Field | Purpose |
|
||||
|---|---|
|
||||
| `cellId` | Cross-reference with the cell-load dump. |
|
||||
| `foot.W` (world XY,Z) | Where the foot sphere is. |
|
||||
| `foot.L` (cell-local XY,Z) | Cell-local position used by the BSP. |
|
||||
| `floorPolyCount` | How many walkable-eligible polys this cell holds. |
|
||||
| `nearest.polyId` | Poly id of the closest walkable poly (XY-overlapping foot, then nearest by `dz`). |
|
||||
| `nearest.containsFootXY` | `true` if foot XY is inside the poly's local-XY bounding box. |
|
||||
| `nearest.dz` | Signed vertical distance from foot to the poly plane (positive = foot above poly). |
|
||||
| `nearest.normalZ` | Normal Z of the poly (high = horizontal floor, low = steep ramp). |
|
||||
| `landcell.hasTerrain` | Does `engine.SampleTerrainWalkable(foot.X, foot.Y)` return non-null? |
|
||||
| `landcell.terrainZ` | If yes: the terrain plane Z at that XY (compute as `-D/normal.Z`). |
|
||||
| `landcell.dz` | `foot.Z - landcell.terrainZ` (positive = foot above terrain). |
|
||||
|
||||
Once-per-cell, when `CellPhysics` is cached, emit a `[floor-polys]`
|
||||
dump:
|
||||
|
||||
| Field | Purpose |
|
||||
|---|---|
|
||||
| `cellId` | The envCell. |
|
||||
| `walkableCount` | Polys with `Plane.Normal.Z >= FloorZ`. |
|
||||
| Per walkable poly: `id, normalZ, bboxLocalXY, planeZAtBboxCenter` | Lets us reconstruct floor coverage offline. |
|
||||
|
||||
Both gated on a single new `ProbeWalkMissEnabled` flag.
|
||||
|
||||
## Disambiguation matrix (after the run)
|
||||
|
||||
Aggregate the `[walk-miss]` lines from the capture, then:
|
||||
|
||||
- **`landcell.hasTerrain == true` AND `abs(landcell.dz) < 0.2 m`** → **H1
|
||||
confirmed.** Multi-cell iteration would have grounded the player on the
|
||||
outdoor LandCell's terrain at the threshold. Fix: port retail's
|
||||
`check_other_cells` loop.
|
||||
- **`nearest.containsFootXY == true` AND `nearest.dz > 0.5 m` AND
|
||||
`nearest.dz < 5 m`** → **H2 confirmed.** Floor poly XY-overlaps the
|
||||
foot but sits below the 0.5 m probe distance. Fix: increase
|
||||
`INDOOR_WALKABLE_PROBE_DISTANCE` (with the retail-faithful constant
|
||||
pulled from `acclient_2013_pseudo_c.txt` or the OBJECTINFO step-down
|
||||
height).
|
||||
- **`nearest.containsFootXY == true` AND `nearest.dz <= 0.5 m` AND
|
||||
`nearest.normalZ >= FloorZ`** → **H3 candidate.** Poly is XY-aligned,
|
||||
within the probe, walkable-slope — should hit but doesn't. Next step:
|
||||
cdb attach to retail at `BSPLEAF::find_walkable` to compare poly iter
|
||||
with ours.
|
||||
- **`nearest.containsFootXY == false` AND `landcell.hasTerrain == false`**
|
||||
→ **H1+H3 combination.** Both indoor and outdoor have no floor here;
|
||||
retail must do something we haven't found. cdb attach required.
|
||||
|
||||
## Components
|
||||
|
||||
### 1. `PhysicsDiagnostics.ProbeWalkMissEnabled`
|
||||
|
||||
New static property in `src/AcDream.Core/Physics/PhysicsDiagnostics.cs`,
|
||||
matching the existing pattern (`ProbeIndoorBspEnabled`, etc):
|
||||
|
||||
- Env var: `ACDREAM_PROBE_WALK_MISS=1`.
|
||||
- Runtime-toggleable via property setter.
|
||||
- No DebugPanel mirror — one-shot diagnostic, not a persistent toggle.
|
||||
|
||||
### 2. Per-MISS `[walk-miss]` log line
|
||||
|
||||
In `Transition.FindEnvCollisions` (`TransitionTypes.cs:1538` — the
|
||||
existing MISS branch of `TryFindIndoorWalkablePlane`'s caller). When
|
||||
`ProbeWalkMissEnabled`:
|
||||
|
||||
1. Walk `cellPhysics.Resolved` for walkable-eligible polys (where
|
||||
`poly.Plane.Normal.Z >= PhysicsGlobals.FloorZ`).
|
||||
2. For each: compute the XY bbox from `poly.Vertices`, compute the
|
||||
plane Z at the foot's local XY (`Z = (-D - normal.X*X - normal.Y*Y) /
|
||||
normal.Z`), track the one with smallest `|dz|` where foot XY ∈ bbox
|
||||
(tiebreaker: smallest `dz` across all polys).
|
||||
3. Call `engine.SampleTerrainWalkable(foot.X, foot.Y)` for the LandCell
|
||||
probe.
|
||||
4. Emit the line.
|
||||
|
||||
Zero cost when flag is off (single bool check guards the whole block).
|
||||
|
||||
### 3. One-shot `[floor-polys]` cell-load dump
|
||||
|
||||
In `PhysicsDataCache.CacheCellStruct` (immediately after the existing
|
||||
`[cell-cache]` block at `PhysicsDataCache.cs:182`). When
|
||||
`ProbeWalkMissEnabled`:
|
||||
|
||||
- For each `poly` in `resolved` where `Normal.Z >= FloorZ`: log id,
|
||||
normalZ, bbox, `planeZAtCenter`.
|
||||
- Aggregate into one line if walkableCount ≤ 6, else multi-line.
|
||||
|
||||
Fires once per cell — `_cellStruct` is keyed by id with `ConcurrentDictionary` so a second cache call is a no-op (existing path).
|
||||
|
||||
### 4. Test coverage
|
||||
|
||||
`tests/AcDream.Core.Tests/Physics/IndoorWalkMissProbeTests.cs`:
|
||||
|
||||
- `ProbeWalkMiss_StaticApi_Roundtrip` — flag get/set roundtrip with
|
||||
finally-restore (mirrors `PhysicsDiagnosticsTests`).
|
||||
- `WalkMissDiagnostic_SelectsNearestWalkablePoly_WhenFootXYInsideBbox`
|
||||
— given a `CellPhysics` with two walkable polys at different Z, the
|
||||
per-MISS aggregator picks the one closest by `dz`. Uses the same
|
||||
`IndoorWalkablePlaneTests` fixture style.
|
||||
- `WalkMissDiagnostic_FallsBackToNearestPolyByDz_WhenFootXYOutsideAllBboxes`
|
||||
— given a foot XY outside every floor-poly bbox, the aggregator
|
||||
reports `containsFootXY=false` and returns the nearest by `dz` for
|
||||
context (or null if no walkable polys exist).
|
||||
|
||||
The actual log-format roundtrip (capturing stdout) is not unit-tested —
|
||||
that's verified by the live capture per acceptance criterion below.
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
1. `dotnet build` green.
|
||||
2. `dotnet test` green (existing tests untouched + 3 new tests).
|
||||
3. A live capture at Holtburg with the env var on produces:
|
||||
- `[floor-polys]` lines for every loaded indoor cell.
|
||||
- At least one `[walk-miss]` line at the cottage doorway threshold.
|
||||
- At least one `[walk-miss]` line at an inn 2nd-floor edge.
|
||||
- Aggregated counts let us classify each MISS into H1/H2/H3 per the
|
||||
disambiguation matrix.
|
||||
4. When `ACDREAM_PROBE_WALK_MISS` is unset, normal play produces zero
|
||||
`[walk-miss]` / `[floor-polys]` lines (zero-cost gate).
|
||||
|
||||
## Out of scope
|
||||
|
||||
- No fix to the underlying glitch — that's a follow-up session after the
|
||||
probe data lands.
|
||||
- No changes to `TryFindIndoorWalkablePlane`, `FindWalkableSphere`,
|
||||
`BSPQuery.FindCollisions`, or any physics behavior.
|
||||
- No retail cdb attach — that's the H3-only fallback path.
|
||||
- No new DebugPanel checkbox — keep the probe lean.
|
||||
- No removal of the existing `[indoor-bsp]` / `[indoor-walkable]` /
|
||||
`[cp-write]` probes — they're complementary.
|
||||
|
||||
## Risks
|
||||
|
||||
- **R1: `[floor-polys]` dump blows up the log.** Holtburg has hundreds
|
||||
of loaded cells. Per cell, typical walkable poly count is 1-4
|
||||
(cottages) up to ~10 (inn ground floor). Worst-case dump volume ~3 KB
|
||||
per cell × 200 cells = ~600 KB. Acceptable.
|
||||
*Mitigation:* none needed at this scale.
|
||||
|
||||
- **R2: Per-MISS line at 99.87% miss rate floods the log.** A 60-second
|
||||
capture at 30 Hz with one MISS per tick = ~1800 lines. At ~300 bytes
|
||||
each ≈ 540 KB. Acceptable. The user already produced 51k-line cp-write
|
||||
logs without issue.
|
||||
*Mitigation:* none needed.
|
||||
|
||||
- **R3: The probe doesn't disambiguate — the data falls into the
|
||||
"H1+H3 combination" cell.** Then we attach cdb to retail and gather
|
||||
ground truth. This is the planned fallback per CLAUDE.md "Retail
|
||||
debugger toolchain" section.
|
||||
*Mitigation:* the disambiguation matrix has an entry for this case.
|
||||
|
||||
- **R4: The probe finds the answer is *also* in a code path we haven't
|
||||
read yet** (e.g. `precipice_slide`, `cliff_slide`, `step_up_slide`).
|
||||
Then the next session's design exercises a new decomp read.
|
||||
*Mitigation:* unavoidable — the probe is the cheapest first move.
|
||||
|
||||
## Why this is the right next step
|
||||
|
||||
- **Falsifiable in one capture** — no iterative back-and-forth.
|
||||
- **No behavior change** — can't regress the M1 demo paths (door open,
|
||||
pickup, NPC click).
|
||||
- **Cheap implementation** — ~80 lines of code, 3 tests, one commit.
|
||||
- **The previous session's premature design lesson (Bug A) explicitly
|
||||
flagged "probe-first, design-second" as the rule.** This is that rule
|
||||
applied.
|
||||
|
||||
## References
|
||||
|
||||
- `docs/research/2026-05-20-indoor-walking-bug-a-handoff.md` — the
|
||||
comprehensive previous-session handoff that motivated this spec.
|
||||
- `docs/research/2026-05-21-indoor-walking-doorway-investigation-prompt.md`
|
||||
— the pickup brief.
|
||||
- `docs/superpowers/specs/2026-05-20-indoor-bsp-worldorigin-fix-design.md`
|
||||
— Bug B (shipped), the immediately-prior spec in the series.
|
||||
- Retail decomp anchors:
|
||||
- `acclient_2013_pseudo_c.txt:272717-272798` —
|
||||
`CTransition::check_other_cells` (H1 candidate).
|
||||
- `:323725-323939` — `BSPTREE::find_collisions` (H3 candidate).
|
||||
- `:323006-323028` — `CPolygon::walkable_hits_sphere` (H3 candidate).
|
||||
- Code anchors:
|
||||
- `src/AcDream.Core/Physics/TransitionTypes.cs:1294` —
|
||||
`TryFindIndoorWalkablePlane`.
|
||||
- `src/AcDream.Core/Physics/TransitionTypes.cs:1538` — MISS log site.
|
||||
- `src/AcDream.Core/Physics/PhysicsDiagnostics.cs:223` —
|
||||
`ProbeCellCacheEnabled` (template for new flag).
|
||||
- `src/AcDream.Core/Physics/PhysicsDataCache.cs:182` — `[cell-cache]`
|
||||
pattern (template for `[floor-polys]`).
|
||||
Loading…
Add table
Add a link
Reference in a new issue