From bb026b79914896c67bd94b6a84231a71d5592a14 Mon Sep 17 00:00:00 2001 From: Erik Date: Wed, 6 May 2026 08:17:56 +0200 Subject: [PATCH] =?UTF-8?q?diag(motion):=20#39=20=E2=80=94=20per-tick=20[C?= =?UTF-8?q?URRNODE]=20for=20sequencer=20node=20identity?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Visual-verify of fix #2 (commit 863d96b) showed [SCFULL] correctly reports currNodeIsCyclic=True after each direct Walk↔Run SetCycle (the link is removed and _currNode is set to _firstCyclic). User report still: - Animation continues running visually after Shift toggle to Walk - Body slows ("speed decreases"), causing rubber-banding - Adding a turn motion in that state makes the cycle finally transition to walking So either: - _currNode is reset to a stale node BETWEEN SetCycle and Advance - _currNode is correctly on the new cycle but its AnimRef is wrong (e.g., the same Animation as the previous cycle, dat-side issue) - BuildBlendedFrame reads from somewhere other than _currNode Adds CurrentNodeDiag + FirstCyclicAnimRefHash properties on AnimationSequencer that expose the active node's Animation identity-hash, IsLooping, Framerate, frame range, and FramePosition. TickAnimations logs them on every SEQSTATE tick (1 Hz throttle, gated on ACDREAM_REMOTE_VEL_DIAG=1). The [CURRNODE] line with animRef vs firstCyclicAnimRef proves whether _currNode is actually on the new cycle's anim or has drifted to something else. Compared across SetCycle SCFULL log lines + the following CURRNODE ticks, we can see the exact moment the cycle diverges from what SetCycle set. No code-behavior changes. Pure read-only instrumentation. Per Phase 4.5 of systematic-debugging — STOP attempting fixes; gather evidence first. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/AcDream.App/Rendering/GameWindow.cs | 17 ++++++++++ .../Physics/AnimationSequencer.cs | 33 +++++++++++++++++++ 2 files changed, 50 insertions(+) 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;