The user live-tested route 4a and reported two defects on player remotes: a
remote holds the falling animation after landing before finally landing, and 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. Do NOT revert 44830a0e — reverting would
restore the per-packet render slam 4a removed without touching either defect.
Bug B's root cause is identified and already covered by open issue #32, whose
text names both symptoms in one sentence. Both landing sites assert
TransientState |= Contact | OnWalkable unconditionally, where retail derives it
from the contact plane — CPhysicsObj::SetPositionInternal @0x00515330
(`if (contact_plane.N.z < floor_z) set_on_walkable(0) else set_on_walkable(1)`).
A steep roof is contact but NOT on_walkable; asserting both suppresses the slide
response, so the body sits until the server's positions walk 4 m away and
AP-87's threshold snaps it. That is the blip. Verified byte-identical pre-4a via
`git show 19d95094:`.
Bug B's *visible shape* IS 4a's: pre-4a every packet slammed the render entity
to the wire pose, so a stuck body flickered toward the true sliding position
5-10x per second — jitter rather than a clean hold.
Bug A stops at the goal's stop-condition rather than getting a speculative fix.
Three hypotheses with non-overlapping fixes; picking wrong means changing a
retail-ported gate on a guess. Retail's mechanism is already fully decoded, so
what is missing is OUR runtime state — no cdb trace against retail is needed.
Adds ACDREAM_PROBE_REMOTE_LANDING (PhysicsDiagnostics, read once at startup per
the diagnostic-owner rule, one bool check when off). It logs both landing sites
immediately before HitGround, and — the most diagnostic signal — emits a
separate line when a site is reached but the gravity gate is about to no-op,
which is hypothesis 1 (a wholesale Body.State write wiping the transient Gravity
bit mid-air, exactly AP-81's stated risk). Temporary instrumentation, marked for
stripping once the evidence is in.
Evidence recorded rather than new bugs filed: #32 gains the observation, the
root cause and the #173/AD-10 dependency caveat; AP-87 gains a live instance of
its stated risk; AD-10's stale file:line is corrected to RemoteMotionCombiner
with a note that its terrain-only normal cannot see a house roof at all.
Also files #308 — a SECOND flaky test, distinct from #302, which was twice
misattributed to it before being written down. #302 is a GC-allocation assertion
in App.Tests; #308 is a wall-clock deadline loop in Core.Net.Tests that fails
only under full-suite CPU contention (0 failures in 4 isolated runs). Conflating
them hides one, and an agent told to "ignore the known flake" would wave through
a real transport regression.
Gates: complete Release solution 10,938 passed / 4 skipped / 0 failed.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
142 lines
7.8 KiB
Markdown
142 lines
7.8 KiB
Markdown
# 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.
|