fix(anim): instant-engage Falling cycle on jump start (skip transition link)
User reported transition from running to jumping looked
slow -- the character stood still for ~100 ms at the start
of the jump before the legs folded into Falling.
Root cause: AnimationSequencer.SetCycle resolves a
transition link (e.g. RunForward -> Falling) from the
motion table and enqueues those non-looping link frames
BEFORE the Falling cycle. The link is the "stop running,
prepare to fall" anim -- a few frames of standing-style
pose. While it drained, the character looked frozen.
Fix: SetCycle gains a skipTransitionLink parameter. When
true, the GetLink call is bypassed AND the entire queue is
cleared (so any in-flight non-cyclic frames from a
previous transition don't continue draining). Only the
target cycle gets enqueued, cursor goes straight to its
start.
Both call sites pass true for Falling:
- OnLiveVectorUpdated (remote-jump VectorUpdate handler)
- UpdatePlayerAnimation (local airborne path) when
animCommand == Falling. Other transitions
(Walk -> Run, Run -> Ready, etc.) keep the link --
smooth transitions stay smooth, only the jump start
is hard-cut.
Tests stay 1222 green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
ff504e9ec1
commit
b86e77e837
2 changed files with 48 additions and 12 deletions
|
|
@ -2427,12 +2427,14 @@ public sealed class GameWindow : IDisposable
|
||||||
// K-fix10 (2026-04-26): force the Falling animation cycle on
|
// K-fix10 (2026-04-26): force the Falling animation cycle on
|
||||||
// the remote so the legs match the arc. Mirrors the local
|
// the remote so the legs match the arc. Mirrors the local
|
||||||
// player's UpdatePlayerAnimation path which sets
|
// player's UpdatePlayerAnimation path which sets
|
||||||
// animCommand = Falling whenever !IsOnGround. Without this,
|
// animCommand = Falling whenever !IsOnGround.
|
||||||
// the remote's existing locomotion cycle (RunForward,
|
//
|
||||||
// Ready, etc.) keeps playing through the jump — body goes
|
// K-fix18 (2026-04-26): pass skipTransitionLink:true so the
|
||||||
// up but legs stay running. Style is the sequencer's
|
// RunForward→Falling transition frames don't play first.
|
||||||
// current style (NonCombat 0x8000003D for humanoids); cycle
|
// Without that flag the remote stood still for ~100 ms at
|
||||||
// pace = 1.0 (Falling animation has its own baked rate).
|
// the start of the jump while the link drained, then
|
||||||
|
// folded into Falling. Skipping the link makes the pose
|
||||||
|
// engage immediately on jump start.
|
||||||
if (_entitiesByServerGuid.TryGetValue(update.Guid, out var ent)
|
if (_entitiesByServerGuid.TryGetValue(update.Guid, out var ent)
|
||||||
&& _animatedEntities.TryGetValue(ent.Id, out var ae)
|
&& _animatedEntities.TryGetValue(ent.Id, out var ae)
|
||||||
&& ae.Sequencer is not null)
|
&& ae.Sequencer is not null)
|
||||||
|
|
@ -2440,7 +2442,8 @@ public sealed class GameWindow : IDisposable
|
||||||
uint style = ae.Sequencer.CurrentStyle != 0
|
uint style = ae.Sequencer.CurrentStyle != 0
|
||||||
? ae.Sequencer.CurrentStyle
|
? ae.Sequencer.CurrentStyle
|
||||||
: 0x8000003Du; // NonCombat default
|
: 0x8000003Du; // NonCombat default
|
||||||
ae.Sequencer.SetCycle(style, AcDream.Core.Physics.MotionCommand.Falling, 1.0f);
|
ae.Sequencer.SetCycle(style, AcDream.Core.Physics.MotionCommand.Falling, 1.0f,
|
||||||
|
skipTransitionLink: true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -5067,7 +5070,16 @@ public sealed class GameWindow : IDisposable
|
||||||
{
|
{
|
||||||
animScale = s;
|
animScale = s;
|
||||||
}
|
}
|
||||||
ae.Sequencer.SetCycle(fullStyle, animCommand, animSpeed * animScale);
|
// K-fix18 (2026-04-26): when transitioning into Falling
|
||||||
|
// (jump start), skip the link so the legs engage Falling
|
||||||
|
// immediately. Without this the local player visibly
|
||||||
|
// stood still for ~100 ms at the start of every jump
|
||||||
|
// while the RunForward→Falling transition link drained.
|
||||||
|
// For everything else (Walk → Run, Run → Ready, etc.) we
|
||||||
|
// keep the link so transitions stay smooth.
|
||||||
|
bool skipLink = animCommand == AcDream.Core.Physics.MotionCommand.Falling;
|
||||||
|
ae.Sequencer.SetCycle(fullStyle, animCommand, animSpeed * animScale,
|
||||||
|
skipTransitionLink: skipLink);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Legacy path: update the manual slerp fields (for entities without sequencer)
|
// Legacy path: update the manual slerp fields (for entities without sequencer)
|
||||||
|
|
|
||||||
|
|
@ -322,7 +322,15 @@ public sealed class AnimationSequencer
|
||||||
/// <param name="style">MotionCommand style / stance (e.g. NonCombat 0x003D0000).</param>
|
/// <param name="style">MotionCommand style / stance (e.g. NonCombat 0x003D0000).</param>
|
||||||
/// <param name="motion">Target motion command (e.g. WalkForward 0x45000005).</param>
|
/// <param name="motion">Target motion command (e.g. WalkForward 0x45000005).</param>
|
||||||
/// <param name="speedMod">Speed multiplier applied to framerates (1.0 = normal).</param>
|
/// <param name="speedMod">Speed multiplier applied to framerates (1.0 = normal).</param>
|
||||||
public void SetCycle(uint style, uint motion, float speedMod = 1f)
|
/// <param name="skipTransitionLink">K-fix18 (2026-04-26): when true, do
|
||||||
|
/// NOT enqueue the transition-link frames between the previous and
|
||||||
|
/// new cycle. Used when the caller wants the new cycle to engage
|
||||||
|
/// instantly — e.g. swapping to Falling on a jump start, where the
|
||||||
|
/// RunForward→Falling link is a short "stop running" pose that
|
||||||
|
/// makes the jump look delayed (legs stand still for ~100 ms while
|
||||||
|
/// the link drains, then fold into Falling). Defaults to false to
|
||||||
|
/// preserve normal smooth transitions for everything else.</param>
|
||||||
|
public void SetCycle(uint style, uint motion, float speedMod = 1f, bool skipTransitionLink = false)
|
||||||
{
|
{
|
||||||
// ── adjust_motion: remap left→right / backward→forward variants ───
|
// ── adjust_motion: remap left→right / backward→forward variants ───
|
||||||
// ACE MotionInterp.cs:394-428. The MotionTable never stores TurnLeft,
|
// ACE MotionInterp.cs:394-428. The MotionTable never stores TurnLeft,
|
||||||
|
|
@ -372,9 +380,12 @@ public sealed class AnimationSequencer
|
||||||
// CurrentSpeedMod defaults to 1.0 (positive) on a fresh sequencer,
|
// CurrentSpeedMod defaults to 1.0 (positive) on a fresh sequencer,
|
||||||
// so a Ready → WalkBackward transition correctly enters GetLink's
|
// so a Ready → WalkBackward transition correctly enters GetLink's
|
||||||
// negative-speed (reversed-key) branch.
|
// negative-speed (reversed-key) branch.
|
||||||
MotionData? linkData = CurrentMotion != 0
|
// K-fix18: when the caller asked to skip the transition link
|
||||||
? GetLink(style, CurrentMotion, CurrentSpeedMod, adjustedMotion, adjustedSpeed)
|
// (instant-engage cases like Falling on jump start), force
|
||||||
: null;
|
// linkData to null so only the cycle gets enqueued.
|
||||||
|
MotionData? linkData = (skipTransitionLink || CurrentMotion == 0)
|
||||||
|
? null
|
||||||
|
: GetLink(style, CurrentMotion, CurrentSpeedMod, adjustedMotion, adjustedSpeed);
|
||||||
|
|
||||||
// Resolve target cycle using the ADJUSTED motion (TurnRight not TurnLeft).
|
// Resolve target cycle using the ADJUSTED motion (TurnRight not TurnLeft).
|
||||||
int cycleKey = (int)(((style & 0xFFFFu) << 16) | (adjustedMotion & 0xFFFFFFu));
|
int cycleKey = (int)(((style & 0xFFFFu) << 16) | (adjustedMotion & 0xFFFFFFu));
|
||||||
|
|
@ -384,6 +395,19 @@ public sealed class AnimationSequencer
|
||||||
// been played yet (ACE behaviour: non-cyclic anims drain naturally).
|
// been played yet (ACE behaviour: non-cyclic anims drain naturally).
|
||||||
ClearCyclicTail();
|
ClearCyclicTail();
|
||||||
|
|
||||||
|
// K-fix18: when the caller asked for instant-engage, ALSO drain
|
||||||
|
// any in-flight non-cyclic transition frames from the previous
|
||||||
|
// cycle. Without this, the old RunForward → ??? link would
|
||||||
|
// continue draining for ~100 ms before the new Falling cycle
|
||||||
|
// starts, defeating the "skip the link" intent.
|
||||||
|
if (skipTransitionLink)
|
||||||
|
{
|
||||||
|
_queue.Clear();
|
||||||
|
_currNode = null;
|
||||||
|
_firstCyclic = null;
|
||||||
|
_framePosition = 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
// Clear sequence-wide physics before the rebuild. Retail's
|
// Clear sequence-wide physics before the rebuild. Retail's
|
||||||
// GetObjectSequence calls sequence.clear_physics() before each
|
// GetObjectSequence calls sequence.clear_physics() before each
|
||||||
// add_motion chain (MotionTable.cs L100-L101, L152-L153).
|
// add_motion chain (MotionTable.cs L100-L101, L152-L153).
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue