From c26bbbb84e89f2a8300da8dec0c5197a72d74e84 Mon Sep 17 00:00:00 2001 From: Erik Date: Tue, 5 May 2026 15:35:42 +0200 Subject: [PATCH] fix(motion): L.3 M4 jump-CellId + file #42 airborne XY drift MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- docs/ISSUES.md | 105 ++++++++++++++++++++++++ src/AcDream.App/Rendering/GameWindow.cs | 10 +++ 2 files changed, 115 insertions(+) diff --git a/docs/ISSUES.md b/docs/ISSUES.md index 40e3c14..b1cc364 100644 --- a/docs/ISSUES.md +++ b/docs/ISSUES.md @@ -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 ~5–10 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 diff --git a/src/AcDream.App/Rendering/GameWindow.cs b/src/AcDream.App/Rendering/GameWindow.cs index 08a8a05..2b07ebd 100644 --- a/src/AcDream.App/Rendering/GameWindow.cs +++ b/src/AcDream.App/Rendering/GameWindow.cs @@ -3432,6 +3432,16 @@ public sealed class GameWindow : IDisposable // position only; heading would otherwise lag the queue. rmState.Body.Orientation = rot; + // Adopt server's cell ID on every UP (airborne or grounded). + // Required by the legacy airborne path's per-tick + // ResolveWithTransition gate (rm.CellId != 0) — without this, + // an airborne player remote falls through the floor because + // the sphere sweep is skipped, K-fix15 landing detection never + // fires, and the body only re-grounds when the next UM forces + // ACE to broadcast a fresh IsGrounded=true UP that hits our + // landing transition branch below. + rmState.CellId = p.LandblockId; + // Diagnostic (ACDREAM_REMOTE_VEL_DIAG=1): roll the previous // server-pos snapshot forward AND print the per-UP comparison // between the max sequencer speed observed since last UP and