fix(movement): jump works locally (airborne velocity preserved)
Two fixes for jump physics: - Skip ground-snap when velocity Z > 0 (prevents immediate re-landing at high framerates where per-frame Z delta < 0.05 snap threshold) - Guard apply_current_movement velocity write behind OnWalkable check (prevents MotionInterpreter.DoMotion from zeroing jump velocity on every frame while airborne) - Guard PlayerMovementController velocity replacement behind OnWalkable (preserves momentum during airborne flight) Jump works locally but server packet not yet sent (BUG-002). Facing direction mismatch logged as BUG-003. RunRate not verified as BUG-004. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
e08a06ac5b
commit
157ed9d974
4 changed files with 68 additions and 31 deletions
|
|
@ -189,12 +189,6 @@ public sealed class PlayerMovementController
|
|||
_body.Orientation = Quaternion.CreateFromAxisAngle(Vector3.UnitZ, Yaw - MathF.PI / 2f);
|
||||
|
||||
// ── 2. Set velocity via MotionInterpreter state machine ───────────────
|
||||
// Snapshot the current vertical velocity BEFORE calling DoMotion.
|
||||
// DoMotion routes through apply_current_movement → set_local_velocity,
|
||||
// which overwrites _body.Velocity with the horizontal state speed and
|
||||
// zeros Z. We must snapshot Z first so we can restore it afterward.
|
||||
float savedWorldVz = _body.Velocity.Z;
|
||||
|
||||
// Determine the dominant forward/backward command and speed.
|
||||
uint forwardCmd;
|
||||
float forwardCmdSpeed;
|
||||
|
|
@ -205,7 +199,6 @@ public sealed class PlayerMovementController
|
|||
}
|
||||
else if (input.Backward)
|
||||
{
|
||||
// WalkBackward is tracked in interpreted state; we negate Y velocity below.
|
||||
forwardCmd = MotionCommand.WalkBackward;
|
||||
forwardCmdSpeed = 1.0f;
|
||||
}
|
||||
|
|
@ -215,7 +208,7 @@ public sealed class PlayerMovementController
|
|||
forwardCmdSpeed = 1.0f;
|
||||
}
|
||||
|
||||
// Update interpreted motion state.
|
||||
// Update interpreted motion state (needed for animation + server messages).
|
||||
_motion.DoMotion(forwardCmd, forwardCmdSpeed);
|
||||
|
||||
// Sidestep.
|
||||
|
|
@ -229,28 +222,30 @@ public sealed class PlayerMovementController
|
|||
_motion.StopInterpretedMotion(MotionCommand.SideStepLeft, modifyInterpretedState: true);
|
||||
}
|
||||
|
||||
// get_state_velocity gives us the body-local speed magnitude from retail constants.
|
||||
var stateVel = _motion.get_state_velocity();
|
||||
// Only replace velocity with motion interpreter output when grounded.
|
||||
// While airborne, the physics body's integrated velocity (from LeaveGround)
|
||||
// persists — gravity pulls Z down, horizontal momentum is preserved.
|
||||
// Retail AC works this way: you maintain momentum in the air.
|
||||
if (_body.OnWalkable)
|
||||
{
|
||||
float savedWorldVz = _body.Velocity.Z;
|
||||
var stateVel = _motion.get_state_velocity();
|
||||
|
||||
// Build the body-local velocity from the retail-derived speed values.
|
||||
// get_state_velocity only fills +X for SideStepRight and +Y for forward;
|
||||
// we must handle WalkBackward (negate Y) and SideStepLeft (negate X) manually.
|
||||
float localY = 0f;
|
||||
float localX = 0f;
|
||||
float localY = 0f;
|
||||
float localX = 0f;
|
||||
|
||||
if (input.Forward)
|
||||
localY = stateVel.Y; // WalkAnimSpeed or RunAnimSpeed
|
||||
else if (input.Backward)
|
||||
localY = -(MotionInterpreter.WalkAnimSpeed * 0.65f); // retail backward is ~65% walk
|
||||
if (input.Forward)
|
||||
localY = stateVel.Y;
|
||||
else if (input.Backward)
|
||||
localY = -(MotionInterpreter.WalkAnimSpeed * 0.65f);
|
||||
|
||||
if (input.StrafeRight)
|
||||
localX = MotionInterpreter.SidestepAnimSpeed * 0.5f;
|
||||
else if (input.StrafeLeft)
|
||||
localX = -MotionInterpreter.SidestepAnimSpeed * 0.5f;
|
||||
if (input.StrafeRight)
|
||||
localX = MotionInterpreter.SidestepAnimSpeed * 0.5f;
|
||||
else if (input.StrafeLeft)
|
||||
localX = -MotionInterpreter.SidestepAnimSpeed * 0.5f;
|
||||
|
||||
// Restore the vertical velocity snapshotted before DoMotion clobbered it.
|
||||
// Rotation about Z does not affect the Z component, so world Vz == local Vz.
|
||||
_body.set_local_velocity(new Vector3(localX, localY, savedWorldVz));
|
||||
_body.set_local_velocity(new Vector3(localX, localY, savedWorldVz));
|
||||
}
|
||||
|
||||
// ── 3. Jump (charged) ─────────────────────────────────────────────────
|
||||
// Hold spacebar to charge (0→1 over JumpChargeRate seconds).
|
||||
|
|
@ -301,9 +296,9 @@ public sealed class PlayerMovementController
|
|||
float groundZ = resolveResult.Position.Z;
|
||||
float bodyZ = _body.Position.Z;
|
||||
|
||||
if (bodyZ <= groundZ + 0.05f)
|
||||
if (bodyZ <= groundZ + 0.05f && _body.Velocity.Z <= 0f)
|
||||
{
|
||||
// Player is at or below the ground — snap to surface and land.
|
||||
// Player is at or below the ground AND not jumping upward — snap to surface.
|
||||
_body.Position = new Vector3(_body.Position.X, _body.Position.Y, groundZ);
|
||||
|
||||
bool wasAirborne = !_body.OnWalkable;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue