From 5612ce718a285a4799d3ff80c9c600c59ecf7e0d Mon Sep 17 00:00:00 2001 From: Erik Date: Fri, 15 May 2026 06:22:07 +0200 Subject: [PATCH] =?UTF-8?q?feat(B.6):=20honor=20wire=20WalkRunThreshold=20?= =?UTF-8?q?=E2=80=94=20walk=20vs=20run=20per=20retail=20semantics?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit User-observed behaviour: 'When at a distance X it should start running towards the double clicked target and then stop close to it. When at a shorter distance it should walk to it.' That's retail's MoveToManager behaviour driven by the wire's WalkRunThreshold field, which Slice 2 ignored (it always synthesised Run=true regardless of distance). ACE's defaults (MoveToParameters.SetDefaults): WalkRunThreshold=15.0 m for Use/PickUp paths — so close-range auto-walks are walks, not runs. ACE's combat-charge override: 1.0 m — chase runs until the last metre. Both retail and ACE compute Run vs Walk per-frame from remaining distance vs threshold. Wire WalkRunThreshold: - Already parsed into CreateObject.MoveToPathData.WalkRunThreshold. - Now plumbed through to PlayerMovementController.BeginServerAutoWalk as a new parameter, stored in _autoWalkWalkRunThreshold. - ApplyAutoWalkOverlay sets Run = (dist > _autoWalkWalkRunThreshold) per frame. The synthesised input flips Run as the body approaches. The motion-interpreter pipeline downstream picks RunForward vs WalkForward from input.Run, so the animation cycle naturally switches as the body crosses into the walk band. Run rate falls back to the local PlayerWeenie.InqRunRate as before (ACE sends mtRun=0.00 for Use/PickUp, so we never read mtRun; this is unchanged from Slice 2). [autowalk-begin] diagnostic now includes walkRunThresh={x:F2} so the threshold is visible alongside dest/minDist/objDist in the trace. --- .../Input/PlayerMovementController.cs | 29 +++++++++++++++---- src/AcDream.App/Rendering/GameWindow.cs | 5 ++-- 2 files changed, 27 insertions(+), 7 deletions(-) diff --git a/src/AcDream.App/Input/PlayerMovementController.cs b/src/AcDream.App/Input/PlayerMovementController.cs index 2a4896b..63b92ac 100644 --- a/src/AcDream.App/Input/PlayerMovementController.cs +++ b/src/AcDream.App/Input/PlayerMovementController.cs @@ -234,6 +234,12 @@ public sealed class PlayerMovementController private float _autoWalkMinDistance; private float _autoWalkDistanceToObject; private bool _autoWalkMoveTowards; + // Wire's WalkRunThreshold — retail semantic: locomotion runs while + // remaining distance > threshold, walks once inside threshold. ACE's + // Use/PickUp path uses MoveToParameters.SetDefaults() = 15.0f, so + // most pickup targets walk the entire way. ACE's charge path sets + // it to 1.0f so combat chase runs almost the whole way. + private float _autoWalkWalkRunThreshold; /// /// True while a server-initiated auto-walk (MoveToObject inbound) is @@ -343,13 +349,15 @@ public sealed class PlayerMovementController Vector3 destinationWorld, float minDistance, float distanceToObject, - bool moveTowards) + bool moveTowards, + float walkRunThreshold) { _autoWalkActive = true; _autoWalkDestination = destinationWorld; _autoWalkMinDistance = minDistance; _autoWalkDistanceToObject = distanceToObject; _autoWalkMoveTowards = moveTowards; + _autoWalkWalkRunThreshold = walkRunThreshold; } /// @@ -444,14 +452,25 @@ public sealed class PlayerMovementController while (Yaw < -MathF.PI) Yaw += 2f * MathF.PI; } - // Synthesize "running forward" input. The rest of Update reads + // Walk vs run per the wire's WalkRunThreshold. Retail semantics: + // dist > threshold → RUN, dist ≤ threshold → WALK. ACE's default + // for Use/PickUp is 15.0 m (so close targets walk the whole way); + // ACE's combat charge path sets it to 1.0 m (so chase runs to the + // last metre then walks the final approach). Matches the user's + // observed retail behaviour: "When at a distance X it should + // start running towards the double clicked target… When at a + // shorter distance it should walk to it." + bool shouldRun = dist > _autoWalkWalkRunThreshold; + + // Synthesize "moving forward" input. The rest of Update reads // Yaw + input.Forward + input.Run to drive _motion + body - // velocity exactly as it does for user-driven W+Shift. We zero - // any mouse delta so a stale frame doesn't fight the steering. + // velocity exactly as it does for user-driven W (+ optional Shift). + // We zero any mouse delta so a stale frame doesn't fight the + // steering. return input with { Forward = true, - Run = true, + Run = shouldRun, Backward = false, StrafeLeft = false, StrafeRight = false, diff --git a/src/AcDream.App/Rendering/GameWindow.cs b/src/AcDream.App/Rendering/GameWindow.cs index 6d002a9..bc81639 100644 --- a/src/AcDream.App/Rendering/GameWindow.cs +++ b/src/AcDream.App/Rendering/GameWindow.cs @@ -3338,11 +3338,12 @@ public sealed class GameWindow : IDisposable destWorld, pathData.MinDistance, pathData.DistanceToObject, - update.MotionState.MoveTowards); + update.MotionState.MoveTowards, + pathData.WalkRunThreshold); if (AcDream.Core.Physics.PhysicsDiagnostics.ProbeAutoWalkEnabled) { Console.WriteLine(System.FormattableString.Invariant( - $"[autowalk-begin] dest=({destWorld.X:F2},{destWorld.Y:F2},{destWorld.Z:F2}) minDist={pathData.MinDistance:F2} objDist={pathData.DistanceToObject:F2} towards={update.MotionState.MoveTowards}")); + $"[autowalk-begin] dest=({destWorld.X:F2},{destWorld.Y:F2},{destWorld.Z:F2}) minDist={pathData.MinDistance:F2} objDist={pathData.DistanceToObject:F2} walkRunThresh={pathData.WalkRunThreshold:F2} towards={update.MotionState.MoveTowards}")); } } // Note: do NOT cancel auto-walk on a non-MoveTo motion