fix(animation): close #61 + smooth stop from backward/sidestep-left/turn-left
Two related AnimationSequencer fixes for visible animation glitches at motion-cycle boundaries. 1. Link-tail blend hold (closes #61). BuildBlendedFrame was wrapping nextIdx unconditionally to rangeLo at the high-frame boundary — correct for looping cyclic nodes (idle/run/walk loops), wrong for one-shot links and action overlays. During the ~30 ms fractional tail before the sequencer transitions to the next queue node, the blend mixed frame[end] with frame[0], producing a one-frame flash through the anim's starting pose. Symptoms: door swing-open flap (frame 0 = closed pose) and player run-stop twitch (frame 0 = mid-stride). Fix: gate the wrap on curr.IsLooping; non-looping nodes hold the boundary frame until AdvanceToNextAnimation fires. 2. Stop-anim direction fallback. Stopping from WalkBackward / SideStepLeft / TurnLeft hit a null linkData from GetLink (the dat authors a single forward/right stop link and reuses it for both directions). SetCycle then enqueued only the Ready cycle, snapping straight to idle with no leg-settle blend. Fix: when the primary GetLink lookup is null, retry with the substate's low byte remapped to its forward/right peer (0x06→0x05, 0x10→0x0F, 0x0E→0x0D). Both fixes are pinned by new regression tests in AnimationSequencerTests that fail against the prior code (Y=5.02 for the link tail wrap → frame 0 blend; Y=0 for the backward stop snapping to Ready cycle). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
6c4f6be1b4
commit
9f069e14c9
2 changed files with 149 additions and 2 deletions
|
|
@ -479,6 +479,29 @@ public sealed class AnimationSequencer
|
|||
? null
|
||||
: GetLink(style, CurrentMotion, CurrentSpeedMod, adjustedMotion, adjustedSpeed);
|
||||
|
||||
// Stop-anim fallback: dat-authored leg-settle / turn-stop links are
|
||||
// keyed under the FORWARD/RIGHT variant only. Stopping from
|
||||
// WalkBackward / SideStepLeft / TurnLeft hits a null linkData and
|
||||
// would visibly snap to Ready. The settle anim is direction-agnostic
|
||||
// (legs come to standing the same way regardless of which way you
|
||||
// were walking), so retry GetLink with the substate's low-byte
|
||||
// remapped to its forward/right peer.
|
||||
if (linkData is null && !skipTransitionLink && CurrentMotion != 0)
|
||||
{
|
||||
uint substateLow = CurrentMotion & 0xFFu;
|
||||
uint adjustedSubstate = substateLow switch
|
||||
{
|
||||
0x06u => (CurrentMotion & 0xFFFFFF00u) | 0x05u, // WalkBackward → WalkForward
|
||||
0x10u => (CurrentMotion & 0xFFFFFF00u) | 0x0Fu, // SideStepLeft → SideStepRight
|
||||
0x0Eu => (CurrentMotion & 0xFFFFFF00u) | 0x0Du, // TurnLeft → TurnRight
|
||||
_ => CurrentMotion,
|
||||
};
|
||||
if (adjustedSubstate != CurrentMotion)
|
||||
{
|
||||
linkData = GetLink(style, adjustedSubstate, CurrentSpeedMod, adjustedMotion, adjustedSpeed);
|
||||
}
|
||||
}
|
||||
|
||||
// Resolve target cycle using the ADJUSTED motion (TurnRight not TurnLeft).
|
||||
int cycleKey = (int)(((style & 0xFFFFu) << 16) | (adjustedMotion & 0xFFFFFFu));
|
||||
_mtable.Cycles.TryGetValue(cycleKey, out var cycleData);
|
||||
|
|
@ -1419,18 +1442,24 @@ public sealed class AnimationSequencer
|
|||
frameIdx = Math.Clamp(frameIdx, rangeLo, rangeHi);
|
||||
|
||||
// Next frame for interpolation: step in the playback direction.
|
||||
// Wrap to opposite end ONLY for looping cyclic nodes. For one-shot
|
||||
// nodes (link transitions, action overlays), hold the boundary
|
||||
// frame instead — otherwise the fractional tail of the anim
|
||||
// blends frame[end] with frame[0], producing a brief flash through
|
||||
// the anim's starting pose at the link→cycle boundary (issue #61:
|
||||
// door swing-open flap; run-stop twitch).
|
||||
int nextIdx;
|
||||
if (curr.Framerate >= 0.0)
|
||||
{
|
||||
nextIdx = frameIdx + 1;
|
||||
if (nextIdx > rangeHi || nextIdx >= numPartFrames)
|
||||
nextIdx = rangeLo; // wrap forward
|
||||
nextIdx = curr.IsLooping ? rangeLo : frameIdx;
|
||||
}
|
||||
else
|
||||
{
|
||||
nextIdx = frameIdx - 1;
|
||||
if (nextIdx < rangeLo)
|
||||
nextIdx = rangeHi; // wrap backward
|
||||
nextIdx = curr.IsLooping ? rangeHi : frameIdx;
|
||||
}
|
||||
|
||||
// Fractional blend weight (always in [0, 1]).
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue