From 13cc08e5067bce115df4a9990a735ae7275b7bbe Mon Sep 17 00:00:00 2001 From: Erik Date: Sun, 26 Apr 2026 17:51:37 +0200 Subject: [PATCH] feat(net): remote retail jumps now show Falling animation + diag for height-mismatch investigation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- .../Input/PlayerMovementController.cs | 29 +++++++++++++++++++ src/AcDream.App/Rendering/GameWindow.cs | 19 ++++++++++++ 2 files changed, 48 insertions(+) diff --git a/src/AcDream.App/Input/PlayerMovementController.cs b/src/AcDream.App/Input/PlayerMovementController.cs index fbc1425..4326599 100644 --- a/src/AcDream.App/Input/PlayerMovementController.cs +++ b/src/AcDream.App/Input/PlayerMovementController.cs @@ -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; diff --git a/src/AcDream.App/Rendering/GameWindow.cs b/src/AcDream.App/Rendering/GameWindow.cs index 308da9e..1f6e726 100644 --- a/src/AcDream.App/Rendering/GameWindow.cs +++ b/src/AcDream.App/Rendering/GameWindow.cs @@ -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")