# Remote-entity motion — open questions resolved via cdb live trace **Date:** 2026-05-02 **Companion to:** the three agent research reports (paste them in alongside if needed; they live in worktree `adoring-torvalds-d796cf`, `sleepy-grothendieck-9d7483`, `gracious-wright-7af984`). The three reports converged on the same overall pipeline (queued position-chase via `InterpolationManager`) but diverged on three specifics. We resolved all three with a focused cdb attach to the live retail acclient.exe v11.4186. ## Resolution method 1. Static decomp dive into `MoveOrTeleport`, `InterpolateTo`, `InterpolationManager::adjust_offset`, `set_velocity`, `GetAutonomyBlipDistance`, `set_local_velocity`. 2. Constant-value lookup for every named distance/velocity referenced by those functions (`.formats poi(acclient!NAME)`). 3. Live trace with bps on the routing functions while the user did a ~30-second mixed-motion scenario in retail (walk, strafe, jump, run-jump). Captured per-bp hit counts + `set_velocity` caller return addresses. Scripts: `interp_discovery.cdb`, `interp_constants.cdb`, `interp_const2.cdb`, `interp_trace.cdb`. Logs in worktree. ## Question 1 — distance threshold value(s) **Answer:** the agents weren't disagreeing — **they were describing two different thresholds doing two different jobs.** | Threshold | Constant | Value | Where | Decision | |---|---|---:|---|---| | Routing gate | `MAX_PHYSICS_DISTANCE` | **96 m** | `CPhysicsObj::MoveOrTeleport` | within → InterpolateTo (queue); beyond → SetPositionSimple (slide-snap) | | Enqueue blip | `GetAutonomyBlipDistance()` | 100 m outdoor / 20 m indoor (creature)
100 m outdoor / 25 m indoor (player) | `CPhysicsObj::InterpolateTo` | beyond → enqueue InterpolationNode; within → simpler path (set_heading + StopInterpolating) | | Reach / duplicate-prune | `DESIRED_DISTANCE` | 0.05 m | `InterpolateTo` + `adjust_offset` | node "reached"; tail-prune duplicates | The agents who said "96" cited `MAX_PHYSICS_DISTANCE` in MoveOrTeleport correctly. The one who said "100/25" cited `GetAutonomyBlipDistance` correctly but had the indoor-creature value off by 5 (it's 20, not 25 — **25 is the PLAYER indoor distance**, used for self-correction not for remote entities). `GetAutonomyBlipDistance` decoded: ```c float CPhysicsObj::GetAutonomyBlipDistance(this) { bool isPlayer = (this == CPhysicsObj::player_object); bool isIndoor = (this->cell_id & 0xFFFF) >= 0x100; // 2-byte cell IDs are dungeon/inside if (isPlayer) { return isIndoor ? PLAYER_INSIDE_BLIP_DISTANCE // 25 : PLAYER_OUTSIDE_BLIP_DISTANCE; // 100 } else { return isIndoor ? CREATURE_INSIDE_BLIP_DISTANCE // 20 : CREATURE_OUTSIDE_BLIP_DISTANCE; // 100 } } ``` ## Question 2 — polarity of the 96 m branch **Answer: within 96 m → queue (InterpolateTo). Beyond 96 m → snap (SetPositionSimple).** Decoded from the disasm AND confirmed by trace. Disasm of `CPhysicsObj::MoveOrTeleport` at +0x60: ``` fld [esi+20h] ; this->distance_to_player fcomp MAX_PHYSICS_DISTANCE ; vs 96.0 fnstsw ax test ah, 5 jp +0x91 ; JP fires when distance > 96 → snap branch ; FALL-THROUGH (distance ≤ 96): queue path call CPhysicsObj::InterpolateTo +0x91: ; (distance > 96): snap branch call PositionManager::StopInterpolating call CPhysicsObj::SetPositionSimple ``` Trace results (~30 sec mixed motion in Holtburg, all entities visible within ~30 m of camera): | BP | Hits | Notes | |---|---:|---| | `MoveOrTeleport` | 207 | every inbound UpdatePosition | | `InterpolateTo` | 207 | **100 % routed to queue** | | `SetPositionSimple` | **0** | no slide-snaps | | `SetPosition` | 0 | no teleports/no-cell | Confirmed: every routing decision in the test went to the queue. SetPositionSimple is the rare exception, only used when the entity is beyond camera range of significance. ## Question 3 — does walking-remote use UpdatePosition velocity (`set_velocity`)? **Answer: NO.** R3 was correct. Walking remote entities never call `set_velocity`; their `m_velocityVector` stays at zero (or whatever prior). Position progress is achieved entirely through `adjust_offset` walking the body toward queued waypoints. Trace evidence: | BP | Hits | Notes | |---|---:|---| | `set_velocity` | 7 | All 7 had **caller = `0x00511534`** | | `HandleVectorUpdate` (inbound 0xF74E) | 4 | jumps | Resolved `0x00511534` → it's inside `CPhysicsObj::set_local_velocity` (starts at `0x005114d0`). And `set_local_velocity` is what retail's local-jump path (CMotionInterp::LeaveGround) uses to stuff the launch velocity into the body. So: - Local player jumps 4 times → `LeaveGround → set_local_velocity → set_velocity` fires repeatedly (7 hits across 4 jumps; charge-frames + release). - Inbound 0xF74E packets arrive (4) — these did NOT cause additional `set_velocity` hits on remote physobjs in our window. Either retail gates inside `DoVectorUpdate` based on entity type, or the velocity field got applied via a different path that doesn't trip our bp. - 207 walking-remote `UpdatePosition`s → **zero `set_velocity` hits**. So **for walking remotes, `m_velocityVector` is zero and the `UpdatePhysicsInternal` Euler integration `position += velocity*dt` contributes nothing.** All visible motion is from `adjust_offset` walking the body toward queue head. ## Bonus — the rest of the constants we needed Resolved while we were in there: | Constant | Value | Where used | |---|---:|---| | `MAX_INTERPOLATED_VELOCITY_MOD` | **2.0** | `adjust_offset` — multiplier on `minterp->get_adjusted_max_speed()` (the catch-up gain) | | `MAX_INTERPOLATED_VELOCITY` | 7.5 m/s | `adjust_offset` fallback when minterp is unavailable | | `MIN_DISTANCE_TO_REACH_POSITION` | 0.20 m | `adjust_offset` per-5-frame progress threshold | | `max_velocity` | 50 m/s | `set_velocity` magnitude clamp | | `BIG_DISTANCE` | 999999 m | sentinel | | `CAMERA_MAP_DISTANCE` | 450 m | unrelated; map render only | Constraint distances (used by the constraint sub-system, not the queue): | Constant | Value | |---|---:| | `PLAYER_OUTSIDE_CONSTRAINT_DISTANCE_START` | 10 | | `PLAYER_OUTSIDE_CONSTRAINT_DISTANCE_MAX` | 50 | | `PLAYER_INSIDE_CONSTRAINT_DISTANCE_START` | 5 | | `PLAYER_INSIDE_CONSTRAINT_DISTANCE_MAX` | 20 | | `CREATURE_OUTSIDE_CONSTRAINT_DISTANCE_START` | 10 | | `CREATURE_OUTSIDE_CONSTRAINT_DISTANCE_MAX` | 50 | | `CREATURE_INSIDE_CONSTRAINT_DISTANCE_START` | 5 | | `CREATURE_INSIDE_CONSTRAINT_DISTANCE_MAX` | 20 | ## Where this leaves the implementation plan The picture is now fully drawn. To match retail, acdream needs to: 1. **Implement `InterpolationManager`** — FIFO queue (cap 20), `InterpolateTo(targetPosition, isMovingTo)` enqueues with GetAutonomyBlipDistance + DESIRED_DISTANCE prune, `adjust_offset(dt)` per-tick walks toward head at `min(minterp.get_adjusted_max_speed() × 2, MAX_INTERPOLATED_VELOCITY_FALLBACK 7.5) × dt`, `NodeCompleted` pops on arrival within `DESIRED_DISTANCE 0.05`, `UseTime` periodic stall detection (every 5 frames; if progress < 30 % of expected → fail counter; > 3 fails → blip-to head). 2. **Implement `MoveOrTeleport` routing** in the inbound UpdatePosition handler. Replace acdream's current hard-snap with: - Stale-sequence (instance/position) → ignore. - Teleport-sequence newer or no-cell → SetPosition (hard-snap). - has_contact false → no-op. - has_contact true && distance ≤ 96 → InterpolateTo. - has_contact true && distance > 96 → SetPositionSimple slide-snap. 3. **Drop velocity-based dead-reckoning for walking remotes.** Remote `m_velocityVector` should stay at zero unless an inbound 0xF74E VectorUpdate sets it. The body's progress comes from `adjust_offset`, not from Euler integration of state-derived velocity. 4. **Apply VectorUpdate.Omega.** Currently parsed but not applied — fix to make jumping/turning remote arcs match. acdream code that goes away when this lands: - `RemoteMotion.SnapResidualDecayRate` and the soft-snap residual blend. - The locally-recomputed velocity drive between UpdatePosition packets (`apply_current_movement → get_state_velocity → Euler` path on remote entities). ## Files - `interp_discovery.cdb` / `.log` — symbol resolution + prologues - `interp_constants.cdb` / `.log` — first constant lookup - `interp_const2.cdb` / `.log` — remaining constant lookup - `interp_trace.cdb` / `.log` — live routing distribution + set_velocity callers - This doc consolidates the answers