diff --git a/src/AcDream.App/Input/PlayerMovementController.cs b/src/AcDream.App/Input/PlayerMovementController.cs index 71f3239..74c6a44 100644 --- a/src/AcDream.App/Input/PlayerMovementController.cs +++ b/src/AcDream.App/Input/PlayerMovementController.cs @@ -445,15 +445,24 @@ public sealed class PlayerMovementController float dy = _autoWalkDestination.Y - pos.Y; float dist = MathF.Sqrt(dx * dx + dy * dy); - // Arrival predicate (RemoteMoveToDriver convention; matches retail). + // Arrival predicate. CRITICAL: ACE's server-side WithinUseRadius + // is strict (dist <= radius), so arriving exactly at the radius + // boundary fails — ACE rejects the action and replies with + // another MoveToObject. To ensure the re-sent action lands + // INSIDE ACE's radius, we apply a safety margin that walks + // 0.3–0.5 m past the boundary (capped to 40 % of the threshold + // so tight pickup radii like 0.6 m stay reachable without + // collapsing to zero). float arrivalThreshold = _autoWalkMoveTowards ? _autoWalkDistanceToObject : _autoWalkMinDistance; + float safetyMargin = MathF.Min(0.5f, arrivalThreshold * 0.4f); + float effectiveArrival = MathF.Max(arrivalThreshold - safetyMargin, 0.1f); bool arrived = (_autoWalkMoveTowards - && dist <= arrivalThreshold + RemoteMoveToDriver.ArrivalEpsilon) + && dist <= effectiveArrival) || (!_autoWalkMoveTowards - && dist >= arrivalThreshold - RemoteMoveToDriver.ArrivalEpsilon); + && dist >= arrivalThreshold + RemoteMoveToDriver.ArrivalEpsilon); if (arrived) { EndServerAutoWalk("arrived");