acdream/docs/research/2026-07-03-r5-managers/r5-wiring-handoff.md
Erik aa734f3409 docs: R5 arc close-out — V5 facade shipped, arc DONE; M1.5 critical path next
- r5-wiring-handoff.md: arc close-out banner (V1-V5 trail, relay/anchor
  inventory, carried follow-ons #167 / R6 TS-42 / TS-43)
- roadmap: R5 ledger entry completed through V5 (incl. the #170/#171/V4
  gate history that had only lived in memory/handoffs)
- milestones: the interleaved parity-track note — R5 arc DONE 2026-07-05
- CLAUDE.md Current state: R5 arc DONE; next work = M1.5 critical path
  (#137 dungeon collision, #138 teleport-OUT, A7 dungeon lighting)
- pickup prompt marked DONE

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 11:12:37 +02:00

12 KiB
Raw Blame History

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 + .MotionGameWindow 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<uint, EntityPhysicsHost> _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 StickToObjecthost.stick_to_object(guid)PositionManager.StickTo; 0x2 StandingLongJumpMotionInterpreter.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.