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>
91 lines
3.7 KiB
C#
91 lines
3.7 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 — <c>Position::cylinder_distance</c>, the pure-math shape per
|
|
/// r4-moveto-decomp.md §5a (<c>MoveToManager::GetCurrentDistance</c>,
|
|
/// <c>005291b0</c>): edge-to-edge distance between two vertical cylinders
|
|
/// (own radius/height, target radius/height, both positions). Object moves
|
|
/// (use_spheres set on the wire) use this; position moves use plain
|
|
/// Euclidean <c>Position::distance</c> (§5a: "position moves use center
|
|
/// distance" — <see cref="MoveToMath.CylinderDistance"/> is the object-move
|
|
/// variant only; center distance is <c>Vector3.Distance</c>, already
|
|
/// available, not re-ported here).
|
|
///
|
|
/// <para>
|
|
/// The retail signature's exact combination math for radius/height beyond
|
|
/// "edge-to-edge, own+target cylinders" is not spelled out in the raw (BN
|
|
/// garbles the x87 plumbing) — ported per the PDB argument ORDER
|
|
/// (own radius/height, own position, target radius/height, target
|
|
/// position) with the standard cylinder-distance shape: horizontal
|
|
/// (planar) distance minus the sum of the two radii (clamped at 0), since
|
|
/// that is the only shape consistent with "edge-to-edge" and with
|
|
/// <c>distance_to_object</c>'s ctor default of 0.6 (melee range from
|
|
/// surface to surface, not center to center).
|
|
/// </para>
|
|
/// </summary>
|
|
public sealed class MoveToMathCylinderDistanceTests
|
|
{
|
|
[Fact]
|
|
public void TwoCylinders_HorizontallySeparated_SubtractsBothRadii()
|
|
{
|
|
// centers 10 units apart on X, radii 1 and 2 → edge distance = 10-1-2=7
|
|
float d = MoveToMath.CylinderDistance(
|
|
ownRadius: 1f, ownHeight: 2f, ownPos: Vector3.Zero,
|
|
targetRadius: 2f, targetHeight: 2f, targetPos: new Vector3(10f, 0f, 0f));
|
|
|
|
Assert.Equal(7f, d, 3);
|
|
}
|
|
|
|
[Fact]
|
|
public void TwoCylinders_Overlapping_ClampsAtZero_NoNegativeDistance()
|
|
{
|
|
// centers 1 unit apart, radii 5 and 5 → would be -9, clamps to 0
|
|
float d = MoveToMath.CylinderDistance(
|
|
ownRadius: 5f, ownHeight: 2f, ownPos: Vector3.Zero,
|
|
targetRadius: 5f, targetHeight: 2f, targetPos: new Vector3(1f, 0f, 0f));
|
|
|
|
Assert.Equal(0f, d, 3);
|
|
}
|
|
|
|
[Fact]
|
|
public void TwoCylinders_ZeroRadii_ReducesToCenterDistance()
|
|
{
|
|
float d = MoveToMath.CylinderDistance(
|
|
ownRadius: 0f, ownHeight: 2f, ownPos: Vector3.Zero,
|
|
targetRadius: 0f, targetHeight: 2f, targetPos: new Vector3(3f, 4f, 0f));
|
|
|
|
Assert.Equal(5f, d, 3); // 3-4-5 triangle
|
|
}
|
|
|
|
[Fact]
|
|
public void TwoCylinders_IgnoresVerticalSeparation_PlanarOnly()
|
|
{
|
|
// Same X/Y, large Z separation — cylinder_distance in retail's own
|
|
// callers (GetCurrentDistance) is used for horizontal arrival gates;
|
|
// the Z axis is height, not part of the radial edge-to-edge gap.
|
|
float d1 = MoveToMath.CylinderDistance(
|
|
ownRadius: 1f, ownHeight: 2f, ownPos: new Vector3(0, 0, 0),
|
|
targetRadius: 1f, targetHeight: 2f, targetPos: new Vector3(5f, 0f, 0f));
|
|
float d2 = MoveToMath.CylinderDistance(
|
|
ownRadius: 1f, ownHeight: 2f, ownPos: new Vector3(0, 0, 50f),
|
|
targetRadius: 1f, targetHeight: 2f, targetPos: new Vector3(5f, 0f, -50f));
|
|
|
|
Assert.Equal(d1, d2, 3);
|
|
Assert.Equal(3f, d1, 3); // 5 - 1 - 1
|
|
}
|
|
|
|
[Fact]
|
|
public void SamePosition_ZeroDistance_ClampsNotNegative()
|
|
{
|
|
float d = MoveToMath.CylinderDistance(
|
|
ownRadius: 0.5f, ownHeight: 2f, ownPos: Vector3.Zero,
|
|
targetRadius: 0.5f, targetHeight: 2f, targetPos: Vector3.Zero);
|
|
|
|
Assert.Equal(0f, d, 3);
|
|
}
|
|
}
|