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