feat(physics): Cluster A — indoor BSP collision probe
Adds the [indoor-bsp] probe + ProbeIndoorBspEnabled toggle for the Indoor walking Phase 1 BSP-cluster investigation. Mirrors the existing [resolve] / [cell-transit] / [indoor-*] pattern: one log line per BSPQuery.FindCollisions call from FindEnvCollisions' cell branch, capturing cell id, sphere local-pos, result TransitionState, and the hit poly's normal + side-type via the LastBspHitPoly side-channel (already wired for ProbeBuildingEnabled, now also fires for the indoor flag). Toggle via ACDREAM_PROBE_INDOOR_BSP=1 env var or DebugPanel checkbox. Zero-cost when off. Predecessor for the three fix commits that will close ISSUES.md #84/#85/#86 after the capture session. Spec: docs/superpowers/specs/2026-05-19-indoor-walking-phase1-bsp-cluster-design.md Plan: docs/superpowers/plans/2026-05-19-indoor-walking-phase1-bsp-cluster.md Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
18a2e28875
commit
27d7de11d8
6 changed files with 96 additions and 9 deletions
|
|
@ -1215,7 +1215,7 @@ public static class BSPQuery
|
||||||
{
|
{
|
||||||
collisions.SetCollisionNormal(collisionNormal);
|
collisions.SetCollisionNormal(collisionNormal);
|
||||||
// L.2d slice 1 (2026-05-13): diagnostic side-channel.
|
// L.2d slice 1 (2026-05-13): diagnostic side-channel.
|
||||||
if (PhysicsDiagnostics.ProbeBuildingEnabled)
|
if (PhysicsDiagnostics.ProbeBuildingEnabled || PhysicsDiagnostics.ProbeIndoorBspEnabled)
|
||||||
PhysicsDiagnostics.LastBspHitPoly = hitPoly;
|
PhysicsDiagnostics.LastBspHitPoly = hitPoly;
|
||||||
return TransitionState.Collided;
|
return TransitionState.Collided;
|
||||||
}
|
}
|
||||||
|
|
@ -1228,14 +1228,14 @@ public static class BSPQuery
|
||||||
// the early-out — collisions.SetCollisionNormal isn't called on
|
// the early-out — collisions.SetCollisionNormal isn't called on
|
||||||
// this path, but the caller's CollisionInfo.CollisionNormalValid
|
// this path, but the caller's CollisionInfo.CollisionNormalValid
|
||||||
// check will catch the parent slide site's normal write instead.
|
// check will catch the parent slide site's normal write instead.
|
||||||
if (PhysicsDiagnostics.ProbeBuildingEnabled)
|
if (PhysicsDiagnostics.ProbeBuildingEnabled || PhysicsDiagnostics.ProbeIndoorBspEnabled)
|
||||||
PhysicsDiagnostics.LastBspHitPoly = hitPoly;
|
PhysicsDiagnostics.LastBspHitPoly = hitPoly;
|
||||||
return TransitionState.Collided;
|
return TransitionState.Collided;
|
||||||
}
|
}
|
||||||
|
|
||||||
collisions.SetCollisionNormal(collisionNormal);
|
collisions.SetCollisionNormal(collisionNormal);
|
||||||
// L.2d slice 1 (2026-05-13): diagnostic side-channel.
|
// L.2d slice 1 (2026-05-13): diagnostic side-channel.
|
||||||
if (PhysicsDiagnostics.ProbeBuildingEnabled)
|
if (PhysicsDiagnostics.ProbeBuildingEnabled || PhysicsDiagnostics.ProbeIndoorBspEnabled)
|
||||||
PhysicsDiagnostics.LastBspHitPoly = hitPoly;
|
PhysicsDiagnostics.LastBspHitPoly = hitPoly;
|
||||||
|
|
||||||
var adjusted = validPos.Center - checkPos.Center;
|
var adjusted = validPos.Center - checkPos.Center;
|
||||||
|
|
@ -1551,7 +1551,7 @@ public static class BSPQuery
|
||||||
// is the dominant grounded-player path; without this the
|
// is the dominant grounded-player path; without this the
|
||||||
// probe's [resolve-bldg] line for every grounded BSP hit was
|
// probe's [resolve-bldg] line for every grounded BSP hit was
|
||||||
// mis-labeled as "n/a (cylinder)".
|
// mis-labeled as "n/a (cylinder)".
|
||||||
if (PhysicsDiagnostics.ProbeBuildingEnabled)
|
if (PhysicsDiagnostics.ProbeBuildingEnabled || PhysicsDiagnostics.ProbeIndoorBspEnabled)
|
||||||
PhysicsDiagnostics.LastBspHitPoly = hitPoly0;
|
PhysicsDiagnostics.LastBspHitPoly = hitPoly0;
|
||||||
|
|
||||||
var worldNormal = L2W(hitPoly0!.Plane.Normal);
|
var worldNormal = L2W(hitPoly0!.Plane.Normal);
|
||||||
|
|
@ -1585,7 +1585,7 @@ public static class BSPQuery
|
||||||
// L.2d slice 1.5 (2026-05-13): same early-record as foot
|
// L.2d slice 1.5 (2026-05-13): same early-record as foot
|
||||||
// sphere — head-sphere wall hits also recurse via
|
// sphere — head-sphere wall hits also recurse via
|
||||||
// StepSphereUp on the grounded path.
|
// StepSphereUp on the grounded path.
|
||||||
if (PhysicsDiagnostics.ProbeBuildingEnabled)
|
if (PhysicsDiagnostics.ProbeBuildingEnabled || PhysicsDiagnostics.ProbeIndoorBspEnabled)
|
||||||
PhysicsDiagnostics.LastBspHitPoly = hitPoly1;
|
PhysicsDiagnostics.LastBspHitPoly = hitPoly1;
|
||||||
|
|
||||||
var worldNormal = L2W(hitPoly1!.Plane.Normal);
|
var worldNormal = L2W(hitPoly1!.Plane.Normal);
|
||||||
|
|
@ -1669,7 +1669,7 @@ public static class BSPQuery
|
||||||
collisions.SetCollisionNormal(worldNormal0);
|
collisions.SetCollisionNormal(worldNormal0);
|
||||||
collisions.SetSlidingNormal(worldNormal0);
|
collisions.SetSlidingNormal(worldNormal0);
|
||||||
// L.2d slice 1 (2026-05-13): diagnostic side-channel.
|
// L.2d slice 1 (2026-05-13): diagnostic side-channel.
|
||||||
if (PhysicsDiagnostics.ProbeBuildingEnabled)
|
if (PhysicsDiagnostics.ProbeBuildingEnabled || PhysicsDiagnostics.ProbeIndoorBspEnabled)
|
||||||
PhysicsDiagnostics.LastBspHitPoly = hitPoly0;
|
PhysicsDiagnostics.LastBspHitPoly = hitPoly0;
|
||||||
return TransitionState.Slid;
|
return TransitionState.Slid;
|
||||||
}
|
}
|
||||||
|
|
@ -1679,7 +1679,7 @@ public static class BSPQuery
|
||||||
path.SetCollide(worldNormal0);
|
path.SetCollide(worldNormal0);
|
||||||
path.WalkableAllowance = PhysicsGlobals.LandingZ;
|
path.WalkableAllowance = PhysicsGlobals.LandingZ;
|
||||||
// L.2d slice 1 (2026-05-13): diagnostic side-channel.
|
// L.2d slice 1 (2026-05-13): diagnostic side-channel.
|
||||||
if (PhysicsDiagnostics.ProbeBuildingEnabled)
|
if (PhysicsDiagnostics.ProbeBuildingEnabled || PhysicsDiagnostics.ProbeIndoorBspEnabled)
|
||||||
PhysicsDiagnostics.LastBspHitPoly = hitPoly0;
|
PhysicsDiagnostics.LastBspHitPoly = hitPoly0;
|
||||||
return TransitionState.Adjusted;
|
return TransitionState.Adjusted;
|
||||||
}
|
}
|
||||||
|
|
@ -1709,7 +1709,7 @@ public static class BSPQuery
|
||||||
collisions.SetCollisionNormal(worldNormal1);
|
collisions.SetCollisionNormal(worldNormal1);
|
||||||
collisions.SetSlidingNormal(worldNormal1);
|
collisions.SetSlidingNormal(worldNormal1);
|
||||||
// L.2d slice 1 (2026-05-13): diagnostic side-channel.
|
// L.2d slice 1 (2026-05-13): diagnostic side-channel.
|
||||||
if (PhysicsDiagnostics.ProbeBuildingEnabled)
|
if (PhysicsDiagnostics.ProbeBuildingEnabled || PhysicsDiagnostics.ProbeIndoorBspEnabled)
|
||||||
PhysicsDiagnostics.LastBspHitPoly = hitPoly1;
|
PhysicsDiagnostics.LastBspHitPoly = hitPoly1;
|
||||||
return TransitionState.Slid;
|
return TransitionState.Slid;
|
||||||
}
|
}
|
||||||
|
|
@ -1718,7 +1718,7 @@ public static class BSPQuery
|
||||||
path.SetCollide(worldNormal1);
|
path.SetCollide(worldNormal1);
|
||||||
path.WalkableAllowance = PhysicsGlobals.LandingZ;
|
path.WalkableAllowance = PhysicsGlobals.LandingZ;
|
||||||
// L.2d slice 1 (2026-05-13): diagnostic side-channel.
|
// L.2d slice 1 (2026-05-13): diagnostic side-channel.
|
||||||
if (PhysicsDiagnostics.ProbeBuildingEnabled)
|
if (PhysicsDiagnostics.ProbeBuildingEnabled || PhysicsDiagnostics.ProbeIndoorBspEnabled)
|
||||||
PhysicsDiagnostics.LastBspHitPoly = hitPoly1;
|
PhysicsDiagnostics.LastBspHitPoly = hitPoly1;
|
||||||
return TransitionState.Adjusted;
|
return TransitionState.Adjusted;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -166,4 +166,33 @@ public static class PhysicsDiagnostics
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static bool DumpSteepRoofEnabled { get; set; } =
|
public static bool DumpSteepRoofEnabled { get; set; } =
|
||||||
Environment.GetEnvironmentVariable("ACDREAM_DUMP_STEEP_ROOF") == "1";
|
Environment.GetEnvironmentVariable("ACDREAM_DUMP_STEEP_ROOF") == "1";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Indoor walking Phase 1 (2026-05-19). When true, emits one
|
||||||
|
/// <c>[indoor-bsp]</c> line per <see cref="BSPQuery.FindCollisions"/>
|
||||||
|
/// call made from <see cref="Transition.FindEnvCollisions"/>'s indoor
|
||||||
|
/// cell-BSP branch. Captures the cell id, sphere local position,
|
||||||
|
/// resulting <see cref="TransitionState"/>, and the hit poly's id,
|
||||||
|
/// local-normal, and side-type — pinpoints why indoor collision
|
||||||
|
/// returns spurious collisions (#84) and helps cross-check the
|
||||||
|
/// outdoor-in approach path (#85).
|
||||||
|
///
|
||||||
|
/// <para>
|
||||||
|
/// While true, this also un-gates the diagnostic
|
||||||
|
/// <see cref="LastBspHitPoly"/> side-channel inside
|
||||||
|
/// <see cref="BSPQuery"/> — see the OR'd condition at every poly
|
||||||
|
/// write site. Zero-cost when off.
|
||||||
|
/// </para>
|
||||||
|
///
|
||||||
|
/// <para>
|
||||||
|
/// Initial state from <c>ACDREAM_PROBE_INDOOR_BSP=1</c>.
|
||||||
|
/// Runtime-toggleable via DebugPanel.
|
||||||
|
/// </para>
|
||||||
|
///
|
||||||
|
/// <para>
|
||||||
|
/// Spec: <c>docs/superpowers/specs/2026-05-19-indoor-walking-phase1-bsp-cluster-design.md</c>.
|
||||||
|
/// </para>
|
||||||
|
/// </summary>
|
||||||
|
public static bool ProbeIndoorBspEnabled { get; set; } =
|
||||||
|
Environment.GetEnvironmentVariable("ACDREAM_PROBE_INDOOR_BSP") == "1";
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1217,6 +1217,12 @@ public sealed class Transition
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Indoor walking Phase 1 (2026-05-19): clear the LastBspHitPoly
|
||||||
|
// side-channel before the call so a missed write (no collision)
|
||||||
|
// is greppable as "poly=n/a" in the probe line below.
|
||||||
|
if (PhysicsDiagnostics.ProbeIndoorBspEnabled)
|
||||||
|
PhysicsDiagnostics.LastBspHitPoly = null;
|
||||||
|
|
||||||
// Use the full 6-path BSP dispatcher for retail-faithful collision.
|
// Use the full 6-path BSP dispatcher for retail-faithful collision.
|
||||||
// Use pre-resolved polygons (vertices+planes computed at cache time).
|
// Use pre-resolved polygons (vertices+planes computed at cache time).
|
||||||
var cellState = BSPQuery.FindCollisions(
|
var cellState = BSPQuery.FindCollisions(
|
||||||
|
|
@ -1231,6 +1237,18 @@ public sealed class Transition
|
||||||
Quaternion.Identity,
|
Quaternion.Identity,
|
||||||
engine); // engine needed for Path 5 step-up
|
engine); // engine needed for Path 5 step-up
|
||||||
|
|
||||||
|
if (PhysicsDiagnostics.ProbeIndoorBspEnabled)
|
||||||
|
{
|
||||||
|
var hit = PhysicsDiagnostics.LastBspHitPoly;
|
||||||
|
string polyDesc = hit is null
|
||||||
|
? "poly=n/a"
|
||||||
|
: System.FormattableString.Invariant(
|
||||||
|
$"n=({hit.Plane.Normal.X:F3},{hit.Plane.Normal.Y:F3},{hit.Plane.Normal.Z:F3}) sides={hit.SidesType}");
|
||||||
|
Console.WriteLine(System.FormattableString.Invariant(
|
||||||
|
$"[indoor-bsp] cell=0x{sp.CheckCellId:X8} wpos=({footCenter.X:F3},{footCenter.Y:F3},{footCenter.Z:F3}) lpos=({localCenter.X:F3},{localCenter.Y:F3},{localCenter.Z:F3}) lprev=({localCurrCenter.X:F3},{localCurrCenter.Y:F3},{localCurrCenter.Z:F3}) r={sphereRadius:F3} result={cellState} ")
|
||||||
|
+ polyDesc);
|
||||||
|
}
|
||||||
|
|
||||||
if (cellState != TransitionState.OK)
|
if (cellState != TransitionState.OK)
|
||||||
{
|
{
|
||||||
if (!ObjectInfo.State.HasFlag(ObjectInfoState.Contact))
|
if (!ObjectInfo.State.HasFlag(ObjectInfoState.Contact))
|
||||||
|
|
|
||||||
|
|
@ -275,6 +275,9 @@ public sealed class DebugPanel : IPanel
|
||||||
if (r.Checkbox("Indoor: xform (ACDREAM_PROBE_INDOOR_XFORM)", ref probeIndoorXform)) _vm.ProbeIndoorXform = probeIndoorXform;
|
if (r.Checkbox("Indoor: xform (ACDREAM_PROBE_INDOOR_XFORM)", ref probeIndoorXform)) _vm.ProbeIndoorXform = probeIndoorXform;
|
||||||
if (r.Checkbox("Indoor: cull (ACDREAM_PROBE_INDOOR_CULL)", ref probeIndoorCull)) _vm.ProbeIndoorCull = probeIndoorCull;
|
if (r.Checkbox("Indoor: cull (ACDREAM_PROBE_INDOOR_CULL)", ref probeIndoorCull)) _vm.ProbeIndoorCull = probeIndoorCull;
|
||||||
|
|
||||||
|
bool probeIndoorBsp = _vm.ProbeIndoorBsp;
|
||||||
|
if (r.Checkbox("Indoor: BSP collision (ACDREAM_PROBE_INDOOR_BSP)", ref probeIndoorBsp)) _vm.ProbeIndoorBsp = probeIndoorBsp;
|
||||||
|
|
||||||
r.Spacing();
|
r.Spacing();
|
||||||
|
|
||||||
// Cycle / toggle actions live on the VM as Action handles; the
|
// Cycle / toggle actions live on the VM as Action handles; the
|
||||||
|
|
|
||||||
|
|
@ -345,6 +345,20 @@ public sealed class DebugVM
|
||||||
set => RenderingDiagnostics.ProbeIndoorCullEnabled = value;
|
set => RenderingDiagnostics.ProbeIndoorCullEnabled = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Indoor walking Phase 1 (2026-05-19). Runtime mirror of
|
||||||
|
/// <c>PhysicsDiagnostics.ProbeIndoorBspEnabled</c> (env var
|
||||||
|
/// <c>ACDREAM_PROBE_INDOOR_BSP</c>). Toggling here flips the
|
||||||
|
/// <c>[indoor-bsp]</c> probe live — no relaunch required.
|
||||||
|
/// Physics-side companion to the five render-side
|
||||||
|
/// <c>ProbeIndoor*</c> mirrors directly above.
|
||||||
|
/// </summary>
|
||||||
|
public bool ProbeIndoorBsp
|
||||||
|
{
|
||||||
|
get => PhysicsDiagnostics.ProbeIndoorBspEnabled;
|
||||||
|
set => PhysicsDiagnostics.ProbeIndoorBspEnabled = value;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Runtime mirror of <c>RenderingDiagnostics.IndoorAll</c> — toggles all
|
/// Runtime mirror of <c>RenderingDiagnostics.IndoorAll</c> — toggles all
|
||||||
/// five indoor probes together. No dedicated env var; set any individual
|
/// five indoor probes together. No dedicated env var; set any individual
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using AcDream.Core.Combat;
|
using AcDream.Core.Combat;
|
||||||
|
using AcDream.Core.Physics;
|
||||||
using AcDream.UI.Abstractions.Panels.Debug;
|
using AcDream.UI.Abstractions.Panels.Debug;
|
||||||
|
|
||||||
namespace AcDream.UI.Abstractions.Tests.Panels.Debug;
|
namespace AcDream.UI.Abstractions.Tests.Panels.Debug;
|
||||||
|
|
@ -285,4 +286,26 @@ public sealed class DebugVMTests
|
||||||
Assert.Equal(1, weatherHits);
|
Assert.Equal(1, weatherHits);
|
||||||
Assert.Equal(1, wireHits);
|
Assert.Equal(1, wireHits);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ProbeIndoorBsp_ForwardsToPhysicsDiagnostics()
|
||||||
|
{
|
||||||
|
var originalEnabled = PhysicsDiagnostics.ProbeIndoorBspEnabled;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var vm = NewVm();
|
||||||
|
|
||||||
|
vm.ProbeIndoorBsp = true;
|
||||||
|
Assert.True(PhysicsDiagnostics.ProbeIndoorBspEnabled);
|
||||||
|
Assert.True(vm.ProbeIndoorBsp);
|
||||||
|
|
||||||
|
vm.ProbeIndoorBsp = false;
|
||||||
|
Assert.False(PhysicsDiagnostics.ProbeIndoorBspEnabled);
|
||||||
|
Assert.False(vm.ProbeIndoorBsp);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
PhysicsDiagnostics.ProbeIndoorBspEnabled = originalEnabled;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue