Round 1 (5154a3e) tried to fix: - heading locked → orientation snap-on-receipt (good idea) - endless jump → landing detector via UP-with-zero-velocity (didn't work; ACE sends non-zero velocity through arc) Round 2 (f199a6a) tried to fix: - chop at 1 Hz → seed body.Velocity from update.Velocity for between-UP extrapolation (didn't help) - endless jump → reported-Z-near-body-Z + falling-velocity heuristic (didn't catch reliably) The actual problem was scoping: L.3.1's "InterpolationManager only" cannot produce smooth motion. Retail combines animation root motion (L.3.2 / PositionManager) + InterpolationManager corrections. Both halves are required for "remotes look smooth". Reverting toe08accf(Task 6 — VectorUpdate.Omega). The next commits will properly port PositionManager + plumb IsGrounded through the wire parser, replacing L.3.1-only with L.3.1+L.3.2 combined per the revised spec. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
f199a6a075
commit
1641d6ea1b
1 changed files with 16 additions and 96 deletions
|
|
@ -3261,72 +3261,6 @@ 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).
|
||||
// ── AIRBORNE ────────────────────────────────────────────────────────────
|
||||
// Server is authoritative for the arc. Hard-snap position from every UP
|
||||
// while airborne; body.Velocity (set by OnLiveVectorUpdated at jump start,
|
||||
// or unchanged) continues to integrate via UpdatePhysicsInternal/gravity
|
||||
// between UPs. Don't enqueue — the queue is for grounded motion only.
|
||||
//
|
||||
// Landing heuristic (L.3.1): ACE doesn't send an explicit "landed" packet.
|
||||
// Instead we detect landing by two conditions simultaneously:
|
||||
// 1. The server-reported Z is within 0.5m of the body's current Z
|
||||
// (server has snapped to ground level — close to where we are).
|
||||
// 2. Body's vertical velocity is falling or settled (vz <= 0.5 m/s).
|
||||
// Both together mean the arc is complete. We do NOT use "velocity == 0"
|
||||
// because ACE sends non-zero velocity through the entire arc (Bug 2 root
|
||||
// cause in Round 1).
|
||||
if (rmState.Airborne)
|
||||
{
|
||||
bool reportedNearBodyZ =
|
||||
MathF.Abs(worldPos.Z - rmState.Body.Position.Z) < 0.5f;
|
||||
bool velocityFallingOrSettled =
|
||||
rmState.Body.Velocity.Z <= 0.5f;
|
||||
|
||||
if (reportedNearBodyZ && velocityFallingOrSettled)
|
||||
{
|
||||
// LANDED: snap to ground, 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();
|
||||
return;
|
||||
}
|
||||
|
||||
// Still airborne: hard-snap so server is authoritative for the arc.
|
||||
// body.Velocity preserved from VectorUpdate; UpdatePhysicsInternal
|
||||
// integrates gravity between UPs.
|
||||
rmState.Body.Position = worldPos;
|
||||
return;
|
||||
}
|
||||
|
||||
// ── GROUNDED ────────────────────────────────────────────────────────────
|
||||
// Routing mirrors CPhysicsObj::MoveOrTeleport (acclient @ 0x00516330).
|
||||
const float MaxPhysicsDistance = 96f;
|
||||
System.Numerics.Vector3 localPlayerPos =
|
||||
_playerController?.Position ?? System.Numerics.Vector3.Zero;
|
||||
|
|
@ -3337,37 +3271,26 @@ public sealed class GameWindow : IDisposable
|
|||
// Default-true: HasContact not on wire yet (CreateObject.ServerPosition gap).
|
||||
// bool hasContact = true; (implicit — only the teleport and distance branches below)
|
||||
|
||||
if (teleportFlag || dist > MaxPhysicsDistance)
|
||||
if (teleportFlag)
|
||||
{
|
||||
// SetPosition / SetPositionSimple equivalent: hard-snap, clear queue.
|
||||
// Orientation already applied unconditionally above.
|
||||
// Zero velocity so UpdatePhysicsInternal doesn't extrapolate from
|
||||
// a prior walk-direction after a teleport or distant slide-snap.
|
||||
// SetPosition equivalent: hard-snap position + orientation, clear interp queue.
|
||||
rmState.Body.Position = worldPos;
|
||||
rmState.Body.Orientation = rot;
|
||||
rmState.Interp.Clear();
|
||||
}
|
||||
else if (dist > MaxPhysicsDistance)
|
||||
{
|
||||
// SetPositionSimple equivalent: slide-snap (clear queue, then hard-snap).
|
||||
rmState.Interp.Clear();
|
||||
rmState.Body.Position = worldPos;
|
||||
rmState.Body.Velocity = System.Numerics.Vector3.Zero;
|
||||
rmState.Body.Orientation = rot;
|
||||
}
|
||||
else
|
||||
{
|
||||
// InterpolationManager.Enqueue equivalent: queue for AdjustOffset to walk to.
|
||||
// NOTE: do NOT touch rmState.Body.Position here — AdjustOffset owns it.
|
||||
// Orientation already applied unconditionally above.
|
||||
//
|
||||
// L.3.1 WORKAROUND — velocity-extrapolation between UPs:
|
||||
// Retail achieves smooth 60 fps motion via animation root motion feeding
|
||||
// PositionManager (Phase L.3.2 / PositionManager port). Until that lands,
|
||||
// AdjustOffset alone catches up in ~150 ms after each 1-Hz UP then sits
|
||||
// idle the remaining 850 ms — visible as "updates every 1 second" stepping.
|
||||
// Workaround: seed body.Velocity from the UP's velocity field so
|
||||
// UpdatePhysicsInternal integrates position += vel*dt between UPs;
|
||||
// AdjustOffset provides corrective patches when drift accumulates.
|
||||
// When update.Velocity is null the entity is stationary on this UP →
|
||||
// zero velocity → only queue-walking applies. This deviates from the
|
||||
// retail decomp finding that walking remotes have m_velocityVector == 0,
|
||||
// but is the best approximation available without root motion.
|
||||
// InterpolationManager.Enqueue equivalent: queue for adjust_offset to walk to.
|
||||
// NOTE: do NOT touch rmState.Body.Position here — adjust_offset (Task 5) owns it.
|
||||
float headingFromQuat = ExtractYawFromQuaternion(rot);
|
||||
rmState.Interp.Enqueue(worldPos, headingFromQuat, isMovingTo: false);
|
||||
rmState.Body.Velocity = update.Velocity ?? System.Numerics.Vector3.Zero;
|
||||
}
|
||||
|
||||
// Skip the legacy hard-snap path below.
|
||||
|
|
@ -5874,13 +5797,10 @@ public sealed class GameWindow : IDisposable
|
|||
rm.Body.Position += delta;
|
||||
}
|
||||
|
||||
// Velocity policy is owned by OnLivePositionUpdated (grounded) and
|
||||
// OnLiveVectorUpdated (airborne jump start). Do NOT clamp body.Velocity
|
||||
// here — doing so stomped the velocity-extrapolation workaround seeded
|
||||
// on grounded UPs (Bug 1 regression from Round 1). UpdatePhysicsInternal
|
||||
// integrates whatever velocity is set: zero for stationary remotes,
|
||||
// update.Velocity for moving remotes (L.3.1 workaround), or the launch
|
||||
// arc velocity for airborne remotes. Gravity is applied by the same call.
|
||||
// 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;
|
||||
// for airborne remotes it applies gravity to the arc.
|
||||
rm.Body.UpdatePhysicsInternal(dt);
|
||||
|
||||
ae.Entity.Position = rm.Body.Position;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue