# 2026-08-04 — Remote landing investigation (Bug A / Bug B, route 4a live test) **Status:** Report-only. No fix applied. Companion to `docs/ISSUES.md` #32 (the two symptoms below were both already named by that row's "lands on roof in falling animation, can't slide off" line) and to the `ACDREAM_PROBE_REMOTE_LANDING` probe added in `PhysicsDiagnostics.cs`. ## Background The user's live two-client route 4a test surfaced two PLAYER-remote defects: - **Bug A** — a remote stays in the falling animation after landing, then visibly lands (the pose clears) only after a delay. - **Bug B** — a remote jumping onto a house plants on the roof where retail slides off, then blips to the slid-down position. Neither is a route 4a regression (`44830a0e`, 2026-08-04). The landing block in `LiveEntityNetworkUpdateController.cs` that both bugs touch is byte-identical to the pre-4a version — verified via `git show 19d95094:src/AcDream.App/Physics/LiveEntityNetworkUpdateController.cs`, which shows the same unconditional `Body.TransientState |= Contact | OnWalkable` at the same landing site before route 4a existed. **Do not revert `44830a0e`.** ## Bug B — root-caused, not yet fixed See `docs/ISSUES.md` #32's 2026-08-04 addendum and `docs/architecture/retail-divergence-register.md` rows AD-10 and AP-87 for the full citation chain. Summary: the landing block force-sets `OnWalkable` unconditionally, where retail derives it from the contact plane (`CPhysicsObj::SetPositionInternal` @0x00515330, pseudo-C :283501-283509 — `on_walkable = contact_plane.N.z >= floor_z`). A steep roof is Contact but not on_walkable; forcing both suppresses the slide, and the body sits until AP-87's 4 m drift-snap backstop blips it to the server's already-slid position. A correct fix additionally depends on **#173**'s remote collision-velocity reflect (shipped, its dedicated gate folded into the unrun Campaign P matrix scenario 8) and on **AD-10**'s slope projection, which is terrain-only and cannot see building/EnvCell geometry at all. ## Bug A — three hypotheses, no overlapping fix The falling-pose-lingers symptom has three candidate root causes. Each points at a different code path with a non-overlapping fix, so guessing which one applies risks fixing the wrong thing (or "fixing" all three and losing track of which one mattered). The `ACDREAM_PROBE_REMOTE_LANDING=1` probe (added 2026-08-04, `src/AcDream.Core/Physics/PhysicsDiagnostics.cs`: `LogRemoteLanding`/`LogRemoteLandingGateNoOp`) was built specifically to discriminate them from one live capture at both landing-detection sites: `LiveEntityNetworkUpdateController.cs`'s UpdatePosition-driven landing block (`site=controller`) and `RuntimeRemotePhysicsUpdater.cs`'s per-tick VectorUpdate landing branch (`site=per-tick`, ~:493-551). ### H1 — Gravity state bit wiped mid-air, HitGround's gate silently no-ops (CONFIRMED mechanism, occurrence unconfirmed) `MotionInterpreter.HitGround()` (`MotionInterpreter.cs:2426`, retail `CMotionInterp::HitGround` 0x00528ac0) starts with: ```csharp if (!PhysicsObj.State.HasFlag(PhysicsStateFlags.Gravity)) return; ``` This mirrors retail's own gate (`state & 0x400`, decomp raw 305996-306014) — retail requires Gravity still set at landing, same as we do. If something clears the Gravity bit on a remote's body BEFORE its landing edge fires, `HitGround()` returns immediately: no `RemoveLinkAnimations`, no `apply_current_movement` re-dispatch, and the sequencer never receives the command that would swap Falling → the landing link → the grounded cycle. The falling pose then only clears once some LATER event forces a cycle (a subsequent UpdateMotion, or the generic per-tick funnel eventually reasserting a grounded stance by a different path) — matching the observed "delay." Both known Gravity-clear sites in the codebase are the POST-HitGround "DR bookkeeping" clears (`LiveEntityNetworkUpdateController.cs` and `RuntimeRemotePhysicsUpdater.cs`, both guarded by `IsCurrentStateAuthority`/`IsCurrentOwner` version checks) — i.e. the intended clear happens AFTER HitGround, not before. No third site was found that clears Gravity early in this pass; if H1 is the live culprit, the probe should catch a case where an inbound authority-version race (a second UP superseding the landing packet, or the state-authority version check failing) caused the clear to land ahead of a re-entrant landing detection. **Discriminator:** `gravitySet=false` in the `[remote-landing]` line, paired with a `[remote-landing-gate]` `NOOP` line at the same site. ### H2 — No `DefaultSink` bound at the landing edge (hypothesized, plausible) `HitGround()`'s `apply_current_movement` dispatches through `Motion.DefaultSink`. The controller site only calls `EnsureRemoteMotionBindings` (which creates the sink) when `_animatedEntities.TryGetValue(entity.Id, out var aeForLand)` finds an entry AND `aeForLand.Sequencer is not null` (`LiveEntityNetworkUpdateController.cs`, right before the probe call added 2026-08-04). A remote whose `LiveEntityAnimationState`/`Sequencer` hasn't been created yet at the exact frame its landing UP arrives (freshly streamed-in, or a presentation race) would reach `HitGround()` with Gravity still set but no sink to drive — the re-apply computes correctly but has nowhere to write, so nothing visible changes until the sink is bound on a later frame and something else (the stale `VU.land` per-tick branch, or the next ordinary UpdateMotion) catches the pose up. **Discriminator:** `gravitySet=true`, `hasDefaultSink=false` in the `[remote-landing]` line. ### H3 — HitGround dispatches correctly; the delay is downstream in animation-scheduler consumption (hypothesized, weakest evidence) If both Gravity and the sink are fine at the landing edge, the failure (if it still reproduces) is not in the physics/motion-interpreter layer at all — `HitGround()` successfully queues the landing link, but the render-side animation scheduler (`LiveEntityAnimationScheduler`/ `LiveEntityAnimationPresenter`) doesn't drain and apply that queued transition for several frames, so the falling pose visibly persists even though the underlying `MotionInterpreter` state is already correct. **Discriminator:** `gravitySet=true`, `hasDefaultSink=true`, sequencer `seqStyle`/`seqMotion` at the landing edge necessarily still reads the pre-landing (Falling) values (the read happens immediately before HitGround runs, so this alone doesn't distinguish success from H3) — confirming/refuting H3 requires cross-referencing the SAME guid's subsequent `[remote-landing]`/`VU.land`/`ACDREAM_DUMP_MOTION` SetCycle lines over the next several frames to see whether the cycle swap is applied promptly or lags. ## Decision table for tomorrow's capture Run with `ACDREAM_PROBE_REMOTE_LANDING=1` (pair with `ACDREAM_DUMP_MOTION=1` for the existing `VU.land`/SetCycle lines) across a route 4a session that reproduces Bug A, then read the first `[remote-landing]` line for the affected guid at its landing edge: | `gravitySet` | `hasDefaultSink` | Falling pose clears next frame? | Implicates | |---|---|---|---| | `false` (+ `[remote-landing-gate]` NOOP) | — | — | **H1** — fix where Gravity gets cleared/never-set before this landing edge | | `true` | `false` | — | **H2** — fix the sink-binding race (bind before the landing block runs, or defer the landing block until a sink exists) | | `true` | `true` | No — lags several frames in the `VU.land`/SetCycle trail | **H3** — fix the animation-scheduler consumption path, not physics | | `true` | `true` | Yes | Landing worked correctly on this instance — Bug A did not reproduce here; re-run to catch the failing case | Whichever row fires, the fix belongs in a DIFFERENT file/method than the other two rows, so this table should be read for exactly one row per capture before deciding where to touch code — the whole point of the probe was to avoid fixing all three speculatively.