Housekeeping around the R5-V1 core port (3d89446d):
- r5-wiring-handoff.md: the V2/V3/V4 GameWindow-wiring plan (per-entity
IPhysicsObjHost, AP-79→voyeur behavioral-equivalence anchor, TS-39 sticky
integration, mt-0 flags, #164, facade). These are the visual-gated slices.
- ISSUES #167: ConstraintManager leash unported — arming (SmartBox) + two
x87 distance constants BN elided; deferred (needs cdb/Ghidra). TS-35 stays.
- Register TS-35 corrected: R5 recon proved the "write side" is the
ConstraintManager server-position rubber-band leash, NOT the earlier
"per-cell contact-plane / doorway-jamming" guess. Oracle addresses fixed.
- Roadmap Phase R: R5-V1 shipped, R2-R4 visual pass PASSED, next = wiring.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
8.3 KiB
R5 wiring handoff — V1 landed, V2/V3/V4 are the visual-gated wiring
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.TargetInfoextended 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 bothAcDream.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<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'sWorldEntity.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 usesSimTimeSeconds).HandleUpdateTarget= fan to the entity'sMoveToManager.HandleUpdateTarget(+PositionManager.HandleUpdateTargetonce 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
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
- Collapse per-entity
Motion+MoveTointo aMovementManagerowner (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.MoveToInterpretedStateaction loop. - Head stance-change dispatch for mt 6-9 (the
RetailObserverTraceConformance Tests.cs:33"S3 wires the unpack-level style-on-change" exclusion — retail'sunpack_movementhead doesInqStyle != 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.PositionManagername 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.