fix(motion): jump direction, AutoPos cadence, backward/strafe wire & anim
Closes a multi-bug knot in player motion outbound + remote inbound,
discovered via cdb live trace of retail (2026-05-01) and follow-up
visual verification.
Outbound (acdream → ACE):
- JumpAction velocity is BODY-LOCAL, not world (per retail
CPhysicsObj::get_local_physics_velocity at 0x00512140 + ACE
Player.HandleActionJump's set_local_velocity call). Was sending
world; observers saw jump rotated by player yaw.
- Capture get_jump_v_z BEFORE LeaveGround() — the latter resets
JumpExtent to 0, after which get_jump_v_z returned 0. Was sending
Z=0 in every JumpAction.
- Backward/strafe-left jumps lost their horizontal velocity because
LeaveGround → get_state_velocity returns zero for non-canonical
motion (faithful to retail's FUN_00528960; retail papers over via
adjust_motion translation, not yet ported). Compute the correct
body-local launch velocity from input directly and push it back
into the body so local prediction matches what we send.
- IsRunning HoldKey was gated on `input.Run && input.Forward`, so
strafe-run and backward-run incorrectly broadcast as walk to
observers — ACE then animated walk + dead-reckoned at walk speed
while server position moved at run speed (visible as observer
lag). Fixed: gate on any active directional axis.
- AutonomousPosition heartbeat 0.2s → 1.0s to match holtburger's
AUTONOMOUS_POSITION_HEARTBEAT_INTERVAL and the ~1Hz observed in
retail trace.
- Heartbeat now fires while in-world regardless of motion state
(matches holtburger + retail's transient_state-based gate, not
motion-based). Pre-fix the at-rest heartbeat was suppressed.
Inbound (ACE → acdream, remote retail player):
- Remote backward walk arrives as cmd=WalkForward + speed=-1.91
(retail's adjust_motion'd form). Two bugs were stacking:
1. AnimationSequencer fast-path returned without updating when
sign(speedMod) flipped while motion stayed equal — kept playing
forward at old positive framerate. Fixed: bypass fast-path on
sign change so the full re-setup runs.
2. GameWindow clamped negative speedMod to 1.0 when stuffing
InterpretedState.ForwardSpeed, making get_state_velocity
produce forward velocity. Fixed: pass speedMod through verbatim
so the dead-reckoning body translates backward.
Issue #38 filed: 30Hz physics tick produces a chase-camera smoothness
regression at 60+ FPS render. Standard render-time interpolation is
the recommended fix (separate phase).
Findings + comparison vs retail/holtburger:
docs/research/2026-05-01-retail-motion-trace/findings.md
docs/research/2026-05-01-retail-motion-trace/fixes.md
TODO: port retail's adjust_motion (FUN_00528010) properly so
get_state_velocity works for all directions natively — would let us
drop the workaround in PlayerMovementController jump path and the
clamp in GameWindow.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
09e013b7bd
commit
17a9ff1158
6 changed files with 691 additions and 22 deletions
|
|
@ -1926,6 +1926,20 @@ public sealed class GameWindow : IDisposable
|
|||
Console.WriteLine($"\n=== DUMP_CLOTHING: guid=0x{spawn.Guid:X8} name='{spawn.Name}' setup=0x{setup.Id:X8} APC={animPartChanges.Count} ===");
|
||||
foreach (var c in animPartChanges)
|
||||
Console.WriteLine($" APC part={c.PartIndex:D2} -> gfx=0x{c.NewModelId:X8}");
|
||||
|
||||
// #37: per-spawn palette swaps. The server's clothing pipeline
|
||||
// sends a basePalette + a list of (subPaletteId, offset, length)
|
||||
// triples that splice palette ranges into the rendered character.
|
||||
// We need their IDs to know whether the coat texture's underlying
|
||||
// palette is being overridden by a coat-tone subPalette or left
|
||||
// alone (in which case the texture's DefaultPaletteId — a SKIN
|
||||
// palette — leaks through and the coat ends up neck-colored).
|
||||
Console.WriteLine($" basePalette=0x{(spawn.BasePaletteId ?? 0):X8} subPalettes={(spawn.SubPalettes?.Count ?? 0)}");
|
||||
if (spawn.SubPalettes is { } subPaletteList)
|
||||
{
|
||||
foreach (var subPal in subPaletteList)
|
||||
Console.WriteLine($" SP id=0x{subPal.SubPaletteId:X8} offset={subPal.Offset} length={subPal.Length}");
|
||||
}
|
||||
}
|
||||
foreach (var change in animPartChanges)
|
||||
{
|
||||
|
|
@ -2722,7 +2736,14 @@ public sealed class GameWindow : IDisposable
|
|||
// get_state_velocity returns 0 because the gate is
|
||||
// RunForward||WalkForward — body stops moving forward.
|
||||
remoteMot.Motion.InterpretedState.ForwardCommand = fullMotion;
|
||||
remoteMot.Motion.InterpretedState.ForwardSpeed = speedMod <= 0f ? 1f : speedMod;
|
||||
// Pass speedMod through verbatim — preserve sign so retail's
|
||||
// adjust_motion'd backward walk (cmd=WalkForward, spd<0)
|
||||
// produces backward velocity in get_state_velocity, NOT
|
||||
// forward. Pre-fix used `<=0 ? 1 : speedMod` which clamped
|
||||
// negative to 1.0 and made the dead-reckoned body translate
|
||||
// forward despite the reverse-playback animation — visually
|
||||
// "still walking forward" from the observer's POV.
|
||||
remoteMot.Motion.InterpretedState.ForwardSpeed = speedMod;
|
||||
|
||||
if (update.MotionState.IsServerControlledMoveTo
|
||||
&& update.MotionState.MoveToPath is { } path)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue