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;