InterpretedMotionState + the unified RawMotionState (LegacyRawMotionState
folded away) gain retail's action FIFO + ApplyMotion/RemoveMotion, ported
verbatim from the raw named decomp (0x0051e790/0x0051eb60 region, pc
293252-293703) including two genuine retail quirks pinned by tests and
independently re-verified against the raw text before commit:
- RawMotionState::ApplyMotion's RunForward (0x44000007) dead branch — a
cycle-class apply of literal RunForward writes NOTHING (retail encodes
run as WalkForward + HoldKey_Run on the raw state; same family as the
D6 apply_run_to_command early-return).
- InterpretedMotionState::RemoveMotion's exact-match-only TurnRight/
SideStepRight handling (left variants fall through to the style/
forward branches).
MovementParameters verbatim (A4 pin): 18 named flags per the absolute
mask table, ctor = 0x1EE0F expansion + distance_to_object 0.6 /
fail_distance FLT_MAX / speed 1 / walk_run_threshhold 15 / hold_key
Invalid — with the two ACE-divergence traps ported RETAIL-side
(can_charge FALSE where ACE defaults true; threshold 15.0 not 1.0).
MotionNode {ContextId, Motion, JumpErrorCode} (acclient.h:53293) — the
pending_motions node W2 consumes.
WeenieError renumbered to retail's numeric semantics (A10 table):
NotGrounded=0x24, GeneralMovementFailure 0x24→0x47, new 0x3f/0x40/0x41
combat-stance rejects + 0x42 chat-emote + 0x45 action-depth; the
airborne jump/action returns corrected 0x48→0x24 per the A10 sweep
(incl. jump_is_allowed's null-physics-obj 0x24-not-8 divergence from
ACE). Codes are local-only — wire untouched.
Outbound packer proof: RawMotionStatePacker unmodified; all 6
golden-byte RawMotionStatePackTests pass unmodified. TS-24 register row
updated (capability landed; outbound wiring still open).
Implemented by a dedicated agent against the W0-pinned spec; quirks,
scope, and suite independently verified. Full suite green: 3,531
(374+425+713+2015+4 pre-existing skips).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
244 lines
8.4 KiB
C#
244 lines
8.4 KiB
C#
using AcDream.Core.Physics;
|
|
using AcDream.Core.Physics.Motion;
|
|
using Xunit;
|
|
|
|
namespace AcDream.Core.Tests.Physics.Motion;
|
|
|
|
/// <summary>
|
|
/// R3-W1 — action FIFO discipline + <c>ApplyMotion</c>/<c>RemoveMotion</c>
|
|
/// field effects on <see cref="RawMotionState"/> (closes J2). Oracle:
|
|
/// docs/research/named-retail/acclient_2013_pseudo_c.txt — verbatim bodies
|
|
/// quoted in RawMotionState.cs doc comments:
|
|
/// <c>RawMotionState::AddAction</c> (0x0051e840), <c>RemoveAction</c>
|
|
/// (0x0051e8a0), <c>ApplyMotion</c> (0x0051eb60), <c>RemoveMotion</c>
|
|
/// (0x0051e6e0).
|
|
/// </summary>
|
|
public sealed class RawMotionStateActionFifoTests
|
|
{
|
|
// ── AddAction / RemoveAction / GetNumActions FIFO discipline ──────────
|
|
|
|
[Fact]
|
|
public void AddAction_AppendsInOrder()
|
|
{
|
|
var raw = new RawMotionState();
|
|
raw.AddAction(0x1000004Bu, 1.0f, 1, autonomous: false);
|
|
raw.AddAction(0x10000050u, 1.5f, 2, autonomous: true);
|
|
|
|
Assert.Equal(2, raw.Actions.Count);
|
|
Assert.Equal((ushort)0x004Bu, raw.Actions[0].Command); // widened to ushort on wire, verified below
|
|
Assert.Equal((ushort)0x0050u, raw.Actions[1].Command);
|
|
}
|
|
|
|
[Fact]
|
|
public void RemoveAction_PopsHeadFirst_FifoOrder()
|
|
{
|
|
var raw = new RawMotionState();
|
|
raw.AddAction(0x1000004Bu, 1.0f, 1, autonomous: false);
|
|
raw.AddAction(0x10000050u, 1.5f, 2, autonomous: true);
|
|
|
|
uint first = raw.RemoveAction();
|
|
uint second = raw.RemoveAction();
|
|
|
|
Assert.Equal(0x004Bu, first); // head popped first (FIFO)
|
|
Assert.Equal(0x0050u, second);
|
|
Assert.Empty(raw.Actions);
|
|
}
|
|
|
|
[Fact]
|
|
public void RemoveAction_Empty_ReturnsZero()
|
|
{
|
|
var raw = new RawMotionState();
|
|
Assert.Equal(0u, raw.RemoveAction());
|
|
}
|
|
|
|
[Fact]
|
|
public void AddAction_StoresSpeedStampAutonomous()
|
|
{
|
|
var raw = new RawMotionState();
|
|
raw.AddAction(0x1000004Bu, speed: 2.5f, actionStamp: 0x7FFFu, autonomous: true);
|
|
|
|
var a = raw.Actions[0];
|
|
Assert.Equal(2.5f, a.Speed);
|
|
Assert.Equal((ushort)0x7FFFu, a.Stamp);
|
|
Assert.True(a.Autonomous);
|
|
}
|
|
|
|
// ── ApplyMotion field effects (0x0051eb60) ─────────────────────────────
|
|
|
|
[Fact]
|
|
public void ApplyMotion_TurnRight_SetsTurnCommandAndSpeed_HonorsSetHoldKeyBit()
|
|
{
|
|
var raw = new RawMotionState();
|
|
var p = new MovementParameters { Speed = 1.5f, SetHoldKey = true };
|
|
|
|
raw.ApplyMotion(0x6500000du, p); // TurnRight
|
|
|
|
Assert.Equal(0x6500000du, raw.TurnCommand);
|
|
Assert.Equal(1.5f, raw.TurnSpeed);
|
|
Assert.Equal(HoldKey.Invalid, raw.TurnHoldKey); // SetHoldKey bit set -> Invalid
|
|
}
|
|
|
|
[Fact]
|
|
public void ApplyMotion_TurnRight_SetHoldKeyClear_UsesHoldKeyToApply()
|
|
{
|
|
var raw = new RawMotionState();
|
|
var p = new MovementParameters { Speed = 1.5f, SetHoldKey = false, HoldKeyToApply = HoldKey.Run };
|
|
|
|
raw.ApplyMotion(0x6500000du, p);
|
|
|
|
Assert.Equal(HoldKey.Run, raw.TurnHoldKey);
|
|
}
|
|
|
|
[Fact]
|
|
public void ApplyMotion_SideStepRight_SetsSidestepCommandAndSpeed()
|
|
{
|
|
var raw = new RawMotionState();
|
|
var p = new MovementParameters { Speed = 1.248f, SetHoldKey = true };
|
|
|
|
raw.ApplyMotion(0x6500000fu, p); // SideStepRight
|
|
|
|
Assert.Equal(0x6500000fu, raw.SidestepCommand);
|
|
Assert.Equal(1.248f, raw.SidestepSpeed);
|
|
Assert.Equal(HoldKey.Invalid, raw.SidestepHoldKey);
|
|
}
|
|
|
|
[Fact]
|
|
public void ApplyMotion_ForwardClassMotion_SetsForwardCommandAndSpeed()
|
|
{
|
|
// WalkForward = 0x45000005 has bit 0x40000000 set (forward-class)
|
|
// and is NOT 0x44000007 (RunForward) -> the write branch fires.
|
|
var raw = new RawMotionState();
|
|
var p = new MovementParameters { Speed = 1.0f, SetHoldKey = true };
|
|
|
|
raw.ApplyMotion(0x45000005u, p);
|
|
|
|
Assert.Equal(0x45000005u, raw.ForwardCommand);
|
|
Assert.Equal(1.0f, raw.ForwardSpeed);
|
|
Assert.Equal(HoldKey.Invalid, raw.ForwardHoldKey);
|
|
}
|
|
|
|
[Fact]
|
|
public void ApplyMotion_RunForwardExactId_ForwardClassButExcluded_NoWrite()
|
|
{
|
|
// Verbatim retail quirk (0x0051eb60): arg2 == 0x44000007 (RunForward)
|
|
// with the forward-class bit (0x40000000) set falls through BOTH
|
|
// inner branches — no field write occurs. Port verbatim, not fixed.
|
|
var raw = new RawMotionState();
|
|
var before = raw.ForwardCommand;
|
|
var p = new MovementParameters { Speed = 3.0f };
|
|
|
|
raw.ApplyMotion(0x44000007u, p);
|
|
|
|
Assert.Equal(before, raw.ForwardCommand); // untouched
|
|
Assert.Equal(1.0f, raw.ForwardSpeed); // untouched (still ctor default)
|
|
}
|
|
|
|
[Fact]
|
|
public void ApplyMotion_StyleClassMotion_SetsCurrentStyleAndResetsForwardToReady()
|
|
{
|
|
// High bit set (>= 0x80000000) and current_style differs -> style branch.
|
|
var raw = new RawMotionState { ForwardCommand = 0x45000005u };
|
|
var p = new MovementParameters();
|
|
|
|
raw.ApplyMotion(0x80000042u, p);
|
|
|
|
Assert.Equal(0x41000003u, raw.ForwardCommand); // reset to Ready
|
|
Assert.Equal(0x80000042u, raw.CurrentStyle);
|
|
}
|
|
|
|
[Fact]
|
|
public void ApplyMotion_StyleClassMotion_SameAsCurrentStyle_NoOp()
|
|
{
|
|
var raw = new RawMotionState { CurrentStyle = 0x80000042u, ForwardCommand = 0x45000005u };
|
|
var p = new MovementParameters();
|
|
|
|
raw.ApplyMotion(0x80000042u, p);
|
|
|
|
// current_style already equals arg2 -> the style branch's condition
|
|
// (current_style != arg2) is false, so forward_command is untouched.
|
|
Assert.Equal(0x45000005u, raw.ForwardCommand);
|
|
Assert.Equal(0x80000042u, raw.CurrentStyle);
|
|
}
|
|
|
|
[Fact]
|
|
public void ApplyMotion_ActionClassMotion_AddsAction()
|
|
{
|
|
// Outside turn/sidestep range, bit 0x40000000 clear, arg2 >= 0
|
|
// (not style-class), bit 0x10000000 set -> AddAction.
|
|
var raw = new RawMotionState();
|
|
var p = new MovementParameters { Speed = 1.0f, ActionStamp = 42u, Autonomous = true };
|
|
|
|
raw.ApplyMotion(0x1000004Bu, p); // Jumpup action id
|
|
|
|
Assert.Single(raw.Actions);
|
|
var a = raw.Actions[0];
|
|
Assert.Equal((ushort)0x004Bu, a.Command);
|
|
Assert.Equal(1.0f, a.Speed);
|
|
Assert.Equal((ushort)42u, a.Stamp);
|
|
Assert.True(a.Autonomous);
|
|
}
|
|
|
|
// ── RemoveMotion field effects (0x0051e6e0) ────────────────────────────
|
|
|
|
[Fact]
|
|
public void RemoveMotion_TurnRange_ClearsTurnCommand()
|
|
{
|
|
var raw = new RawMotionState { TurnCommand = 0x6500000du };
|
|
raw.RemoveMotion(0x6500000du);
|
|
Assert.Equal(0u, raw.TurnCommand);
|
|
}
|
|
|
|
[Fact]
|
|
public void RemoveMotion_TurnLeftRange_ClearsTurnCommand()
|
|
{
|
|
var raw = new RawMotionState { TurnCommand = 0x6500000eu };
|
|
raw.RemoveMotion(0x6500000eu);
|
|
Assert.Equal(0u, raw.TurnCommand);
|
|
}
|
|
|
|
[Fact]
|
|
public void RemoveMotion_SidestepRange_ClearsSidestepCommand()
|
|
{
|
|
var raw = new RawMotionState { SidestepCommand = 0x6500000fu };
|
|
raw.RemoveMotion(0x6500000fu);
|
|
Assert.Equal(0u, raw.SidestepCommand);
|
|
}
|
|
|
|
[Fact]
|
|
public void RemoveMotion_ForwardClassMotion_MatchingCommand_ResetsToReady()
|
|
{
|
|
var raw = new RawMotionState { ForwardCommand = 0x45000005u, ForwardSpeed = 3.0f };
|
|
raw.RemoveMotion(0x45000005u);
|
|
|
|
Assert.Equal(0x41000003u, raw.ForwardCommand);
|
|
Assert.Equal(1f, raw.ForwardSpeed);
|
|
}
|
|
|
|
[Fact]
|
|
public void RemoveMotion_ForwardClassMotion_NonMatchingCommand_NoOp()
|
|
{
|
|
var raw = new RawMotionState { ForwardCommand = 0x45000005u, ForwardSpeed = 3.0f };
|
|
raw.RemoveMotion(0x44000007u); // different forward-class id
|
|
|
|
Assert.Equal(0x45000005u, raw.ForwardCommand); // untouched
|
|
Assert.Equal(3.0f, raw.ForwardSpeed);
|
|
}
|
|
|
|
[Fact]
|
|
public void RemoveMotion_StyleClassMotion_MatchingCurrentStyle_ResetsToNonCombat()
|
|
{
|
|
var raw = new RawMotionState { CurrentStyle = 0x80000042u };
|
|
raw.RemoveMotion(0x80000042u);
|
|
|
|
Assert.Equal(0x8000003du, raw.CurrentStyle); // reset to NonCombat
|
|
}
|
|
|
|
[Fact]
|
|
public void RemoveMotion_StyleClassMotion_NonMatchingCurrentStyle_NoOp()
|
|
{
|
|
var raw = new RawMotionState { CurrentStyle = 0x80000042u };
|
|
raw.RemoveMotion(0x80000099u); // different style id
|
|
|
|
Assert.Equal(0x80000042u, raw.CurrentStyle); // untouched
|
|
}
|
|
}
|