feat(net): remote retail jumps now show Falling animation + diag for height-mismatch investigation

User report:
1. ACdream watching retail-client jump shows no animation at all
   (legs don't fold during the arc).
2. Local jump arc in ACdream is shorter than what retail observes
   for the same character — formula mismatch somewhere.

Item 1 (animation): K-fix9 wired the body velocity but didn't
swap the sequencer cycle. The remote kept playing whatever
locomotion cycle was active (Ready/RunForward/etc.) through the
arc, so the legs stayed running while the body went up.
OnLiveVectorUpdated now also calls
  ae.Sequencer.SetCycle(currentStyle, MotionCommand.Falling, 1.0f)
when the velocity has +Z > 0.5 m/s. Mirrors the local-player
UpdatePlayerAnimation path that forces animCommand=Falling
whenever !IsOnGround. Style defaults to NonCombat (0x8000003D)
when the sequencer hasn't established one yet (rare on remotes).

Landing transitions back to the locomotion cycle naturally via
the next UpdateMotion the server sends after HitGround.

Item 2 (height): added per-jump diagnostic so we can compare
the formula-predicted peak (sentVz²/(2g) = sentVz²/19.6) with
the actually-rendered peak Δz. Logs:
  [jump.send] extent=... sentVz=... formulaPeak=...m startZ=...
  [jump.peak] sentVz=... formulaPeak=...m actualPeakDz=...m
              startZ=... peakZ=... landZ=...
Strip after the height-mismatch root cause is found.

Drive-by: previous diagnostic left an if/else hijack in the
resolve branch that broke 3 PlayerMovementControllerTests. Fixed.

Tests stay 1222 green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Erik 2026-04-26 17:51:37 +02:00
parent b609b5ea6e
commit 13cc08e506
2 changed files with 48 additions and 0 deletions

View file

@ -130,6 +130,12 @@ public sealed class PlayerMovementController
// Jump charge state.
private bool _jumpCharging;
private float _jumpExtent;
// K-fix10 diag (2026-04-26): track airborne arc height for the
// jump-too-low investigation. Strip after fix.
private bool _jumpDiagSampling;
private float _jumpDiagStartZ;
private float _jumpDiagPeakZ;
private float _jumpDiagSentVz;
// K-fix6 (2026-04-26): retail's PowerBar charge constant for jump is
// not legible in the named decomp (the divisor was clobbered in
// GetPowerBarLevel's FPU stack reordering at FUN_0056ade0). 2.0/s
@ -391,6 +397,16 @@ public sealed class PlayerMovementController
_motion.LeaveGround();
outJumpExtent = _jumpExtent;
outJumpVelocity = _body.Velocity; // capture after LeaveGround applies it
_jumpDiagSampling = true;
_jumpDiagStartZ = _body.Position.Z;
_jumpDiagPeakZ = _body.Position.Z;
_jumpDiagSentVz = _body.Velocity.Z;
Console.WriteLine(
$"[jump.send] extent={_jumpExtent:F3} sentVz={_body.Velocity.Z:F3} " +
$"sentVel=({_body.Velocity.X:F2},{_body.Velocity.Y:F2},{_body.Velocity.Z:F2}) " +
$"formulaPeak={_body.Velocity.Z * _body.Velocity.Z / 19.6f:F2}m " +
$"startZ={_body.Position.Z:F2}");
}
_jumpCharging = false;
_jumpExtent = 0f;
@ -435,6 +451,14 @@ public sealed class PlayerMovementController
{
_motion.HitGround();
justLanded = true;
if (_jumpDiagSampling)
{
Console.WriteLine(
$"[jump.peak] sentVz={_jumpDiagSentVz:F3} formulaPeak={_jumpDiagSentVz * _jumpDiagSentVz / 19.6f:F2}m " +
$"actualPeakDz={(_jumpDiagPeakZ - _jumpDiagStartZ):F2}m " +
$"startZ={_jumpDiagStartZ:F2} peakZ={_jumpDiagPeakZ:F2} landZ={_body.Position.Z:F2}");
_jumpDiagSampling = false;
}
}
}
else
@ -451,6 +475,11 @@ public sealed class PlayerMovementController
_body.calc_acceleration();
}
// K-fix10 diag: peak Z tracking — placed AFTER the resolve branch
// so it doesn't disrupt control flow.
if (_jumpDiagSampling && _body.Position.Z > _jumpDiagPeakZ)
_jumpDiagPeakZ = _body.Position.Z;
_wasAirborneLastFrame = !_body.OnWalkable;
CellId = resolveResult.CellId;