fix(physics): AD-25 - remote collision response through ported HandleAllCollisions

Campaign P Slice P3 item 2. CPhysicsObj::handle_all_collisions
(0x00514780, pc:282647) is one uniform function retail calls
unconditionally after every SetPositionInternal, player or remote. The
gate is shouldReflect = !(prevOnWalkable && nowOnWalkable && !sledding).

RuntimeRemotePhysicsUpdater.Tick's post-resolve reflect was still the
2026-07-05 (#173) hand-inlined block, gated on
resolveResult.CollisionNormalValid and using two ad-hoc branches that
diverge from retail in exactly the cases the register row named:
  - non-sledding: old = "!prevOnWalkable && !nowOnWalkable" (reflects
    ONLY airborne-before-AND-after); retail reflects on every transition
    except grounded-before-AND-after.
  - sledding: old = "!(prevOnWalkable && nowOnWalkable)" (suppresses the
    bounce exactly when both grounded); retail's "!sledding" term forces
    shouldReflect = true unconditionally when sledding, the opposite
    polarity.

Both gaps meant a remote's post-landing reflect never ran on a
grounded-transition tick at all -- the "acdream lands clean and dead"
half of #166's slope-landing composite.

Replace the hand-inlined block with a direct call to
PhysicsObjUpdate.HandleAllCollisions -- the same verbatim port the
local player and every ordinary body already use via
CommitSetPositionTransition -- passing the same
prevContact/prevOnWalkable/nowOnWalkable values the old code already
computed. Narrower swap per the research's explicit recommendation:
does not fold in CommitSetPositionTransition's HitGround/LeaveGround
dispatch, leaving the remote's bespoke landing-detection block
(interp-queue-clear, animation-hook-specific logic) untouched. The call
is now unconditional (matching retail's own unconditional call site)
rather than gated behind CollisionNormalValid, since HandleAllCollisions
already no-ops the reflect step internally when no normal was found but
still runs the frames-stationary-fall bleed regardless.

PhysicsObjUpdate.HandleAllCollisionsTests already exhaustively pins the
retail formula in isolation; this change is a mechanical wiring swap to
the already-tested function using values the removed block already
computed. Full regression suites (Core.Tests 3991/2 skip, Runtime.Tests
425/0, App.Tests 3968/3 skip) pass unchanged -- no existing test pinned
the old broken formula.

Register: AD-25 retired (both the local-player and remote halves are now
the ported HandleAllCollisions); #166's reattribution note updated to
reflect the closure, leaving only TS-4 as the remaining blocker on that
issue's downhill-jump-glide acceptance.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-07-30 09:21:53 +02:00
parent dae5b1ea68
commit 8b5425498c
3 changed files with 65 additions and 67 deletions

View file

@ -4297,24 +4297,37 @@ now ports retail's confirmed 0.25f threshold); TS-4's removal was attempted
per its own fixture-first requirement and reproduced the historical
2026-04-30 wedge, so **TS-4 stays deferred** (see its register row and the
research doc §7 item 6 for the precise mechanism and the concrete next
step). Closure of #166 therefore pends: (a) TS-4 actually landing, and (b)
step).
**AD-25 closed (2026-07-30, Campaign P Slice P3):** the remote dead-
reckoning post-resolve now calls the exact ported
`PhysicsObjUpdate.HandleAllCollisions` — the same function the local
player and every ordinary body already use — instead of its own
hand-inlined, narrower reflect gate (`RuntimeRemotePhysicsUpdater.cs`,
`Tick`). The old gate reflected only airborne-before-AND-after and
suppressed the sledding case backwards; the ported gate reflects on any
transition except grounded→grounded-and-not-sledding, matching retail's
`shouldReflect = !(prevOnWalkable && nowOnWalkable && !sledding)`. Both
halves of AD-25 (local player and remote) are now retired.
Closure of #166 therefore pends only: (a) TS-4 actually landing, and (b)
re-checking Campaign P's final visual matrix item 5 ("Downhill jump
landing: sled glide + bounce") against a fresh capture — if the glide/
bounce still visibly mismatches retail after AP-7 alone (with TS-4 still
deferred), that capture, not a guess, is what should drive any further
work here, and it should go through cdb against live retail before any
client-side Sledding-state mechanism is written.
bounce still visibly mismatches retail after AP-7 and AD-25 (with TS-4
still deferred), that capture, not a guess, is what should drive any
further work here, and it should go through cdb against live retail
before any client-side Sledding-state mechanism is written.
**Where:** `PlayerMovementController.cs:874` (AD-25 suppression, remote
half only — local half already ported via #182),
**Where:** `src/AcDream.Runtime/Physics/RuntimeRemotePhysicsUpdater.cs`
(remote reflect, AD-25 — DONE 2026-07-30),
`src/AcDream.Core/Physics/PhysicsBody.cs` (`calc_friction`, AP-7 — DONE
2026-07-30), `src/AcDream.Core/Physics/BSPQuery.cs` (Path 6 steep branches,
TS-4 — deferred, shortcut still in place).
**Acceptance:** side-by-side downhill jump: acdream glides/bounces like
retail; flat-ground landings unchanged; no micro-bounce death spiral
(the reason AD-25 exists) reintroduced. Blocked on TS-4's eventual landing
per the note above.
(the reason AD-25 existed) reintroduced. Blocked on TS-4's eventual
landing per the note above.
## #164 — UM action-replay dispatches drop the per-action Autonomous bit