fix(net): remote jumps were stuck at apex — let physics detect landing instead of UP-driven clear
Cause of "remote characters jump up and get stuck in the air":
K-fix9 cleared rm.Airborne on every UpdatePosition, but ACE
broadcasts UPs during the arc (peak / mid-fall / land) at
~5-10 Hz. The first UP after a jump:
1. Snapped body position to server mid-arc Z (often the apex).
2. Cleared rm.Airborne -- restored Contact + OnWalkable, removed
the Gravity flag.
3. Next per-tick: apply_current_movement reads
InterpretedState (Ready) and stomps Body.Velocity to
(X, Y, 0).
Body stuck at apex Z forever.
Fix: do not auto-clear Airborne on UP. The position snap stays
authoritative -- if ACE says the body is at Z=68 mid-arc we
render Z=68, but we keep integrating gravity from there.
Per-tick post-resolve now detects a real landing -- mirrors the
local-player landing path in PlayerMovementController: when the
resolver returns IsOnGround && Velocity.Z <= 0, clear Airborne,
restore Contact + OnWalkable, remove Gravity, zero residual
downward velocity, and call HitGround so the sequencer can swap
Falling to idle/locomotion.
ACDREAM_DUMP_MOTION=1 logs each landing as
"VU.land guid=0x... Z=...".
Tests stay 1222 green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
8db7a9ec28
commit
68d521df4f
1 changed files with 49 additions and 14 deletions
|
|
@ -2506,20 +2506,26 @@ public sealed class GameWindow : IDisposable
|
|||
rmState.Body.Orientation = rot;
|
||||
}
|
||||
rmState.Body.Position = worldPos;
|
||||
// K-fix9 (2026-04-26): UpdatePosition is the server's
|
||||
// authoritative re-grounding signal after a jump. Clear the
|
||||
// airborne flag, restore Contact + OnWalkable, and disable
|
||||
// gravity so the next per-tick remote update goes back to
|
||||
// the regular ground-clamped path. The server typically
|
||||
// sends a UP at the apex / mid-arc / land — our integration
|
||||
// fills in between.
|
||||
if (rmState.Airborne)
|
||||
{
|
||||
rmState.Airborne = false;
|
||||
rmState.Body.TransientState |= AcDream.Core.Physics.TransientStateFlags.Contact
|
||||
| AcDream.Core.Physics.TransientStateFlags.OnWalkable;
|
||||
rmState.Body.State &= ~AcDream.Core.Physics.PhysicsStateFlags.Gravity;
|
||||
}
|
||||
// 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
|
||||
// the FIRST UP after the jump, which:
|
||||
// * restored Contact + OnWalkable,
|
||||
// * removed the Gravity flag,
|
||||
// * caused the next per-tick to stomp Velocity via
|
||||
// apply_current_movement (reading InterpretedState =
|
||||
// Ready, so Velocity.Z went to 0),
|
||||
// …so the body got stuck at the server-broadcast apex Z,
|
||||
// visibly hovering. The fix: leave Airborne true; the
|
||||
// per-tick post-resolve logic detects an actual landing
|
||||
// (resolveResult.IsOnGround && Velocity.Z <= 0) and clears
|
||||
// it then. Mirrors how PlayerMovementController re-grounds
|
||||
// the local player at the bottom of its arc.
|
||||
//
|
||||
// The position-snap above is still authoritative — if ACE
|
||||
// says the body is at Z=68 mid-arc, we render Z=68. But we
|
||||
// continue integrating gravity from there, so the body
|
||||
// proceeds along the parabolic path between UPs.
|
||||
// Adopt the server's cell ID as the transition starting cell.
|
||||
// Retail authoritatively hard-snaps cell membership here too; our
|
||||
// per-tick ResolveWithTransition sweep then advances CheckCellId
|
||||
|
|
@ -4723,6 +4729,35 @@ public sealed class GameWindow : IDisposable
|
|||
rm.Body.Position = resolveResult.Position;
|
||||
if (resolveResult.CellId != 0)
|
||||
rm.CellId = resolveResult.CellId;
|
||||
|
||||
// K-fix15 (2026-04-26): post-resolve landing
|
||||
// detection for airborne remotes. Mirrors
|
||||
// PlayerMovementController's local-player landing
|
||||
// path: when the resolver says we're on ground AND
|
||||
// velocity is no longer pointing up, transition
|
||||
// back to grounded — clear Airborne, restore
|
||||
// Contact + OnWalkable, remove Gravity, zero any
|
||||
// residual downward velocity, and trigger
|
||||
// HitGround so the sequencer can swap from
|
||||
// Falling → idle/locomotion. Without this, an
|
||||
// airborne remote falls through the floor (gravity
|
||||
// keeps building Velocity.Z negative until the
|
||||
// sphere-sweep clamps each frame, but Airborne
|
||||
// stays true forever).
|
||||
if (rm.Airborne
|
||||
&& resolveResult.IsOnGround
|
||||
&& rm.Body.Velocity.Z <= 0f)
|
||||
{
|
||||
rm.Airborne = false;
|
||||
rm.Body.TransientState |= AcDream.Core.Physics.TransientStateFlags.Contact
|
||||
| AcDream.Core.Physics.TransientStateFlags.OnWalkable;
|
||||
rm.Body.State &= ~AcDream.Core.Physics.PhysicsStateFlags.Gravity;
|
||||
rm.Body.Velocity = new System.Numerics.Vector3(
|
||||
rm.Body.Velocity.X, rm.Body.Velocity.Y, 0f);
|
||||
rm.Motion.HitGround();
|
||||
if (Environment.GetEnvironmentVariable("ACDREAM_DUMP_MOTION") == "1")
|
||||
Console.WriteLine($"VU.land guid=0x{serverGuid:X8} Z={rm.Body.Position.Z:F2}");
|
||||
}
|
||||
}
|
||||
|
||||
ae.Entity.Position = rm.Body.Position;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue