From c1bfd64834200bf3a354fe8233728a37878dccda Mon Sep 17 00:00:00 2001 From: Erik Date: Sun, 3 May 2026 10:32:42 +0200 Subject: [PATCH] fix(motion): port calc_acceleration + sequencer omega to retail tick (L.3.1+L.3.2) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Visual verification (Task 4) revealed two missing pieces from the retail per-frame tick port (acclient!CPhysicsObj::update_object @ 0x00513730): 1. body.calc_acceleration() must run BEFORE UpdatePhysicsInternal so gravity (set via PhysicsStateFlags.Gravity in OnLiveVectorUpdated) actually decays jump velocity. Without it body.Acceleration stays stale or zero → endless rise on jumps. 2. sequencer.CurrentOmega must be applied to body.Orientation per frame. Retail's TurnRight/TurnLeft cycles bake angular velocity that drives smooth rotation between UPs; we were only snapping orientation on UP receipt (~1 Hz), producing visible chop on turning remotes. Both fixes are part of the retail tick we already started porting in PositionManager — just missing pieces. Speed-overshoot bug (sequencer.CurrentVelocity > server's actual broadcast pace) is still being investigated in a follow-up. Co-Authored-By: Claude Opus 4.7 --- src/AcDream.App/Rendering/GameWindow.cs | 35 +++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/src/AcDream.App/Rendering/GameWindow.cs b/src/AcDream.App/Rendering/GameWindow.cs index 59a2a28..d87df04 100644 --- a/src/AcDream.App/Rendering/GameWindow.cs +++ b/src/AcDream.App/Rendering/GameWindow.cs @@ -5789,14 +5789,23 @@ public sealed class GameWindow : IDisposable if (System.Environment.GetEnvironmentVariable("ACDREAM_INTERP_MANAGER") == "1") { // ── NEW PATH: PositionManager (animation root motion + InterpolationManager) ── - // (L.3.1+L.3.2 Task 3 — ACDREAM_INTERP_MANAGER=1 gates this path) + // (L.3.1+L.3.2 Task 3/follow-up — ACDREAM_INTERP_MANAGER=1 gates this path) // // Always-run-all-steps per retail CPhysicsObj::UpdateObjectInternal // (acclient @ 0x00513730): // 1+2. animation root motion + interpolation correction (combined) - // 3. physics integration (gravity for airborne; no-op for grounded) + // 2.5 sequencer omega → body orientation (TurnRight/TurnLeft angular velocity) + // 3. calc_acceleration (gravity flag → body.Acceleration) + // 4. physics integration (gravity for airborne; no-op for grounded) + + // Sequencer-driven motion sources: linear velocity (root motion) + // AND angular velocity (turn-cycle omega). System.Numerics.Vector3 seqVel = ae.Sequencer?.CurrentVelocity ?? System.Numerics.Vector3.Zero; + System.Numerics.Vector3 seqOmega = ae.Sequencer?.CurrentOmega + ?? System.Numerics.Vector3.Zero; + + // Step 1+2: animation root motion + Interp correction (combined via PositionManager). float maxSpeed = rm.Motion.GetMaxSpeed(); System.Numerics.Vector3 offset = rm.Position.ComputeOffset( dt: (double)dt, @@ -5806,6 +5815,28 @@ public sealed class GameWindow : IDisposable interp: rm.Interp, maxSpeed: maxSpeed); rm.Body.Position += offset; + + // Step 2.5: animation-driven rotation. Retail's sequencer bakes Omega + // for TurnRight/TurnLeft cycles; we apply it as a per-frame quaternion + // rotation. seqOmega is body-local angular velocity (axis-angle: axis + // is omega.Normalized, magnitude is rad/sec). For Z-axis turns it's + // (0, 0, ±π/2 × turnSpeed) typically. + if (seqOmega.LengthSquared() > 1e-9f) + { + float angleDelta = seqOmega.Length() * (float)dt; + System.Numerics.Vector3 axis = System.Numerics.Vector3.Normalize(seqOmega); + var rot = System.Numerics.Quaternion.CreateFromAxisAngle(axis, angleDelta); + rm.Body.Orientation = System.Numerics.Quaternion.Normalize( + System.Numerics.Quaternion.Concatenate(rm.Body.Orientation, rot)); + } + + // Step 3: calc_acceleration sets body.Acceleration from the Gravity flag + // (mirrors retail CPhysicsObj::calc_acceleration @ FUN_00511420, called + // per-frame in update_object). Without this, body.Acceleration stays stale + // or zero → gravity never decays jump velocity → endless rise on jumps. + rm.Body.calc_acceleration(); + + // Step 4: physics integration (Euler: pos += vel*dt + 0.5*accel*dt²). rm.Body.UpdatePhysicsInternal(dt); ae.Entity.Position = rm.Body.Position;