fix(motion): retail-faithful per-frame remote tick (L.3.2 follow-up)
Multi-bug fix for the env-var-gated retail-faithful remote tick path (ACDREAM_INTERP_MANAGER=1). Combines four previously-stacked defects into one coherent rewrite: 1. PositionManager.ComputeOffset was additive (rootMotion + correction). Retail's PositionManager::adjust_offset (acclient @ 0x00555190 → InterpolationManager::adjust_offset @ 0x00555d30) REPLACES the offset frame via Frame::operator=(arg2, &__return) when catch-up engages — it does NOT add to the rootOffset that CPartArray::Update wrote. Switched to "correction overrides root motion" semantics. 2. MotionInterpreter.GetMaxSpeed was returning RunAnimSpeed × rate (~11.7 m/s for run skill 200). The retail decomp at acclient_2013_pseudo_c.txt:305127 shows get_max_speed returns the bare run rate (~2.94) — the function's float return rides the x87 FPU stack, which Binary Ninja shows as void. Caller multiplies by 2.0 to get the catch-up speed. With the wrong return our catch-up was 23.5 m/s instead of retail's 5.88 m/s — the queue would walk the body 4× too aggressively. 3. The env-var TickAnimations branch was DOUBLE-COUNTING forward translation: it applied seqVel × dt via PositionManager.ComputeOffset AND let UpdatePhysicsInternal advance body.Position += body.Velocity × dt. Both were ~11.7 m/s for run, so body raced at 23.4 m/s — "way too fast" per the user. Pass seqVel=Vector3.Zero to ComputeOffset; let body.Velocity (refreshed per tick by apply_current_movement) drive the bulk translation alone. 4. Body orientation only applied sequencer.CurrentOmega per tick. For the running-in-circles case ACE broadcasts ForwardCommand=RunForward AND TurnCommand=TurnLeft on the same UpdateMotion; the sequencer picks the RunForward cycle whose synthesized CurrentOmega is zero, so body never rotated between UPs and body.Velocity stayed in an out-of-date world direction — the visible "rectangle when running circles" effect. Prefer ObservedOmega (set explicitly in OnLiveMotionUpdated from the wire's TurnCommand + signed TurnSpeed) when present; fall back to seqOmega for standalone turn cycles. Also adds: - Sequencer-reset call in the env-var landing-fallback so the legs un-fold from Falling on land (mirrors the legacy K-fix17 path). - LastServerZ now only updates on IsGrounded UPs, so the per-tick landing-fallback floor doesn't drift up to the player's airborne peak Z and force-land mid-arc — fixes the user-reported "small landing in the air before landing on the ground" when jumping while moving. - VEL_DIAG now samples at UP arrival with overlapping windows, plus TURN_WIRE / OMEGA_DIAG / FWD_WIRE diagnostics gated on ACDREAM_REMOTE_VEL_DIAG=1 used to trace these bugs to ground truth. Verified via live retail-driven character observation 2026-05-03: turn-left now rotates left (was animating right with snap), running in circles is much smoother, jumping lands on ground (no mid-air pause). Residual ~20% steady-state overshoot for walk remains — WalkAnimSpeed=3.12 (decompiled retail constant) doesn't match ACE's actual broadcast walk pace (~2.6 m/s). Tracked separately. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
9960ce3bce
commit
842dfcd092
3 changed files with 228 additions and 99 deletions
|
|
@ -42,14 +42,35 @@ public sealed class PositionManager
|
|||
InterpolationManager interp,
|
||||
float maxSpeed)
|
||||
{
|
||||
// Step 1: animation root motion (body-local → world).
|
||||
Vector3 rootMotionLocal = seqVel * (float)dt;
|
||||
Vector3 rootMotionWorld = Vector3.Transform(rootMotionLocal, ori);
|
||||
|
||||
// Step 2: interpolation correction (world-space already).
|
||||
// Retail-faithful per-frame combiner. Mirrors
|
||||
// CPhysicsObj::UpdatePositionInternal (acclient @ 0x00512c30) +
|
||||
// InterpolationManager::adjust_offset (@ 0x00555d30):
|
||||
//
|
||||
// 1. CPartArray::Update writes rootOffset (animation root motion)
|
||||
// into the per-tick Frame.
|
||||
// 2. PositionManager::adjust_offset → InterpolationManager::adjust_offset
|
||||
// either:
|
||||
// a) RETURNS EARLY when distance(body, head) < 0.05m
|
||||
// (NodeCompleted; arg2 unmodified) — body uses root motion.
|
||||
// b) OVERWRITES arg2 with `direction × min(catchUpSpeed × dt,
|
||||
// distance)` when body is far from head — catch-up REPLACES
|
||||
// root motion for this frame.
|
||||
//
|
||||
// It is NOT additive. Our prior port added rootMotion + correction
|
||||
// every frame, which stacked the animation push (≈ RunAnimSpeed ×
|
||||
// speedMod, ≈ 11.7 m/s) on top of the queue catch-up (capped at
|
||||
// ≈ 23.5 m/s) so the body advanced at up to ~3× the server's
|
||||
// broadcast pace and the head-behind-body case produced a backward
|
||||
// correction every UP — the visible 1-Hz blip the user reported.
|
||||
//
|
||||
// AdjustOffset returns Vector3.Zero in two cases mapped to retail's
|
||||
// early-return: empty queue OR distance < DesiredDistance (0.05m).
|
||||
// In both, body falls back to animation root motion.
|
||||
Vector3 correction = interp.AdjustOffset(dt, currentBodyPosition, maxSpeed);
|
||||
if (correction.LengthSquared() > 0f)
|
||||
return correction;
|
||||
|
||||
// Step 3: combined delta.
|
||||
return rootMotionWorld + correction;
|
||||
Vector3 rootMotionLocal = seqVel * (float)dt;
|
||||
return Vector3.Transform(rootMotionLocal, ori);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue