fix(motion): L.3 M4 jump-CellId + file #42 airborne XY drift

CellId fix:

L.3 M2 introduced OnLivePositionUpdated player-remote routing that
returned without setting `rmState.CellId = p.LandblockId`. The legacy
path always set this (formerly at line 3601). Airborne player remotes
fall through to the legacy TickAnimations path which gates
ResolveWithTransition on `rm.CellId != 0`; without the cell-id update
the sphere sweep was skipped, K-fix15 landing detection never fired,
and the body fell through the floor on jumps.

Fix: set `rmState.CellId = p.LandblockId` early in the M2 player-remote
branch (after orientation snap, before any return).

User-verified 2026-05-05: jumps now land cleanly with sequencer
leaving Falling on landing.

#42 filed:

Visual verification of M4 also exposed a ~1 m horizontal drift on
stationary jumps (body arcs through the air offset from actor's actual
position; lands at offset; snaps back on next UM). User confirms this
is pre-existing, masked by the legacy path's hard-snap-on-every-UP
behavior that M2 explicitly removed per retail spec
(03-up-routing.md § 3 "AIRBORNE NO-OP"). Filed as #42 with three
candidate fix paths (pragmatic legacy-restore, root-cause investigation,
or hybrid soft-correction).

M5 NPCs verified clean (legacy path unchanged).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Erik 2026-05-05 15:35:42 +02:00
parent d57ace0177
commit c26bbbb84e
2 changed files with 115 additions and 0 deletions

View file

@ -124,6 +124,111 @@ the same direction. Add a `LastUMUpdateTime` grace window (e.g.
- No spurious cycle thrashing during turning while running (ObservedOmega
doesn't trigger velocity-bucket changes).
## #42 — Airborne XY drift on observed player remote jumps (~1 m horizontal offset over arc)
**Status:** OPEN
**Severity:** MEDIUM (pre-existing; exposed by L.3 M2 airborne UP no-op)
**Filed:** 2026-05-05
**Component:** physics / motion (airborne local-integration)
**Description:** When observing a retail-controlled remote that jumps
in place (no horizontal input), the visible jump arc renders with
a small horizontal offset from the actor's actual position — typically
~1 m to one side and slightly forward. Body lands at offset position
(~X+1m). On the next inbound UM/UP from the actor (e.g., turning or
moving), the body snaps back to the server's authoritative X.
User report 2026-05-05 (after M4 CellId fix): "I stand at position X
and jump, it looks like im jumping slightly to the left of X like
1m-ish (if I observe jumping char from behind). It also lands at
X + 1m-ish. Position resets to X when I issue some other command
to the client like turning."
**Why it surfaced now:**
Pre-M2 (legacy path), `OnLivePositionUpdated` hard-snapped
`rmState.Body.Position = worldPos` on EVERY UP including mid-arc
airborne ones. ACE broadcasts intermediate UPs at ~510 Hz during
the jump arc with the actor's authoritative mid-arc position;
each snap kept our local body close to server, masking
local-integration error.
L.3 M2 (commit 40d88b9) implemented the retail-spec airborne no-op
in `OnLivePositionUpdated`:
```csharp
if (!update.IsGrounded) {
entity.Position = rmState.Body.Position;
return;
}
```
Per `docs/research/2026-05-04-l3-port/03-up-routing.md` § 3:
> Air branch (`has_contact == 0`): the function falls through to
> `return 0`. This is the "AIRBORNE NO-OP" … The body keeps
> integrating gravity locally; received position is discarded.
This matches retail `MoveOrTeleport @ 0x00516330` semantics. But it
removes the periodic server snapping that was masking ~1 m of
accumulated local-integration drift. The drift is pre-existing — the
user reports having seen it before — but is now visible for the
full arc duration instead of being corrected every ~200 ms.
**Suspected sources of XY drift on a stationary jump:**
1. **ACE wire VectorUpdate may have non-trivial XY components** even
when the actor is standing still. `OnLiveVectorUpdated` (line
3235) sets `rm.Body.Velocity = update.Velocity` verbatim; no
filtering. Worth instrumenting `[VU.WIRE]` to confirm wire XY for
stationary jumps.
2. **Pre-jump `rm.Body.Velocity` residual** — should be zero for the
M3 grounded path (cleared each tick at line 6118 area), but worth
confirming via diag.
3. **EdgeSlide during sphere sweep** — if `ResolveWithTransition`
catches an edge mid-arc (unlikely for a stationary in-place jump
but possible), the sweep could push the body horizontally.
4. **Render-rate-dependent dt** — local Euler integration uses
`Silk.NET.OnRender(double deltaSeconds)` raw; retail clamps to
30 Hz. Sub-tick error accumulates over a 2 s arc.
**Fix paths:**
a. **Pragmatic** (revert to legacy behavior): hard-snap
`rm.Body.Position = worldPos` on airborne UPs too. Diverges from
retail spec, but ACE behavior diverges from retail too (ACE
broadcasts mid-arc UPs while retail apparently doesn't). Masks
the drift identically to pre-M2. Lowest-risk visual fix.
b. **Investigate** the actual XY source via VU/UP instrumentation
and `body.Velocity` snapshots, then fix the root cause. May be
an ACE-specific velocity-quirk we should clamp at
`OnLiveVectorUpdated`, or a clock-source mismatch in our Euler.
c. **Hybrid**: keep airborne no-op for body.Position translation but
re-introduce a soft-correction on mid-arc UPs (server-position-
biased lerp) — slowly pulls body toward server truth without
the rubber-band.
**Files:**
- `src/AcDream.App/Rendering/GameWindow.cs` `OnLiveVectorUpdated`
L3228+ (sets velocity from wire verbatim)
- `src/AcDream.App/Rendering/GameWindow.cs` `OnLivePositionUpdated`
player-remote airborne no-op L3502+ (the no-op that exposed this)
- `src/AcDream.App/Rendering/GameWindow.cs` legacy airborne TickAnimations
L6478+ (gravity integration via UpdatePhysicsInternal)
**Acceptance:**
- Visual jump arc + landing render at the actor's actual XY position,
no perceptible horizontal offset, no snap-back on next UM/UP.
---
## #41 — Residual sub-decimeter blips on observed player remotes (M3 baseline)
**Status:** OPEN