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:
parent
b609b5ea6e
commit
13cc08e506
2 changed files with 48 additions and 0 deletions
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -2380,6 +2380,25 @@ public sealed class GameWindow : IDisposable
|
|||
rm.Body.TransientState &= ~(AcDream.Core.Physics.TransientStateFlags.Contact
|
||||
| AcDream.Core.Physics.TransientStateFlags.OnWalkable);
|
||||
rm.Body.State |= AcDream.Core.Physics.PhysicsStateFlags.Gravity;
|
||||
|
||||
// K-fix10 (2026-04-26): force the Falling animation cycle on
|
||||
// the remote so the legs match the arc. Mirrors the local
|
||||
// player's UpdatePlayerAnimation path which sets
|
||||
// animCommand = Falling whenever !IsOnGround. Without this,
|
||||
// the remote's existing locomotion cycle (RunForward,
|
||||
// Ready, etc.) keeps playing through the jump — body goes
|
||||
// up but legs stay running. Style is the sequencer's
|
||||
// current style (NonCombat 0x8000003D for humanoids); cycle
|
||||
// pace = 1.0 (Falling animation has its own baked rate).
|
||||
if (_entitiesByServerGuid.TryGetValue(update.Guid, out var ent)
|
||||
&& _animatedEntities.TryGetValue(ent.Id, out var ae)
|
||||
&& ae.Sequencer is not null)
|
||||
{
|
||||
uint style = ae.Sequencer.CurrentStyle != 0
|
||||
? ae.Sequencer.CurrentStyle
|
||||
: 0x8000003Du; // NonCombat default
|
||||
ae.Sequencer.SetCycle(style, AcDream.Core.Physics.MotionCommand.Falling, 1.0f);
|
||||
}
|
||||
}
|
||||
|
||||
if (Environment.GetEnvironmentVariable("ACDREAM_DUMP_MOTION") == "1")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue