From 157ed9d97412d093a7c5d0036b8cad330e8cea32 Mon Sep 17 00:00:00 2001 From: Erik Date: Tue, 14 Apr 2026 00:12:11 +0200 Subject: [PATCH] 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) --- docs/bugs.md | 31 +++++++++++ docs/plans/2026-04-13-rendering-rebuild.md | 4 +- .../Input/PlayerMovementController.cs | 51 +++++++++---------- src/AcDream.Core/Physics/MotionInterpreter.cs | 13 ++++- 4 files changed, 68 insertions(+), 31 deletions(-) diff --git a/docs/bugs.md b/docs/bugs.md index 7eee168..e027538 100644 --- a/docs/bugs.md +++ b/docs/bugs.md @@ -18,6 +18,37 @@ the "Fixed" section with the commit hash that resolved it. or the CreaturePalette pipeline in `InstancedMeshRenderer`. - **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 diff --git a/docs/plans/2026-04-13-rendering-rebuild.md b/docs/plans/2026-04-13-rendering-rebuild.md index b4eb17c..99caef0 100644 --- a/docs/plans/2026-04-13-rendering-rebuild.md +++ b/docs/plans/2026-04-13-rendering-rebuild.md @@ -1,9 +1,11 @@ -# Rendering Rebuild from ACME +# Rendering Rebuild from ACME — COMPLETE Port ACME's rendering pipeline to acdream. Each step produces a visually testable result. The animation system stays unchanged (ACME has none — ours is ported from the decompiled client). +**Status:** All 5 steps shipped 2026-04-13. + ## Step 1: Port StaticObject shader + instanced rendering The biggest performance win. Replace per-entity DrawElements with diff --git a/src/AcDream.App/Input/PlayerMovementController.cs b/src/AcDream.App/Input/PlayerMovementController.cs index 205f35e..28ff407 100644 --- a/src/AcDream.App/Input/PlayerMovementController.cs +++ b/src/AcDream.App/Input/PlayerMovementController.cs @@ -189,12 +189,6 @@ public sealed class PlayerMovementController _body.Orientation = Quaternion.CreateFromAxisAngle(Vector3.UnitZ, Yaw - MathF.PI / 2f); // ── 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. uint forwardCmd; float forwardCmdSpeed; @@ -205,7 +199,6 @@ public sealed class PlayerMovementController } else if (input.Backward) { - // WalkBackward is tracked in interpreted state; we negate Y velocity below. forwardCmd = MotionCommand.WalkBackward; forwardCmdSpeed = 1.0f; } @@ -215,7 +208,7 @@ public sealed class PlayerMovementController forwardCmdSpeed = 1.0f; } - // Update interpreted motion state. + // Update interpreted motion state (needed for animation + server messages). _motion.DoMotion(forwardCmd, forwardCmdSpeed); // Sidestep. @@ -229,28 +222,30 @@ public sealed class PlayerMovementController _motion.StopInterpretedMotion(MotionCommand.SideStepLeft, modifyInterpretedState: true); } - // get_state_velocity gives us the body-local speed magnitude from retail constants. - var stateVel = _motion.get_state_velocity(); + // Only replace velocity with motion interpreter output when grounded. + // 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. - // get_state_velocity only fills +X for SideStepRight and +Y for forward; - // we must handle WalkBackward (negate Y) and SideStepLeft (negate X) manually. - float localY = 0f; - float localX = 0f; + float localY = 0f; + float localX = 0f; - if (input.Forward) - localY = stateVel.Y; // WalkAnimSpeed or RunAnimSpeed - else if (input.Backward) - localY = -(MotionInterpreter.WalkAnimSpeed * 0.65f); // retail backward is ~65% walk + if (input.Forward) + localY = stateVel.Y; + else if (input.Backward) + localY = -(MotionInterpreter.WalkAnimSpeed * 0.65f); - if (input.StrafeRight) - localX = MotionInterpreter.SidestepAnimSpeed * 0.5f; - else if (input.StrafeLeft) - localX = -MotionInterpreter.SidestepAnimSpeed * 0.5f; + if (input.StrafeRight) + localX = MotionInterpreter.SidestepAnimSpeed * 0.5f; + else if (input.StrafeLeft) + localX = -MotionInterpreter.SidestepAnimSpeed * 0.5f; - // Restore the vertical velocity snapshotted before DoMotion clobbered it. - // Rotation about Z does not affect the Z component, so world Vz == local Vz. - _body.set_local_velocity(new Vector3(localX, localY, savedWorldVz)); + _body.set_local_velocity(new Vector3(localX, localY, savedWorldVz)); + } // ── 3. Jump (charged) ───────────────────────────────────────────────── // Hold spacebar to charge (0→1 over JumpChargeRate seconds). @@ -301,9 +296,9 @@ public sealed class PlayerMovementController float groundZ = resolveResult.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); bool wasAirborne = !_body.OnWalkable; diff --git a/src/AcDream.Core/Physics/MotionInterpreter.cs b/src/AcDream.Core/Physics/MotionInterpreter.cs index bfc2165..a6193b6 100644 --- a/src/AcDream.Core/Physics/MotionInterpreter.cs +++ b/src/AcDream.Core/Physics/MotionInterpreter.cs @@ -539,8 +539,17 @@ public sealed class MotionInterpreter if (InterpretedState.ForwardCommand == MotionCommand.RunForward) MyRunRate = InterpretedState.ForwardSpeed; - var localVelocity = get_state_velocity(); - PhysicsObj.set_local_velocity(localVelocity); + // Only replace velocity when grounded. While airborne, the physics + // 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 ───────────────────────────────────────────────────