fix(anim): left-side motion fallback to right-side animation

AC MotionTables typically define cycles for TurnRight and SideStepRight
but not TurnLeft/SideStepLeft — the left variants reuse the right-side
animation played in reverse. When the left-side command doesn't resolve
to a cycle, fall back to the right-side equivalent so the player isn't
stuck in idle pose.

Fallback map:
  TurnLeft (0x000E) → TurnRight (0x000D)
  SideStepLeft (0x0010) → SideStepRight (0x000F)
  WalkBackward (0x0006) → WalkForward (0x0005)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Erik 2026-04-13 00:49:47 +02:00
parent e77dc1a0e8
commit 0868849b3d

View file

@ -2054,6 +2054,28 @@ public sealed class GameWindow : IDisposable
stanceOverride: NonCombatStance,
commandOverride: cmdOverride);
// AC reuses right-side animations for left-side motions (played in
// reverse). If the left-side command has no cycle, fall back to the
// right-side equivalent so the player isn't stuck in idle.
if (cycle is null || cycle.Framerate == 0f || cycle.HighFrame <= cycle.LowFrame)
{
ushort fallback = cmdOverride switch
{
0x000E => 0x000D, // TurnLeft → TurnRight
0x0010 => 0x000F, // SideStepLeft → SideStepRight
0x0006 => 0x0005, // WalkBackward → WalkForward
_ => (ushort)0,
};
if (fallback != 0)
{
cycle = AcDream.Core.Meshing.MotionResolver.GetIdleCycle(
ae.Setup, _dats,
motionTableIdOverride: _playerMotionTableId,
stanceOverride: NonCombatStance,
commandOverride: fallback);
}
}
if (cycle is null || cycle.Framerate == 0f || cycle.HighFrame <= cycle.LowFrame) return;
// If the entity has a sequencer, use SetCycle for transition-link-aware