diff --git a/tests/AcDream.Core.Tests/Physics/CellarUpTrajectoryReplayTests.cs b/tests/AcDream.Core.Tests/Physics/CellarUpTrajectoryReplayTests.cs index 762e602..059af68 100644 --- a/tests/AcDream.Core.Tests/Physics/CellarUpTrajectoryReplayTests.cs +++ b/tests/AcDream.Core.Tests/Physics/CellarUpTrajectoryReplayTests.cs @@ -96,14 +96,23 @@ public class CellarUpTrajectoryReplayTests private const float StepDownHeight = 0.04f; /// - /// Sphere center starts above cellar floor by exactly the radius - /// (bottom resting on floor). Y=9.5 is ~0.75 m before the ramp foot - /// at Y=8.75 (live-capture ramp plane equation: - /// 0.719·y + 0.695·z = 69.5035 → y=8.75 at z=90.95). + /// Sphere center starts slightly ABOVE its resting position on the + /// cellar floor (offset by an additional 0.05 m above sphere-bottom- + /// on-floor) to avoid the BSP query's floating-point boundary at + /// exact contact. With sphere center at exactly Z=floor+radius, the + /// engine reports hit=yes (back-face contact) and the body goes + /// airborne; with a 0.05 m lift, step-down on tick 1 should snap + /// the sphere cleanly to the floor. + /// + /// + /// Y=9.5 is ~0.75 m before the ramp foot at Y=8.75 (live-capture + /// ramp plane: 0.719·y + 0.695·z = 69.5035 → y=8.75 at z=90.95). /// X=141.5 matches the live capture's X. + /// /// + private const float InitialZLift = 0.05f; private static readonly Vector3 InitialSphereWorld = - new(141.5f, 9.5f, CellarFloorZ + SphereRadius); + new(141.5f, 9.5f, CellarFloorZ + SphereRadius + InitialZLift); /// /// Per-tick forward offset (−Y direction toward the ramp). @@ -168,6 +177,60 @@ public class CellarUpTrajectoryReplayTests } } + /// + /// Experiment: drive without a PhysicsBody (no CP seeding, no + /// cross-tick state). Tests whether the airborne-at-tick-1 issue + /// is caused by the seeded CP creating a false collision against + /// the cellar floor. + /// + [Fact] + public void Harness_DiagnosticDump_NoBodySeed() + { + PhysicsDiagnostics.ProbeResolveEnabled = true; + try + { + var (engine, _) = BuildEngineWithCellarFixtures(); + + uint cellId = CellarId; + bool isOnGround = true; + Vector3 pos = InitialSphereWorld; + + var trajectory = new List + { + new(0, pos, cellId, isOnGround, false), + }; + + for (int tick = 1; tick <= 10; tick++) + { + Vector3 target = pos + PerTickOffset; + var result = engine.ResolveWithTransition( + pos, target, cellId, + SphereRadius, SphereHeight, + StepUpHeight, StepDownHeight, + isOnGround, + body: null, // ← no body, no CP seed + moverFlags: ObjectInfoState.IsPlayer | ObjectInfoState.EdgeSlide, + movingEntityId: 0); + + pos = result.Position; + cellId = result.CellId; + isOnGround = result.IsOnGround; + trajectory.Add(new(tick, pos, cellId, isOnGround, false)); + } + + var msg = "No-body trajectory (10 ticks):\n " + + string.Join("\n ", trajectory.Select(p => + $"tick={p.Tick} pos=({p.Position.X:F4},{p.Position.Y:F4},{p.Position.Z:F4}) " + + $"onGround={p.IsOnGround}")); + + Assert.True(true, msg); + } + finally + { + PhysicsDiagnostics.ProbeResolveEnabled = false; + } + } + /// /// Documents finding #2: at the initial grounded position, the /// engine reports the cellar floor as a non-walkable collision