fix(motion): SetCycle forces _currNode onto first newly-enqueued node;
skip SubState commands in UM Commands list iteration
Two related fixes for the "remote-driven character animation cycle
does not visibly switch" bug:
1. AnimationSequencer.SetCycle now snapshots _queue.Last BEFORE
appending the new link/cycle nodes, then forces _currNode onto
preEnqueueTail.Next (= first newly-added node). Without this,
_currNode could stay pointing into stale non-cyclic head frames
left over from the previous cycle (typically a Walk_link or
Ready_link's tail), and the visible animation continues playing
those stale frames before the queue advances naturally to the
new cycle. Local player avoided the bug because
PlayerMovementController fires SetCycle in a tight per-input loop
that keeps the queue clean; remote player accumulates stale
link drains across many bundled UMs.
2. OnLiveMotionUpdated's UM Commands list iteration now skips
SubState class commands (high byte 0x40-0x4F like Ready
0x41000003). The router's SetCycle call for those would silently
override the animCycle picker's own SetCycle a few lines above
in the same UM packet — verified via SETCYCLE diag captures
showing run/walk being immediately re-cycled to Ready. Only
Action / Modifier / ChatEmote class commands (overlays that
interleave with the cycle) belong in this list iteration.
This fixed the landing-from-jump animation issue (user-confirmed:
"landing now works"). Walk↔run direct transitions still don't
visibly switch the leg cycle for observed retail-driven characters
even though ae.Sequencer.CurrentMotion correctly transitions
(per-tick SEQSTATE diag added — proves the sequencer's logical
state holds the right motion). Bug is somewhere downstream of
SetCycle's queue/state setup, possibly in Advance/BuildBlendedFrame
or in how seqFrames are applied to MeshRefs for remote entities.
Filed for next investigation.
Adds env-var-gated diagnostics (ACDREAM_REMOTE_VEL_DIAG=1):
CMD_LIST — what's in the UM's Commands list at receive time
HASCYCLE — whether the requested cycle exists in the dat
SEQSTATE — per-tick sequencer.CurrentMotion + CurrentSpeedMod
for the observed retail char (1Hz throttled)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
a2ae2aefcc
commit
357dcc0547
2 changed files with 84 additions and 2 deletions
|
|
@ -447,6 +447,21 @@ public sealed class AnimationSequencer
|
|||
// add_motion chain (MotionTable.cs L100-L101, L152-L153).
|
||||
ClearPhysics();
|
||||
|
||||
// Snapshot the queue tail BEFORE appending new motion data so we
|
||||
// can locate the first newly-added node afterward and force
|
||||
// _currNode onto it. Without this, _currNode can stay pointing
|
||||
// into stale non-cyclic head frames left over from the previous
|
||||
// cycle (typically a Walk_link or Ready_link's tail), and the
|
||||
// visible animation continues playing those stale frames before
|
||||
// the queue advances naturally to the new cycle. For remote
|
||||
// entities receiving many bundled UMs over time, this stale-head
|
||||
// build-up was the root cause of "transitions between cycles
|
||||
// don't visibly switch the leg pose" even though SetCycle's
|
||||
// CurrentMotion/CurrentSpeedMod were updated correctly. Local
|
||||
// player avoided the bug because PlayerMovementController fires
|
||||
// SetCycle in a tight per-input loop that keeps the queue clean.
|
||||
var preEnqueueTail = _queue.Last;
|
||||
|
||||
// Enqueue link frames (with adjusted speed for left→right remapping).
|
||||
if (linkData is { Anims.Count: > 0 })
|
||||
EnqueueMotionData(linkData, adjustedSpeed, isLooping: false);
|
||||
|
|
@ -478,9 +493,21 @@ public sealed class AnimationSequencer
|
|||
}
|
||||
}
|
||||
|
||||
// If we have no current anim, start at the beginning of the queue.
|
||||
if (_currNode == null)
|
||||
// Force _currNode onto the FIRST NEWLY-ENQUEUED node so the
|
||||
// visible animation switches to the new cycle/link immediately
|
||||
// instead of finishing whatever stale head frames were sitting
|
||||
// at the front of the queue. preEnqueueTail.Next is the first
|
||||
// newly-added node; if preEnqueueTail was null (queue was empty
|
||||
// before enqueue), the first new node is _queue.First.
|
||||
var firstNew = preEnqueueTail is null ? _queue.First : preEnqueueTail.Next;
|
||||
if (firstNew is not null)
|
||||
{
|
||||
_currNode = firstNew;
|
||||
_framePosition = _currNode.Value.GetStartFramePosition();
|
||||
}
|
||||
else if (_currNode == null)
|
||||
{
|
||||
// Defensive fallback: nothing newly added AND no current node.
|
||||
_currNode = _queue.First;
|
||||
_framePosition = _currNode?.Value.GetStartFramePosition() ?? 0.0;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue