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>
112 lines
4.2 KiB
C#
112 lines
4.2 KiB
C#
using AcDream.Core.Physics;
|
|
using AcDream.Core.Physics.Motion;
|
|
using Xunit;
|
|
|
|
namespace AcDream.Core.Tests.Physics.Motion;
|
|
|
|
/// <summary>
|
|
/// R3-W1 — <c>MovementParameters</c> ctor-default pins. Oracle:
|
|
/// docs/research/2026-07-02-r3-motioninterp/W0-pins.md §A4 (bitfield 0x1EE0F
|
|
/// expansion) + r3-motioninterp-decomp.md §0 (scalar ctor, raw 300510-300534,
|
|
/// 0x00524380). Every flag asserted individually against the retail
|
|
/// 0x1EE0F default so a future bit-numbering slip fails loudly, plus the two
|
|
/// ACE-divergence traps (CanCharge, WalkRunThreshhold) get dedicated tests.
|
|
/// </summary>
|
|
public sealed class MovementParametersTests
|
|
{
|
|
[Fact]
|
|
public void Ctor_SetFlags_MatchRetail0x1EE0FExpansion()
|
|
{
|
|
var p = new MovementParameters();
|
|
|
|
// Bits SET in 0x1EE0F: 0x1,0x2,0x4,0x8,0x200,0x400,0x800,0x2000,0x4000,0x8000,0x10000
|
|
Assert.True(p.CanWalk); // 0x1
|
|
Assert.True(p.CanRun); // 0x2
|
|
Assert.True(p.CanSidestep); // 0x4
|
|
Assert.True(p.CanWalkBackwards); // 0x8
|
|
Assert.True(p.MoveTowards); // 0x200
|
|
Assert.True(p.UseSpheres); // 0x400
|
|
Assert.True(p.SetHoldKey); // 0x800
|
|
Assert.True(p.ModifyRawState); // 0x2000
|
|
Assert.True(p.ModifyInterpretedState); // 0x4000
|
|
Assert.True(p.CancelMoveTo); // 0x8000
|
|
Assert.True(p.StopCompletelyFlag); // 0x10000
|
|
}
|
|
|
|
[Fact]
|
|
public void Ctor_ClearFlags_MatchRetail0x1EE0FExpansion()
|
|
{
|
|
var p = new MovementParameters();
|
|
|
|
// Bits CLEAR in 0x1EE0F: 0x10,0x20,0x40,0x80,0x100,0x1000,0x20000
|
|
Assert.False(p.CanCharge); // 0x10 — ACE-divergence trap
|
|
Assert.False(p.FailWalk); // 0x20
|
|
Assert.False(p.UseFinalHeading); // 0x40
|
|
Assert.False(p.Sticky); // 0x80
|
|
Assert.False(p.MoveAway); // 0x100
|
|
Assert.False(p.Autonomous); // 0x1000
|
|
Assert.False(p.DisableJumpDuringLink); // 0x20000
|
|
}
|
|
|
|
[Fact]
|
|
public void Ctor_Bitfield_ReconstitutesExactly0x1EE0F()
|
|
{
|
|
var p = new MovementParameters();
|
|
|
|
uint bitfield = 0;
|
|
if (p.CanWalk) bitfield |= 0x1;
|
|
if (p.CanRun) bitfield |= 0x2;
|
|
if (p.CanSidestep) bitfield |= 0x4;
|
|
if (p.CanWalkBackwards) bitfield |= 0x8;
|
|
if (p.CanCharge) bitfield |= 0x10;
|
|
if (p.FailWalk) bitfield |= 0x20;
|
|
if (p.UseFinalHeading) bitfield |= 0x40;
|
|
if (p.Sticky) bitfield |= 0x80;
|
|
if (p.MoveAway) bitfield |= 0x100;
|
|
if (p.MoveTowards) bitfield |= 0x200;
|
|
if (p.UseSpheres) bitfield |= 0x400;
|
|
if (p.SetHoldKey) bitfield |= 0x800;
|
|
if (p.Autonomous) bitfield |= 0x1000;
|
|
if (p.ModifyRawState) bitfield |= 0x2000;
|
|
if (p.ModifyInterpretedState) bitfield |= 0x4000;
|
|
if (p.CancelMoveTo) bitfield |= 0x8000;
|
|
if (p.StopCompletelyFlag) bitfield |= 0x10000;
|
|
if (p.DisableJumpDuringLink) bitfield |= 0x20000;
|
|
|
|
Assert.Equal(0x1EE0Fu, bitfield);
|
|
}
|
|
|
|
[Fact]
|
|
public void Ctor_ScalarDefaults_MatchRetail()
|
|
{
|
|
var p = new MovementParameters();
|
|
|
|
Assert.Equal(0f, p.MinDistance);
|
|
Assert.Equal(0.6f, p.DistanceToObject);
|
|
Assert.Equal(float.MaxValue, p.FailDistance);
|
|
Assert.Equal(0f, p.DesiredHeading);
|
|
Assert.Equal(1f, p.Speed);
|
|
// ACE-divergence trap: retail is 15.0, NOT ACE's 1.0.
|
|
Assert.Equal(15f, p.WalkRunThreshhold);
|
|
Assert.Equal(0u, p.ContextId);
|
|
Assert.Equal(HoldKey.Invalid, p.HoldKeyToApply);
|
|
Assert.Equal(0u, p.ActionStamp);
|
|
}
|
|
|
|
[Fact]
|
|
public void Ctor_CanCharge_DefaultsFalse_NotAceTrue()
|
|
{
|
|
// Dedicated regression test for the single highest-risk ACE trap:
|
|
// ACE's MovementParameters.cs:58 sets CanCharge = true at construction.
|
|
// Retail's 0x1EE0F has bit 0x10 CLEAR.
|
|
var p = new MovementParameters();
|
|
Assert.False(p.CanCharge);
|
|
}
|
|
|
|
[Fact]
|
|
public void Ctor_WalkRunThreshhold_Is15_NotAce1()
|
|
{
|
|
var p = new MovementParameters();
|
|
Assert.Equal(15f, p.WalkRunThreshhold);
|
|
}
|
|
}
|