diff --git a/src/AcDream.App/Rendering/GameWindow.cs b/src/AcDream.App/Rendering/GameWindow.cs index 223c8d8..9c23303 100644 --- a/src/AcDream.App/Rendering/GameWindow.cs +++ b/src/AcDream.App/Rendering/GameWindow.cs @@ -99,6 +99,7 @@ public sealed class GameWindow : IDisposable private bool _playerMode; private uint _playerServerGuid; private uint? _playerCurrentAnimCommand; + private uint? _playerMotionTableId; // server-sent MotionTable override for the player's character // Accumulated mouse X delta for player turning; written in mouse-move // callback, consumed + reset in OnUpdate each frame. private float _playerMouseDeltaX; @@ -776,6 +777,13 @@ public sealed class GameWindow : IDisposable // UpdateMotion / UpdatePosition events can reseat this entity by guid. _entitiesByServerGuid[spawn.Guid] = entity; + // Phase B.2: capture the server-sent MotionTableId for our own + // character so UpdatePlayerAnimation can pass it to GetIdleCycle. + // The Setup's DefaultMotionTable is often 0 for human characters; + // the real table comes from PhysicsDescriptionFlag.MTable. + if (spawn.Guid == _playerServerGuid && spawn.MotionTableId is not null) + _playerMotionTableId = spawn.MotionTableId; + // Phase 6.4: register for per-frame playback if we resolved a real // cycle with a non-zero framerate and at least two frames in the // cycle (single-frame poses are static and don't need ticking). @@ -1512,8 +1520,13 @@ public sealed class GameWindow : IDisposable if (_entitiesByServerGuid.TryGetValue(_playerServerGuid, out var pe)) { pe.Position = result.Position; + // AC character models face +Y in their default orientation. + // Our yaw convention has cos(yaw)=+X at yaw=0, so yaw=0 + // means facing +X. Offset by -PI/2 so the model faces the + // actual walk direction (at yaw=0, model rotation = -PI/2 + // = facing +X instead of the model's default +Y). pe.Rotation = System.Numerics.Quaternion.CreateFromAxisAngle( - System.Numerics.Vector3.UnitZ, _playerController.Yaw); + System.Numerics.Vector3.UnitZ, _playerController.Yaw - MathF.PI / 2f); } // Update chase camera. @@ -1801,7 +1814,13 @@ public sealed class GameWindow : IDisposable ushort cmdOverride = (ushort)(animCommand & 0xFFFFu); var cycle = AcDream.Core.Meshing.MotionResolver.GetIdleCycle( - ae.Setup, _dats, commandOverride: cmdOverride); + ae.Setup, _dats, + motionTableIdOverride: _playerMotionTableId, + commandOverride: cmdOverride); + + Console.WriteLine($"[PLAYER-ANIM] cmd=0x{animCommand:X8} cmdOverride=0x{cmdOverride:X4} " + + $"cycle={(cycle is null ? "NULL" : $"fr={cycle.Framerate:F1} low={cycle.LowFrame} high={cycle.HighFrame}")} " + + $"setup=0x{pe.SourceGfxObjOrSetupId:X8} mtable=0x{(uint)ae.Setup.DefaultMotionTable:X8}"); if (cycle is null || cycle.Framerate == 0f || cycle.HighFrame <= cycle.LowFrame) return;