using AcDream.Core.Physics.Motion;
namespace AcDream.Core.Physics;
///
/// Retail hold-key enum (acclient.h enum HoldKey, line 3394).
///
public enum HoldKey : uint
{
Invalid = 0x0,
None = 0x1,
Run = 0x2,
}
///
/// One entry in — a discrete motion
/// event (jump, attack, etc.) layered on top of the continuous
/// forward/sidestep/turn axes.
///
///
/// Retail's in-memory action node (RawMotionState::AddAction,
/// 0x0051e840, and InterpretedMotionState::AddAction, 0x0051e9e0 —
/// both a 0x14-byte LListData-derived node) carries FOUR fields:
/// motion (+4), speed (+8), action_stamp (+0xc),
/// autonomous (+0x10). is the in-memory-only
/// field the R3-W1 action FIFO needs for ApplyMotion's velocity
/// bookkeeping; it is NOT part of the wire encoding.
///
///
///
/// Retail packs each action ON THE WIRE as u16 command then
/// u16 ((stamp & 0x7FFF) | (autonomous ? 0x8000 : 0))
/// (RawMotionState::Pack, 0x0051ed10, decomp lines ~293998-294010)
/// — // are
/// exactly the packed triple; reads only
/// those three.
///
///
public readonly record struct RawMotionAction(
ushort Command,
ushort Stamp,
bool Autonomous,
float Speed = 1f);
///
/// R3-W1 — verbatim, full-field port of retail's RawMotionState
/// (acclient.h RawMotionState::PackBitfield, line 46474; ctor
/// RawMotionState::RawMotionState, 0x0051e7f0, decomp lines
/// 293344-293361; packer at RawMotionState::Pack, 0x0051ed10).
///
///
/// R3-W1 fold (closes J2): this type now ALSO serves as
/// MotionInterpreter.RawState — the former LegacyRawMotionState
/// (a flat 7-field struct with no action FIFO) is deleted. One raw-state
/// type end to end: the physics-side motion interpreter mutates it via
/// //
/// /, and the SAME
/// instance's is read by
/// when building the outbound wire packet. Previously the packer's action
/// list was always empty (register TS-24) because nothing populated it —
/// starting R3-W2 (the add_to_queue/MotionDone port),
/// CMotionInterp populates this list locally exactly like retail
/// does, so the packed bytes will start reflecting real queued actions.
/// W1 itself does not change what gets packed (no caller invokes
/// yet) — only adds the capability + tests.
///
///
///
/// PURE DATA: no PacketWriter / GL / Net dependency. Lives in
/// AcDream.Core.Physics so the motion interpreter, which is
/// also Core.Physics, can populate it without taking a Core.Net dependency
/// (Code Structure Rule #2).
///
///
public sealed class RawMotionState
{
/// Retail current_holdkey (ctor default HoldKey_None).
public HoldKey CurrentHoldKey { get; set; } = HoldKey.None;
/// Retail current_style (ctor default 0x8000003D, NonCombat).
public uint CurrentStyle { get; set; } = 0x8000003Du;
/// Retail forward_command (ctor default 0x41000003, Ready).
public uint ForwardCommand { get; set; } = 0x41000003u;
/// Retail forward_holdkey (ctor default HoldKey_Invalid).
public HoldKey ForwardHoldKey { get; set; } = HoldKey.Invalid;
/// Retail forward_speed (ctor default 1.0).
public float ForwardSpeed { get; set; } = 1.0f;
/// Retail sidestep_command (ctor default 0).
public uint SidestepCommand { get; set; }
/// Retail sidestep_holdkey (ctor default HoldKey_Invalid).
public HoldKey SidestepHoldKey { get; set; } = HoldKey.Invalid;
/// Retail sidestep_speed (ctor default 1.0).
public float SidestepSpeed { get; set; } = 1.0f;
/// Retail turn_command (ctor default 0).
public uint TurnCommand { get; set; }
/// Retail turn_holdkey (ctor default HoldKey_Invalid).
public HoldKey TurnHoldKey { get; set; } = HoldKey.Invalid;
/// Retail turn_speed (ctor default 1.0).
public float TurnSpeed { get; set; } = 1.0f;
private readonly List _actions = new();
///
/// Discrete action FIFO (retail actions, an LListData
/// tail-append queue). Retail packs num_actions (count, not the
/// values) into bits 11-15 of the flags dword, then emits each action's
/// command+stamp pair unconditionally after the continuous-axis fields
/// ().
///
///
/// Settable via object-initializer for test fixtures / the wire-packer
/// call sites that build a one-shot snapshot; assigning replaces the
/// whole FIFO. / are
/// the retail-faithful mutators for live FIFO discipline.
///
///
public IReadOnlyList Actions
{
get => _actions;
set
{
_actions.Clear();
_actions.AddRange(value);
}
}
///
/// Retail defaults — a field bit is set in the packed flags dword ONLY
/// when the live value differs from these (RawMotionState::Pack,
/// 0x0051ed10). NEVER mutate this shared instance.
///
public static readonly RawMotionState Default = new();
///
/// RawMotionState::AddAction (0x0051e840, decomp 293365-293392):
/// unconditional tail-append of
/// {motion, speed, action_stamp, autonomous}.
///
/// Retail arg2 — the action motion id.
/// Retail arg3.
/// Retail arg4.
/// Retail arg5 (nonzero = autonomous).
public void AddAction(uint motion, float speed, uint actionStamp, bool autonomous)
{
_actions.Add(new RawMotionAction(
Command: (ushort)motion,
Stamp: (ushort)actionStamp,
Autonomous: autonomous,
Speed: speed));
}
///
/// RawMotionState::RemoveAction (0x0051e8a0, decomp 293396-293414):
/// pop the FIFO head unconditionally, returning its motion field
/// (0 when empty — retail returns *(head+4), i.e. the stored
/// arg2/Command, widened; the C# port returns the widened
/// as a uint).
///
public uint RemoveAction()
{
if (_actions.Count == 0)
return 0;
var head = _actions[0];
_actions.RemoveAt(0);
return head.Command;
}
///
/// RawMotionState::ApplyMotion (0x0051eb60, decomp 293630-293703).
/// Verbatim dispatch, quoted from the raw named decomp:
///
/// if ((arg2 - 0x6500000d) > 3) { // outside [0x6500000d, 0x65000010]
/// if ((arg2 & 0x40000000) == 0) { // NOT forward-class
/// if (arg2 >= 0) { // NOT style-class (high bit clear)
/// if ((arg2 & 0x10000000) != 0)
/// AddAction(arg2, params.speed, params.action_stamp, (params.bitfield>>0xc)&1);
/// } else if (current_style != arg2) {
/// forward_command = 0x41000003; current_style = arg2;
/// }
/// } else if (arg2 != 0x44000007) { // forward-class, NOT RunForward
/// forward_command = arg2;
/// if (params.bitfield byte1 & 8 != 0) { forward_holdkey = Invalid; forward_speed = params.speed; }
/// else { forward_holdkey = params.hold_key_to_apply; forward_speed = params.speed; }
/// }
/// // arg2 == 0x44000007 (RunForward) with the forward-class bit set: falls through, no write.
/// return;
/// }
/// switch (arg2) {
/// case 0x6500000d: case 0x6500000e: // TurnRight/TurnLeft
/// turn_command = arg2;
/// if (SetHoldKey bit) { turn_holdkey = Invalid; turn_speed = params.speed; }
/// else { turn_holdkey = params.hold_key_to_apply; turn_speed = params.speed; }
/// return;
/// case 0x6500000f: case 0x65000010: // SideStepRight/SideStepLeft
/// sidestep_command = arg2;
/// if (SetHoldKey bit) { sidestep_holdkey = Invalid; sidestep_speed = params.speed; }
/// else { sidestep_holdkey = params.hold_key_to_apply; sidestep_speed = params.speed; }
/// return;
/// }
///
/// The arg2 == 0x44000007 (RunForward) no-write fallthrough on the
/// forward-class branch is a genuine retail quirk (RunForward bit
/// 0x40000000 IS set, but the `else if (arg2 != 0x44000007)` guard
/// excludes it) — ported verbatim, not "fixed".
///
/// Retail arg2 — the ORIGINAL (pre-adjustment)
/// motion id per the DoMotion/StopMotion mirror-discipline callers.
/// Retail arg3 (MovementParameters const*).
public void ApplyMotion(uint motion, MovementParameters p)
{
if (motion - 0x6500000du > 3u)
{
if ((motion & 0x40000000u) == 0)
{
if (motion < 0x80000000u) // arg2 >= 0 as signed int32
{
if ((motion & 0x10000000u) != 0)
AddAction(motion, p.Speed, p.ActionStamp, p.Autonomous);
}
else if (CurrentStyle != motion)
{
ForwardCommand = 0x41000003u;
CurrentStyle = motion;
}
}
else if (motion != 0x44000007u)
{
ForwardCommand = motion;
if (p.SetHoldKey)
{
ForwardHoldKey = HoldKey.Invalid;
ForwardSpeed = p.Speed;
}
else
{
ForwardHoldKey = p.HoldKeyToApply;
ForwardSpeed = p.Speed;
}
}
return;
}
switch (motion)
{
case 0x6500000du: // TurnRight
case 0x6500000eu: // TurnLeft
TurnCommand = motion;
if (p.SetHoldKey)
{
TurnHoldKey = HoldKey.Invalid;
TurnSpeed = p.Speed;
}
else
{
TurnHoldKey = p.HoldKeyToApply;
TurnSpeed = p.Speed;
}
return;
case 0x6500000fu: // SideStepRight
case 0x65000010u: // SideStepLeft
SidestepCommand = motion;
if (p.SetHoldKey)
{
SidestepHoldKey = HoldKey.Invalid;
SidestepSpeed = p.Speed;
}
else
{
SidestepHoldKey = p.HoldKeyToApply;
SidestepSpeed = p.Speed;
}
return;
}
}
///
/// RawMotionState::RemoveMotion (0x0051e6e0, decomp 293252-293288):
///
/// if ((arg2 - 0x6500000d) > 3) {
/// if ((arg2 & 0x40000000) == 0) {
/// if (arg2 < 0 && arg2 == current_style) current_style = 0x8000003d;
/// } else if (arg2 == forward_command) {
/// forward_command = 0x41000003; forward_speed = 1f;
/// }
/// return;
/// }
/// switch (arg2) {
/// case 0x6500000d: case 0x6500000e: turn_command = 0; return;
/// case 0x6500000f: case 0x65000010: sidestep_command = 0; return;
/// }
///
///
/// Retail arg2 — the ORIGINAL motion id.
public void RemoveMotion(uint motion)
{
if (motion - 0x6500000du > 3u)
{
if ((motion & 0x40000000u) == 0)
{
if (motion >= 0x80000000u && motion == CurrentStyle)
CurrentStyle = 0x8000003du;
}
else if (motion == ForwardCommand)
{
ForwardCommand = 0x41000003u;
ForwardSpeed = 1f;
}
return;
}
switch (motion)
{
case 0x6500000du:
case 0x6500000eu:
TurnCommand = 0;
return;
case 0x6500000fu:
case 0x65000010u:
SidestepCommand = 0;
return;
}
}
}