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; } } }