diff --git a/src/AcDream.Core/Physics/AnimationSequencer.cs b/src/AcDream.Core/Physics/AnimationSequencer.cs index e65acad..90c8542 100644 --- a/src/AcDream.Core/Physics/AnimationSequencer.cs +++ b/src/AcDream.Core/Physics/AnimationSequencer.cs @@ -170,6 +170,10 @@ public sealed class AnimationSequencer /// Current cyclic motion command. public uint CurrentMotion { get; private set; } + // Diagnostics + public int QueueCount => _queue.Count; + public bool HasCurrentNode => _currNode != null; + // ── Private state ──────────────────────────────────────────────────────── private readonly Setup _setup; @@ -379,11 +383,10 @@ public sealed class AnimationSequencer else { // ── REVERSE PLAYBACK ───────────────────────────────────── - // After FUN_005267E0 swaps low↔high for negative speed: - // StartFrame = high (e.g. 3), EndFrame = low (e.g. 0) - // GetStartFramePosition placed cursor at (EndFrame+1)-eps ≈ 0.99999. - // The cursor counts DOWN toward EndFrame. Boundary = EndFrame. - double minBoundary = (double)curr.EndFrame; + // No swap: StartFrame is still the LOW value (e.g. 0). + // GetStartFramePosition placed cursor at (EndFrame+1)-eps (near high). + // The cursor counts DOWN toward StartFrame. + double minBoundary = (double)curr.StartFrame; if (newPos <= minBoundary) { // How much time spilled past the lower boundary? @@ -485,21 +488,13 @@ public sealed class AnimationSequencer double fr = (double)ad.Framerate * (double)speedMod; - // ── FUN_005267E0 multiply_framerate ────────────────────────────── - // When speed is negative (TurnLeft→TurnRight, SideStepLeft→SideStepRight), - // swap Low↔High so the advance loop counts DOWN from the swapped EndFrame - // toward the swapped StartFrame. The pseudocode says: - // if speedScale < 0: swap startFrame ↔ endFrame - if (fr < 0.0) - { - (low, high) = (high, low); - // After swap: StartFrame > EndFrame (the loop detects delta < 0 and - // uses StartFrame as the lower boundary to count down toward). - } - else - { - if (low > high) high = low; // clamp for positive-speed case only - } + // Do NOT swap StartFrame↔EndFrame for negative speed. + // The Advance loop handles negative delta by checking against + // StartFrame as the lower boundary. GetStartFramePosition uses + // EndFrame (the HIGH value) to start the cursor near the top + // for reverse playback, so the cursor traverses all frames + // from high→low instead of being stuck in [0,1). + if (low > high) high = low; return new AnimNode(anim, fr, startFrame: low, endFrame: high, isLooping); } diff --git a/tests/AcDream.Core.Tests/Physics/AnimationSequencerTests.cs b/tests/AcDream.Core.Tests/Physics/AnimationSequencerTests.cs index 09af88d..f582aba 100644 --- a/tests/AcDream.Core.Tests/Physics/AnimationSequencerTests.cs +++ b/tests/AcDream.Core.Tests/Physics/AnimationSequencerTests.cs @@ -456,13 +456,12 @@ public sealed class AnimationSequencerTests // CurrentMotion should record the original TurnLeft command. Assert.Equal(TurnLeft, seq.CurrentMotion); - // After FUN_005267E0 (multiply_framerate) swaps low↔high for negative speed: - // StartFrame = 3 (was high), EndFrame = 0 (was low) - // GetStartFramePosition for negative speed = (EndFrame + 1) - EPSILON = (0+1) - eps ≈ 0.99999. - // The cursor starts just below frame 1 and counts DOWN toward EndFrame(=0). + // Without swap: StartFrame=0, EndFrame=3 (original range preserved). + // GetStartFramePosition for negative speed = (EndFrame+1)-eps = (3+1)-eps ≈ 3.99999. + // The cursor starts near the HIGH end and counts DOWN toward StartFrame(=0). double pos = GetFramePosition(seq); - Assert.True(pos > 0.9 && pos < 1.0, - $"Expected framePosition near 0.99999 (reverse start near EndFrame+1) but got {pos}"); + Assert.True(pos > 3.9 && pos < 4.0, + $"Expected framePosition near 3.99999 (reverse start near EndFrame+1) but got {pos}"); } [Fact]