MovementParameters gains the verbatim selection family: GetCommand 0x0052aa00 (the walk-vs-run cascade INCLUDING the CanCharge 0x10 fast-path ACE dropped - retail's default can_charge=false + the fast-path present, the A13+A15 canceling-pair trap avoided; inclusive threshold edge per the raw), TowardsAndAway, GetDesiredHeading per the live Ghidra decompile (fwd-towards 0 / fwd-away 180 / back-towards 180 / back-away 0), FromWire/FromWireTurnTo (UnPackNet semantics, all 18 A4 masks round-tripped). New MoveToMath: HeadingDiff per the live Ghidra decompile of 0x00528fb0 (the 360-diff NOT-TurnRight mirror + F_EPSILON 0.000199999995f - the BN "arg unused" artifact corrected), HeadingGreater (the visible TurnRight idiom), PositionHeading/Get/SetHeading reusing the codebase's single yaw-heading convention (P5), CylinderDistance (PDB arg order; planar-minus-radii shape documented as the interpretation - the raw's x87 body is garbled; seam noted). MovementType gains Invalid + retail 6/7/8/9; MovementStruct widened (ObjectId/TopLevelId/Pos/Radius/Height/Params, additive); WeenieError += 0x0B/0x36/0x37/0x38/0x3D with retail-meaning doc comments. 148 new conformance tests. Full suite: 3,860 passed. Implemented by a dedicated agent against the V0-pinned spec; scope + suite independently verified. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
108 lines
3.1 KiB
C#
108 lines
3.1 KiB
C#
using System.Numerics;
|
|
using AcDream.Core.Physics;
|
|
using AcDream.Core.Physics.Motion;
|
|
using Xunit;
|
|
|
|
namespace AcDream.Core.Tests.Physics.Motion;
|
|
|
|
/// <summary>
|
|
/// R4-V1 — <see cref="MovementStruct"/> widening per r4-moveto-decomp.md §0
|
|
/// (acclient.h:38069, struct #4067):
|
|
/// <code>
|
|
/// struct __cppobj MovementStruct
|
|
/// {
|
|
/// MovementTypes::Type type;
|
|
/// unsigned int motion; // types 1-4 only
|
|
/// unsigned int object_id; // types 6, 8
|
|
/// unsigned int top_level_id; // types 6, 8
|
|
/// Position pos; // type 7
|
|
/// float radius; // type 6
|
|
/// float height; // type 6
|
|
/// MovementParameters *params; // types 1-4, 6-9
|
|
/// };
|
|
/// </code>
|
|
/// Additive-only (M11, mechanical) — no consumer wires these fields yet;
|
|
/// this test just pins the shape exists and round-trips.
|
|
/// </summary>
|
|
public sealed class MovementStructWideningTests
|
|
{
|
|
[Fact]
|
|
public void ObjectId_TopLevelId_RoundTrip()
|
|
{
|
|
var mvs = new MovementStruct
|
|
{
|
|
Type = MovementType.MoveToObject,
|
|
ObjectId = 0x50001234u,
|
|
TopLevelId = 0x50005678u,
|
|
};
|
|
|
|
Assert.Equal(0x50001234u, mvs.ObjectId);
|
|
Assert.Equal(0x50005678u, mvs.TopLevelId);
|
|
}
|
|
|
|
[Fact]
|
|
public void Pos_RoundTrips_WorldPositionAndCell()
|
|
{
|
|
var pos = new Position(0x12340001u, new Vector3(10f, 20f, 3f), Quaternion.Identity);
|
|
var mvs = new MovementStruct
|
|
{
|
|
Type = MovementType.MoveToPosition,
|
|
Pos = pos,
|
|
};
|
|
|
|
Assert.Equal(pos, mvs.Pos);
|
|
Assert.Equal(0x12340001u, mvs.Pos.ObjCellId);
|
|
Assert.Equal(new Vector3(10f, 20f, 3f), mvs.Pos.Frame.Origin);
|
|
}
|
|
|
|
[Fact]
|
|
public void Radius_Height_RoundTrip()
|
|
{
|
|
var mvs = new MovementStruct
|
|
{
|
|
Type = MovementType.MoveToObject,
|
|
Radius = 0.75f,
|
|
Height = 1.8f,
|
|
};
|
|
|
|
Assert.Equal(0.75f, mvs.Radius);
|
|
Assert.Equal(1.8f, mvs.Height);
|
|
}
|
|
|
|
[Fact]
|
|
public void Params_HoldsMovementParametersReference()
|
|
{
|
|
var p = new MovementParameters { CanCharge = true };
|
|
var mvs = new MovementStruct
|
|
{
|
|
Type = MovementType.TurnToHeading,
|
|
Params = p,
|
|
};
|
|
|
|
Assert.Same(p, mvs.Params);
|
|
}
|
|
|
|
[Fact]
|
|
public void ExistingFields_Type_Motion_StillPresent_NoRegression()
|
|
{
|
|
// The pre-R4 fields (Type/Motion/Speed/Autonomous/ModifyInterpretedState/
|
|
// ModifyRawState) must survive the widening untouched — R4 is
|
|
// additive-only per the plan (M11, "no consumer changes").
|
|
var mvs = new MovementStruct
|
|
{
|
|
Type = MovementType.RawCommand,
|
|
Motion = 0x45000005u,
|
|
Speed = 1.5f,
|
|
Autonomous = true,
|
|
ModifyInterpretedState = true,
|
|
ModifyRawState = false,
|
|
};
|
|
|
|
Assert.Equal(MovementType.RawCommand, mvs.Type);
|
|
Assert.Equal(0x45000005u, mvs.Motion);
|
|
Assert.Equal(1.5f, mvs.Speed);
|
|
Assert.True(mvs.Autonomous);
|
|
Assert.True(mvs.ModifyInterpretedState);
|
|
Assert.False(mvs.ModifyRawState);
|
|
}
|
|
}
|