fix(motion): heading + jump bugs in InterpolationManager path (L.3.1)
Visual verification (Task 7) revealed two bugs in the new env-var gated path: 1. Heading locked at login direction. Cause: AdjustOffset returns position delta only; the dist≤96 enqueue branch never updated body.Orientation. Fix: apply orientation unconditionally on every UpdatePosition (snap-on-receipt). Position lerps via queue. 2. Endless jumping. Cause: (a) body.Velocity persisted forever after arc landed because apply_current_movement no longer ran; (b) UpdatePositions during the arc were enqueued, fighting the gravity sim. Fix: skip enqueue when rm.Airborne (mirrors retail MoveOrTeleport has_contact=false → no-op); zero non-airborne body.Velocity each tick (mirrors legacy apply_current_movement); detect landed when receiving UpdatePosition while airborne with no/zero velocity. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
e08accf7c2
commit
5154a3eae1
1 changed files with 57 additions and 3 deletions
|
|
@ -3261,6 +3261,48 @@ public sealed class GameWindow : IDisposable
|
|||
// - has_contact && distance ≤ 96 → InterpolationManager.Enqueue (queue)
|
||||
// - has_contact && distance > 96 → SetPositionSimple (slide-snap)
|
||||
|
||||
// Bug 1 fix (L.3.1 visual verification): apply orientation unconditionally
|
||||
// on every UpdatePosition, regardless of the routing branch below.
|
||||
// InterpolationManager.AdjustOffset returns a position delta only — it
|
||||
// never updates Orientation. Without this, the dist≤96 enqueue branch
|
||||
// never touched Body.Orientation, so remote heading was locked at whatever
|
||||
// it was at login. Position lerps via the queue; heading snaps on receipt,
|
||||
// which is both perceptually correct and mirrors retail's set_frame behavior
|
||||
// (FUN_00514b90 @ chunk_00510000.c:5637 — direct assignment).
|
||||
rmState.Body.Orientation = rot;
|
||||
|
||||
// Bug 2b fix (L.3.1 visual verification): if the remote is currently
|
||||
// airborne (body.Velocity set by VectorUpdate, gravity integrating), skip
|
||||
// enqueueing position waypoints. The queue and the gravity sim would
|
||||
// double-step position. Mirrors retail MoveOrTeleport returning false when
|
||||
// has_contact == false (acclient @ 0x00516330). The landing UpdatePosition
|
||||
// (received after arc completes with no/zero velocity) will arrive with
|
||||
// rmState.Airborne == false and proceed normally.
|
||||
//
|
||||
// Bug 2c fix: detect "just landed" — if Airborne was true but this
|
||||
// UpdatePosition carries no non-trivial velocity, treat it as ground
|
||||
// contact: clear Airborne, zero body.Velocity, restore contact flags.
|
||||
// This is the signal ACE uses (VectorUpdate only fires on jump start;
|
||||
// no corresponding "landed" packet — the next plain UpdatePosition is it).
|
||||
if (rmState.Airborne)
|
||||
{
|
||||
bool velocityIsNegligible = update.Velocity is null
|
||||
|| update.Velocity.Value.LengthSquared() < 0.04f;
|
||||
if (velocityIsNegligible)
|
||||
{
|
||||
// Landed: snap to server position, re-ground the body.
|
||||
rmState.Airborne = false;
|
||||
rmState.Body.Velocity = System.Numerics.Vector3.Zero;
|
||||
rmState.Body.State &= ~AcDream.Core.Physics.PhysicsStateFlags.Gravity;
|
||||
rmState.Body.TransientState |= AcDream.Core.Physics.TransientStateFlags.Contact
|
||||
| AcDream.Core.Physics.TransientStateFlags.OnWalkable;
|
||||
rmState.Body.Position = worldPos;
|
||||
rmState.Interp.Clear();
|
||||
}
|
||||
// Still airborne: don't enqueue — let gravity arc continue.
|
||||
return;
|
||||
}
|
||||
|
||||
const float MaxPhysicsDistance = 96f;
|
||||
System.Numerics.Vector3 localPlayerPos =
|
||||
_playerController?.Position ?? System.Numerics.Vector3.Zero;
|
||||
|
|
@ -3273,22 +3315,23 @@ public sealed class GameWindow : IDisposable
|
|||
|
||||
if (teleportFlag)
|
||||
{
|
||||
// SetPosition equivalent: hard-snap position + orientation, clear interp queue.
|
||||
// SetPosition equivalent: hard-snap position, clear interp queue.
|
||||
// Orientation already applied unconditionally above.
|
||||
rmState.Body.Position = worldPos;
|
||||
rmState.Body.Orientation = rot;
|
||||
rmState.Interp.Clear();
|
||||
}
|
||||
else if (dist > MaxPhysicsDistance)
|
||||
{
|
||||
// SetPositionSimple equivalent: slide-snap (clear queue, then hard-snap).
|
||||
// Orientation already applied unconditionally above.
|
||||
rmState.Interp.Clear();
|
||||
rmState.Body.Position = worldPos;
|
||||
rmState.Body.Orientation = rot;
|
||||
}
|
||||
else
|
||||
{
|
||||
// InterpolationManager.Enqueue equivalent: queue for adjust_offset to walk to.
|
||||
// NOTE: do NOT touch rmState.Body.Position here — adjust_offset (Task 5) owns it.
|
||||
// Orientation already applied unconditionally above.
|
||||
float headingFromQuat = ExtractYawFromQuaternion(rot);
|
||||
rmState.Interp.Enqueue(worldPos, headingFromQuat, isMovingTo: false);
|
||||
}
|
||||
|
|
@ -5797,6 +5840,17 @@ public sealed class GameWindow : IDisposable
|
|||
rm.Body.Position += delta;
|
||||
}
|
||||
|
||||
// Bug 2a fix (L.3.1 visual verification): grounded remotes must keep
|
||||
// body.Velocity == 0 so it doesn't fight the queue. In the legacy path
|
||||
// apply_current_movement achieved this by recomputing velocity from
|
||||
// InterpretedState each tick; the new path skips apply_current_movement,
|
||||
// so we explicitly clamp. Airborne remotes keep their VectorUpdate-set
|
||||
// velocity for gravity arc integration (UpdatePhysicsInternal below).
|
||||
if (!rm.Airborne)
|
||||
{
|
||||
rm.Body.Velocity = System.Numerics.Vector3.Zero;
|
||||
}
|
||||
|
||||
// Gravity integration: retail's UpdatePhysicsInternal still
|
||||
// fires every frame regardless of the interpolation path.
|
||||
// For grounded remotes body.Velocity == 0 so this is a no-op;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue