From a2e7a87c25459d336a784009d06fe4288978bdab Mon Sep 17 00:00:00 2001 From: Erik Date: Wed, 20 May 2026 10:38:53 +0200 Subject: [PATCH] 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) --- src/AcDream.Core/Physics/PhysicsDataCache.cs | 18 ++++++++ src/AcDream.Core/Physics/TransitionTypes.cs | 45 ++++++++++++++++++++ 2 files changed, 63 insertions(+) diff --git a/src/AcDream.Core/Physics/PhysicsDataCache.cs b/src/AcDream.Core/Physics/PhysicsDataCache.cs index ee58a7c..934b3a1 100644 --- a/src/AcDream.Core/Physics/PhysicsDataCache.cs +++ b/src/AcDream.Core/Physics/PhysicsDataCache.cs @@ -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(); + } } /// diff --git a/src/AcDream.Core/Physics/TransitionTypes.cs b/src/AcDream.Core/Physics/TransitionTypes.cs index c941f67..977cd8a 100644 --- a/src/AcDream.Core/Physics/TransitionTypes.cs +++ b/src/AcDream.Core/Physics/TransitionTypes.cs @@ -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(