fix(items): use retail cylinder range for loot windows

Keep external-container visibility tied to the signed surface gap between the player and container physics cylinders, matching gmExternalContainerUI's authored UseRadius range watcher. This prevents a valid ViewContents response from being closed at ACE's natural approach endpoint.

Port Position::cylinder_distance and ACCWeenieObject::ObjectsInRange into Core with conformance coverage for 3-D separation, overlap, mode precedence, and inclusive boundaries.

Release build succeeds and all 5,895 tests pass with five intentional skips.

Co-authored-by: OpenAI Codex <codex@openai.com>
This commit is contained in:
Erik 2026-07-17 20:32:34 +02:00
parent e6dd8bf6fa
commit 0f82a08f0a
6 changed files with 199 additions and 50 deletions

View file

@ -0,0 +1,53 @@
using System.Numerics;
using AcDream.Core.Physics.Motion;
namespace AcDream.Core.Physics;
/// <summary>
/// Retail object-range predicate used by <c>CPlayerSystem</c> range handlers.
/// Port of <c>ACCWeenieObject::ObjectsInRange @ 0x0058C1A0</c>.
/// </summary>
public static class ObjectRangeMath
{
/// <summary>
/// Tests two live physics objects against a caller-supplied range. Retail
/// gives <paramref name="ignoreZDelta"/> precedence; otherwise
/// <paramref name="useRadii"/> selects cylinder-gap distance instead of
/// ordinary center distance.
/// </summary>
public static bool ObjectsInRange(
Vector3 firstPosition,
float firstRadius,
float firstHeight,
Vector3 secondPosition,
float secondRadius,
float secondHeight,
double range,
bool useRadii,
bool ignoreZDelta)
{
double distance;
if (ignoreZDelta)
{
double dx = secondPosition.X - firstPosition.X;
double dy = secondPosition.Y - firstPosition.Y;
distance = Math.Sqrt(dx * dx + dy * dy);
}
else if (useRadii)
{
distance = MoveToMath.CylinderDistance(
firstRadius,
firstHeight,
firstPosition,
secondRadius,
secondHeight,
secondPosition);
}
else
{
distance = Vector3.Distance(firstPosition, secondPosition);
}
return distance <= range;
}
}