# R5 wiring handoff — ARC DONE 2026-07-05 (V1-V5 all shipped) > **ARC CLOSE-OUT 2026-07-05.** All five slices landed: V1 (Core classes, > `3d89446d`), V2 (TargetManager voyeur wiring, AP-79 retired, `fffe90b3`), > V3 (sticky melee #171, three slices, TS-39 retired, gate PASSED), V4 > behavioral items (head stance dispatch + #164 + mt-0 flags, `f423884b`, > gate PASSED), **V5 (MovementManager facade — structural, zero behavior > change)**: `src/AcDream.Core/Physics/Motion/MovementManager.cs` (retail > acclient.h `/* 3463 */`; MakeMoveToManager 0x00524000, PerformMovement > 0x005240d0, UseTime 0x005242f0, HitGround 0x00524300, HandleExitWorld > 0x00524350, CancelMoveTo 0x005241b0, HandleUpdateTarget 0x00524790, > IsMovingTo 0x00524260) owns each entity's interp+moveto pair. The three > wiring sites (`EnsureRemoteMotionBindings`, `EnterPlayerModeNow`, the > chase harness) construct through `MoveToFactory` + `MakeMoveToManager()` > — the factory closure is the acdream stand-in for retail's > physics_obj/weenie_obj backpointers; `RemoteMotion.Motion/.MoveTo` and > `PlayerMovementController.MoveTo` are now child VIEWS of the facade > (`RemoteMotion.Movement` / `PlayerMovementController.Movement`), so the > comment-dense call sites read unchanged. Relay call sites repointed: > both landing HitGround pairs + the player landing pair, despawn > HandleExitWorld, TickRemoteMoveTo + the player Update UseTime, > RouteServerMoveTo (now takes the facade; routes through the retail > PerformMovement dispatch), InstallSpeculativeTurnToTarget, both host > HandleUpdateTarget/InterruptCurrentMovement closures, TS-36 interrupt > chains. NOT absorbed (per slice spec): unpack_movement stays App > (RouteServerMoveTo + the UM heads), TS-42 per-tick order untouched (R6), > #170/#171 gate-passed machinery untouched. 15 facade conformance tests > (`MovementManagerTests.cs`); suite 4052 green, protected suites > unmodified. Register: TS-41/TS-42 wording freshened, AD-36 retire note > corrected (facade half closed; no new rows). **Open follow-ons: #167 > ConstraintManager arming (TS-35), R6 per-tick order (TS-42), TS-43 > remote teleport hook.** Next work per the milestones doc: M1.5 critical > path — #137 dungeon collision, #138 teleport-OUT, A7 dungeon lighting. (Original handoff below, kept for the trail.) Successor to `2026-07-03-r5-entry-handoff.md`. **R5-V1 shipped** (`3d89446d`): the retail movement-manager family is ported to Core as faithful, fully-tested, UNWIRED classes. The remaining slices (V2/V3/V4) are GameWindow integration whose acceptance is **visual verification against the running client** — the one thing the CLAUDE.md says requires the user. Do them one at a time, visual-gate each, do NOT stack. ## What V1 landed (`3d89446d`, full suite 4006 green) New Core (`src/AcDream.Core/Physics/Motion/`), all with conformance tests: - `StickyManager`, `ConstraintManager`, `PositionManager` (facade), `TargetManager` + `TargettedVoyeurInfo`, `IPhysicsObjHost` (the CPhysicsObj back-pointer seam the App implements per entity), `MotionDeltaFrame`. - `TargetInfo` extended to the full retail 10-field struct (additive defaults). - `MoveToMath`: `CylinderDistanceNoZ` (signed), `NormalizeCheckSmall`, `GlobalToLocalVec`. - **Rename**: `AcDream.Core.Physics.PositionManager` (the misnamed remote anim+interp combiner) → `RemoteMotionCombiner`. This freed the name for the faithful facade and removed the ambiguity that breaks any file importing both `AcDream.Core.Physics` + `.Motion` — **GameWindow imports both**, so the wiring below could not compile without this rename. Decomp + ACE cross-ref + decoded math: this directory (`r5-*-decomp.md`, `r5-ace-crossref.md`, `r5-port-plan.md`). The port plan §2 has the decoded x87 mush (sticky steer, constraint taper, voyeur gates) — trust it over the raw BN. ## The wiring model (all slices) acdream has no per-entity `CPhysicsObj`. Introduce one `IPhysicsObjHost` per entity — **in a dedicated App class `EntityPhysicsHost`, NOT inline in GameWindow** (code-structure rule #1). GameWindow keeps a `Dictionary _physicsHosts`; each host's `GetObjectA` is `id => _physicsHosts.GetValueOrDefault(id)` (cross-entity resolve for the voyeur round-trip). Construct a host in `EnsureRemoteMotionBindings` (remotes) and `EnterPlayerModeNow` (player); remove it + `NotifyVoyeurOfEvent(ExitWorld)` on despawn/exit-world. Host accessor mapping (behavioral-equivalence anchor — match AP-79 exactly): - `Position` = the entity's **`WorldEntity.Position`** (world-space) via `_entitiesByServerGuid[id].Position`. This is EXACTLY the source the AP-79 poll used for a target (`trackedEnt.Position`), so for the moveto case (quantum 0, `GetInterpolatedPosition` = Position) the voyeur system delivers the identical position the adapter did. - `Velocity` = body velocity (only used for quantum>0, i.e. sticky). - `Radius` = setup cylsphere radius (today MoveToManager passes 0 — the P4 note; wiring the real radius here is the "finally needed" bit). - `InContact` = transient Contact bit (constraint gate; V3). - `MinterpMaxSpeed` = `interp.GetMaxSpeed()` or null (sticky speed; V3). - `CurTime`/`PhysicsTimerTime` = the real clocks (R4-V5 fixed the remote clock; reuse the same epoch-seconds source; the player uses `SimTimeSeconds`). - `HandleUpdateTarget` = fan to the entity's `MoveToManager.HandleUpdateTarget` (+ `PositionManager.HandleUpdateTarget` once V3 adds it). ### V2 — wire TargetManager, retire AP-79 (behaviorally a no-op refactor) Repoint MoveToManager's `setTarget`/`clearTarget`/`getTargetQuantum`/ `setTargetQuantum` seams at `host.TargetManager`. Per tick, call `host.TargetManager.HandleTargetting()` BEFORE `mtm.UseTime()` (retail `UpdateObjectInternal` order) — in `TickRemoteMoveTo` (remotes, GameWindow.cs ~4469, both call sites ~9688/9911) and the player pre-Update block (GameWindow.cs ~7994). **Every entity that can be a target must tick HandleTargetting** — the creature-chase target is the player, so the player's host MUST tick it (that's what pushes updates to the chasing creatures). Delete the AP-79 fields (`RemoteMotion.TrackedTarget*`, the `_playerMoveToTarget*` GameWindow fields) and the two manual poll blocks. **Delete the AP-79 register row same commit.** Keep the 183-case/funnel/moveto suites green (unmodified). Behavioral-equivalence: the voyeur radius = MoveToManager's `set_target` radius (0.5) = AP-79's `TrackedTargetRadius`; both deliver `HandleUpdateTarget(Ok)` when the target drifts >radius; both deliver ExitWorld on despawn. Identical for the moveto case. **VISUAL GATE**: a server-directed creature still chases the player, and player auto-walk-to-object still tracks — no visible change, confirm nothing broke. Lifecycle watchouts: (1) when a watcher despawns, its host removal must let the target drop the dead voyeur (RemoveVoyeur on ClearTarget, or prune on send failure). (2) The immediate-snapshot-on-subscribe (retail AddVoyeur) means the watcher gets one `HandleUpdateTarget(Ok)` the instant it sets a target — matches AP-79's `!FedOnce` first delivery. (3) HandleTargetting self-throttles to 0.5s; the AP-79 poll ran every frame but only DELIVERED on drift — same effective cadence for delivery, but a target moving fast between 0.5s ticks sends less often. If chase feels choppier than AP-79, that's the throttle — retail-faithful, but note it. ### V3 — wire PositionManager (sticky), retire TS-39, apply mt-0 flags > **STATUS 2026-07-04: SHIPPED + visual gate PASSED** ("Looks good, ship it"; > three slices: `5bd2b8bc` binding+radii, `7a823176` UP-snap suppression while > stuck TS-44, `69966950` deep-overlap sign pin AP-82) > — sticky seams bound (StickTo/Unstick/UnstickFromObject, remote + player), > AdjustOffset at the UpdatePositionInternal slot (combiner chained as the > interp stage in the remote-player branch; pre-sweep compose in the NPC > branch + player physics tick), UseTime at the UpdateObjectInternal tail, > real setup cylsphere radii threaded, exit-world/teleport teardown (remote > teleport gap = TS-43), SERVERVEL yields to a stuck entity (TS-41). TS-39 > retired. **The mt-0 wire flags (0x1 StickToObject / 0x2 StandingLongJump) > were NOT part of the user-approved #171 scope — they move to V4.** Add a `PositionManager` to each host. Bind `MoveToManager.StickTo → host.PositionManager.StickTo`, `MoveToManager.Unstick → host.PositionManager.UnStick`. The load-bearing part: integrate `host.PositionManager.AdjustOffset(deltaFrame, quantum)` into the per-frame body integration at the composed-delta chokepoint (retail `UpdatePositionInternal` — in acdream that's where `RemoteMotionCombiner. ComputeOffset` runs, GameWindow ~9716; sticky steer must ADD to the body's per-tick motion). Per tick `host.PositionManager.UseTime()` AFTER `mtm.UseTime()`. Apply the mt-0 wire flags (`UpdateMotion.cs` parses both, unconsumed): `0x1 StickToObject` → `host.stick_to_object(guid)` → `PositionManager.StickTo`; `0x2 StandingLongJump` → `MotionInterpreter.StandingLongJump`. **Delete the TS-39 register row same commit.** **VISUAL GATE**: a sticky scenario (server /follow-style sticky moveto or a moving-platform stick) HOLDS the target instead of stopping-and-drifting. ### V4 (capstone) — MovementManager facade + #164 + head-stance dispatch + docs > **STATUS 2026-07-04: the three BEHAVIORAL items SHIPPED + visual gate > PASSED** ("looks good", `f423884b`): head style-on-change at both GameWindow routing heads > (@00524502-0052452c, all movement types), #164 action-replay Autonomous > bit (raw 305982), mt-0 wire flags consumed (0x1 stick_to_object > 0x005127e0 → PositionManager.StickTo at both case-0 tails; 0x2 → > `Motion.StandingLongJump`, unconditional per @0052458e). Conformance: > stance-on-arm harness scenario + the autonomy funnel test; suite 4041 > green. **The MovementManager facade DEFERRED to its own slice** (the > handoff's own "optional if the arc runs long" clause — this arc ran two > full gate cycles). **→ SHIPPED 2026-07-05 as R5-V5; see the arc > close-out banner at the top of this doc.** - Collapse per-entity `Motion`+`MoveTo` into a `MovementManager` owner (structural; keep 183-case/funnel/moveto green UNMODIFIED). Optional if the arc runs long — the retirements above don't need it. - #164: action-replay Autonomous bit (params 0x1000 from per-action autonomy flag, raw 305982) in `MotionInterpreter.MoveToInterpretedState` action loop. - Head stance-change dispatch for mt 6-9 (the `RetailObserverTraceConformance Tests.cs:33` "S3 wires the unpack-level style-on-change" exclusion — retail's `unpack_movement` head does `InqStyle != wire-style → DoMotion(style)` independent of movement type; acdream applies style only on the mt-0 path). - Final register/ISSUES/roadmap/memory + successor handoff. ## Open items carried - **#167**: ConstraintManager leash-arming + the two unknown x87 constants (`GetStart/MaxConstraintDistance`) — deferred; needs cdb/Ghidra. TS-35 stays open (register corrected: the mechanism is the leash, not doorway-jamming). - Verify against named-retail before treating as ground truth: the sticky/target constants (StickyRadius 0.3 / StickyTime 1.0 / follow ×5 / fallback 15; HandleTargetting 0.5s throttle / 10s staleness) — ACE values, cross-checked against the mush but ACE flagged its own `// ref?` uncertainty on the TargetInfo copy semantics (ported as copy — retail copy-constructs at every fan-out). ## Load-bearing lessons (from V1) - The `Physics.PositionManager` name collision was a REAL compile break for any file importing both namespaces, not cosmetic — the recon agent missed the existing (misnamed) class entirely (grepped for retail semantics). Renaming it was the "do it right" fix. Lesson: grep for the NAME too, not just the concept. - ConstraintManager is NOT a general joint/constraint system — it's a narrow server-position rubber-band leash (3 call sites total: SmartBox arm, teleport disarm, jump-gate read). Don't over-scope it. - The voyeur system is peer-to-peer intra-client: it needs EVERY entity to be a host + tick HandleTargetting, or cross-entity delivery silently no-ops.