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:
parent
d57ace0177
commit
c26bbbb84e
2 changed files with 115 additions and 0 deletions
105
docs/ISSUES.md
105
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
|
- No spurious cycle thrashing during turning while running (ObservedOmega
|
||||||
doesn't trigger velocity-bucket changes).
|
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)
|
## #41 — Residual sub-decimeter blips on observed player remotes (M3 baseline)
|
||||||
|
|
||||||
**Status:** OPEN
|
**Status:** OPEN
|
||||||
|
|
|
||||||
|
|
@ -3432,6 +3432,16 @@ public sealed class GameWindow : IDisposable
|
||||||
// position only; heading would otherwise lag the queue.
|
// position only; heading would otherwise lag the queue.
|
||||||
rmState.Body.Orientation = rot;
|
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
|
// Diagnostic (ACDREAM_REMOTE_VEL_DIAG=1): roll the previous
|
||||||
// server-pos snapshot forward AND print the per-UP comparison
|
// server-pos snapshot forward AND print the per-UP comparison
|
||||||
// between the max sequencer speed observed since last UP and
|
// between the max sequencer speed observed since last UP and
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue