fix(#171): gate NPC UP hard-snaps while stuck — the sticky/snap tug-of-war (gate-1 residuals)
Gate 1 (2026-07-04): "better in general" + three residuals — monsters pushed INTO the player, attacks with stale facing, position "flashing/flapping instead of gliding". All three are ONE mechanism: the legacy NPC UpdatePosition handler hard-snaps position, orientation, and velocity/cycle UNCONDITIONALLY, fighting the armed sticky every UP (ACE's authoritative rest pose sits ~0.6 m out and its server-side facing lags the strafing target; the client stick holds 0.3 m + live facing — oscillation at UP cadence). Retail is immune by architecture, not by tuning: UP corrections flow through the InterpolationManager into the SAME per-tick PositionManager::adjust_offset chain where StickyManager::adjust_offset OVERWRITES them while armed (0x00555190 chain order; 0x00555430 assigns m_fOrigin). A server correction can never fight an armed stick frame-by-frame. The remote-player branch already has exactly that (queue -> combiner -> sticky overwrite); the legacy NPC path snaps outside the chain. Fix: suppress the NPC UP position/orientation/velocity-adoption snaps while the entity is stuck (PositionManager.GetStickyObjectId() != 0) — the retail chain semantics translated to the snap architecture. LastServerPos/Time + cell bookkeeping still record; server truth reasserts on the first UP after unstick, bounded by the 1 s sticky lease. Register row TS-44 (same commit); retires with the S6/R6 interp-queue unification of the NPC path. Apparatus: ACDREAM_PROBE_STICKY=1 — per-guid [sticky] lifecycle lines (STICK / UNSTICK / LEASE-EXPIRE / TARGET-status teardown), per-armed- tick steer lines (signed gap dist, applied delta, heading delta, live resolve), and [sticky-snap-skip] at the suppressed-snap site. PhysicsDiagnostics.ProbeStickyEnabled owns the flag (rule #5). Full suite 4038 green. Awaiting gate 2 (pack melee vs retail). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
5bd2b8bc8b
commit
7a82317614
5 changed files with 90 additions and 4 deletions
|
|
@ -5675,7 +5675,34 @@ public sealed class GameWindow : IDisposable
|
|||
rmState.ServerVelocity = System.Numerics.Vector3.Zero;
|
||||
rmState.HasServerVelocity = false;
|
||||
}
|
||||
rmState.Body.Position = worldPos;
|
||||
// R5-V3 #171 residual (2026-07-04 gate: "flashing/flapping",
|
||||
// stale facing, pushed-into-player): while an entity is STUCK,
|
||||
// the sticky steer owns its frame — retail's UP corrections flow
|
||||
// through the InterpolationManager into the SAME adjust_offset
|
||||
// chain where StickyManager OVERWRITES them while armed
|
||||
// (PositionManager::adjust_offset 0x00555190 order; sticky
|
||||
// assigns m_fOrigin 0x00555430), so a server correction can
|
||||
// never fight an armed stick frame-by-frame. This legacy NPC
|
||||
// path hard-snaps OUTSIDE that chain, producing a visible
|
||||
// snap-out/steer-back oscillation at UP cadence (position) and
|
||||
// a stale-facing stomp (orientation). Faithful translation to
|
||||
// the snap architecture: suppress the position/orientation/
|
||||
// velocity snaps while stuck. LastServerPos/Time bookkeeping
|
||||
// still records below — server truth reasserts on the first UP
|
||||
// after unstick (bounded by the 1 s sticky lease). Register
|
||||
// row with TS-41/TS-44.
|
||||
bool snapSuppressedByStick =
|
||||
(rmState.Host?.PositionManager.GetStickyObjectId() ?? 0u) != 0u;
|
||||
if (snapSuppressedByStick
|
||||
&& AcDream.Core.Physics.PhysicsDiagnostics.ProbeStickyEnabled)
|
||||
{
|
||||
float snapDist = System.Numerics.Vector3.Distance(
|
||||
worldPos, rmState.Body.Position);
|
||||
Console.WriteLine(FormattableString.Invariant(
|
||||
$"[sticky-snap-skip] guid=0x{update.Guid:X8} d={snapDist:F3} srv=({worldPos.X:F2},{worldPos.Y:F2}) body=({rmState.Body.Position.X:F2},{rmState.Body.Position.Y:F2})"));
|
||||
}
|
||||
if (!snapSuppressedByStick)
|
||||
rmState.Body.Position = worldPos;
|
||||
// K-fix15 (2026-04-26): DON'T auto-clear airborne on UP.
|
||||
// ACE broadcasts UPs during the arc (peak / mid-fall / land)
|
||||
// at ~5-10 Hz. The previous K-fix9 logic cleared Airborne on
|
||||
|
|
@ -5711,7 +5738,13 @@ public sealed class GameWindow : IDisposable
|
|||
// turn starts incorporates the pre-turn interval and produces
|
||||
// a halved "observed" rate → visible slow-start. Formula-only
|
||||
// is stable and simple; hard-snap fixes any drift.
|
||||
rmState.Body.Orientation = rot;
|
||||
// R5-V3 #171 residual: gated on the stick (see the position-snap
|
||||
// comment above) — ACE's server-side facing lags the strafing
|
||||
// target; stomping it over the sticky's per-tick face-tracking
|
||||
// was the "monster attacking while facing a different direction"
|
||||
// gate report.
|
||||
if (!snapSuppressedByStick)
|
||||
rmState.Body.Orientation = rot;
|
||||
rmState.LastServerPos = worldPos;
|
||||
rmState.LastServerPosTime = nowSec;
|
||||
// Align the body's physics clock with our clock so update_object
|
||||
|
|
@ -5750,12 +5783,14 @@ public sealed class GameWindow : IDisposable
|
|||
}
|
||||
}
|
||||
}
|
||||
else if (!IsPlayerGuid(update.Guid) && rmState.HasServerVelocity)
|
||||
else if (!IsPlayerGuid(update.Guid) && rmState.HasServerVelocity
|
||||
&& !snapSuppressedByStick)
|
||||
{
|
||||
rmState.Body.Velocity = rmState.ServerVelocity;
|
||||
}
|
||||
|
||||
if (rmState.HasServerVelocity
|
||||
&& !snapSuppressedByStick
|
||||
&& _animatedEntities.TryGetValue(entity.Id, out var aeForVelocity))
|
||||
{
|
||||
// NPC/monster remotes: PlanFromVelocity cycle selection from
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue