fix(B.6): close-range turn-to-face — install overlay on Use/PickUp send

User report: 'It should always face the NPC. When I'm close I'm not
facing. But now it turns if I'm far away.'

Cause: ACE skips MoveToChain when the player is already within
WithinUseRadius (Player_Move.cs:66 shortcut) — it rotates the body
server-side via Rotate(target) but doesn't broadcast a MovementType=6
to us, so our auto-walk overlay never installs. The local body never
turns; the player remains facing wherever the camera/mouse last left
them.

Fix has two pieces:

1. PlayerMovementController.ApplyAutoWalkOverlay: arrival is now
   gated on BOTH within-radius AND aligned. Previously a body that
   started already in-range ended the overlay before turning. Now
   it turns in place, then ends once facing.

   Also: forward motion stays suppressed while withinArrival (we
   just need to finish the turn, no point stepping forward into a
   target we're already touching).

2. GameWindow.SendUse / SendPickUp: install a speculative auto-walk
   overlay at send time via new InstallSpeculativeTurnToTarget
   helper. For far targets ACE's MovementType=6 arrives moments
   later and overwrites with its wire-supplied use-radius. For
   close targets our overlay is the only thing that runs — body
   turns, then ends.

The per-type use-radius mirrors the picker's heuristic (3 m
creature / 2 m large flat / 0.6 m item).
This commit is contained in:
Erik 2026-05-15 12:05:37 +02:00
parent 32352af583
commit 5b908bcca2
2 changed files with 95 additions and 8 deletions

View file

@ -451,21 +451,25 @@ public sealed class PlayerMovementController
// longer needed. Tiny 0.05 m margin remains to absorb the
// sub-tick race between local arrival-fire and the next
// heartbeat's outbound packet.
//
// ARRIVAL IS GATED ON ALIGNMENT: we only end the auto-walk once
// the body is BOTH within use-radius AND facing the target.
// Without the alignment gate, a Use on a close target while
// facing away would end immediately and the body wouldn't turn
// at all (user feedback 2026-05-15: 'when I'm close I'm not
// facing'). The alignment check is computed below in the same
// block as the heading-step; we defer the arrival fire-and-end
// until after we've inspected `aligned`.
float arrivalThreshold = _autoWalkMoveTowards
? _autoWalkDistanceToObject
: _autoWalkMinDistance;
const float TinyMargin = 0.05f;
float effectiveArrival = MathF.Max(arrivalThreshold - TinyMargin, 0.1f);
bool arrived =
bool withinArrival =
(_autoWalkMoveTowards
&& dist <= effectiveArrival)
|| (!_autoWalkMoveTowards
&& dist >= arrivalThreshold + RemoteMoveToDriver.ArrivalEpsilon);
if (arrived)
{
EndServerAutoWalk("arrived");
return input; // falls through to Ready (no Forward) → body stops
}
// Step Yaw toward target. Convention from Update line 364:
// _body.Orientation = Quaternion.CreateFromAxisAngle(Z, Yaw - π/2),
@ -506,6 +510,16 @@ public sealed class PlayerMovementController
aligned = MathF.Abs(delta) <= WalkWhileTurningRad;
}
// End the auto-walk once the body is BOTH within use radius
// AND aligned with the target. This is the alignment-gated
// arrival the comment above flagged: a close-range Use on a
// target behind the player still rotates the body first.
if (withinArrival && aligned)
{
EndServerAutoWalk("arrived");
return input;
}
// Walk vs run decided ONCE at BeginServerAutoWalk based on
// initial distance — held for the rest of the auto-walk so the
// character keeps running all the way to the target instead of
@ -516,8 +530,9 @@ public sealed class PlayerMovementController
// Turn-first gate: if not yet aligned with the target, suppress
// forward motion so the body turns in place rather than
// walking an arc.
bool moveForward = aligned;
// walking an arc. Also suppress when already within arrival —
// we just turned to face it; no need to step forward into it.
bool moveForward = aligned && !withinArrival;
// Synthesize "moving forward" input. The rest of Update reads
// Yaw + input.Forward + input.Run to drive _motion + body