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>
This commit is contained in:
Erik 2026-05-20 10:38:53 +02:00
parent 31da57c94c
commit a2e7a87c25
2 changed files with 63 additions and 0 deletions

View file

@ -218,6 +218,24 @@ public sealed class PhysicsDataCache
Console.WriteLine(System.FormattableString.Invariant(
$"[cell-cache] envCellId=0x{envCellId:X8} physicsPolyCount={cellStruct.PhysicsPolygons?.Count ?? 0} resolvedCount={resolved.Count} bspTotalLeafPolys={bspTotalLeafPolys} bspUnmatchedIds={bspUnmatchedIds} {bsStr} portalCount={portals.Count} visibleCells={visibleCellIds.Count} cellBspRoot={(cellStruct.CellBSP?.Root is null ? "null" : "ok")} worldOrigin=({worldOrigin.X:F2},{worldOrigin.Y:F2},{worldOrigin.Z:F2})"));
}
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();
}
}
/// <summary>

View file

@ -1540,6 +1540,51 @@ public sealed class Transition
}
}
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}"));
}
if (walkableHit)
{
return ValidateWalkable(