# Indoor walk-miss probe — implementation plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** Ship a diagnostic probe set (`[walk-miss]` per-miss line + `[floor-polys]` per-cell-load dump) that disambiguates H1/H2/H3 for ISSUES #83 in a single Holtburg capture session. No physics behavior changes. **Architecture:** One new env-gated flag in `PhysicsDiagnostics`. One new static aggregator (`WalkMissDiagnostic`) that's pure-function over a `CellPhysics.Resolved` dict. Two emission sites: `Transition.FindEnvCollisions` MISS branch (per-frame) and `PhysicsDataCache.CacheCellStruct` (one-shot per cell). **Tech Stack:** C# .NET 10, xUnit, existing `PhysicsDiagnostics` / `ResolvedPolygon` types. **Spec:** [`docs/superpowers/specs/2026-05-21-indoor-walk-miss-probe-design.md`](../specs/2026-05-21-indoor-walk-miss-probe-design.md) --- ## File Structure | File | Action | Responsibility | |---|---|---| | `src/AcDream.Core/Physics/PhysicsDiagnostics.cs` | Modify | Add `ProbeWalkMissEnabled` flag (matches existing pattern). | | `src/AcDream.Core/Physics/WalkMissDiagnostic.cs` | Create | Pure aggregator: given a `CellPhysics.Resolved` dict + foot local position, returns the nearest walkable-poly stats. Also enumerates walkable polys for the per-cell dump. | | `src/AcDream.Core/Physics/TransitionTypes.cs` | Modify | Add `[walk-miss]` emission inside the existing MISS branch at the `[indoor-walkable]` log site (~line 1538). | | `src/AcDream.Core/Physics/PhysicsDataCache.cs` | Modify | Add `[floor-polys]` one-shot emission immediately after the existing `[cell-cache]` block (~line 220). | | `tests/AcDream.Core.Tests/Physics/WalkMissDiagnosticTests.cs` | Create | 3 unit tests: flag roundtrip + aggregator logic. | --- ## Task 1: PhysicsDiagnostics.ProbeWalkMissEnabled flag **Files:** - Modify: `src/AcDream.Core/Physics/PhysicsDiagnostics.cs` (add property after `ProbeContactPlaneEnabled` at line 244) - Create: `tests/AcDream.Core.Tests/Physics/WalkMissDiagnosticTests.cs` (new file, flag roundtrip test only) - [ ] **Step 1: Write the failing test** Create `tests/AcDream.Core.Tests/Physics/WalkMissDiagnosticTests.cs`: ```csharp using AcDream.Core.Physics; using DatReaderWriter.Enums; using System.Collections.Generic; using System.Numerics; using Xunit; namespace AcDream.Core.Tests.Physics; /// /// Tests for the ISSUES #83 H-disambiguation probe spike (spec /// 2026-05-21-indoor-walk-miss-probe-design.md). /// /// Covers: /// 1. PhysicsDiagnostics.ProbeWalkMissEnabled flag get/set roundtrip. /// 2. WalkMissDiagnostic.AggregateNearestWalkable selects the nearest /// walkable polygon by |dz| when the foot XY lies inside a poly's /// local XY bounding box. /// 3. WalkMissDiagnostic.AggregateNearestWalkable falls back to the /// nearest poly by |dz| when no walkable poly XY-contains the foot, /// reporting ContainsFootXY=false. /// public class WalkMissDiagnosticTests { [Fact] public void ProbeWalkMiss_StaticApi_Roundtrip() { bool initial = PhysicsDiagnostics.ProbeWalkMissEnabled; try { PhysicsDiagnostics.ProbeWalkMissEnabled = true; Assert.True(PhysicsDiagnostics.ProbeWalkMissEnabled); PhysicsDiagnostics.ProbeWalkMissEnabled = false; Assert.False(PhysicsDiagnostics.ProbeWalkMissEnabled); } finally { PhysicsDiagnostics.ProbeWalkMissEnabled = initial; } } } ``` - [ ] **Step 2: Run test to verify it fails** Run: `dotnet test tests/AcDream.Core.Tests/AcDream.Core.Tests.csproj --filter "FullyQualifiedName~WalkMissDiagnosticTests"` Expected: FAIL with "PhysicsDiagnostics does not contain a definition for ProbeWalkMissEnabled". - [ ] **Step 3: Add the flag to PhysicsDiagnostics** Edit `src/AcDream.Core/Physics/PhysicsDiagnostics.cs`. Insert after the `ProbeContactPlaneEnabled` property (line 244) and before the `LogCpBoolWrite` helper (line 246): ```csharp /// /// Indoor walking ISSUES #83 H-disambiguation spike (2026-05-21). /// When true, two diagnostic emissions activate: /// /// One [walk-miss] line per /// MISS /// event, dumping foot world/local position, the nearest /// walkable polygon in the cell (with XY-containment flag and /// vertical gap), and whether the LandCell terrain at the same /// XY would have grounded the player. /// One [floor-polys] line per indoor /// cell cached, enumerating each walkable-eligible polygon's /// id, normal Z, local-XY bounding box, and plane Z at the /// bbox center. /// /// Together these answer H1 (multi-cell iteration missing) vs H2 /// (probe distance too short) vs H3 (poly absent / /// walkable_hits_sphere rejection) for the ISSUES #83 /// stuck-falling bug. Spike-only — remove once the root cause is /// identified and the fix lands. /// /// /// Initial state from ACDREAM_PROBE_WALK_MISS=1. /// No DebugPanel mirror — one-shot diagnostic. /// /// /// /// Spec: docs/superpowers/specs/2026-05-21-indoor-walk-miss-probe-design.md. /// /// public static bool ProbeWalkMissEnabled { get; set; } = Environment.GetEnvironmentVariable("ACDREAM_PROBE_WALK_MISS") == "1"; ``` - [ ] **Step 4: Run test to verify it passes** Run: `dotnet test tests/AcDream.Core.Tests/AcDream.Core.Tests.csproj --filter "FullyQualifiedName~WalkMissDiagnosticTests"` Expected: PASS — 1 test passing. - [ ] **Step 5: Commit** ```bash git add src/AcDream.Core/Physics/PhysicsDiagnostics.cs tests/AcDream.Core.Tests/Physics/WalkMissDiagnosticTests.cs git commit -m "$(cat <<'EOF' feat(physics): ProbeWalkMissEnabled flag for ISSUES #83 H-disambiguation Adds a new diagnostic flag for the indoor-walking walk-miss probe spike per docs/superpowers/specs/2026-05-21-indoor-walk-miss-probe-design.md. Env var ACDREAM_PROBE_WALK_MISS=1, runtime-toggleable via property. No DebugPanel mirror — spike-only. Following commits wire the [walk-miss] and [floor-polys] emissions to this flag. Co-Authored-By: Claude Opus 4.7 (1M context) EOF )" ``` --- ## Task 2: WalkMissDiagnostic aggregator **Files:** - Create: `src/AcDream.Core/Physics/WalkMissDiagnostic.cs` - Modify: `tests/AcDream.Core.Tests/Physics/WalkMissDiagnosticTests.cs` (add 2 aggregator tests) - [ ] **Step 1: Write the failing tests** Append to `tests/AcDream.Core.Tests/Physics/WalkMissDiagnosticTests.cs`, inside the class body (after `ProbeWalkMiss_StaticApi_Roundtrip`): ```csharp private static ResolvedPolygon MakeFloorPoly( Vector3 v00, Vector3 v10, Vector3 v11, Vector3 v01) { var verts = new[] { v00, v10, v11, v01 }; var normal = Vector3.Normalize(Vector3.Cross(v10 - v00, v01 - v00)); float d = -Vector3.Dot(normal, v00); return new ResolvedPolygon { Vertices = verts, Plane = new System.Numerics.Plane(normal, d), NumPoints = 4, SidesType = CullMode.None, }; } /// /// Foot at (0,0,1). Two walkable polys: a low one at Z=0 (foot is /// 1 m above) and a high one at Z=0.8 (foot is 0.2 m above). /// Aggregator picks the high one — smaller |dz|. /// [Fact] public void AggregateNearestWalkable_PicksNearestByDz_WhenFootXYInsideMultiplePolys() { var lowFloor = MakeFloorPoly( new Vector3(-5f, -5f, 0f), new Vector3( 5f, -5f, 0f), new Vector3( 5f, 5f, 0f), new Vector3(-5f, 5f, 0f)); var highFloor = MakeFloorPoly( new Vector3(-2f, -2f, 0.8f), new Vector3( 2f, -2f, 0.8f), new Vector3( 2f, 2f, 0.8f), new Vector3(-2f, 2f, 0.8f)); var resolved = new Dictionary { [1] = lowFloor, [2] = highFloor, }; var result = WalkMissDiagnostic.AggregateNearestWalkable( resolved, footLocal: new Vector3(0f, 0f, 1f), floorZ: PhysicsGlobals.FloorZ); Assert.True(result.Found); Assert.Equal((ushort)2, result.PolyId); Assert.True(result.ContainsFootXY); Assert.Equal(0.2f, result.Dz, precision: 5); Assert.Equal(1.0f, result.NormalZ, precision: 5); } /// /// Foot at (10,10,1) — outside both poly XY bboxes. Aggregator /// returns the poly with smallest |dz| but with ContainsFootXY=false. /// [Fact] public void AggregateNearestWalkable_FallsBackByDz_WhenFootXYOutsideAllBboxes() { var poly = MakeFloorPoly( new Vector3(-1f, -1f, 0.5f), new Vector3( 1f, -1f, 0.5f), new Vector3( 1f, 1f, 0.5f), new Vector3(-1f, 1f, 0.5f)); var resolved = new Dictionary { [42] = poly }; var result = WalkMissDiagnostic.AggregateNearestWalkable( resolved, footLocal: new Vector3(10f, 10f, 1f), floorZ: PhysicsGlobals.FloorZ); Assert.True(result.Found); Assert.Equal((ushort)42, result.PolyId); Assert.False(result.ContainsFootXY); Assert.Equal(0.5f, result.Dz, precision: 5); } ``` - [ ] **Step 2: Run tests to verify they fail** Run: `dotnet test tests/AcDream.Core.Tests/AcDream.Core.Tests.csproj --filter "FullyQualifiedName~WalkMissDiagnosticTests"` Expected: FAIL — 2 new tests fail with "The name 'WalkMissDiagnostic' does not exist". - [ ] **Step 3: Create WalkMissDiagnostic.cs with the aggregator** Create `src/AcDream.Core/Physics/WalkMissDiagnostic.cs`: ```csharp using System.Collections.Generic; using System.Numerics; namespace AcDream.Core.Physics; /// /// ISSUES #83 H-disambiguation spike (2026-05-21). Pure-function /// aggregator over a dict — picks /// the nearest walkable-eligible polygon to a given foot position /// (cell-local space) and reports XY-containment + vertical gap so /// the [walk-miss] emission site can disambiguate H1/H2/H3 /// without re-walking the dictionary itself. /// /// /// Also enumerates walkable polygons for the one-shot /// [floor-polys] dump at cell-cache time. /// /// /// /// Spec: docs/superpowers/specs/2026-05-21-indoor-walk-miss-probe-design.md. /// /// public static class WalkMissDiagnostic { public readonly struct AggregateResult { public bool Found { get; init; } public ushort PolyId { get; init; } public bool ContainsFootXY { get; init; } public float Dz { get; init; } public float NormalZ { get; init; } } public readonly struct WalkableEntry { public ushort PolyId { get; init; } public float NormalZ { get; init; } public Vector3 BboxMin { get; init; } public Vector3 BboxMax { get; init; } public float PlaneZAtBboxCenter { get; init; } } /// /// Walks , considering only polygons /// whose plane normal Z is at least /// (walkable slope). Selection rule: /// /// Polygons whose local-XY bounding box contains /// 's XY are preferred. Among them, /// the one with smallest |dz| wins. /// If no poly contains the foot XY, the poly /// with smallest |dz| across all walkable polys wins, /// and is false. /// /// public static AggregateResult AggregateNearestWalkable( IReadOnlyDictionary resolved, Vector3 footLocal, float floorZ) { bool bestFound = false; bool bestContainsFootXY = false; ushort bestPolyId = 0; float bestAbsDz = float.MaxValue; float bestSignedDz = 0f; float bestNormalZ = 0f; foreach (var kvp in resolved) { var poly = kvp.Value; if (poly.Plane.Normal.Z < floorZ) continue; if (poly.Vertices.Length < 3) continue; // Local-XY bounding box. float minX = float.MaxValue, minY = float.MaxValue; float maxX = float.MinValue, maxY = float.MinValue; for (int i = 0; i < poly.Vertices.Length; i++) { var v = poly.Vertices[i]; if (v.X < minX) minX = v.X; if (v.Y < minY) minY = v.Y; if (v.X > maxX) maxX = v.X; if (v.Y > maxY) maxY = v.Y; } bool containsFootXY = footLocal.X >= minX && footLocal.X <= maxX && footLocal.Y >= minY && footLocal.Y <= maxY; // Signed vertical gap from foot to the polygon's plane at // the foot's XY: plane.D + n.x*X + n.y*Y + n.z*Z = 0 // ⇒ planeZ = -(D + n.x*X + n.y*Y) / n.z // ⇒ dz = footZ - planeZ float planeZ = -(poly.Plane.D + poly.Plane.Normal.X * footLocal.X + poly.Plane.Normal.Y * footLocal.Y) / poly.Plane.Normal.Z; float signedDz = footLocal.Z - planeZ; float absDz = System.MathF.Abs(signedDz); // Preference: prefer XY-containing polys. Among the // preferred set, smallest |dz| wins. bool preferOver = !bestFound || (containsFootXY && !bestContainsFootXY) || (containsFootXY == bestContainsFootXY && absDz < bestAbsDz); if (preferOver) { bestFound = true; bestContainsFootXY = containsFootXY; bestPolyId = kvp.Key; bestAbsDz = absDz; bestSignedDz = signedDz; bestNormalZ = poly.Plane.Normal.Z; } } return new AggregateResult { Found = bestFound, PolyId = bestPolyId, ContainsFootXY = bestContainsFootXY, Dz = bestSignedDz, NormalZ = bestNormalZ, }; } /// /// Enumerates walkable-eligible polygons (normal Z >= floorZ) /// with their local-XY bounding boxes and plane Z at the bbox /// center. Used by the one-shot [floor-polys] cell-load /// dump. /// public static IEnumerable EnumerateWalkable( IReadOnlyDictionary resolved, float floorZ) { foreach (var kvp in resolved) { var poly = kvp.Value; if (poly.Plane.Normal.Z < floorZ) continue; if (poly.Vertices.Length < 3) continue; float minX = float.MaxValue, minY = float.MaxValue, minZ = float.MaxValue; float maxX = float.MinValue, maxY = float.MinValue, maxZ = float.MinValue; for (int i = 0; i < poly.Vertices.Length; i++) { var v = poly.Vertices[i]; if (v.X < minX) minX = v.X; if (v.Y < minY) minY = v.Y; if (v.Z < minZ) minZ = v.Z; if (v.X > maxX) maxX = v.X; if (v.Y > maxY) maxY = v.Y; if (v.Z > maxZ) maxZ = v.Z; } float cx = (minX + maxX) * 0.5f; float cy = (minY + maxY) * 0.5f; float planeZAtCenter = -(poly.Plane.D + poly.Plane.Normal.X * cx + poly.Plane.Normal.Y * cy) / poly.Plane.Normal.Z; yield return new WalkableEntry { PolyId = kvp.Key, NormalZ = poly.Plane.Normal.Z, BboxMin = new Vector3(minX, minY, minZ), BboxMax = new Vector3(maxX, maxY, maxZ), PlaneZAtBboxCenter = planeZAtCenter, }; } } } ``` - [ ] **Step 4: Run tests to verify all 3 pass** Run: `dotnet test tests/AcDream.Core.Tests/AcDream.Core.Tests.csproj --filter "FullyQualifiedName~WalkMissDiagnosticTests"` Expected: PASS — 3 tests passing. - [ ] **Step 5: Run the full Core test suite for regression** Run: `dotnet test tests/AcDream.Core.Tests/AcDream.Core.Tests.csproj` Expected: PASS — no new failures vs. the pre-change baseline. (The pre-existing 8-failure physics baseline mentioned in the L.2a slice 3 ship notes is the floor; we should not introduce new failures.) - [ ] **Step 6: Commit** ```bash git add src/AcDream.Core/Physics/WalkMissDiagnostic.cs tests/AcDream.Core.Tests/Physics/WalkMissDiagnosticTests.cs git commit -m "$(cat <<'EOF' feat(physics): WalkMissDiagnostic aggregator for ISSUES #83 probe spike Pure-function aggregator that, given a CellPhysics.Resolved dict and a foot local position, picks the nearest walkable-eligible polygon (normal Z >= FloorZ) and reports XY-containment + signed vertical gap. Also enumerates walkable polys with local-XY bboxes for the one-shot [floor-polys] cell-load dump. Pure-function, no behavior change. Wiring to emission sites lands in the next commit. Co-Authored-By: Claude Opus 4.7 (1M context) EOF )" ``` --- ## Task 3: Wire emission sites — `[walk-miss]` + `[floor-polys]` **Files:** - Modify: `src/AcDream.Core/Physics/TransitionTypes.cs` (~line 1538, inside the existing MISS branch) - Modify: `src/AcDream.Core/Physics/PhysicsDataCache.cs` (~line 220, immediately after the existing `[cell-cache]` block) No new tests — Task 2 covers the aggregator logic; the emission lines themselves are verified by the live capture per the spec's acceptance criteria. - [ ] **Step 1: Add the `[walk-miss]` emission to TransitionTypes.cs** Edit `src/AcDream.Core/Physics/TransitionTypes.cs`. The MISS branch already emits `[indoor-walkable] ... result=MISS` at ~line 1538. Immediately after that `Console.WriteLine` (still inside the `if (PhysicsDiagnostics.ProbeIndoorBspEnabled) { ... else { ... }}` block but *outside* its enclosing scope so it doesn't depend on the `ProbeIndoorBspEnabled` flag), add a new block guarded by `ProbeWalkMissEnabled`. Find this code at ~line 1525-1541: ```csharp if (PhysicsDiagnostics.ProbeIndoorBspEnabled) { if (walkableHit) { // dz = signed gap between foot and synthesized plane. // ... float dz = footCenter.Z + indoorPlane.D / indoorPlane.Normal.Z; Console.WriteLine(System.FormattableString.Invariant( $"[indoor-walkable] cell=0x{sp.CheckCellId:X8} ...")); } else { Console.WriteLine(System.FormattableString.Invariant( $"[indoor-walkable] cell=0x{sp.CheckCellId:X8} wpos=({footCenter.X:F3},{footCenter.Y:F3},{footCenter.Z:F3}) probe={INDOOR_WALKABLE_PROBE_DISTANCE:F2} result=MISS")); } } ``` Add directly after the closing `}` of the outer `if (PhysicsDiagnostics.ProbeIndoorBspEnabled)` block (before `if (walkableHit) { return ValidateWalkable(...); }` at line 1543): ```csharp if (!walkableHit && PhysicsDiagnostics.ProbeWalkMissEnabled) { var agg = WalkMissDiagnostic.AggregateNearestWalkable( cellPhysics.Resolved, footLocal: localCenter, floorZ: PhysicsGlobals.FloorZ); // Count walkable polys for the line (cheap re-scan; the // probe is opt-in so cost is bounded to MISS frames). int walkableCount = 0; foreach (var kvp in cellPhysics.Resolved) { if (kvp.Value.Plane.Normal.Z >= PhysicsGlobals.FloorZ && kvp.Value.Vertices.Length >= 3) walkableCount++; } // Outdoor terrain probe at the same world XY — the // "would multi-cell iteration have grounded us?" check. var terrain = engine.SampleTerrainWalkable(footCenter.X, footCenter.Y); string terrainPart; if (terrain is null) { terrainPart = "landcell.hasTerrain=false"; } else { var tp = terrain.Value.Plane; float terrainZ = -(tp.D + tp.Normal.X * footCenter.X + tp.Normal.Y * footCenter.Y) / tp.Normal.Z; float terrainDz = footCenter.Z - terrainZ; terrainPart = System.FormattableString.Invariant( $"landcell.hasTerrain=true landcell.terrainZ={terrainZ:F3} landcell.dz={terrainDz:+0.000;-0.000;+0.000}"); } string nearestPart = agg.Found ? System.FormattableString.Invariant( $"nearest.polyId=0x{agg.PolyId:X4} nearest.containsFootXY={agg.ContainsFootXY} nearest.dz={agg.Dz:+0.000;-0.000;+0.000} nearest.normalZ={agg.NormalZ:F3}") : "nearest=none"; Console.WriteLine(System.FormattableString.Invariant( $"[walk-miss] cell=0x{sp.CheckCellId:X8} foot.W=({footCenter.X:F3},{footCenter.Y:F3},{footCenter.Z:F3}) foot.L=({localCenter.X:F3},{localCenter.Y:F3},{localCenter.Z:F3}) floorPolyCount={walkableCount} {nearestPart} {terrainPart}")); } ``` - [ ] **Step 2: Add the `[floor-polys]` emission to PhysicsDataCache.cs** Edit `src/AcDream.Core/Physics/PhysicsDataCache.cs`. Immediately after the closing `}` of the existing `if (PhysicsDiagnostics.ProbeCellCacheEnabled)` block at ~line 220 (the line ending with `worldOrigin=(...)"));`), add a new block: ```csharp if (PhysicsDiagnostics.ProbeWalkMissEnabled) { int walkableCount = 0; foreach (var entry in WalkMissDiagnostic.EnumerateWalkable( resolved, PhysicsGlobals.FloorZ)) walkableCount++; Console.Write(System.FormattableString.Invariant( $"[floor-polys] cellId=0x{envCellId:X8} walkableCount={walkableCount}")); foreach (var entry in WalkMissDiagnostic.EnumerateWalkable( resolved, PhysicsGlobals.FloorZ)) { Console.Write(System.FormattableString.Invariant( $" [id=0x{entry.PolyId:X4} nz={entry.NormalZ:F3} bbox=({entry.BboxMin.X:F2},{entry.BboxMin.Y:F2})..({entry.BboxMax.X:F2},{entry.BboxMax.Y:F2}) planeZ@center={entry.PlaneZAtBboxCenter:F3}]")); } Console.WriteLine(); } ``` - [ ] **Step 3: Build the project** Run: `dotnet build` Expected: PASS — no compile errors. - [ ] **Step 4: Run the full Core test suite for regression** Run: `dotnet test tests/AcDream.Core.Tests/AcDream.Core.Tests.csproj` Expected: PASS — no new failures vs. the pre-change baseline. - [ ] **Step 5: Smoke-check zero-cost gate** Run a brief 5-second `dotnet run` launch (or rely on developer eyeball) with `ACDREAM_PROBE_WALK_MISS` *unset* and confirm no `[walk-miss]` / `[floor-polys]` lines appear in stdout. Skip this step if user explicitly asks to skip to the live capture. - [ ] **Step 6: Commit** ```bash git add src/AcDream.Core/Physics/TransitionTypes.cs src/AcDream.Core/Physics/PhysicsDataCache.cs git commit -m "$(cat <<'EOF' feat(physics): [walk-miss] + [floor-polys] diagnostic emissions Wires the WalkMissDiagnostic aggregator + flag into the two emission sites per docs/superpowers/specs/2026-05-21-indoor-walk-miss-probe-design.md. - [walk-miss] (per-frame, MISS branch of TryFindIndoorWalkablePlane): foot world+local position, nearest walkable poly with XY-containment flag and vertical gap, and LandCell terrain probe at the same XY. - [floor-polys] (one-shot per cell at cache time): walkable poly id, normal Z, local-XY bbox, plane Z at bbox center. Both gated on ACDREAM_PROBE_WALK_MISS=1. No physics behavior changes. The live capture at the Holtburg cottage doorway + inn 2nd floor + cellar descent disambiguates H1 (multi-cell iteration), H2 (probe distance), H3 (poly absent / walkable_hits_sphere rejection) for ISSUES #83. Co-Authored-By: Claude Opus 4.7 (1M context) EOF )" ``` --- ## Task 4: Live capture (manual — outside the plan's automated scope) The probe is now ready. To collect data: ```powershell $env:ACDREAM_DAT_DIR = "$env:USERPROFILE\Documents\Asheron's Call" $env:ACDREAM_LIVE = "1" $env:ACDREAM_TEST_HOST = "127.0.0.1" $env:ACDREAM_TEST_PORT = "9000" $env:ACDREAM_TEST_USER = "testaccount" $env:ACDREAM_TEST_PASS = "testpassword" $env:ACDREAM_DEVTOOLS = "1" $env:ACDREAM_PROBE_WALK_MISS = "1" $env:ACDREAM_PROBE_INDOOR_BSP = "1" $env:ACDREAM_PROBE_CELL_CACHE = "1" dotnet build -c Debug dotnet run --project src\AcDream.App\AcDream.App.csproj --no-build -c Debug 2>&1 | Tee-Object -FilePath "launch-walk-miss.log" ``` Scenarios to walk: 1. Cottage doorway threshold (cross in, cross out). 2. Holtburg inn ground → upper-floor stairs → upper-floor edge walk. 3. Cellar descent (or any single-floor → lower-floor stair pair). Then convert log to UTF-8 for grep: ```powershell Get-Content launch-walk-miss.log -Encoding Unicode | Out-File launch-walk-miss.utf8.log -Encoding utf8 ``` Aggregate the `[walk-miss]` lines, classify per the disambiguation matrix in the spec, write up findings in `docs/research/2026-05-21-walk-miss-capture-findings.md`. The fix design happens in a separate follow-up session. --- ## Self-review checklist - [x] **Spec coverage**: each spec component (flag, aggregator, two emissions, three tests) maps to a task step. - [x] **No placeholders**: every step has the exact file path + the exact code to insert. - [x] **Type consistency**: `AggregateResult` / `WalkableEntry` / property names match across Task 2 definition and Task 3 usage. `WalkMissDiagnostic.AggregateNearestWalkable` signature stable. - [x] **Test names match spec**: `AggregateNearestWalkable_PicksNearestByDz_WhenFootXYInsideMultiplePolys` covers spec test 2 (renamed for clarity); `AggregateNearestWalkable_FallsBackByDz_WhenFootXYOutsideAllBboxes` covers spec test 3. - [x] **Commits are atomic**: 3 commits, each green-tests + green-build at HEAD. - [x] **Acceptance criteria**: live capture is Task 4 (manual), spec criteria 1-2 covered by Task 3 step 3+4, criterion 4 covered by step 5.