fix(anim): physics velocity now sourced from MotionData — option B / r03 §1.3
The decompiled get_state_velocity (FUN_00528960) literally computes
`RunAnimSpeed * ForwardSpeed` — a 4.0 × runRate world velocity. That
matches retail only when the character's MotionTable happens to bake
MotionData.Velocity.Y = 4.0 on RunForward (true for Humanoid, not
necessarily for other creatures or swapped weapon-style cycles).
When MotionData.Velocity ≠ RunAnimSpeed, the body's world velocity
drifts away from the animation's baked-in root-motion velocity, and
you see the classic "legs cycle too slowly for how fast the body is
sliding" visual bug. User reports ~30% discrepancy ("running animation
is too slow"), consistent with Humanoid RunForward's actual dat
Velocity being ~3.0 rather than the 4.0 constant.
The fix per r03 §1.3: physics body velocity = MotionData.Velocity ×
speedMod. That's exactly what AnimationSequencer.CurrentVelocity
already exposes. Route it into MotionInterpreter via an opt-in
Func<Vector3> accessor. When wired, get_state_velocity uses the
sequencer's cycle velocity as the primary forward-axis drive; when
unwired (tests, physics bodies without a sequencer), falls back to
the decompiled constant path — byte-compatible with retail on the
shapes where it actually matters.
The RunAnimSpeed × rate max-speed clamp at the bottom of
FUN_00528960 stays intact — Option B only replaces the *drive*, not
the clamp. 20 m/s phantom MotionData can't teleport the player.
Wiring: GameWindow attaches `playerAE.Sequencer.CurrentVelocity` to
`_playerController` on Tab-player-mode entry. The sequencer is always
built before the player enters chase mode, so timing is safe.
Sidestep continues to use SidestepAnimSpeed — the sequencer only
tracks the current forward cycle, so strafe is a separate axis.
6 new MotionInterpreterTests verify: accessor overrides constant path,
zero Y falls back to constant (link transitions), clamp still applies,
Ready state doesn't leak accessor value, sidestep axis is untouched.
All 717 tests green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
5bd976e0c6
commit
795d9c8a88
4 changed files with 267 additions and 17 deletions
|
|
@ -164,6 +164,33 @@ public sealed class PlayerMovementController
|
|||
_weenie.SetSkills(runSkill, jumpSkill);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wire the player's AnimationSequencer current cycle velocity into
|
||||
/// <see cref="MotionInterpreter.GetCycleVelocity"/>. When attached,
|
||||
/// <c>get_state_velocity</c> uses <c>MotionData.Velocity * speedMod</c>
|
||||
/// as the primary forward-axis drive, keeping the body's world velocity
|
||||
/// locked to the animation's baked-in root-motion velocity.
|
||||
///
|
||||
/// <para>
|
||||
/// Without this accessor, the decompiled constant path
|
||||
/// (<c>RunAnimSpeed * ForwardSpeed</c>) is used — matches retail only
|
||||
/// when the character's MotionTable happens to bake Velocity=4.0 on
|
||||
/// RunForward, which is true for Humanoid but not for arbitrary
|
||||
/// creatures. See <see cref="MotionInterpreter.GetCycleVelocity"/>
|
||||
/// for the full rationale.
|
||||
/// </para>
|
||||
///
|
||||
/// <para>
|
||||
/// Called once from <c>GameWindow.CreateAnimatedEntity</c> after the
|
||||
/// player's <c>AnimatedEntity.Sequencer</c> is constructed.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public void AttachCycleVelocityAccessor(Func<Vector3> accessor)
|
||||
{
|
||||
if (accessor is null) throw new ArgumentNullException(nameof(accessor));
|
||||
_motion.GetCycleVelocity = accessor;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Apply a server-echoed run rate (ForwardSpeed from UpdateMotion) to the
|
||||
/// player's MotionInterpreter. The server broadcasts the real RunRate
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue