diff --git a/docs/research/2026-05-05-issue-42-handoff.md b/docs/research/2026-05-05-issue-42-handoff.md new file mode 100644 index 0000000..b5c63dd --- /dev/null +++ b/docs/research/2026-05-05-issue-42-handoff.md @@ -0,0 +1,96 @@ +# #42 follow-up — PhysicsEngine.ResolveWithTransition airborne XY drift + +**Context:** L.3 motion port landed in this branch (commits `de129bc` +`40d88b9` `2365c8c` `d57ace0` `c26bbbb` `b37b713`) — player-remote +running/walking/strafing/NPCs all visually verified clean. M4 jump +landing was fixed (CellId update). But that fix re-enabled +`ResolveWithTransition` per-tick during airborne arcs and exposed a +pre-existing PhysicsEngine bug: stationary jumps render with ~1 m +horizontal offset from the actor's actual XY, then snap back on the +next UM/UP. + +**Confirmed root cause** (A/B-tested 2026-05-05): drift originates +inside `ResolveWithTransition`, not from wire data, local Euler error, +or stale velocity. With the sweep skipped, jumps render geometrically +correct (but body falls through floor). With it enabled, jumps land +correctly but show the drift. So the bug lives in the sweep. + +## Most likely mechanism + +**Initial-overlap depenetration along non-+Z terrain normal.** At +jump start the collision sphere is touching the floor at body Z. Most +outdoor terrain triangles have non-vertical normals (small horizontal +component). The sweep's first-frame action is to resolve the +penetration by separating the sphere along the contact normal — and +on a tilted triangle that separation has horizontal magnitude. The +body gets shoved sideways the first frame; the rest of the arc +carries the offset. + +Direction-correlation test: jump at multiple landblock positions; if +drift direction varies with terrain slope orientation (not actor +facing), this hypothesis is confirmed. + +Other candidates ranked by probability: +2. Step-down probe firing despite `isOnGround: false` parameter. +3. EdgeSlide on near-vertical motion against near-vertical surface. + +## Files to investigate + +- `src/AcDream.Core/Physics/PhysicsEngine.cs` — `ResolveWithTransition` + + any internal `CTransition` / `find_valid_position` helpers. The + initial-overlap depenetration path is the primary target. +- Reference at `docs/research/named-retail/acclient_2013_pseudo_c.txt`: + - `CTransition::find_valid_position` (called from `transition()`) + - `SpherePath` initialization + - Verbatim retail depenetration logic for airborne bodies + +If our port differs from retail in this region, that diff is the bug. + +## Fix paths (in order of preference) + +**(a) Skip initial-overlap depenetration when airborne.** Gate the +"separate from initial contact plane" step inside +`ResolveWithTransition` on `isOnGround: true`. Trusts the previous +tick's resolve to have left the body in a non-overlapping position. +Most likely correct fix. + +**(b) Zero step-up/down for airborne sweeps.** Pass +`stepUpHeight: 0f, stepDownHeight: 0f` from +`GameWindow.cs:6478+` when `rm.Airborne`. No side effects since +airborne bodies don't step. + +**(c) Stripped airborne sweep.** Replace the full sphere sweep with a +simpler vertical sphere-vs-terrain intersection + wall-collision +stop. Loses retail fidelity but eliminates all three mechanisms. +Probably overkill if (a) or (b) suffices. + +## Repro + +1. Launch acdream + retail client side-by-side on local ACE + (127.0.0.1:9000). +2. Have a retail-controlled toon stand still on outdoor terrain. +3. Jump in place. +4. Observe acdream window: arc shows ~1 m XY offset, lands offset, + snaps back on next inbound UM. + +To verify hypothesis (1) specifically: repeat the jump on terrain +patches with different visible slope orientations; if drift direction +changes accordingly, depenetration is confirmed. + +## Acceptance + +- Stationary jumps render at the actor's actual XY (no perceptible + drift, no snap-back on next UM). +- Wall-collision airborne still works (jumping into doorways, jumping + puzzles where you have to thread between platforms or through arches). + +## Operating notes + +- This is a `PhysicsEngine` bug, not a motion-port bug. The L.3 work + is done; this is a separate investigation. +- The `[VU.WIRE]` instrumentation idea from #42's earlier draft can + be skipped — we already proved wire data isn't the source via the + A/B test. +- cdb attach to retail (`docs/research/2026-05-04-l3-port/`-adjacent + toolchain documented in CLAUDE.md) is available if comparing + retail's airborne sweep behavior against ours becomes useful.