fix(physics): jump apex velocity zeroing bug

SmallVelocity threshold (0.25 m/s) in UpdatePhysicsInternal was zeroing
velocity every frame while airborne at the jump apex. With vel~0.01 m/s
and gravity adding only 0.012/frame, the zeroing won every frame and
the character got stuck at peak height forever.

Fix: only apply small-velocity zeroing when OnWalkable (grounded).
While airborne, gravity must accumulate freely through the zero-crossing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Erik 2026-04-14 10:13:27 +02:00
parent 157ed9d974
commit 31cd5480dc
3 changed files with 32 additions and 8 deletions

View file

@ -97,6 +97,8 @@ public sealed class PlayerMovementController
// Jump charge state.
private bool _jumpCharging;
private int _debugSpeedLogCounter;
private int _debugJumpFrames;
private float _jumpExtent;
private const float JumpChargeRate = 1.0f; // 0→1 over 1 second
@ -244,6 +246,8 @@ public sealed class PlayerMovementController
else if (input.StrafeLeft)
localX = -MotionInterpreter.SidestepAnimSpeed * 0.5f;
if (input.Forward && _debugSpeedLogCounter++ % 60 == 0)
Console.WriteLine($"DEBUG speed: stateVel.Y={stateVel.Y:F2} MyRunRate={_motion.MyRunRate:F3} localY={localY:F2}");
_body.set_local_velocity(new Vector3(localX, localY, savedWorldVz));
}
@ -271,6 +275,12 @@ public sealed class PlayerMovementController
{
_motion.LeaveGround();
outJumpExtent = _jumpExtent;
_debugJumpFrames = 500; // log until landing
Console.WriteLine($"DEBUG jump FIRED: extent={_jumpExtent:F2} vel={_body.Velocity} onWalk={_body.OnWalkable}");
}
else
{
Console.WriteLine($"DEBUG jump FAILED: extent={_jumpExtent:F2} result={jumpResult} onWalk={_body.OnWalkable} contact={_body.InContact}");
}
_jumpCharging = false;
_jumpExtent = 0f;
@ -285,6 +295,12 @@ public sealed class PlayerMovementController
_body.calc_acceleration();
_body.UpdatePhysicsInternal(dt);
if (_debugJumpFrames > 0)
{
_debugJumpFrames--;
Console.WriteLine($"DEBUG jump frame: pos.Z={_body.Position.Z:F3} vel.Z={_body.Velocity.Z:F3} onWalk={_body.OnWalkable} accel.Z={_body.Acceleration.Z:F1} dt={dt:F4}");
}
// ── 5. Terrain/cell Z snap and ground-contact detection ───────────────
// Use PhysicsEngine.Resolve to find the ground surface Z under the player.
// We pass a zero delta because PhysicsBody already moved the position.
@ -310,7 +326,11 @@ public sealed class PlayerMovementController
_body.Velocity = new Vector3(_body.Velocity.X, _body.Velocity.Y, 0f);
if (wasAirborne)
{
Console.WriteLine($"DEBUG LANDED: bodyZ={bodyZ:F3} groundZ={groundZ:F3} vel.Z={_body.Velocity.Z:F3}");
_motion.HitGround();
_debugJumpFrames = 0;
}
}
else
{