fix(movement): jump works locally (airborne velocity preserved)
Two fixes for jump physics: - Skip ground-snap when velocity Z > 0 (prevents immediate re-landing at high framerates where per-frame Z delta < 0.05 snap threshold) - Guard apply_current_movement velocity write behind OnWalkable check (prevents MotionInterpreter.DoMotion from zeroing jump velocity on every frame while airborne) - Guard PlayerMovementController velocity replacement behind OnWalkable (preserves momentum during airborne flight) Jump works locally but server packet not yet sent (BUG-002). Facing direction mismatch logged as BUG-003. RunRate not verified as BUG-004. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
e08a06ac5b
commit
157ed9d974
4 changed files with 68 additions and 31 deletions
31
docs/bugs.md
31
docs/bugs.md
|
|
@ -18,6 +18,37 @@ the "Fixed" section with the commit hash that resolved it.
|
||||||
or the CreaturePalette pipeline in `InstancedMeshRenderer`.
|
or the CreaturePalette pipeline in `InstancedMeshRenderer`.
|
||||||
- **Phase:** Unassigned (investigate after rendering rebuild Step 5).
|
- **Phase:** Unassigned (investigate after rendering rebuild Step 5).
|
||||||
|
|
||||||
|
### BUG-002: Jump not visible from retail client
|
||||||
|
- **Observed:** 2026-04-14
|
||||||
|
- **Description:** Jump works locally (player rises and falls) but no
|
||||||
|
jump packet is sent to the server. Other clients don't see the jump.
|
||||||
|
- **Repro:** Jump in acdream, observe from retail client — no jump visible.
|
||||||
|
- **Likely area:** GameWindow.cs jump packet sending (currently a TODO
|
||||||
|
Console.WriteLine). Need to research jump packet format from holtburger.
|
||||||
|
- **Phase:** B.2 (movement completion)
|
||||||
|
|
||||||
|
### BUG-003: Facing direction mismatch with server
|
||||||
|
- **Observed:** 2026-04-14
|
||||||
|
- **Description:** Character facing direction in acdream doesn't match
|
||||||
|
what other clients see. Our quaternion convention for outbound
|
||||||
|
MoveToState/AutonomousPosition packets is wrong.
|
||||||
|
AC convention: heading 0 = West, 90 = North, 180 = East, 270 = South.
|
||||||
|
Our convention: Yaw 0 = +X (East in AC terms). The wireRot quaternion
|
||||||
|
needs to use holtburger's `from_heading` formula:
|
||||||
|
`theta = (450 - degrees).to_radians()`, `w=cos(theta/2)`, `z=sin(theta/2)`.
|
||||||
|
- **Repro:** Walk in acdream, observe from retail client — character faces
|
||||||
|
wrong direction.
|
||||||
|
- **Likely area:** GameWindow.cs wireRot computation (line ~1899).
|
||||||
|
- **Phase:** B.2 (movement completion)
|
||||||
|
|
||||||
|
### BUG-004: Run speed not verified working
|
||||||
|
- **Observed:** 2026-04-14
|
||||||
|
- **Description:** RunRate wiring exists (Task 1) but user reported no
|
||||||
|
speed change. Either the server's UpdateMotion ForwardSpeed isn't being
|
||||||
|
received, or it's not propagating to the velocity computation. Need to
|
||||||
|
add diagnostic logging to verify the UpdateMotion path fires.
|
||||||
|
- **Phase:** B.2 (movement completion)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Fixed
|
## Fixed
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,11 @@
|
||||||
# Rendering Rebuild from ACME
|
# Rendering Rebuild from ACME — COMPLETE
|
||||||
|
|
||||||
Port ACME's rendering pipeline to acdream. Each step produces a
|
Port ACME's rendering pipeline to acdream. Each step produces a
|
||||||
visually testable result. The animation system stays unchanged (ACME
|
visually testable result. The animation system stays unchanged (ACME
|
||||||
has none — ours is ported from the decompiled client).
|
has none — ours is ported from the decompiled client).
|
||||||
|
|
||||||
|
**Status:** All 5 steps shipped 2026-04-13.
|
||||||
|
|
||||||
## Step 1: Port StaticObject shader + instanced rendering
|
## Step 1: Port StaticObject shader + instanced rendering
|
||||||
|
|
||||||
The biggest performance win. Replace per-entity DrawElements with
|
The biggest performance win. Replace per-entity DrawElements with
|
||||||
|
|
|
||||||
|
|
@ -189,12 +189,6 @@ public sealed class PlayerMovementController
|
||||||
_body.Orientation = Quaternion.CreateFromAxisAngle(Vector3.UnitZ, Yaw - MathF.PI / 2f);
|
_body.Orientation = Quaternion.CreateFromAxisAngle(Vector3.UnitZ, Yaw - MathF.PI / 2f);
|
||||||
|
|
||||||
// ── 2. Set velocity via MotionInterpreter state machine ───────────────
|
// ── 2. Set velocity via MotionInterpreter state machine ───────────────
|
||||||
// Snapshot the current vertical velocity BEFORE calling DoMotion.
|
|
||||||
// DoMotion routes through apply_current_movement → set_local_velocity,
|
|
||||||
// which overwrites _body.Velocity with the horizontal state speed and
|
|
||||||
// zeros Z. We must snapshot Z first so we can restore it afterward.
|
|
||||||
float savedWorldVz = _body.Velocity.Z;
|
|
||||||
|
|
||||||
// Determine the dominant forward/backward command and speed.
|
// Determine the dominant forward/backward command and speed.
|
||||||
uint forwardCmd;
|
uint forwardCmd;
|
||||||
float forwardCmdSpeed;
|
float forwardCmdSpeed;
|
||||||
|
|
@ -205,7 +199,6 @@ public sealed class PlayerMovementController
|
||||||
}
|
}
|
||||||
else if (input.Backward)
|
else if (input.Backward)
|
||||||
{
|
{
|
||||||
// WalkBackward is tracked in interpreted state; we negate Y velocity below.
|
|
||||||
forwardCmd = MotionCommand.WalkBackward;
|
forwardCmd = MotionCommand.WalkBackward;
|
||||||
forwardCmdSpeed = 1.0f;
|
forwardCmdSpeed = 1.0f;
|
||||||
}
|
}
|
||||||
|
|
@ -215,7 +208,7 @@ public sealed class PlayerMovementController
|
||||||
forwardCmdSpeed = 1.0f;
|
forwardCmdSpeed = 1.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update interpreted motion state.
|
// Update interpreted motion state (needed for animation + server messages).
|
||||||
_motion.DoMotion(forwardCmd, forwardCmdSpeed);
|
_motion.DoMotion(forwardCmd, forwardCmdSpeed);
|
||||||
|
|
||||||
// Sidestep.
|
// Sidestep.
|
||||||
|
|
@ -229,28 +222,30 @@ public sealed class PlayerMovementController
|
||||||
_motion.StopInterpretedMotion(MotionCommand.SideStepLeft, modifyInterpretedState: true);
|
_motion.StopInterpretedMotion(MotionCommand.SideStepLeft, modifyInterpretedState: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// get_state_velocity gives us the body-local speed magnitude from retail constants.
|
// Only replace velocity with motion interpreter output when grounded.
|
||||||
var stateVel = _motion.get_state_velocity();
|
// While airborne, the physics body's integrated velocity (from LeaveGround)
|
||||||
|
// persists — gravity pulls Z down, horizontal momentum is preserved.
|
||||||
|
// Retail AC works this way: you maintain momentum in the air.
|
||||||
|
if (_body.OnWalkable)
|
||||||
|
{
|
||||||
|
float savedWorldVz = _body.Velocity.Z;
|
||||||
|
var stateVel = _motion.get_state_velocity();
|
||||||
|
|
||||||
// Build the body-local velocity from the retail-derived speed values.
|
float localY = 0f;
|
||||||
// get_state_velocity only fills +X for SideStepRight and +Y for forward;
|
float localX = 0f;
|
||||||
// we must handle WalkBackward (negate Y) and SideStepLeft (negate X) manually.
|
|
||||||
float localY = 0f;
|
|
||||||
float localX = 0f;
|
|
||||||
|
|
||||||
if (input.Forward)
|
if (input.Forward)
|
||||||
localY = stateVel.Y; // WalkAnimSpeed or RunAnimSpeed
|
localY = stateVel.Y;
|
||||||
else if (input.Backward)
|
else if (input.Backward)
|
||||||
localY = -(MotionInterpreter.WalkAnimSpeed * 0.65f); // retail backward is ~65% walk
|
localY = -(MotionInterpreter.WalkAnimSpeed * 0.65f);
|
||||||
|
|
||||||
if (input.StrafeRight)
|
if (input.StrafeRight)
|
||||||
localX = MotionInterpreter.SidestepAnimSpeed * 0.5f;
|
localX = MotionInterpreter.SidestepAnimSpeed * 0.5f;
|
||||||
else if (input.StrafeLeft)
|
else if (input.StrafeLeft)
|
||||||
localX = -MotionInterpreter.SidestepAnimSpeed * 0.5f;
|
localX = -MotionInterpreter.SidestepAnimSpeed * 0.5f;
|
||||||
|
|
||||||
// Restore the vertical velocity snapshotted before DoMotion clobbered it.
|
_body.set_local_velocity(new Vector3(localX, localY, savedWorldVz));
|
||||||
// Rotation about Z does not affect the Z component, so world Vz == local Vz.
|
}
|
||||||
_body.set_local_velocity(new Vector3(localX, localY, savedWorldVz));
|
|
||||||
|
|
||||||
// ── 3. Jump (charged) ─────────────────────────────────────────────────
|
// ── 3. Jump (charged) ─────────────────────────────────────────────────
|
||||||
// Hold spacebar to charge (0→1 over JumpChargeRate seconds).
|
// Hold spacebar to charge (0→1 over JumpChargeRate seconds).
|
||||||
|
|
@ -301,9 +296,9 @@ public sealed class PlayerMovementController
|
||||||
float groundZ = resolveResult.Position.Z;
|
float groundZ = resolveResult.Position.Z;
|
||||||
float bodyZ = _body.Position.Z;
|
float bodyZ = _body.Position.Z;
|
||||||
|
|
||||||
if (bodyZ <= groundZ + 0.05f)
|
if (bodyZ <= groundZ + 0.05f && _body.Velocity.Z <= 0f)
|
||||||
{
|
{
|
||||||
// Player is at or below the ground — snap to surface and land.
|
// Player is at or below the ground AND not jumping upward — snap to surface.
|
||||||
_body.Position = new Vector3(_body.Position.X, _body.Position.Y, groundZ);
|
_body.Position = new Vector3(_body.Position.X, _body.Position.Y, groundZ);
|
||||||
|
|
||||||
bool wasAirborne = !_body.OnWalkable;
|
bool wasAirborne = !_body.OnWalkable;
|
||||||
|
|
|
||||||
|
|
@ -539,8 +539,17 @@ public sealed class MotionInterpreter
|
||||||
if (InterpretedState.ForwardCommand == MotionCommand.RunForward)
|
if (InterpretedState.ForwardCommand == MotionCommand.RunForward)
|
||||||
MyRunRate = InterpretedState.ForwardSpeed;
|
MyRunRate = InterpretedState.ForwardSpeed;
|
||||||
|
|
||||||
var localVelocity = get_state_velocity();
|
// Only replace velocity when grounded. While airborne, the physics
|
||||||
PhysicsObj.set_local_velocity(localVelocity);
|
// body's integrated velocity (from LeaveGround) should persist —
|
||||||
|
// gravity pulls Z down, horizontal momentum is preserved.
|
||||||
|
// The retail client's apply_current_movement also gates on Contact+OnWalkable
|
||||||
|
// before writing velocity (the full decompiled flow routes through
|
||||||
|
// update_object which checks transient state).
|
||||||
|
if (PhysicsObj.OnWalkable)
|
||||||
|
{
|
||||||
|
var localVelocity = get_state_velocity();
|
||||||
|
PhysicsObj.set_local_velocity(localVelocity);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── FUN_00529390 — jump ───────────────────────────────────────────────────
|
// ── FUN_00529390 — jump ───────────────────────────────────────────────────
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue