feat(physics): P5 commit 2 - arm the ConstraintManager leash on accepted positions (#167)

Wire ConstraintManager.ConstrainTo at every current acdream inbound-position
acceptance seam, matching retail SmartBox::HandleReceivedPosition
(0x00453fd0):

- Remote (player + NPC): LiveEntityNetworkUpdateController arms right after
  the hard-teleport branch (remotePlacementRequired) returns - reaching that
  point already means MoveOrTeleport did NOT hard-place - anchored to the
  object's own live IPhysicsObjHost.Position.
- Local player teleport: PlayerMovementController.SetPositionCore now runs
  UnConstrain (retail teleport_hook 0x00514ed0, previously a no-op because
  nothing armed the leash) then re-arms anchored to the just-snapped
  position, composing with the existing StopCompletelyAtPhysicsObjectBoundary
  velocity zero rather than duplicating it. CommitPreparedPosition mirrors
  the same pair for the deferred player-mode-entry commit path.
- Local player ForcePosition: PlayerMovementController.BlipPosition arms
  with NO preceding UnConstrain (retail BlipPlayer/SetPositionSimple
  survives motion/velocity/stick, and the leash is no different).

Push PhysicsBody.IsFullyConstrained from PositionManager.IsFullyConstrained
at the SAME per-tick chokepoint each pump already runs AdjustOffset
(PlayerMovementController.Update, RuntimeRemotePhysicsUpdater.Tick/TickHidden)
so TS-35's read gate in jump_is_allowed sees live state instead of a stub
that is never written.

Tests: local-player arm/teardown/rearm/taper/jump-refusal (Runtime.Tests,
PlayerMovementControllerTests), remote-tick IsFullyConstrained push
(Runtime.Tests, RuntimePhysicsStateTests). Full Core/Runtime/App suites
green with no regressions.
This commit is contained in:
Erik 2026-07-30 12:05:19 +02:00
parent 378d0b6ca0
commit 7719d25bc5
5 changed files with 330 additions and 3 deletions

View file

@ -1353,6 +1353,30 @@ internal sealed class LiveEntityNetworkUpdateController
return;
}
// #167 (Campaign P P5): retail SmartBox::HandleReceivedPosition
// (0x00453fd0) arms the ConstraintManager leash for every remote
// MoveOrTeleport call that returns nonzero (did NOT hard-teleport —
// the remotePlacementRequired branch above already handled and
// returned on the hard-teleport case), anchored to the object's OWN
// current position, generically for player AND NPC remotes (the
// disassembly's "this == player" branch loads identical constants
// either way — see ConstraintDistance). ConstraintManager.ConstrainTo
// captures ConstraintPosOffset = distance(anchor, host.Position) at
// call time; since the anchor here IS host.Position (read live,
// matching every other PositionManager/TargetManager consumer's
// notion of "this object's position"), this always (re)starts the
// leash at zero displacement on a fresh accepted Position, matching
// retail's per-packet re-anchor.
// docs/research/2026-07-30-constraint-leash-constants.md §2/§3.2.
if (rmState.Host is { } remoteConstraintHost)
{
AcDream.Core.Physics.Position anchor = remoteConstraintHost.Position;
remoteConstraintHost.PositionManager.ConstrainTo(
anchor,
AcDream.Core.Physics.Motion.ConstraintDistance.GetStartConstraintDistance(anchor.ObjCellId),
AcDream.Core.Physics.Motion.ConstraintDistance.GetMaxConstraintDistance(anchor.ObjCellId));
}
// L.3 M2 (2026-05-05): retail-faithful MoveOrTeleport routing for
// player remotes. Mirrors CPhysicsObj::MoveOrTeleport
// (acclient @ 0x00516330) — airborne no-op, far-snap, near