using System.Numerics; using AcDream.Core.Physics; using AcDream.Core.Physics.Motion; using Xunit; namespace AcDream.Core.Tests.Physics.Motion; /// /// R4-V1 — Position::cylinder_distance, the pure-math shape per /// r4-moveto-decomp.md §5a (MoveToManager::GetCurrentDistance, /// 005291b0): 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 Position::distance (§5a: "position moves use center /// distance" — is the object-move /// variant only; center distance is Vector3.Distance, already /// available, not re-ported here). /// /// /// 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 /// distance_to_object's ctor default of 0.6 (melee range from /// surface to surface, not center to center). /// /// 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); } }