feat(movement): wire server RunRate into player MotionInterpreter

Parse ForwardSpeed from UpdateMotion (0xF74C) InterpretedMotionState.
Feed server-echoed RunRate into the player's MotionInterpreter so
get_state_velocity produces the correct speed. Previously hardcoded
at 1.0 (4.0 m/s), now matches character's Run skill.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-04-13 23:11:49 +02:00
parent 4988ea02c0
commit c7fa1d36fb
5 changed files with 71 additions and 7 deletions

View file

@ -109,7 +109,7 @@ public static class CreateObject
/// Nullified Statue of a Drudge, which is rendered in the wrong pose
/// if you only consult the MotionTable's default style.
/// </summary>
public readonly record struct ServerMotionState(ushort Stance, ushort? ForwardCommand);
public readonly record struct ServerMotionState(ushort Stance, ushort? ForwardCommand, float? ForwardSpeed = null);
/// <summary>
/// Server instruction to replace the surface texture at
@ -479,6 +479,7 @@ public static class CreateObject
p += 2;
ushort? forwardCommand = null;
float? forwardSpeed = null;
// 0 = Invalid is the only union variant we care about for static
// entities. Walking/turning entities use the other variants but
@ -513,13 +514,21 @@ public static class CreateObject
forwardCommand = BinaryPrimitives.ReadUInt16LittleEndian(mv.Slice(p));
p += 2;
}
// Remaining fields (SideStep, Turn, speeds, commands list,
// align) are deliberately not parsed — we already have what
// the resolver needs and the outer length tells the caller
// where MovementData ends.
// SidestepCommand (0x4) — skip
if ((flags & 0x4u) != 0) { if (mv.Length - p < 2) goto done; p += 2; }
// TurnCommand (0x8) — skip
if ((flags & 0x8u) != 0) { if (mv.Length - p < 2) goto done; p += 2; }
// ForwardSpeed (0x10)
if ((flags & 0x10u) != 0)
{
if (mv.Length - p < 4) goto done;
forwardSpeed = BinaryPrimitives.ReadSingleLittleEndian(mv.Slice(p));
p += 4;
}
done:;
}
return new ServerMotionState(currentStyle, forwardCommand);
return new ServerMotionState(currentStyle, forwardCommand, forwardSpeed);
}
catch
{

View file

@ -102,6 +102,7 @@ public static class UpdateMotion
pos += 2;
ushort? forwardCommand = null;
float? forwardSpeed = null;
if (movementType == 0)
{
@ -130,9 +131,21 @@ public static class UpdateMotion
forwardCommand = BinaryPrimitives.ReadUInt16LittleEndian(body.Slice(pos));
pos += 2;
}
// SidestepCommand (0x4) — skip
if ((flags & 0x4u) != 0) { if (body.Length - pos < 2) goto done; pos += 2; }
// TurnCommand (0x8) — skip
if ((flags & 0x8u) != 0) { if (body.Length - pos < 2) goto done; pos += 2; }
// ForwardSpeed (0x10)
if ((flags & 0x10u) != 0)
{
if (body.Length - pos < 4) goto done;
forwardSpeed = BinaryPrimitives.ReadSingleLittleEndian(body.Slice(pos));
pos += 4;
}
done:;
}
return new Parsed(guid, new CreateObject.ServerMotionState(currentStyle, forwardCommand));
return new Parsed(guid, new CreateObject.ServerMotionState(currentStyle, forwardCommand, forwardSpeed));
}
catch
{