diff --git a/src/AcDream.App/Rendering/GameWindow.cs b/src/AcDream.App/Rendering/GameWindow.cs index 100ea54..4ea7f0a 100644 --- a/src/AcDream.App/Rendering/GameWindow.cs +++ b/src/AcDream.App/Rendering/GameWindow.cs @@ -2111,7 +2111,48 @@ public sealed class GameWindow : IDisposable { 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; if (!ae.Sequencer.HasCycle(fullStyle, cycleToPlay)) { + uint requested = cycleToPlay; // RunForward (0x44000007) → WalkForward (0x45000005) if ((cycleToPlay & 0xFFu) == 0x07 && ae.Sequencer.HasCycle(fullStyle, 0x45000005u)) @@ -2621,6 +2663,14 @@ public sealed class GameWindow : IDisposable { 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) ae.Sequencer.SetCycle(fullStyle, cycleToPlay, animSpeed);