acdream/docs/research/2026-07-03-r5-managers/r5-acdream-seams.md
Erik 3d89446d98 feat(physics): R5-V1 — port PositionManager/Sticky/Constraint + TargetManager (Core, unwired)
The retail movement-manager family the R4 MoveToManager port left as
do-not-invent seams (decomp §9f/§9g). Faithful C# ports of retail's
PositionManager facade + StickyManager + ConstraintManager + the
TargetManager voyeur system, with full conformance tests. NO wiring yet
— purely additive, no behavior change. Wiring (retiring TS-39 sticky +
AP-79 target adapter) is R5-V2/V3.

New Core classes (src/AcDream.Core/Physics/Motion/):
- StickyManager (0x00555400): follow-a-target steering. adjust_offset's
  dense x87 mush decoded via ACE (StickyRadius 0.3, StickyTime 1.0,
  follow speed ×5 / fallback 15) — speed-clamped signed-distance steer +
  bounded turn-to-face; 1 s watchdog; Ok→initialized / non-Ok→teardown.
- ConstraintManager (0x00556090): the server-position rubber-band leash.
  90% IsFullyConstrained jump gate + grounded linear brake taper.
  Structural only — acdream never ARMS it (retail arms from
  SmartBox::HandleReceivedPosition, which acdream lacks, with two x87
  constants BN elided). IsFullyConstrained stays false = TS-35 behavior;
  leash-arming + the unknown constants are a deferred issue.
- PositionManager facade (0x00555160): lazy Sticky/Constraint + fan-out.
- TargetManager (0x0051a370) + TargettedVoyeurInfo: the peer-to-peer
  voyeur subscription system (0.5 s throttle, 10 s staleness,
  send-on-drift-past-radius, dead-reckon GetInterpolatedPosition). A
  faithful superset of the AP-79 adapter — SetTarget subscribes ON the
  target; the target's HandleTargetting pushes updates back.
- IPhysicsObjHost: the CPhysicsObj back-pointer seam (position/velocity/
  radius/contact/GetObjectA + target-tracking fan-out) the App wires per
  entity in V2/V3. MotionDeltaFrame: mutable retail-Frame delta accumulator.

Supporting:
- TargetInfo extended to the full retail 10-field struct (additive
  defaults keep the R4 4-arg call sites compiling).
- MoveToMath: signed CylinderDistanceNoZ, NormalizeCheckSmall,
  GlobalToLocalVec.
- Rename: the misnamed AcDream.Core.Physics.PositionManager (a remote
  anim+interp per-frame combiner, NOT the retail facade) → RemoteMotion
  Combiner, freeing the name and removing the ambiguity that breaks every
  file importing both Physics + Physics.Motion (GameWindow will in V2/V3).

Tests: 42 new conformance cases (Sticky/Constraint/Position facade +
TargetManager incl. the full cross-entity voyeur round-trip). Full suite
4006 green (+2 skipped), no regressions.

Decomp + ACE cross-ref + port plan: docs/research/2026-07-03-r5-managers/.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 19:34:49 +02:00

27 KiB

R5 seam recon — current acdream integration points MovementManager/PositionManager/TargetManager will replace

Repo root: C:/Users/erikn/source/repos/acdream/.claude/worktrees/vigorous-joliot-f0c3ad Read-only recon. All line numbers verified against files as of this session (2026-07-03).


1. MoveToManager seams — src/AcDream.Core/Physics/Motion/MoveToManager.cs (1605 lines)

Ctor-required seam delegates (lines 78-138, ctor 184-222)

All Action/Func fields, all REQUIRED (non-nullable, thrown in ctor if null):

  • _stopCompletely (L78) — retail CPhysicsObj::StopCompletely
  • _getPosition (L83) — retail physics_obj->m_position
  • _getHeading (L87) — retail CPhysicsObj::get_heading
  • _setHeading (L94) — retail CPhysicsObj::set_heading(heading, send) — the ONE heading snap, fired only from HandleTurnToHeading (L1168)
  • _getOwnRadius (L99), _getOwnHeight (L103) — mover's own cylinder dims
  • _contact (L107) — retail transient_state & 1 (CONTACT bit) — UseTime's tick gate
  • _isInterpolating (L112) — retail CPhysicsObj::IsInterpolating → PositionManager (R5 body doesn't exist yet)
  • _getVelocity (L116) — feeds Phase-3 TargetManager quantum retune
  • _getSelfId (L120) — self-target detection
  • _setTarget (L126) — retail CPhysicsObj::set_targetTargetManager::SetTarget (R5, call-shape only)
  • _clearTarget (L130) — retail CPhysicsObj::clear_targetTargetManager::ClearTarget (R5, call-shape only)
  • _getTargetQuantum (L134) / _setTargetQuantum (L138) — TargetManager quantum (R5, call-shape only)
  • _curTime (L175, optional param, defaults to a per-call 1/30s stub)

AP-79 target seams (setTarget/clearTarget)

  • _setTarget(0, TopLevelObjectId, 0.5f, 0.0) called from MoveToObject (L471) and TurnToObject (L592) — always context 0, radius 0.5, quantum 0.0.
  • _clearTarget() called from CleanUp (L1499) when TopLevelObjectId != 0 && MovementTypeState != Invalid.
  • Consumer of the callback: HandleUpdateTarget(TargetInfo info) (L1229-1283) — the P4 TargetTracker feed point. Ignores updates for any info.ObjectId != TopLevelObjectId (L1237). Two paths: deferred-start (Initialized==false, first callback) vs retarget-while-running.
  • Today's producer of TargetInfo is NOT a real TargetManager — it's GameWindow.TickRemoteMoveTo (App-layer, §2c below) polling _entitiesByServerGuid each tick and manually diffing against TrackedTargetRadius.

TS-39 sticky seams (StickTo/Unstick)

  • public Action? Unstick { get; set; } (L145) — retail CPhysicsObj::unstick_from_objectPositionManager::UnStick (R5 body). Called unconditionally at the head of PerformMovement (L414). Optional — null = silent no-op.
  • public Action<uint,float,float>? StickTo { get; set; } (L151) — retail PositionManager::StickTo(object_id, radius, height) (R5 StickyManager body). Called from BeginNextNode's sticky arrival handoff (L708) with pre-CleanUp values (tlid, radius, height). Optional — null = silent no-op.
  • Today, NEITHER Unstick nor StickTo is bound to anything in GameWindow.cs — grep of EnsureRemoteMotionBindings and EnterPlayerModeNow shows no rm.MoveTo.StickTo = ... or .Unstick = ... assignment anywhere. Both are permanently null (silent no-op) in production today. (Separately, MotionInterpreter.UnstickFromObject — a DIFFERENT, unrelated seam on the interp, not the MoveToManager — IS bound in some contexts; see §3.)

setHeading seam

  • _setHeading: Action<float,bool> — bound per-manager at construction (remote: mtBody.Orientation = MoveToMath.SetHeading(...), GameWindow.cs:4277-4278; player: pcMoveTo.Yaw = MoveToMath.YawFromHeading(h), GameWindow.cs:12996-12997). The bool send (network-echo flag) param is UNCONSUMED on both sides — register row TS-33 (GameWindow.cs:12983-12988 comment): a stationary heading snap never reaches the wire.

Public surface (entry points + drivers)

  • PerformMovement(MovementStruct mvs) (L411) — the type-6..9 dispatch entry, called from RouteServerMoveTo (GameWindow.cs, §5below)
  • MoveToObject / MoveToPosition / TurnToObject / TurnToHeading (L450, 501, 560, 613)
  • UseTime() (L953) — THE TICK GATE: !_contact() / no pending node / (object-move Initialized gate) → dispatches HandleMoveToPosition / HandleTurnToHeading. Called from GameWindow.TickRemoteMoveTo (L4504) for remotes; not shown called for the player in the same function (player pump is via _playerController/OnUpdateFrame — see §2c).
  • HandleUpdateTarget(TargetInfo info) (L1229) — AP-79 feed point (see above)
  • HitGround() (L1292) — no-op if MovementTypeState==Invalid, else BeginNextNode(). Called from GameWindow at L5426 (remote landing) and L10070 (another remote landing site — see §2d).
  • CancelMoveTo(WeenieError error) (L1461) — reentrancy-guarded drain+cleanup+stop; the error param is NEVER read in retail's body (kept for parity/logging only)
  • IsMovingTo() (L397), Destroy() (L387), CleanUp() (L1485), CleanUpAndCallWeenie() (L1516)
  • MoveToComplete (L168) — CLIENT ADDITION, not retail — fires WeenieError.None on natural completion only (never on cancel). Bound in EnterPlayerModeNow (GameWindow.cs:13024-13030) to re-fire the deferred AD-27 Use/PickUp action.

2. GameWindow wiring — src/AcDream.App/Rendering/GameWindow.cs (13,587 lines)

2a. EnsureRemoteMotionBindings (GameWindow.cs:4234-4308)

Binds, once per RemoteMotion (guarded by rm.Sink is not null early-return, L4239):

  • rm.Sink = new MotionTableDispatchSink(ae.Sequencer) with TurnApplied/TurnStopped callbacks (L4243-4255) feeding rmForSink.ObservedOmega
  • rm.Motion.DefaultSink = rm.Sink (L4256)
  • rm.Motion.RemoveLinkAnimations = ae.Sequencer.RemoveAllLinkAnimations (L4257)
  • rm.Motion.InitializeMotionTables = () => ae.Sequencer.Manager.InitializeState() (L4258)
  • rm.Motion.CheckForCompletedMotions = ae.Sequencer.Manager.CheckForCompletedMotions (L4261) — R3-W5 per-op zero-tick flush
  • rm.MoveTo = new MoveToManager(...) (L4270-4300) — full seam wiring: stopCompletely, getPosition (world-space, rmT.CellId + mtBody.Position/Orientation), getHeading/setHeading via MoveToMath, getOwnRadius/getOwnHeight both hardcoded 0f (comment: "pin P4 note — setup cylsphere radius lands with R5's TargetManager port"), contact: () => mtBody.OnWalkable, isInterpolating: () => rmT.Interp.IsActive, getVelocity, getSelfId: () => serverGuid, setTarget/clearTarget writing rmT.TrackedTargetGuid/Radius/FedOnce (L4285-4291), getTargetQuantum/setTargetQuantum on rmT.TargetQuantum, real-clock curTime (L4294-4300, R4-V5 fix — was per-call stub before)
  • rm.Motion.InterruptCurrentMovement = () => rmT.MoveTo?.CancelMoveTo(WeenieError.ActionCancelled) (L4304-4306) — TS-36, retail's interrupt_current_movement → MovementManager::CancelMoveTo(0x36) chain
  • NOT bound here: rm.MoveTo.StickTo, rm.MoveTo.Unstick, rm.MoveTo.MoveToComplete — all left null for remotes.

2b. EnterPlayerModeNow (GameWindow.cs:12960-13145+)

Player-side construction, mirrors 2a with player-specific deltas (documented at L12970-12988):

  • (a) heading via pcMoveTo.Yaw bridge, not a quaternion write directly
  • (b) contact: () => pcMoveTo.BodyInContact — real Contact transient bit, not OnWalkable proxy
  • (c) isInterpolating: () => false — player has no InterpolationManager
  • playerMoveTo = new MoveToManager(...) (L12990-13013)
  • playerMoveTo.MoveToComplete = err => { ...; if (err==None) OnAutoWalkArrivedSendDeferredAction(); } (L13024-13030) — the ONLY MoveToComplete binding in the codebase (AD-27 reanchor)
  • _playerController.MoveTo = playerMoveTo (L13031)
  • _playerController.Motion.InterruptCurrentMovement = () => playerMoveTo.CancelMoveTo(WeenieError.ActionCancelled) (L13039-13045) — TS-36 player side
  • _playerController.Motion.CheckForCompletedMotions = playerSeq.Manager.CheckForCompletedMotions (L13145-13146), inside the if (_animatedEntities.TryGetValue(...)) sequencer-bind block (L13135+) — the R4-V5 "moveto-stall fix #2" comment (L13126-13134) documents this MUST run before the initial SetPosition or the sequencer's pending_motions node orphans.
  • NOT bound: playerMoveTo.StickTo, playerMoveTo.Unstick.
  • Player MoveToManager tick pump: not inside EnterPlayerModeNow — search shows no direct playerMoveTo.UseTime() call in that function; it's driven per-frame from OnUpdateFrame via _playerMoveToTarget* fields (see §2c) and the RouteServerMoveTo call at L4772 (§2e).

2c. AP-79 tracker block + player pre-Update feed

  • Remote-side tracker: TickRemoteMoveTo(RemoteMotion rm) (GameWindow.cs:4469-4505). Feeds HandleUpdateTarget(Ok) from _entitiesByServerGuid lookup when the tracked target moved beyond rm.TrackedTargetRadius (voyeur-radius diff, L4479-4491), or ExitWorld if the guid vanished from the live table (L4493-4502); then unconditionally calls mtm.UseTime() (L4504).
  • Player-side fields: _playerMoveToTargetGuid / _playerMoveToTargetRadius / _playerMoveToTargetFedOnce / _playerMoveToTargetQuantum are declared and seeded in EnterPlayerModeNow (L13006-13016) via the ctor's setTarget/clearTarget/getTargetQuantum/setTargetQuantum closures, but the actual per-tick "feed HandleUpdateTarget + call UseTime()" loop for the player was NOT found inside EnterPlayerModeNow — grep of TickRemoteMoveTo-equivalent player logic did not surface a distinct function; it likely lives in OnUpdateFrame (13,587-line file — not fully read this pass). Flag for R5 authors: locate/confirm the player pump site before assuming parity with TickRemoteMoveTo.

2d. HitGround / LeaveGround / MotionDone / UseTime / CheckForCompletedMotions call sites

  • rm.Motion.LeaveGround() — GameWindow.cs:5100 (remote ground departure, retail LeaveGround 0x00528b00)
  • rmState.Motion.HitGround() + rmState.MoveTo?.HitGround() — GameWindow.cs:5425-5426 (remote landing; comment cites #161, retail MovementManager::HitGround)
  • rm.Motion.HitGround() + rm.MoveTo?.HitGround() — GameWindow.cs:10062, 10070 (a SECOND remote-landing site, same pattern — the file has two independent landing branches)
  • ae.Sequencer.MotionDoneTarget = (m, ok) => { ...; interp?.MotionDone(m, ok); } — GameWindow.cs:10142-10160 (R3-W2, bound once per sequencer; retail CPhysicsObj::MotionDone 0x0050fdb0 chain)
  • ae.Sequencer.Manager.UseTime() — GameWindow.cs:10194 (this is the ANIMATION sequencer's own UseTime, not MoveToManager's — do not conflate)
  • mtm.UseTime() — GameWindow.cs:4504 inside TickRemoteMoveTo (the MoveToManager tick, per remote, per frame)
  • rm.Motion.CheckForCompletedMotions = ... — GameWindow.cs:4261 (bind site, in EnsureRemoteMotionBindings)

2e. RouteServerMoveTo (the type-6..9 funnel entry — see §5 also)

RouteServerMoveTo(MoveToManager mgr, MotionInterpreter interp, uint cellId, EntityMotionUpdate update) — GameWindow.cs:4361-4457. Two call sites:

  • Player: GameWindow.cs:4772 — RouteServerMoveTo(playerMoveTo, _playerController.Motion, _playerController.CellId, update), inside the update.Guid == _playerServerGuid branch (L4716), gated behind _playerController is not null (L4753) and preceded by _playerController.Motion.InterruptCurrentMovement?.Invoke() + .UnstickFromObject?.Invoke() (L4768-4769) — the retail unpack_movement head (@300566).
  • Remote: GameWindow.cs:4835 — RouteServerMoveTo(moveMgr, remoteMot.Motion, remoteMot.CellId, update), inside the else (non-player) branch (L4784), also preceded by remoteMot.Motion.InterruptCurrentMovement?.Invoke() + .UnstickFromObject?.Invoke() (L4828-4829).

2f. RemoteMotion class — GameWindow.cs:411-510+ (nested internal sealed class RemoteMotion, NOT a separate file)

Manager-related fields:

  • PhysicsBody Body, MotionInterpreter Motion (L413-414)
  • MotionTableDispatchSink? Sink (L419)
  • MoveToManager? MoveTo (L436) — R4-V4
  • AP-79 tracker state: TrackedTargetGuid, TrackedTargetRadius, TrackedTargetLastFedPos, TrackedTargetFedOnce, TargetQuantum (L442-446)
  • Legacy/parallel RemoteMoveToDriver-oriented fields still present: ServerMoveToActive, HasMoveToDestination, MoveToDestinationWorld, MoveToMinDistance, MoveToDistanceToObject, MoveToMoveTowards, LastMoveToPacketTime (L448-503) — comments reference AcDream.Core.Physics.RemoteMoveToDriver, which per the R4 handoff docs was retired in the R4 "three approximations" cleanup (per MEMORY.md: "RemoteMoveToDriver/PlanMoveToStart/B.6-auto-walk all deleted"). These fields may now be dead weight predating that retirement — worth confirming during R5 cleanup.

3. MotionInterpreter sticky/action seams — src/AcDream.Core/Physics/MotionInterpreter.cs (3,298 lines)

  • StickTo/Unstick/stick grep: NO StickTo seam exists on MotionInterpreter — only UnstickFromObject (L779: public Action? UnstickFromObject { get; set; }, doc L771-807), the R3-W2 no-op seam standing in for retail CPhysicsObj::unstick_from_object. This is a DIFFERENT seam from MoveToManager.Unstick/.StickTo (§1) — MotionInterpreter's copy is called at L2329 and L2375 inside its own dispatch/cancel paths, unrelated to the MoveToManager sticky handoff.
  • StandingLongJump: field public bool StandingLongJump; (L747). Arms ONLY at ChargeJump (L25 comment, L1970 StandingLongJump = true;). Cleared at L2023 and L2511 (StandingLongJump = false;). Consumed in ApplyInterpretedMovement's three-way branch (L2947, quoted below) and in DoInterpretedMotion's verbatim retail body pseudocode (L2995-2997, label_528440 goto).

The #164 action-dispatch loop — ApplyInterpretedMovement (MotionInterpreter.cs:2920-2981)

public void ApplyInterpretedMovement(
    uint currentStyle, IInterpretedMotionSink? sink,
    bool cancelMoveTo = false, bool allowJump = false)
{
    if (PhysicsObj is null) return;

    // Retail's rewritten var_2c (raw 305778 decoded; ACE 444-449):
    // Speed is re-set per axis below, everything else rides the pass.
    var p = new MovementParameters
    {
        SetHoldKey = false,
        ModifyInterpretedState = false,
        CancelMoveTo = cancelMoveTo,
        DisableJumpDuringLink = !allowJump,
    };

    if (InterpretedState.ForwardCommand == MotionCommand.RunForward)
        MyRunRate = InterpretedState.ForwardSpeed;

    p.Speed = 1.0f;
    DoInterpretedMotion(currentStyle, p, sink);

    if (!contact_allows_move(InterpretedState.ForwardCommand))
    {
        p.Speed = 1.0f; // raw 305728: var_18_2 = 0x3f800000
        DoInterpretedMotion(MotionCommand.Falling, p, sink);
    }
    else if (StandingLongJump)
    {
        p.Speed = 1.0f;
        DoInterpretedMotion(MotionCommand.Ready, p, sink);
        StopInterpretedMotion(MotionCommand.SideStepRight, p, sink);
    }
    else
    {
        p.Speed = InterpretedState.ForwardSpeed;
        DoInterpretedMotion(InterpretedState.ForwardCommand, p, sink);
        if (InterpretedState.SideStepCommand == 0)
        {
            StopInterpretedMotion(MotionCommand.SideStepRight, p, sink);
        }
        else
        {
            p.Speed = InterpretedState.SideStepSpeed;
            DoInterpretedMotion(InterpretedState.SideStepCommand, p, sink);
        }
    }

    if (InterpretedState.TurnCommand != 0)
    {
        p.Speed = InterpretedState.TurnSpeed;
        DoInterpretedMotion(InterpretedState.TurnCommand, p, sink);
        return; // retail early return — no idle-stop this call
    }

    // Tail: unconditional StopInterpretedMotion(TurnRight, params).
    StopInterpretedMotion(MotionCommand.TurnRight, p, sink);
}

Correction to the task framing: the dispatched MovementParameters p is built ONCE at the top with Autonomous implicitly false (default, never set) and explicitly sets SetHoldKey=false, ModifyInterpretedState=false, CancelMoveTo=cancelMoveTo, DisableJumpDuringLink=!allowJump — only p.Speed is mutated per-branch (not re-constructed per dispatch as new MovementParameters { Speed }). The #161 doc comment (L2892-2918) explicitly discusses this exact bitfield and calls ModifyInterpretedState=false "load-bearing." The Autonomous bit is never read or written anywhere in this method — confirms the framing that the loop drops/never-carries the Autonomous bit through to per-dispatch MovementParameters.

This is called from MoveToInterpretedState (L2820-2866, the funnel entry) at L2843: ApplyInterpretedMovement(ims.CurrentStyle, sink, cancelMoveTo: true, allowJump: allowJump); — always cancelMoveTo: true for inbound wire state.


4. UpdateMotion wire parsing — src/AcDream.Core.Net/Messages/UpdateMotion.cs (479 lines)

mt-0 header flag parsing

  • MotionFlags byte (wire term "header" in decomp comments) parsed at L161: byte motionFlags = body[pos]; pos += 1; — comment (L148-149) documents 0x1 = StickToObject, 0x2 = StandingLongJump (ACE MotionFlags enum names). This is NOT the same as the "0x100"/"0x200" framing in the task — those are decomp-doc shorthand for the SAME bits at a different byte-shift (header & 0x100 in the decomp refers to bit 0 of this byte after the 8-bit motionFlags is logically at bit-position 8 of the larger unpack_movement header dword). The actual C# test is (motionFlags & 0x1) != 0 for sticky (L290) — there is no separate literal 0x100/0x200 mask in this file; grep for those literals returns nothing in UpdateMotion.cs.
  • 0x1 (StickToObject) parse site: L279-295, comment block cites ACE MovementInvalid.Write + decomp §2f case 0 @0052455d. Code:
    if ((motionFlags & 0x1) != 0 && body.Length - pos >= 4)
    {
        stickyObjectGuid = BinaryPrimitives.ReadUInt32LittleEndian(body.Slice(pos));
        pos += 4;
    }
    
    Only fires for movementType == 0 (InterpretedMotionState branch, L188). The trailing u32 is read AFTER the InterpretedMotionState body + Commands list (matches ACE's write order).
  • 0x2 (StandingLongJump) — NOT consumed: comment L153-160 explicitly states "DOCUMENTED here but NOT consumed — retail's unpack_movement case 0 writes it onto motion_interpreter->standing_longjump (§2f @0052458e), an R5 unpack_movement item (this parser has no MotionInterpreter reference to write it onto...)". No code reads motionFlags & 0x2 anywhere in the file.

Downstream consumers (grep of whole src tree)

  • StickyObjectGuid (the parsed field, carried on CreateObject.ServerMotionState, L317-326 return + CreateObject.cs:265): grep across src/ shows it is assigned into the record and returned, but never read by any production consumer — the only non-definition hits are in tests/AcDream.Core.Net.Tests/Messages/UpdateMotionTests.cs (asserting the parsed value, L662/683/716). No site in GameWindow.cs, MotionInterpreter.cs, or MoveToManager.cs reads .StickyObjectGuid off ServerMotionState.
  • motionFlags & 0x2 / MotionFlags.StandingLongJump: zero matches anywhere in the repo outside the UpdateMotion.cs documentation comment itself.
  • Conclusion for R5: both the sticky-guid trailer and the standing-longjump wire bit are parsed-and-shelved. R5's PositionManager::StickTo (per the UpdateMotion.cs comment, "R5's PositionManager::StickTo body is the eventual consumer") and MovementManager (for standing-longjump) are the intended consumers — neither exists yet.

5. UM dispatch path for movement types 6-9 — funnel + head-stance question

  • Funnel/router: RouteServerMoveTo (GameWindow.cs:4361-4457, quoted in part in §2e) is the ONE function that inspects update.MotionState.IsServerControlledMoveTo/IsServerControlledTurnTo + MovementType (6/7 → MoveToObject/MoveToPosition via path; 8/9 → TurnToObject/TurnToHeading via turnPath) and calls mgr.PerformMovement(ms). Both the player call site (GameWindow.cs:4772) and remote call site (GameWindow.cs:4835) funnel through this SAME function — confirmed shared body, not duplicated per the R4-V5 extraction comment (L4459-4468).
  • Does any site do a head stance-change dispatch (InqStyle != wire style -> DoMotion(style)) BEFORE the type-6..9 switch? NO separate dispatch exists. For the remote path, the stance/style value folds directly into InboundInterpretedState.CurrentStyle (GameWindow.cs:4867: CurrentStyle = stance != 0 ? (0x80000000u | (uint)stance) : 0x8000003Du), which is built and consumed only in the else branch AFTER RouteServerMoveTo returns false (i.e., only for mt-0 / non-moveto events) — see GameWindow.cs:4834-4839 (RouteServerMoveTo call, early-return on true) followed by the ims construction at L4865-4874. So: for a wire event that IS a type-6..9 moveto, the style/stance field is never separately dispatched at this layer — MoveToManager.PerformMovement doesn't take or apply a style at all (it only sets MovementTypeState and queues nodes; no DoMotion(style) call anywhere in PerformMovement/MoveToObject/MoveToPosition/TurnToObject/TurnToHeading, confirmed by reading all of §1's L411-637). The style-on-change dispatch is explicitly flagged as unported: RetailObserverTraceConformanceTests.cs:33 documents an EXCLUSION — "motion==0x80000000 entries (the unpack-level DoMotion(command_ids[0]) style call for wire style index 0) — outside move_to_interpreted_state; S3 wires the unpack-level style-on-change" (not yet done). This is a concrete gap for R5/MovementManager: retail's real unpack_movement head does an InqStyle-vs-wire-style compare and issues a style DoMotion unconditionally (independent of movement type 0 vs 6-9), and acdream currently only applies style via the mt-0 InboundInterpretedState.CurrentStyle path — mt 6-9 events carry no style application at all today.

6. PhysicsBody — src/AcDream.Core/Physics/PhysicsBody.cs (576 lines)

  • IsFullyConstrained (TS-35, doc calls it TS-35 but file comment says "R3-W3 stub"): public bool IsFullyConstrained { get; set; } (L263), doc block L251-262. Retail CPhysicsObj::IsFullyConstrained (0x0050f730), read by CMotionInterp::jump_is_allowed (if (IsFullyConstrained(physics_obj) != 0) return 0x47). Comment: "acdream has no equivalent constraint-tracking yet... stubbed false (never fires) — a real port needs the per-cell shadow-list contact accounting the physics digest tracks." Always false in production (settable property, but nothing in the grep'd files sets it true).
  • TransientState bits available (TransientStateFlags, L62-70, [Flags] enum):
    • Contact = 0x1 (bit 0) — touching any surface
    • OnWalkable = 0x2 (bit 1) — standing on walkable surface
    • Sliding = 0x4 (bit 2) — carry sliding normal into next transition
    • Active = 0x80 (bit 7) — needs per-frame update
    • Convenience getters: OnWalkable (L286), IsActive (L287), InContact (L288) properties on PhysicsBody.
  • PositionManager: does not exist as a class anywhere in the repo. Grep for PositionManager across src/ returns zero hits outside doc comments referencing it as a future R5 port target (e.g. MoveToManager.cs:24, L109-112 doc, UpdateMotion.cs comment). IsInterpolating (the seam MoveToManager consumes) is currently satisfied by ad-hoc booleans: rmT.Interp.IsActive for remotes (GameWindow.cs:4282) and a hardcoded false for the player (GameWindow.cs:13001) — neither is a real PositionManager.

7. Test files (locations, not full case enumeration)

MoveToManager suitetests/AcDream.Core.Tests/Physics/Motion/:

  • MoveToManagerTestHarness.cs — shared harness/fixture
  • MoveToManagerLifecycleTests.cs
  • MoveToManagerNodePlanTests.cs
  • MoveToManagerBeginMoveForwardTests.cs
  • MoveToManagerAuxTurnTests.cs
  • MoveToManagerArrivalAndProgressTests.cs
  • MoveToManagerTurnToHeadingTests.cs
  • MoveToManagerHandleUpdateTargetTests.cs
  • MoveToManagerStickyAndCancelTests.cs
  • MoveToManagerUseTimeGateTests.cs
  • MoveToManagerEndToEndTableDriveTests.cs
  • MoveToManagerCompletionSeamTests.cs
  • MovementTypeWideningTests.cs
  • MoveToMathCylinderDistanceTests.cs
  • MotionTableDispatchSinkTests.cs
  • InterpretedMotionStateActionFifoTests.cs

Funnel / 183-case conformance suite:

  • tests/AcDream.Core.Tests/Physics/RetailObserverTraceConformanceTests.cs (35+) — the live-retail-observer-cdb-trace conformance harness ("183/183" cited in MEMORY.md refers to this file's case count). Contains LoginQueue_DrainsToEmpty_UnderProductionFeed at line 279 ([Fact]).
  • tests/AcDream.Core.Tests/Physics/MotionInterpreterFunnelTests.cs
  • tests/AcDream.Core.Tests/Physics/Motion/ (see above — dispatch-adjacent)

DispatchInterpreted / MotionInterpreter core suitetests/AcDream.Core.Tests/Physics/:

  • MotionInterpreterTests.cs
  • MotionInterpreterDoMotionFamilyTests.cs
  • MotionInterpreterGroundLifecycleTests.cs
  • MotionInterpreterJumpFamilyTests.cs
  • MotionInterpreterPendingMotionsTests.cs
  • ServerControlledLocomotionTests.cs
  • RemoteWeenieRunRateTests.cs
  • InWorldLinkGuardTests.cs
  • MotionNormalizationTests.cs
  • MotionVelocityPipelineTests.cs
  • AnimationSequencerCutoverTraceTests.cs
  • WeenieErrorCodeTableTests.cs

Player moveto cutover:

  • tests/AcDream.Core.Tests/Input/PlayerMoveToCutoverTests.cs

Summary answers to the task's specific questions

Q4 (UpdateMotion sticky/longjump): 0x1 (StickToObject) trailer IS parsed (UpdateMotion.cs:279-295, code at 290-293) into stickyObjectGuidServerMotionState.StickyObjectGuid; 0x2 (StandingLongJump) is documented but the bit is never even read. Neither has a production consumer — grep-wide, StickyObjectGuid is read only by unit tests; MotionFlags.StandingLongJump/0x2 has zero non-comment hits anywhere.

Q5 (type 6-9 head-stance dispatch): No such dispatch exists. RouteServerMoveTo (GameWindow.cs:4361) is the shared funnel for both player and remote; it never touches style/stance. MoveToManager.PerformMovement/its four entry points never call DoMotion(style). Style application only happens on the mt-0 path via InboundInterpretedState.CurrentStyle folded at GameWindow.cs:4867. RetailObserverTraceConformanceTests.cs:33's exclusion comment explicitly flags the unpack-level style-on-change dispatch as not yet wired ("S3 wires the unpack-level style-on-change") — this is a known, documented gap, not an oversight this recon discovered fresh.

Q6 (PhysicsBody / PositionManager): IsFullyConstrained is a settable bool stub, always false in practice (PhysicsBody.cs:263). TransientStateFlags has Contact/OnWalkable/Sliding/Active (PhysicsBody.cs:63-70). No PositionManager class exists anywhere in the repo — IsInterpolating is faked per-caller (Interp.IsActive for remotes, hardcoded false for the player).

File path of the written map: C:/Users/erikn/AppData/Local/Temp/claude/C--Users-erikn-source-repos-acdream--claude-worktrees-vigorous-joliot-f0c3ad/dd264e44-6468-4b4b-8d17-a8b3b15991c9/scratchpad/r5/acdream-seams.md