fix(anim): Phase L.1c spawn-time cycle fallback + diagnostics

User reports same "torso on the ground" symptom after 34d7f4d. Likely
cause: my fallback only covered the OnLiveMotionUpdated path, not the
spawn handler at the CreateObject boundary. If the spawn-time
SetCycle requests a (style, motion) pair the MotionTable lacks,
ClearCyclicTail wipes the cyclic tail at line 396 of
AnimationSequencer.cs and every body part snaps to its setup-default
offset until the first OnLiveMotionUpdated UM applies the path's
fallback there.

Apply the same fallback chain (requested → WalkForward → Ready →
no-op-don't-clear) at the spawn handler. Also add a one-line
diagnostic dump (under ACDREAM_DUMP_MOTION=1) on both code paths so
the next launch confirms whether the fallback is actually firing and
what (mtable, style, motion) tuples are missing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Erik 2026-04-29 10:39:43 +02:00
parent 34d7f4def2
commit e71ed73aa9

View file

@ -2111,7 +2111,48 @@ public sealed class GameWindow : IDisposable
{ {
seqMotion = AcDream.Core.Physics.MotionCommand.Ready; seqMotion = AcDream.Core.Physics.MotionCommand.Ready;
} }
sequencer.SetCycle(seqStyle, seqMotion);
// Phase L.1c followup (2026-04-28): apply the same
// missing-cycle fallback the OnLiveMotionUpdated path
// uses. Without this, a monster spawned in combat
// stance with the wire's seqMotion absent from its
// MotionTable hits ClearCyclicTail() with no
// replacement enqueue, every body part snaps to its
// setup-default offset, and the visual collapses to
// "torso on the ground" — visible to acdream
// observers when another client is in combat with a
// monster, until the first OnLiveMotionUpdated UM
// applies the same fallback there.
uint spawnCycle = seqMotion;
if (!sequencer.HasCycle(seqStyle, spawnCycle))
{
uint origCycle = spawnCycle;
// RunForward → WalkForward → Ready
if ((spawnCycle & 0xFFu) == 0x07
&& sequencer.HasCycle(seqStyle, 0x45000005u))
{
spawnCycle = 0x45000005u;
}
else if (sequencer.HasCycle(seqStyle, 0x41000003u))
{
spawnCycle = 0x41000003u;
}
else
{
spawnCycle = 0;
}
if (Environment.GetEnvironmentVariable("ACDREAM_DUMP_MOTION") == "1")
{
Console.WriteLine(
$"spawn cycle missing for guid=0x{spawn.Guid:X8} mtable=0x{mtableId:X8} " +
$"style=0x{seqStyle:X8} requested=0x{origCycle:X8} " +
$"→ fallback=0x{spawnCycle:X8}");
}
}
if (spawnCycle != 0)
sequencer.SetCycle(seqStyle, spawnCycle);
} }
} }
} }
@ -2603,6 +2644,7 @@ public sealed class GameWindow : IDisposable
uint cycleToPlay = animCycle; uint cycleToPlay = animCycle;
if (!ae.Sequencer.HasCycle(fullStyle, cycleToPlay)) if (!ae.Sequencer.HasCycle(fullStyle, cycleToPlay))
{ {
uint requested = cycleToPlay;
// RunForward (0x44000007) → WalkForward (0x45000005) // RunForward (0x44000007) → WalkForward (0x45000005)
if ((cycleToPlay & 0xFFu) == 0x07 if ((cycleToPlay & 0xFFu) == 0x07
&& ae.Sequencer.HasCycle(fullStyle, 0x45000005u)) && ae.Sequencer.HasCycle(fullStyle, 0x45000005u))
@ -2621,6 +2663,14 @@ public sealed class GameWindow : IDisposable
{ {
cycleToPlay = 0; cycleToPlay = 0;
} }
if (Environment.GetEnvironmentVariable("ACDREAM_DUMP_MOTION") == "1")
{
Console.WriteLine(
$"UM cycle missing for guid=0x{update.Guid:X8} " +
$"style=0x{fullStyle:X8} requested=0x{requested:X8} " +
$"→ fallback=0x{cycleToPlay:X8}");
}
} }
if (cycleToPlay != 0) if (cycleToPlay != 0)
ae.Sequencer.SetCycle(fullStyle, cycleToPlay, animSpeed); ae.Sequencer.SetCycle(fullStyle, cycleToPlay, animSpeed);