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:
parent
157ed9d974
commit
31cd5480dc
3 changed files with 32 additions and 8 deletions
|
|
@ -97,6 +97,8 @@ public sealed class PlayerMovementController
|
||||||
|
|
||||||
// Jump charge state.
|
// Jump charge state.
|
||||||
private bool _jumpCharging;
|
private bool _jumpCharging;
|
||||||
|
private int _debugSpeedLogCounter;
|
||||||
|
private int _debugJumpFrames;
|
||||||
private float _jumpExtent;
|
private float _jumpExtent;
|
||||||
private const float JumpChargeRate = 1.0f; // 0→1 over 1 second
|
private const float JumpChargeRate = 1.0f; // 0→1 over 1 second
|
||||||
|
|
||||||
|
|
@ -244,6 +246,8 @@ public sealed class PlayerMovementController
|
||||||
else if (input.StrafeLeft)
|
else if (input.StrafeLeft)
|
||||||
localX = -MotionInterpreter.SidestepAnimSpeed * 0.5f;
|
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));
|
_body.set_local_velocity(new Vector3(localX, localY, savedWorldVz));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -271,6 +275,12 @@ public sealed class PlayerMovementController
|
||||||
{
|
{
|
||||||
_motion.LeaveGround();
|
_motion.LeaveGround();
|
||||||
outJumpExtent = _jumpExtent;
|
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;
|
_jumpCharging = false;
|
||||||
_jumpExtent = 0f;
|
_jumpExtent = 0f;
|
||||||
|
|
@ -285,6 +295,12 @@ public sealed class PlayerMovementController
|
||||||
_body.calc_acceleration();
|
_body.calc_acceleration();
|
||||||
_body.UpdatePhysicsInternal(dt);
|
_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 ───────────────
|
// ── 5. Terrain/cell Z snap and ground-contact detection ───────────────
|
||||||
// Use PhysicsEngine.Resolve to find the ground surface Z under the player.
|
// Use PhysicsEngine.Resolve to find the ground surface Z under the player.
|
||||||
// We pass a zero delta because PhysicsBody already moved the position.
|
// 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);
|
_body.Velocity = new Vector3(_body.Velocity.X, _body.Velocity.Y, 0f);
|
||||||
|
|
||||||
if (wasAirborne)
|
if (wasAirborne)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"DEBUG LANDED: bodyZ={bodyZ:F3} groundZ={groundZ:F3} vel.Z={_body.Velocity.Z:F3}");
|
||||||
_motion.HitGround();
|
_motion.HitGround();
|
||||||
|
_debugJumpFrames = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -960,21 +960,22 @@ public sealed class GameWindow : IDisposable
|
||||||
&& newCycle.HighFrame > newCycle.LowFrame
|
&& newCycle.HighFrame > newCycle.LowFrame
|
||||||
&& newCycle.Animation.PartFrames.Count > 1;
|
&& newCycle.Animation.PartFrames.Count > 1;
|
||||||
|
|
||||||
if (!newCycleIsGood)
|
// Wire server-echoed RunRate BEFORE the animation early-return.
|
||||||
return;
|
// If the cycle can't resolve (bad stance), we still need the speed.
|
||||||
|
|
||||||
// Wire server-echoed RunRate into the player's MotionInterpreter.
|
|
||||||
// The server broadcasts the character's real Run-skill-derived ForwardSpeed
|
|
||||||
// in UpdateMotion; without this the player would always move at 4.0 m/s
|
|
||||||
// (ForwardSpeed = 1.0 hardcoded in MotionInterpreter defaults).
|
|
||||||
if (_playerController is not null
|
if (_playerController is not null
|
||||||
&& update.Guid == _playerServerGuid
|
&& update.Guid == _playerServerGuid
|
||||||
&& update.MotionState.ForwardSpeed.HasValue
|
&& update.MotionState.ForwardSpeed.HasValue
|
||||||
&& update.MotionState.ForwardSpeed.Value > 0f)
|
&& update.MotionState.ForwardSpeed.Value > 0f)
|
||||||
{
|
{
|
||||||
|
Console.WriteLine($"DEBUG RunRate: guid={update.Guid:X8} fwd={update.MotionState.ForwardSpeed.Value:F3}");
|
||||||
_playerController.ApplyServerRunRate(update.MotionState.ForwardSpeed.Value);
|
_playerController.ApplyServerRunRate(update.MotionState.ForwardSpeed.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!newCycleIsGood)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// (RunRate wiring moved above the early-return)
|
||||||
|
|
||||||
// Sequencer path
|
// Sequencer path
|
||||||
if (ae.Sequencer is not null)
|
if (ae.Sequencer is not null)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -298,7 +298,10 @@ public sealed class PhysicsBody
|
||||||
calc_friction(dt, velocityMag2);
|
calc_friction(dt, velocityMag2);
|
||||||
|
|
||||||
// If velocity fell below the "small" threshold after friction, stop.
|
// If velocity fell below the "small" threshold after friction, stop.
|
||||||
if (velocityMag2 - SmallVelocitySquared < 0.0002f)
|
// Only apply when grounded — while airborne, gravity must accumulate
|
||||||
|
// even when velocity is near zero (e.g., at jump apex).
|
||||||
|
if (velocityMag2 - SmallVelocitySquared < 0.0002f
|
||||||
|
&& TransientState.HasFlag(TransientStateFlags.OnWalkable))
|
||||||
Velocity = Vector3.Zero;
|
Velocity = Vector3.Zero;
|
||||||
|
|
||||||
// Euler integration: position += v*dt + 0.5*a*dt²
|
// Euler integration: position += v*dt + 0.5*a*dt²
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue