diff --git a/CLAUDE.md b/CLAUDE.md
index a030a823..eeed4445 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -131,8 +131,10 @@ was user-accepted 2026-07-29. **Campaign P — physics retail-feel parity
(`docs/plans/2026-07-29-physics-parity-campaign.md`) is the ACTIVE
user-directed detour** closing every physics-scope gap from the 2026-07-29
audit (stat-coupled movement, response-layer edge family, remote residuals,
-entry restrictions/water, #262/#153/#167, ledger). Vendor browsing (Slice 5)
-and authoritative vendor transactions resume after Campaign P closes.
+entry restrictions/water, #262/#153/#167, ledger). P5 item 1 (#167,
+ConstraintManager leash constants + arming) is CLOSED 2026-07-30; #153 and
+#72 remain open in P5. Vendor browsing (Slice 5) and authoritative vendor
+transactions resume after Campaign P closes.
**Modern Runtime/performance status:** Slices A–K of
`docs/plans/2026-07-24-modern-runtime-architecture.md` are complete. Slice L is
diff --git a/docs/ISSUES.md b/docs/ISSUES.md
index b01f33cb..12ecd79f 100644
--- a/docs/ISSUES.md
+++ b/docs/ISSUES.md
@@ -4217,7 +4217,20 @@ Test: `GpuWorldStateTests.RelocateEntity_StrandedInPending_MovesToLoadedTarget`
## #167 — ConstraintManager leash unported (arming + two unknown x87 constants)
-**Status:** OPEN (deferred, filed 2026-07-03 during R5-V1)
+**Status:** DONE — 2026-07-30 (Campaign P Slice P5). Both blockers were
+research-solved without a cdb session: the two x87-elided constants were
+recovered by disassembling the matching retail binary's raw machine code
+(`docs/research/2026-07-30-constraint-leash-constants.md`), and the arming
+site is now every current acdream inbound-position acceptance seam. Commit
+`e0629145` (constants + `ConstraintDistance`), commit `7719d25b` (arming at
+`LiveEntityNetworkUpdateController` for remotes and
+`PlayerMovementController.SetPosition`/`BlipPosition` for the local player,
+plus the per-tick `PhysicsBody.IsFullyConstrained` push), and this commit
+(TS-35 retirement + stale-comment cleanup). Register row **TS-35** is
+deleted in the same session. Full Core/Runtime/App suites pass with no
+regressions; new conformance tests cover leash-armed jump refusal,
+teleport-vs-blip anchor/teardown behavior, taper reduction over ticks, and
+the remote-tick `IsFullyConstrained` push.
**Severity:** LOW (server-position rubber-band + jump-during-rubber-band gate)
**Component:** physics, constraint
@@ -4233,25 +4246,31 @@ not `SmartBox`, so nothing calls `PositionManager.ConstrainTo`, and
`IsFullyConstrained` stays false (= register **TS-35**'s current stub
behavior — jump never blocked by the leash).
-**Blockers:** (1) the two distance constants are **x87 float returns BN
-elided** — `GetStart/MaxConstraintDistance` decompile to a bare
+**Blockers (RESOLVED):** (1) the two distance constants were **x87 float
+returns BN elided** — `GetStart/MaxConstraintDistance` decompile to a bare
`this->m_position;` expression with the actual returned value lost to the
-FPU-return-elision artifact. Recovering them needs a live cdb read of `st0`
-after the call (retail debugger toolchain) or a Ghidra re-decompile with a
-corrected float-return signature. (2) The arming site (`SmartBox`'s inbound
-position-reconciliation branches A/B/C) has no acdream equivalent yet — wiring
-it means teaching acdream's position path to re-anchor the leash on every
-server position update, then feeding the `adjust_offset` taper into the body
-integration (same chokepoint as the sticky wiring, R5-V3).
+FPU-return-elision artifact. Recovered by disassembling the matching binary's
+raw machine code directly (no cdb needed): outdoor start 10 / indoor 5,
+outdoor max 50 / indoor 20 — ACE's start mapping is INVERTED (outdoor 5 /
+indoor 10); the binary wins. (2) The arming site (`SmartBox`'s inbound
+position-reconciliation branches A/B/C) had no acdream equivalent — wired at
+`LiveEntityNetworkUpdateController` (remotes, anchored to the object's own
+position) and `PlayerMovementController.SetPosition`/`BlipPosition` (local
+player, anchored to the received position), feeding the `AdjustOffset` taper
+into the body integration at the same per-tick chokepoint as the sticky
+wiring (R5-V3).
-**Where:** `src/AcDream.Core/Physics/Motion/ConstraintManager.cs` (class,
-unarmed); the read gate is `PhysicsBody.IsFullyConstrained` (TS-35) via
-`jump_is_allowed`. Decomp: `docs/research/2026-07-03-r5-managers/`.
+**Where:** `src/AcDream.Core/Physics/Motion/ConstraintManager.cs` (armed),
+`src/AcDream.Core/Physics/Motion/ConstraintDistance.cs` (constants); the read
+gate is `PhysicsBody.IsFullyConstrained` (former TS-35) via
+`jump_is_allowed`. Decomp: `docs/research/2026-07-03-r5-managers/`,
+`docs/research/2026-07-30-constraint-leash-constants.md`.
-**Acceptance:** the two constants are recovered (cdb/Ghidra), acdream arms the
-leash on inbound server positions, `IsFullyConstrained` fires while
-rubber-banding, and a jump attempt inside the tight leash is blocked
-(0x47) matching retail; TS-35 + this issue retire together.
+**Acceptance:** the two constants are recovered (byte-decoded from the
+binary), acdream arms the leash on inbound server positions,
+`IsFullyConstrained` fires while rubber-banding, and a jump attempt inside
+the tight leash is blocked (0x47) matching retail; TS-35 + this issue retired
+together.
## #160 — Remote moveto: run animation pace vs actual movement speed mismatch
diff --git a/docs/architecture/retail-divergence-register.md b/docs/architecture/retail-divergence-register.md
index 1175d64f..013c0ebf 100644
--- a/docs/architecture/retail-divergence-register.md
+++ b/docs/architecture/retail-divergence-register.md
@@ -262,7 +262,6 @@ AP-94..AP-112 for the confirmed retail-UI completion gaps.
| TS-32 | `ClientObjectTable` has no pre-queue for a child `CreateObject` that arrives before its parent (out-of-order PARENTED create); such objects are ingested as root objects and their `ContainerId` links a not-yet-known container. Retail's `null_object_table` + `null_weenie_object_table` hold unresolvable objects until the parent arrives | `src/AcDream.Core/Items/ClientObjectTable.cs` (`Ingest`) | PD↔`CreateObject` ordering is handled (upsert semantics); out-of-order PARENTED creates are observed only at high packet loss or in vendor/corpse multi-object bursts on non-loopback links; deferred to D.5.5+ | A container's child object arriving before the container is ingested as a root item — it won't appear in `GetContents` until the next `RecordMembership` or a move event corrects the parent link | `CObjectMaint::null_object_table` / `null_weenie_object_table` (acclient.h / named-retail pc) |
| TS-33 | **NARROWED 2026-07-15** — full AP tracker semantics are ported: MTS stamps time only; AP stamps complete cell-local Position + contact plane + time; `ShouldSendPositionEvent` compares cell/contact inside the interval and the complete Frame including orientation afterward. Residual: acdream's single update path snapshots the AP predicate, emits a same-update MTS first when input changed, then AP. Retail proves `UseTime` performs Should→AP, but MTS originates in separate input callbacks; their relative same-tick callback/wire order is not yet traced | `src/AcDream.Runtime/Gameplay/LocalPlayerOutboundController.cs` (pre/post network slots); `src/AcDream.Runtime/Gameplay/PlayerMovementController.cs` (ported tracker) | Preserve the pre-existing acdream wire order until a focused retail packet/breakpoint trace establishes input callback versus `UseTime`; do not infer it from `UseTime` alone | In the rare update where both packets are due, ACE may observe their position timestamps/action sequences in the opposite order from retail, shifting only that correction tick; stationary target-facing is live-gated because full-frame orientation now publishes | `CommandInterpreter::UseTime` 0x006B3BF0; `SendMovementEvent` 0x006B4680; `SendPositionEvent` 0x006B4770; `ShouldSendPositionEvent` 0x006B45E0; `Frame::is_equal` 0x00424C30 |
| TS-40 | Retail's `physics_obj->cell` ("placed in the world") is proxied by the explicit `PhysicsBody.InWorld` flag — set by `SnapToCell` (local player placement) and `RemoteMotion` construction (remotes exist only for world entities); consumed by `CMotionInterp`'s detached-object link-strip guards (`if (cell == 0) RemoveLinkAnimations`, raw @305627). Replaces the UNREGISTERED `CellPosition.ObjCellId == 0` proxy, which only the local player ever seeded (#145 `SnapToCell`), so every REMOTE body read "detached" and every dispatched transition link (door swings, remote walk↔run links) was stripped the same tick it was appended — the 2026-07-03 door-snap bug | `src/AcDream.Core/Physics/PhysicsBody.cs` (`InWorld`); `src/AcDream.Core/Physics/MotionInterpreter.cs` (3 guard sites) | acdream has no per-body CObjCell pointer; a boolean placement flag carries exactly the guard's retail meaning until cell-pointer plumbing exists | A body used without either placement path (a future entity class constructing bodies directly) reads detached and loses transition links until its creation site sets the flag | `CMotionInterp::DoInterpretedMotion` 0x00528360 tail @305627; `CPhysicsObj::RemoveLinkAnimations` |
-| TS-35 | `PhysicsBody.IsFullyConstrained` is a stub property (default `false`, never set by any physics code), read by `jump_is_allowed`'s verbatim `IsFullyConstrained` gate (raw 305524-305525) | `src/AcDream.Core/Physics/PhysicsBody.cs` (`IsFullyConstrained`) | R3-W3 needed the read site to port `jump_is_allowed`'s full chain. **R5-V1 CORRECTED the mechanism** (the earlier "per-cell contact-plane / doorway-jamming" guess was WRONG): the write side is the **ConstraintManager server-position rubber-band leash** — armed by `SmartBox::HandleReceivedPosition` on every inbound server position, `IsFullyConstrained` = `max*0.9 < offset`. R5-V1 ported `ConstraintManager` (`src/AcDream.Core/Physics/Motion/ConstraintManager.cs`) but does NOT arm it (no acdream `SmartBox` + two x87 distance constants BN elided) — so this read stays false. Arming = issue #167 | A body retail would consider fully constrained (still rubber-banding toward a server position inside the tight leash) never refuses the jump (0x47) — a jump succeeds mid-rubber-band where retail blocks it. Low practical risk (the leash band is tight + short-lived) | `CPhysicsObj::IsFullyConstrained` 0x0050ec60 → `ConstraintManager::IsFullyConstrained` 0x005560d0; `jump_is_allowed` 0x005282b0; arming `SmartBox::HandleReceivedPosition` 0x00453fd0 (issue #167) |
| TS-37 | RETIRED misattribution note (not a live divergence — kept here as the historical record R3-W3 closes): the S2a port had `contact_allows_move` (0x00528240) arm `StandingLongJump` as a side effect, explicitly flagged "PRE-EXISTING acdream side effect (not part of 0x00528240)". R3-W3 deletes that side effect; `ChargeJump` (0x005281c0) is now the ONLY arming site, matching retail exactly. No further action — recorded per the register's retire-in-same-commit rule | `src/AcDream.Core/Physics/MotionInterpreter.cs` (`contact_allows_move`, `ChargeJump`) | N/A — retired | N/A — retired | `CMotionInterp::charge_jump` 0x005281c0 @305448 |
| TS-38 | `MotionInterpreter.Initted` defaults to `true` in both constructors, not retail's `false` — retail's `CMotionInterp` is never observed pre-`enter_default_state` (every real construction path calls it before exposing the interpreter); acdream's constructors are used directly by ~40 pre-existing tests and both App call sites as complete, immediately-usable objects with no separate "enter default state" step | `src/AcDream.Core/Physics/MotionInterpreter.cs` (`Initted` property + both constructors) | Defaulting `true` is the C# equivalent of "the constructor already did what `enter_default_state` would have done to this flag" — `EnterDefaultState()` remains available, verbatim, for the REST of retail's reset semantics (state defaults, sentinel enqueue, `LeaveGround` tail) when a caller wants them | None observed: no code path needs `apply_current_movement`/`ReportExhaustion` to no-op before an explicit `EnterDefaultState()` call, since nothing constructs a `MotionInterpreter` and defers initialization today. If a future caller DOES need staged construction (build now, `EnterDefaultState()` later), it must explicitly set `Initted = false` first | `CMotionInterp::enter_default_state` 0x00528c80 @306124 sets `initted = 1`; retire if/when construction is staged through `EnterDefaultState()` uniformly |
| ~~TS-41~~ | **RETIRED 2026-07-07 (remote-creature de-overlap #184)** — the SERVERVEL synth-velocity body-drive (`Body.Velocity = ServerVelocity` / `get_state_velocity()` leg) is DELETED. Grounded NPC remotes now translate by the retail interp CATCH-UP (`RemoteMotionCombiner.ComputeOffset` → `InterpolationManager::adjust_offset` toward the MoveOrTeleport-queued server waypoint) and `MovementManager::UseTime` (`TickRemoteMoveTo`) runs UNCONDITIONALLY per tick — the retail `UpdateObjectInternal` shape (no wire-velocity leg-driver). The de-overlap sweep resolves the catch-up movement; the resolved position is written back into the SHADOW (AP-86) so it persists. Residual: the non-retail anim-cycle stale-stop heuristic (`ApplyServerControlledVelocityCycle(Zero)` on a >0.6 s velocity-staleness timer) is kept as ANIM-only and stays covered by **AP-80**; it no longer drives the body. | `src/AcDream.App/Physics/RemotePhysicsUpdater.cs` (grounded NPC branch) | — | — | `CPhysicsObj::UpdateObjectInternal` 0x005156b0 (`MovementManager::UseTime` @0x00515998, unconditional); `MoveOrTeleport` 0x00516330; `InterpolationManager::adjust_offset` 0x00555d30 |
diff --git a/docs/plans/2026-07-29-physics-parity-campaign.md b/docs/plans/2026-07-29-physics-parity-campaign.md
index 3cb2534c..21ac740e 100644
--- a/docs/plans/2026-07-29-physics-parity-campaign.md
+++ b/docs/plans/2026-07-29-physics-parity-campaign.md
@@ -220,6 +220,19 @@ RestrictionTables residuals. Suite 9,956/0/5.
### P5 — Deferred fidelity — closes #167, #153, #72
+**Status (2026-07-30): item 1 (#167) COMPLETE.** Both blockers resolved
+without Ghidra/cdb — the two x87-elided constants were byte-decoded
+straight from the matching binary's raw machine code
+(`docs/research/2026-07-30-constraint-leash-constants.md`). The leash is
+now armed at every current acdream inbound-position acceptance seam
+(`ConstraintDistance`, `LiveEntityNetworkUpdateController`,
+`PlayerMovementController.SetPosition`/`BlipPosition`), the per-tick
+`PhysicsBody.IsFullyConstrained` push replaces the always-false stub, and
+register row TS-35 is deleted. Full Core/Runtime/App suites pass with new
+conformance tests (leash-armed jump refusal, teleport-vs-blip
+anchor/teardown, taper reduction, remote-tick push). Items 2 (#153) and 3
+(#72) remain open.
+
1. **#167:** decode the two unknown x87 ConstraintManager constants
(Ghidra) and port leash arming.
2. **#153:** the far-teleport arrival onto an unstreamed landblock near
diff --git a/docs/research/2026-07-30-constraint-leash-constants.md b/docs/research/2026-07-30-constraint-leash-constants.md
index 20015f6b..4a7a2cfb 100644
--- a/docs/research/2026-07-30-constraint-leash-constants.md
+++ b/docs/research/2026-07-30-constraint-leash-constants.md
@@ -94,3 +94,52 @@ None for the constants/flow. Remaining implementation risk is only
where acdream's position-acceptance seams sit today (J6.3 moved
teleport correlation into Runtime — the implementer must find the
current owner rather than trusting older file cites).
+
+## As-ported (Campaign P Slice P5, 2026-07-30)
+
+The implementation risk flagged above resolved to these CURRENT seam owners
+(post-J-slices) — recorded here so the next reader doesn't have to re-derive
+them:
+
+- **Constants** — `src/AcDream.Core/Physics/Motion/ConstraintDistance.cs`.
+ Keyed purely on the object's own full cell id's low 16 bits (`>= 0x0100` =
+ indoor); the vestigial player/remote branch from §1 is deliberately not
+ represented as an API parameter.
+- **Remote arm** — `src/AcDream.App/Physics/LiveEntityNetworkUpdateController.cs`,
+ the inbound `UpdatePosition` handler's remote branch (`update.Guid !=
+ _playerServerGuid`), immediately after the `remotePlacementRequired`
+ hard-teleport block returns (that block already covers retail's
+ `MoveOrTeleport` Branch A / hard-place case; everything reached past it is
+ "did not hard-place"). One call site covers BOTH player-remote and NPC
+ remotes — retail's `SmartBox::HandleReceivedPosition` doesn't distinguish
+ them either, only `GetStart/MaxConstraintDistance`'s now-omitted vestigial
+ branch did. Anchored to the live `IPhysicsObjHost.Position` (which reads
+ `RemoteMotion.Body.Position` + the tracked cell id), matching retail's
+ "anchored to the object's own current position" — since the anchor and
+ `_host.Position` read are the same value at call time,
+ `ConstraintManager.ConstrainTo`'s initial offset is always 0 regardless of
+ whether the routing above just far-snapped or left a near-correction
+ queued.
+- **Remote per-tick taper + `IsFullyConstrained` push** — already wired
+ pre-P5 for the taper (`RuntimeRemotePhysicsUpdater.Tick`/`TickHidden` call
+ `PositionManager.AdjustOffset` every tick via the pre-existing R5-V3
+ sticky/constraint chain); P5 added the `PhysicsBody.IsFullyConstrained =
+ host.PositionManager.IsFullyConstrained()` push at the same two call
+ sites, since `MotionInterpreter` only holds a `PhysicsBody` (no host
+ reference) and needs a live value to read.
+- **Local player arm** — `src/AcDream.Runtime/Gameplay/PlayerMovementController.cs`:
+ `SetPositionCore` (teleport: `UnConstrain` then re-`ConstrainTo` after the
+ existing `StopCompletelyAtPhysicsObjectBoundary` velocity zero — composed,
+ not duplicated) and `CommitPreparedPosition` (mirrors the same pair for
+ the deferred player-mode-entry commit path); `BlipPosition` (ForcePosition:
+ `ConstrainTo` only, no teardown — matches `SmartBox::BlipPlayer` surviving
+ motion/velocity/stick). Anchored to `_body.CellPosition` (the just-applied
+ received position).
+- **Local player per-tick taper + push** — `PlayerMovementController.Update`
+ already called `PositionManager.AdjustOffset` every physics tick pre-P5;
+ P5 added the `_body.IsFullyConstrained = PositionManager?.IsFullyConstrained()
+ ?? false` push immediately after, at the same chokepoint.
+- **TS-35 retirement** — `PhysicsBody.IsFullyConstrained` stayed a plain
+ settable bool (not a computed property) so the ~40 pre-existing direct-set
+ unit tests keep working; the per-tick pumps above are its single writers
+ now, matching the project's per-entity single-owner-write pattern.
diff --git a/src/AcDream.Core/Physics/Motion/ConstraintManager.cs b/src/AcDream.Core/Physics/Motion/ConstraintManager.cs
index 72e8ecbf..9d2c85d8 100644
--- a/src/AcDream.Core/Physics/Motion/ConstraintManager.cs
+++ b/src/AcDream.Core/Physics/Motion/ConstraintManager.cs
@@ -13,16 +13,18 @@ namespace AcDream.Core.Physics.Motion;
/// CMotionInterp::jump_is_allowed (block jump while
/// ).
///
-/// Arming is UNPORTED in acdream (R5). Retail arms the leash ONLY
-/// from SmartBox::HandleReceivedPosition (on every inbound server
-/// position packet) with two constants from
-/// CPhysicsObj::GetStart/MaxConstraintDistance whose values BN elided
-/// (x87 returns — unknown, need a cdb read). acdream's position reconciliation
-/// is not SmartBox, so nothing calls — the leash
-/// stays disarmed and stays false, matching
-/// register TS-35's current stub behavior. The class is ported for structural
-/// completeness of ; the leash-arming port + the
-/// two unknown constants are a deferred issue (port-plan §Constraint scope).
+/// Armed as of Campaign P P5 (2026-07-30, #167, closed
+/// register row TS-35). Retail arms the leash ONLY from
+/// SmartBox::HandleReceivedPosition (0x00453fd0, on every inbound
+/// server position packet) with the start/max band from
+/// CPhysicsObj::GetStart/MaxConstraintDistance — byte-decoded (the x87
+/// returns BN elided) in . acdream's
+/// equivalents call at every accepted-position
+/// seam: LiveEntityNetworkUpdateController for remotes (anchored to
+/// the object's own position, right after the hard-teleport branch returns)
+/// and PlayerMovementController.SetPosition/BlipPosition for
+/// the local player (anchored to the received position). See
+/// docs/research/2026-07-30-constraint-leash-constants.md.
///
public sealed class ConstraintManager
{
@@ -77,8 +79,11 @@ public sealed class ConstraintManager
/// Retail ConstraintManager::IsFullyConstrained (0x005560d0):
/// constraint_distance_max * 0.9 < constraint_pos_offset — the
/// object counts as fully constrained once it has strained past 90 % of the
- /// max leash. Read by jump_is_allowed to block jumps. Always false
- /// while the leash is disarmed (acdream never arms it — see class note).
+ /// max leash. Read by jump_is_allowed to block jumps. False while
+ /// unconstrained (no prior or after
+ /// ) since and
+ /// both default to zero (0*0.9 < 0
+ /// is false).
///
public bool IsFullyConstrained()
=> ConstraintDistanceMax * 0.9f < ConstraintPosOffset;
diff --git a/src/AcDream.Core/Physics/Motion/PositionManager.cs b/src/AcDream.Core/Physics/Motion/PositionManager.cs
index 0ed02f04..67fd334b 100644
--- a/src/AcDream.Core/Physics/Motion/PositionManager.cs
+++ b/src/AcDream.Core/Physics/Motion/PositionManager.cs
@@ -54,8 +54,8 @@ public sealed class PositionManager
/// Retail PositionManager::ConstrainTo (0x00555280) —
/// lazily create the and arm the leash.
- /// (Unused in acdream — no arming call site; see
- /// .)
+ /// Armed as of Campaign P P5 (#167) at every accepted-position seam — see
+ /// 's class doc for the call sites.
public void ConstrainTo(Position anchor, float startDistance, float maxDistance)
{
_constraint ??= new ConstraintManager(_host);
diff --git a/src/AcDream.Core/Physics/PhysicsBody.cs b/src/AcDream.Core/Physics/PhysicsBody.cs
index d83cc5ff..5eaa3300 100644
--- a/src/AcDream.Core/Physics/PhysicsBody.cs
+++ b/src/AcDream.Core/Physics/PhysicsBody.cs
@@ -408,16 +408,33 @@ public sealed class PhysicsBody
public double LastUpdateTime { get; set; }
///
- /// R3-W3 stub for retail CPhysicsObj::IsFullyConstrained
- /// (0x0050f730), read by CMotionInterp::jump_is_allowed (raw
- /// 305524-305525: if (IsFullyConstrained(physics_obj) != 0) return
- /// 0x47;). Retail's body walks per-cell contact-plane constraints
- /// (a mover pinned between opposing walkable surfaces / doorway
- /// jamming); acdream has no equivalent constraint-tracking yet.
- /// Register row: stubbed false (never fires) — a real port needs the
- /// per-cell shadow-list contact accounting the physics digest tracks.
- /// See docs/architecture/retail-divergence-register.md (added same
- /// commit as this field).
+ /// Retail CPhysicsObj::IsFullyConstrained (0x0050f730), read by
+ /// CMotionInterp::jump_is_allowed (raw 305524-305525:
+ /// if (IsFullyConstrained(physics_obj) != 0) return 0x47;) to block
+ /// a jump while the object is rubber-banding hard against a server
+ /// position correction.
+ ///
+ /// R3-W3 originally stubbed this as an always-false property under
+ /// a WRONG mechanism guess (per-cell contact-plane / doorway-jamming).
+ /// R5-V1 corrected the mechanism: the real retail source is
+ /// ConstraintManager::IsFullyConstrained (0x005560d0,
+ /// constraint_distance_max * 0.9 < constraint_pos_offset) —
+ /// 's constraint sub-manager. R5-V1
+ /// ported but did not arm it (#167,
+ /// former register row TS-35).
+ ///
+ /// Campaign P P5 (2026-07-30) armed the leash at every current
+ /// inbound-position acceptance seam
+ /// (LiveEntityNetworkUpdateController for remotes,
+ /// PlayerMovementController.SetPosition/BlipPosition for the
+ /// local player — see docs/research/2026-07-30-constraint-leash-constants.md
+ /// §2/§3). only holds a
+ /// reference (no host), so this property stays a
+ /// plain settable bool; the per-tick pump that already runs
+ /// PositionManager.AdjustOffset (PlayerMovementController.Update,
+ /// RuntimeRemotePhysicsUpdater.Tick/TickHidden) is the single
+ /// owner that pushes PositionManager.IsFullyConstrained() here every
+ /// tick, so this read is now live, not stubbed.
///
public bool IsFullyConstrained { get; set; }
diff --git a/src/AcDream.Runtime/Gameplay/PlayerMovementController.cs b/src/AcDream.Runtime/Gameplay/PlayerMovementController.cs
index 688da3bf..d10d788d 100644
--- a/src/AcDream.Runtime/Gameplay/PlayerMovementController.cs
+++ b/src/AcDream.Runtime/Gameplay/PlayerMovementController.cs
@@ -503,7 +503,11 @@ public sealed class PlayerMovementController
/// motion) and UseTime after the completed-motions sweep (retail
/// UpdateObjectInternal tail @0x005159b3 — the sticky 1 s lease
/// watchdog). tears any
- /// stick down (retail teleport_hook @0x00514eee).
+ /// stick down (retail teleport_hook @0x00514eee) and, as of
+ /// Campaign P P5 (#167), also tears down and immediately re-arms the
+ /// constraint leash (UnConstrain then ConstrainTo);
+ /// arms the leash without tearing it down first
+ /// (retail SmartBox::BlipPlayer survives motion/velocity/stick).
///
public AcDream.Core.Physics.Motion.PositionManager? PositionManager { get; set; }
diff --git a/src/AcDream.Runtime/Physics/EntityPhysicsHost.cs b/src/AcDream.Runtime/Physics/EntityPhysicsHost.cs
index 275451e5..b3e50ab8 100644
--- a/src/AcDream.Runtime/Physics/EntityPhysicsHost.cs
+++ b/src/AcDream.Runtime/Physics/EntityPhysicsHost.cs
@@ -170,10 +170,13 @@ public sealed class EntityPhysicsHost : IPhysicsObjHost
public TargetManager TargetManager => _targetManager;
/// R5-V3 — the owned facade (retail
- /// CPhysicsObj::position_manager): sticky follow + (unarmed)
- /// constraint leash. Seam targets: MoveToManager.StickTo/Unstick,
+ /// CPhysicsObj::position_manager): sticky follow + the
+ /// server-position constraint leash (armed as of Campaign P P5, #167).
+ /// Seam targets: MoveToManager.StickTo/Unstick,
/// MotionInterpreter.UnstickFromObject, the per-tick
- /// AdjustOffset/UseTime drivers.
+ /// AdjustOffset/UseTime drivers, and
+ /// LiveEntityNetworkUpdateController's remote-position
+ /// ConstrainTo arm.
public PositionManager PositionManager { get; }
// ── IPhysicsObjHost fan-out / target-tracking seams ────────────────────