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.Orientation = rot;
|
||||||
}
|
}
|
||||||
rmState.Body.Position = worldPos;
|
rmState.Body.Position = worldPos;
|
||||||
// K-fix9 (2026-04-26): UpdatePosition is the server's
|
// K-fix15 (2026-04-26): DON'T auto-clear airborne on UP.
|
||||||
// authoritative re-grounding signal after a jump. Clear the
|
// ACE broadcasts UPs during the arc (peak / mid-fall / land)
|
||||||
// airborne flag, restore Contact + OnWalkable, and disable
|
// at ~5-10 Hz. The previous K-fix9 logic cleared Airborne on
|
||||||
// gravity so the next per-tick remote update goes back to
|
// the FIRST UP after the jump, which:
|
||||||
// the regular ground-clamped path. The server typically
|
// * restored Contact + OnWalkable,
|
||||||
// sends a UP at the apex / mid-arc / land — our integration
|
// * removed the Gravity flag,
|
||||||
// fills in between.
|
// * caused the next per-tick to stomp Velocity via
|
||||||
if (rmState.Airborne)
|
// apply_current_movement (reading InterpretedState =
|
||||||
{
|
// Ready, so Velocity.Z went to 0),
|
||||||
rmState.Airborne = false;
|
// …so the body got stuck at the server-broadcast apex Z,
|
||||||
rmState.Body.TransientState |= AcDream.Core.Physics.TransientStateFlags.Contact
|
// visibly hovering. The fix: leave Airborne true; the
|
||||||
| AcDream.Core.Physics.TransientStateFlags.OnWalkable;
|
// per-tick post-resolve logic detects an actual landing
|
||||||
rmState.Body.State &= ~AcDream.Core.Physics.PhysicsStateFlags.Gravity;
|
// (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.
|
// Adopt the server's cell ID as the transition starting cell.
|
||||||
// Retail authoritatively hard-snaps cell membership here too; our
|
// Retail authoritatively hard-snaps cell membership here too; our
|
||||||
// per-tick ResolveWithTransition sweep then advances CheckCellId
|
// per-tick ResolveWithTransition sweep then advances CheckCellId
|
||||||
|
|
@ -4723,6 +4729,35 @@ public sealed class GameWindow : IDisposable
|
||||||
rm.Body.Position = resolveResult.Position;
|
rm.Body.Position = resolveResult.Position;
|
||||||
if (resolveResult.CellId != 0)
|
if (resolveResult.CellId != 0)
|
||||||
rm.CellId = resolveResult.CellId;
|
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;
|
ae.Entity.Position = rm.Body.Position;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue