refactor(anim): sequence-wide velocity/omega matching retail Sequence
Before: CurrentVelocity was a pass-through of the current AnimNode's Velocity. So during a stance transition, while the link animation played (with no velocity of its own), CurrentVelocity returned (0,0,0) and remote dead-reckoning briefly stopped advancing the entity. Visible as a hitch at every idle → walk or walk → run transition. Retail's model (ACE Sequence.cs L16-L17, L127-L130): Velocity and Omega are Sequence-wide fields updated by MotionTable.add_motion's Sequence.SetVelocity call (MotionTable.cs L358-L370). Every time a new MotionData is appended, the sequence velocity is REPLACED by that data's velocity × speedMod. In SetCycle's rebuild path the order is: 1. clear_physics → zero 2. add_motion(link) → velocity = link's (typically 0) 3. add_motion(cycle) → velocity = cycle's (the real walk/run velocity) After step 3, Sequence.Velocity is the CYCLE's velocity even though CurrAnim is the link node. So dead-reckoning reads the cycle's velocity from frame zero of the transition — no stutter. This commit: - Converts AnimationSequencer.CurrentVelocity / CurrentOmega from per-node computed properties to sequence-wide private-set properties. - Adds ClearPhysics() helper (mirrors Sequence.clear_physics). - EnqueueMotionData now updates the sequence velocity/omega (matching add_motion's SetVelocity semantics). Only replaces when the MotionData's HasVelocity/HasOmega flags are set — zero-HasVelocity modifiers don't zero the running cycle, matching retail. - SetCycle's rebuild path calls ClearPhysics before the new add_motion chain (matches MotionTable.cs L100-L101, L152-L153). - MultiplyCyclicFramerate scales the sequence-wide velocity/omega instead of per-node fields — algebraically equivalent to retail's subtract_motion(old) + combine_motion(new) pair in change_cycle_speed. New test: CurrentVelocity_PersistsThroughLinkTransition — verifies that after SetCycle enqueues [link][cycle], CurrentVelocity is the cycle's velocity even during the link frames. Catches the old bug directly. All 659 tests pass (was 658). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
6e589d3b89
commit
24974cfbb9
2 changed files with 120 additions and 19 deletions
|
|
@ -229,21 +229,26 @@ public sealed class AnimationSequencer
|
|||
public float CurrentSpeedMod { get; private set; } = 1f;
|
||||
|
||||
/// <summary>
|
||||
/// World-space per-second velocity from the currently active
|
||||
/// <see cref="MotionData"/> (Sequence.Velocity in retail). Zero when no
|
||||
/// motion data carries a velocity. Scaled by <c>speedMod</c> at enqueue
|
||||
/// time.
|
||||
/// Sequence-wide velocity mirror of ACE's <c>Sequence.Velocity</c> field.
|
||||
/// Updated each time a MotionData is appended or combined — reflects the
|
||||
/// MOST RECENT MotionData's velocity × speedMod, matching
|
||||
/// <c>Sequence.SetVelocity</c> semantics (ACE Sequence.cs L127-L130,
|
||||
/// <c>MotionTable.add_motion</c> L358-L370).
|
||||
///
|
||||
/// <para>
|
||||
/// Crucially this is **not** per-node: while a link animation plays, the
|
||||
/// surfaced velocity is still the cycle's velocity (the cycle was added
|
||||
/// last, so SetVelocity's latest call wins). Remote entity dead-reckoning
|
||||
/// reads this to integrate position without gapping during stance
|
||||
/// transitions.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public Vector3 CurrentVelocity =>
|
||||
_currNode?.Value.Velocity ?? Vector3.Zero;
|
||||
public Vector3 CurrentVelocity { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Radians-per-second omega (axis-angle integration rate) from the
|
||||
/// currently active <see cref="MotionData"/>. Scaled by <c>speedMod</c>
|
||||
/// at enqueue time.
|
||||
/// Sequence-wide omega, matching <see cref="CurrentVelocity"/>'s semantics.
|
||||
/// </summary>
|
||||
public Vector3 CurrentOmega =>
|
||||
_currNode?.Value.Omega ?? Vector3.Zero;
|
||||
public Vector3 CurrentOmega { get; private set; }
|
||||
|
||||
// Diagnostics
|
||||
public int QueueCount => _queue.Count;
|
||||
|
|
@ -375,6 +380,11 @@ public sealed class AnimationSequencer
|
|||
// been played yet (ACE behaviour: non-cyclic anims drain naturally).
|
||||
ClearCyclicTail();
|
||||
|
||||
// Clear sequence-wide physics before the rebuild. Retail's
|
||||
// GetObjectSequence calls sequence.clear_physics() before each
|
||||
// add_motion chain (MotionTable.cs L100-L101, L152-L153).
|
||||
ClearPhysics();
|
||||
|
||||
// Enqueue link frames (with adjusted speed for left→right remapping).
|
||||
if (linkData is { Anims.Count: > 0 })
|
||||
EnqueueMotionData(linkData, adjustedSpeed, isLooping: false);
|
||||
|
|
@ -445,15 +455,15 @@ public sealed class AnimationSequencer
|
|||
for (var node = _firstCyclic; node != null; node = node.Next)
|
||||
{
|
||||
node.Value.MultiplyFramerate((double)factor);
|
||||
// Velocity/Omega carried on the node scale with the framerate, so
|
||||
// the physics velocity surfaced by CurrentVelocity matches the
|
||||
// animation playback. (ACE does the same: add_motion sets both
|
||||
// to the scaled value and multiply_cyclic_animation_framerate is
|
||||
// preceded by subtract_motion/combine_motion in change_cycle_speed
|
||||
// to keep them aligned — MotionTable.cs:372-379.)
|
||||
node.Value.Velocity *= factor;
|
||||
node.Value.Omega *= factor;
|
||||
}
|
||||
|
||||
// Sequence-wide velocity/omega scale too. Retail's flow is
|
||||
// subtract_motion(oldSpeed) + combine_motion(newSpeed) in
|
||||
// MotionTable.change_cycle_speed (MotionTable.cs L372-L379), which
|
||||
// algebraically equals scaling by newSpeed/oldSpeed — exactly
|
||||
// what the factor represents here.
|
||||
CurrentVelocity *= factor;
|
||||
CurrentOmega *= factor;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -738,6 +748,8 @@ public sealed class AnimationSequencer
|
|||
CurrentStyle = 0;
|
||||
CurrentMotion = 0;
|
||||
CurrentSpeedMod = 1f;
|
||||
CurrentVelocity = Vector3.Zero;
|
||||
CurrentOmega = Vector3.Zero;
|
||||
}
|
||||
|
||||
// ── Private helpers ──────────────────────────────────────────────────────
|
||||
|
|
@ -829,6 +841,17 @@ public sealed class AnimationSequencer
|
|||
omega);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reset the sequence's Velocity + Omega (retail Sequence.clear_physics,
|
||||
/// ACE Sequence.cs L256-L260). Called before a style-transition rebuild
|
||||
/// in SetCycle so we don't inherit velocity from the previous cycle.
|
||||
/// </summary>
|
||||
private void ClearPhysics()
|
||||
{
|
||||
CurrentVelocity = Vector3.Zero;
|
||||
CurrentOmega = Vector3.Zero;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Append all AnimData entries from <paramref name="motionData"/> to the
|
||||
/// queue. Each AnimData becomes one AnimNode. Velocity / Omega from the
|
||||
|
|
@ -842,6 +865,23 @@ public sealed class AnimationSequencer
|
|||
Vector3 omg = motionData.Flags.HasFlag(MotionDataFlags.HasOmega)
|
||||
? motionData.Omega * speedMod : Vector3.Zero;
|
||||
|
||||
// Sequence-wide velocity/omega update, matching ACE's
|
||||
// MotionTable.add_motion (MotionTable.cs L358-L370): SetVelocity
|
||||
// REPLACES the previous sequence velocity. When SetCycle enqueues
|
||||
// link then cycle, the final CurrentVelocity is the cycle's — which
|
||||
// is what dead-reckoning needs to read from the first frame of the
|
||||
// link transition (the cycle velocity is already "queued up" even
|
||||
// while a zero-velocity link plays visually).
|
||||
//
|
||||
// Only replace if HasVelocity (else we'd zero out a running cycle
|
||||
// when a transient HasVelocity=0 modifier enqueues). Matches
|
||||
// retail's conditional behavior: MotionData without HasVelocity
|
||||
// doesn't touch the sequence velocity.
|
||||
if (motionData.Flags.HasFlag(MotionDataFlags.HasVelocity))
|
||||
CurrentVelocity = vel;
|
||||
if (motionData.Flags.HasFlag(MotionDataFlags.HasOmega))
|
||||
CurrentOmega = omg;
|
||||
|
||||
for (int i = 0; i < motionData.Anims.Count; i++)
|
||||
{
|
||||
bool nodeCycling = isLooping && (i == motionData.Anims.Count - 1);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue