fix(anim): Phase L.1c clamp approach velocity to prevent overshoot
User-observed residual after f794832: creature stops to attack but
still runs slightly through the player before stopping.
Cause: at 4 m/s body velocity (RunAnimSpeed × ~1.0 speedMod) and a
60 fps tick (~16 ms), the body advances ~6.4 cm per tick. When dist
falls just below the 0.6 m DistanceToObject arrival threshold, the
arrival predicate fires and zeroes velocity — but the body has
already advanced one full tick INTO the threshold zone. That last
tick is the "running through" the user sees, especially when
combined with a player visual radius of ~0.5 m.
Fix: cap horizontal velocity in the steering branch so the body lands
EXACTLY at the arrival threshold instead of overshooting it. Pure
function in RemoteMoveToDriver (ClampApproachVelocity) so it's
testable; called from GameWindow.cs after apply_current_movement
sets RunForward velocity from the active cycle.
The clamp is a strict scale-down of the X/Y components; Z is left
to gravity / terrain handling. No-op for the flee branch — fleeing
has no overshoot risk by definition.
Tests: 1416 → 1420. Four new clamp scenarios: exact-landing (FP
tolerance), would-overshoot scale-down, already-at-threshold zeroing,
flee no-op.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
f794832ebc
commit
ff6d3d0c94
3 changed files with 141 additions and 0 deletions
|
|
@ -205,6 +205,78 @@ public class RemoteMoveToDriverTests
|
|||
Assert.Equal(bodyRot, newOrient);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ClampApproachVelocity_NoOverShoot_LandsExactlyAtThreshold()
|
||||
{
|
||||
// Body 1 m from destination, running at 4 m/s, dt = 0.1 s.
|
||||
// Naive advance = 0.4 m → would end at 0.6 m from dest, exactly
|
||||
// on the threshold. With threshold=0.6 and remaining=0.4, the
|
||||
// clamp should let the full velocity through (advance == remaining).
|
||||
var bodyPos = new Vector3(0f, 0f, 0f);
|
||||
var dest = new Vector3(0f, 1f, 0f);
|
||||
var vel = new Vector3(0f, 4f, 0f);
|
||||
|
||||
var clamped = RemoteMoveToDriver.ClampApproachVelocity(
|
||||
bodyPos, vel, dest, arrivalThreshold: 0.6f, dt: 0.1f, moveTowards: true);
|
||||
|
||||
// Within float-precision: 4 m/s × 0.1 s = 0.4 m, exactly the
|
||||
// remaining distance. The clamp may apply a 0.99999×-style
|
||||
// tiny scale due to FP rounding — accept anything ≥ 99.9% of
|
||||
// the input as "no meaningful overshoot prevention applied."
|
||||
Assert.InRange(clamped.Y, 4f * 0.999f, 4f);
|
||||
Assert.Equal(0f, clamped.X);
|
||||
Assert.Equal(0f, clamped.Z);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ClampApproachVelocity_WouldOverShoot_ScalesDownToExactLanding()
|
||||
{
|
||||
// Body 1 m from destination, running at 4 m/s, dt = 0.2 s.
|
||||
// Naive advance = 0.8 m → would overshoot 0.6 m threshold by 0.4 m.
|
||||
// remaining = 0.4 m, advance = 0.8 m → scale = 0.5.
|
||||
// Velocity should be halved → 2 m/s.
|
||||
var bodyPos = new Vector3(0f, 0f, 0f);
|
||||
var dest = new Vector3(0f, 1f, 0f);
|
||||
var vel = new Vector3(0f, 4f, 0f);
|
||||
|
||||
var clamped = RemoteMoveToDriver.ClampApproachVelocity(
|
||||
bodyPos, vel, dest, arrivalThreshold: 0.6f, dt: 0.2f, moveTowards: true);
|
||||
|
||||
Assert.InRange(clamped.Y, 2f - Epsilon, 2f + Epsilon);
|
||||
Assert.Equal(0f, clamped.X);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ClampApproachVelocity_AlreadyAtThreshold_ZeroesHorizontal()
|
||||
{
|
||||
// Body exactly 0.6 m from dest with threshold 0.6 → remaining ≈ 0.
|
||||
// Any horizontal velocity would overshoot; clamp must zero it.
|
||||
var bodyPos = new Vector3(0f, 0f, 0f);
|
||||
var dest = new Vector3(0f, 0.6f, 0f);
|
||||
var vel = new Vector3(0f, 4f, 0.5f); // some Z to confirm Z is preserved
|
||||
|
||||
var clamped = RemoteMoveToDriver.ClampApproachVelocity(
|
||||
bodyPos, vel, dest, arrivalThreshold: 0.6f, dt: 0.016f, moveTowards: true);
|
||||
|
||||
Assert.Equal(0f, clamped.X);
|
||||
Assert.Equal(0f, clamped.Y);
|
||||
Assert.Equal(0.5f, clamped.Z); // gravity / Z handling unaffected
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ClampApproachVelocity_FleeBranch_NoOp()
|
||||
{
|
||||
// moveTowards=false (flee): no overshoot risk, return velocity unchanged.
|
||||
var bodyPos = Vector3.Zero;
|
||||
var dest = new Vector3(0f, 1f, 0f);
|
||||
var vel = new Vector3(0f, -4f, 0f);
|
||||
|
||||
var clamped = RemoteMoveToDriver.ClampApproachVelocity(
|
||||
bodyPos, vel, dest, arrivalThreshold: 5f, dt: 0.5f, moveTowards: false);
|
||||
|
||||
Assert.Equal(vel, clamped);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OriginToWorld_AppliesLandblockGridShift()
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue