diff --git a/src/AcDream.App/Rendering/GameWindow.cs b/src/AcDream.App/Rendering/GameWindow.cs
index 3c2454f..12828de 100644
--- a/src/AcDream.App/Rendering/GameWindow.cs
+++ b/src/AcDream.App/Rendering/GameWindow.cs
@@ -6805,6 +6805,23 @@ public sealed class GameWindow : IDisposable
System.Console.WriteLine(
$"[SEQSTATE] guid={serverGuid:X8} CurrentMotion=0x{ae.Sequencer.CurrentMotion:X8} "
+ $"CurrentSpeedMod={ae.Sequencer.CurrentSpeedMod:F3}");
+
+ // #39 fix-3 evidence (2026-05-06): CURRNODE proves
+ // whether _currNode is actually on the cycle (anim
+ // ref hash matches FirstCyclic) or stuck somewhere
+ // else. SCFULL captures _currNode==_firstCyclic only
+ // at SetCycle return; this captures it per render
+ // tick so we can see if something resets it later.
+ var d = ae.Sequencer.CurrentNodeDiag;
+ int firstHash = ae.Sequencer.FirstCyclicAnimRefHash;
+ System.Console.WriteLine(
+ $"[CURRNODE] guid={serverGuid:X8} "
+ + $"animRef=0x{d.AnimRefHash:X8} firstCyclicAnimRef=0x{firstHash:X8} "
+ + $"isOnCyclic={d.AnimRefHash == firstHash && firstHash != 0} "
+ + $"isLooping={d.IsLooping} fr={d.Framerate:F2} "
+ + $"frame=[{d.StartFrame}..{d.EndFrame}] "
+ + $"pos={d.FramePosition:F2} qCount={d.QueueCount}");
+
rmDiag.LastSeqStateLogTime = nowSec;
}
}
diff --git a/src/AcDream.Core/Physics/AnimationSequencer.cs b/src/AcDream.Core/Physics/AnimationSequencer.cs
index 56e03e4..4bae508 100644
--- a/src/AcDream.Core/Physics/AnimationSequencer.cs
+++ b/src/AcDream.Core/Physics/AnimationSequencer.cs
@@ -254,6 +254,39 @@ public sealed class AnimationSequencer
public int QueueCount => _queue.Count;
public bool HasCurrentNode => _currNode != null;
+ ///
+ /// Diagnostic snapshot of _currNode's identity + frame state, for
+ /// the per-tick CURRNODE log line in GameWindow.TickAnimations.
+ /// Lets the caller see whether the actual node being read by Advance /
+ /// BuildBlendedFrame is what SetCycle was supposed to leave it on.
+ /// AnimRefHash uses object-identity hashing on the Animation reference
+ /// so different Walk vs Run anim resources can be distinguished even
+ /// without exposing the full Animation type.
+ ///
+ public (int AnimRefHash, bool IsLooping, double Framerate, int StartFrame, int EndFrame, double FramePosition, int QueueCount) CurrentNodeDiag
+ {
+ get
+ {
+ if (_currNode is null)
+ return (0, false, 0.0, 0, 0, 0.0, _queue.Count);
+ var n = _currNode.Value;
+ int hash = System.Runtime.CompilerServices.RuntimeHelpers.GetHashCode(n.Anim);
+ return (hash, n.IsLooping, n.Framerate, n.StartFrame, n.EndFrame, _framePosition, _queue.Count);
+ }
+ }
+
+ ///
+ /// Diagnostic: the AnimRefHash for the FIRST cyclic node in the queue
+ /// (i.e., what SetCycle is trying to land us on for a locomotion cycle).
+ /// Compare against 's AnimRefHash to see
+ /// whether _currNode is actually pointing at the new cycle or
+ /// something stale.
+ ///
+ public int FirstCyclicAnimRefHash =>
+ _firstCyclic is null
+ ? 0
+ : System.Runtime.CompilerServices.RuntimeHelpers.GetHashCode(_firstCyclic.Value.Anim);
+
// ── Private state ────────────────────────────────────────────────────────
private readonly Setup _setup;