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>
272 lines
27 KiB
Markdown
272 lines
27 KiB
Markdown
# 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_target` → **TargetManager::SetTarget (R5, call-shape only)**
|
|
- `_clearTarget` (L130) — retail `CPhysicsObj::clear_target` → **TargetManager::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_object` → **PositionManager::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)
|
|
```csharp
|
|
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:
|
|
```csharp
|
|
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 suite** — `tests/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 suite** — `tests/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 `stickyObjectGuid` → `ServerMotionState.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`
|