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
687
docs/superpowers/plans/2026-05-21-indoor-walk-miss-probe.md
Normal file
687
docs/superpowers/plans/2026-05-21-indoor-walk-miss-probe.md
Normal file
|
|
@ -0,0 +1,687 @@
|
|||
# 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;
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
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
|
||||
/// <summary>
|
||||
/// Indoor walking ISSUES #83 H-disambiguation spike (2026-05-21).
|
||||
/// When true, two diagnostic emissions activate:
|
||||
/// <list type="bullet">
|
||||
/// <item><description>One <c>[walk-miss]</c> line per
|
||||
/// <see cref="Transition.TryFindIndoorWalkablePlane"/> 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.</description></item>
|
||||
/// <item><description>One <c>[floor-polys]</c> 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.</description></item>
|
||||
/// </list>
|
||||
/// Together these answer H1 (multi-cell iteration missing) vs H2
|
||||
/// (probe distance too short) vs H3 (poly absent /
|
||||
/// <c>walkable_hits_sphere</c> rejection) for the ISSUES #83
|
||||
/// stuck-falling bug. Spike-only — remove once the root cause is
|
||||
/// identified and the fix lands.
|
||||
///
|
||||
/// <para>
|
||||
/// Initial state from <c>ACDREAM_PROBE_WALK_MISS=1</c>.
|
||||
/// No DebugPanel mirror — one-shot diagnostic.
|
||||
/// </para>
|
||||
///
|
||||
/// <para>
|
||||
/// Spec: <c>docs/superpowers/specs/2026-05-21-indoor-walk-miss-probe-design.md</c>.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
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) <noreply@anthropic.com>
|
||||
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,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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|.
|
||||
/// </summary>
|
||||
[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<ushort, ResolvedPolygon>
|
||||
{
|
||||
[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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Foot at (10,10,1) — outside both poly XY bboxes. Aggregator
|
||||
/// returns the poly with smallest |dz| but with ContainsFootXY=false.
|
||||
/// </summary>
|
||||
[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<ushort, ResolvedPolygon> { [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;
|
||||
|
||||
/// <summary>
|
||||
/// ISSUES #83 H-disambiguation spike (2026-05-21). Pure-function
|
||||
/// aggregator over a <see cref="CellPhysics.Resolved"/> dict — picks
|
||||
/// the nearest walkable-eligible polygon to a given foot position
|
||||
/// (cell-local space) and reports XY-containment + vertical gap so
|
||||
/// the <c>[walk-miss]</c> emission site can disambiguate H1/H2/H3
|
||||
/// without re-walking the dictionary itself.
|
||||
///
|
||||
/// <para>
|
||||
/// Also enumerates walkable polygons for the one-shot
|
||||
/// <c>[floor-polys]</c> dump at cell-cache time.
|
||||
/// </para>
|
||||
///
|
||||
/// <para>
|
||||
/// Spec: <c>docs/superpowers/specs/2026-05-21-indoor-walk-miss-probe-design.md</c>.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
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; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Walks <paramref name="resolved"/>, considering only polygons
|
||||
/// whose plane normal Z is at least <paramref name="floorZ"/>
|
||||
/// (walkable slope). Selection rule:
|
||||
/// <list type="number">
|
||||
/// <item><description>Polygons whose local-XY bounding box contains
|
||||
/// <paramref name="footLocal"/>'s XY are preferred. Among them,
|
||||
/// the one with smallest <c>|dz|</c> wins.</description></item>
|
||||
/// <item><description>If no poly contains the foot XY, the poly
|
||||
/// with smallest <c>|dz|</c> across all walkable polys wins,
|
||||
/// and <see cref="AggregateResult.ContainsFootXY"/> is false.</description></item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
public static AggregateResult AggregateNearestWalkable(
|
||||
IReadOnlyDictionary<ushort, ResolvedPolygon> 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,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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 <c>[floor-polys]</c> cell-load
|
||||
/// dump.
|
||||
/// </summary>
|
||||
public static IEnumerable<WalkableEntry> EnumerateWalkable(
|
||||
IReadOnlyDictionary<ushort, ResolvedPolygon> 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) <noreply@anthropic.com>
|
||||
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) <noreply@anthropic.com>
|
||||
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.
|
||||
|
|
@ -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