# L.3 port — UpdateMotion (0xF74C) handling pipeline

**Source:** `docs/research/named-retail/acclient_2013_pseudo_c.txt`
(Sept 2013 EoR build, 18,366 named functions). All line numbers are
into that file unless otherwise noted.

This document traces the full inbound path from the wire (the 0xF74C
packet hitting the network layer) down to body velocity and animation
state. It also covers the OUTBOUND path the local player uses (so we
know what acdream's `+Acdream` sends, and what an observing retail
client receives via the SAME entry point but for a different guid).

---

## 0. Top-level flow (one-line summary)

```
0xF74C wire packet
   └── CM_Physics::DispatchSB_*               (357214) — picks branch by opcode
        └── CPhysics::SetObjectMovement       (271370) — staleness check, exit-29-style timestamps
             └── CPhysicsObj::unpack_movement (280179) — lazy-creates MovementManager
                  └── MovementManager::unpack_movement (300563) — reads MovementType byte + params
                       ├── case 0  (RawCommand)        → move_to_interpreted_state
                       ├── case 6  (MoveToObject)      → MoveToManager.MoveToObject
                       ├── case 7  (MoveToPosition)    → MoveToManager.MoveToPosition
                       ├── case 8  (TurnToObject)      → MoveToManager.TurnToObject
                       └── case 9  (TurnToHeading)     → MoveToManager.TurnToHeading

InterpretedMotionState delivered by case 0 then drives:
   CMotionInterp::move_to_interpreted_state  (305936)
     ↓ copy_movement_from + apply_current_movement
        ↓ apply_interpreted_movement (305713)  — re-runs DoInterpretedMotion for each axis
            ↓ DoInterpretedMotion → contact_allows_move? + ApplyMotion → add_to_queue
            ↓ get_state_velocity (305160) → CPhysicsObj.set_local_velocity
```

Outbound (when local player presses W or releases W):

```
W press (or release)
   └── CommandInterpreter::SendMovementEvent (700274)
        └── MoveToStatePack ctor (RawMotionState snapshot of player)
             └── ACCmdInterp::SendMoveToStateEvent → 0xF61C MoveToState packet
```

That single 0xF61C goes to the server. ACE relays the player's wire
state to nearby observers as 0xF74C UpdateMotion. So the observer
side is the same code path described in §1–§7 below, just with the
remote player's guid.

---

## 1. The 0xF74C dispatcher

**Function:** `CM_Physics::DispatchSB_<...>` — opcode switch.
**Address:** `0x005595d0` (containing line 357211 reference).

Verbatim retail (357211–357240):

```c
00559605          }
005595ff      }
005595ff      else if (((char*)ecx - 0xf74c) <= 0x8f)
005597b8          switch (ecx)
005597b8          {
00559850              case 0xf74c:
00559850              {
00559850                  uint32_t ebp_2 = *(uint32_t*)(buf_ + 4);                       // object guid
00559852                  class CObjectMaint* m_pObjMaint = this->m_pObjMaint;
0055985c                  arg2 = &buf_[8];                                              // payload start
00559860                  class CPhysicsObj* eax_25 = CObjectMaint::GetObjectA(m_pObjMaint, ebp_2);
00559867                  class NetBlob* eax_26 = arg2;
0055986d                  uint16_t ecx_15 = eax_26->vtable;                             // instance_timestamp
00559875                  arg2 = (&eax_26->vtable + 2);                                 // skip 2 bytes
00559875                  
0055987d                  if ((eax_25 != 0 && CPhysicsObj::is_newer(eax_25->update_times[8], ecx_15) == 0))
0055987d                  {
00559896                      int32_t eax_29;
00559896                      eax_29 = eax_25->update_times[8];
005598a2                      if (eax_29 != ecx_15)
005598e8                          return 2;                                            // STALE → drop
005598bc                      if (CPhysics::SetObjectMovement(this->physics, eax_25, arg2, bufSize_) != 0)
005598be                          this->cmdinterp->vtable->LoseControlToServer();
005598d7                      return 1;
0055987d                  }
0055987d                  
005598ef                  SmartBox::QueueBlobForObject(this, ebp_2, ebx);              // entity not yet known → queue
00559902                  return 4;
```

**Behavior:**

1. Read object guid from offset 4.
2. Read 2-byte `instance_timestamp` from payload start (offset 8).
3. Look up the entity. If we don't know it, queue the blob and return.
4. If `update_times[8]` (last seen instance ts) is newer than the
   wire's, **drop the packet (return 2)**. This is the staleness gate.
5. Otherwise hand off the rest of the payload to
   `CPhysics::SetObjectMovement`.

---

## 2. CPhysics::SetObjectMovement

**Function:** `CPhysics::SetObjectMovement`
**Address:** `0x00509690`
**Lines:** 271370–271431.

```c
00509690  int32_t __stdcall CPhysics::SetObjectMovement(class CPhysics* this @ ecx,
                              class CPhysicsObj* arg2,    // entity
                              void* arg3,                 // payload pointer
                              uint32_t arg4,              // remaining size
                              uint16_t arg5,              // instance_timestamp (already read)
                              uint16_t arg6,              // server_control_timestamp
                              int32_t arg7)               // forceTeleport flag
{
    int32_t ebx = 0;
    if (weenie_obj != 0)
        ebx = weenie_obj->vtable->IsThePlayer();          // is this the local player?
    
    weenie_obj = arg2->update_times[1];                   // last instance_timestamp
    int32_t edi = arg5;
    // ... unsigned 16-bit "is wire newer?" comparison via 0x7fff-wrap ...
    if (-((eax_7 - eax_7)) != 0)                          // i.e. newer
    {
        arg2->update_times[1] = edi;                      // record new instance ts
        weenie_obj = arg2->update_times[5];               // last server_control_ts
        edi = arg6;
        // ... same wrap compare on server_control_ts ...
        if (-((eax_14 - eax_14)) != 0)
            return 0;                                     // stale on server_control_ts
        arg2->update_times[5] = edi;
        
        if ((arg7 == 0 || ebx == 0))                      // not "force teleport on player"
        {
            arg2->last_move_was_autonomous = arg7;
            CPhysicsObj::unpack_movement(arg2, &arg3, arg4);
            if (ebx != 0) return 1;                       // local player echo: ask cmdinterp to LoseControl
        }
    }
    return 0;
}
```

**Key behaviors:**

- Two timestamp gates (instance_ts and server_control_ts) before any
  state change — both use 16-bit wrap-aware ordering.
- For the **local player** (`IsThePlayer != 0`), if the timestamps are
  newer AND `forceTeleport == 0`, returns 1 — the dispatcher then
  calls `LoseControlToServer()`. This is how a server overrides the
  local player's prediction (e.g. teleport, frozen, etc).
- For everyone else (remote players, NPCs, monsters), returns 0 and
  proceeds to `unpack_movement`.

---

## 3. CPhysicsObj::unpack_movement — lazy creates MovementManager

**Function:** `CPhysicsObj::unpack_movement`
**Address:** `0x00512040`
**Lines:** 280179–280203.

```c
00512040  void __thiscall CPhysicsObj::unpack_movement(this, arg2, arg3) {
    if (this->movement_manager == 0) {
        this->movement_manager = MovementManager::Create(this, this->weenie_obj);
        // first creation also touches transient_state (sets bit 0x80)
    }
    MovementManager::unpack_movement(this->movement_manager, arg2, arg3);
}
```

Pure dispatch. The interesting work happens in MovementManager.

---

## 4. MovementManager::unpack_movement — the actual wire reader

**Function:** `MovementManager::unpack_movement`
**Address:** `0x00524440`
**Lines:** 300563–300704.

This is the **real entry point for UM payload parsing**. It reads a
2-byte `MovementType` discriminator and a 2-byte initial style, then
branches.

Verbatim core (300563–300668):

```c
00524440  int32_t __thiscall MovementManager::unpack_movement(this, arg2, arg3)
{
    if (this->motion_interpreter != 0)
    {
        if (physics_obj != 0)
        {
            CPhysicsObj::interrupt_current_movement(physics_obj);
            CPhysicsObj::unstick_from_object(this->physics_obj);
            // ... Frame::cache(local_origin) ...
            // var_9c = MovementParameters() with defaults
            // var_28 = InterpretedMotionState() with defaults

            void* eax_1 = *arg2;
            int16_t ecx_4 = *(uint16_t*)eax_1;            // (a) movement_type byte
            *arg2 = eax_1 + 2;
            uint32_t ebp_1 = (uint32_t)ecx_4;             // movement_type

            ecx_4 = *(uint16_t*)(eax_1 + 2);              // (b) style 16-bit MotionCommand low
            *arg2 = eax_1 + 4;
            uint32_t ecx_5 = command_ids[(uint32_t)ecx_4]; // expand to full uint32 cmd

            // If the new style differs from current, fire DoMotion(style, default_params)
            // — that switches the body's currentStyle (combat→peace etc).
            if (CBaseFilter::GetPinVersion(this->motion_interpreter) != ecx_5)
                CMotionInterp::DoMotion(this->motion_interpreter, ecx_5, &var_9c);

            switch (ebp_1) {
                case 0:                                   // RawCommand (the bulk of UMs)
                    InterpretedMotionState::UnPack(&var_28, arg2, arg3);
                    uint32_t ebx_3 = 0;
                    if ((var_a4_1 & 0x100) != 0)          // bit indicates "stick to object" guid present
                    {
                        uint32_t* eax_8 = *arg2;
                        ebx_3 = *eax_8;                   // guid to stick to
                        *arg2 = &eax_8[1];
                    }
                    MovementManager::move_to_interpreted_state(this, &var_28);
                    if (ebx_3 != 0)
                        CPhysicsObj::stick_to_object(this->physics_obj, ebx_3);
                    this->motion_interpreter->standing_longjump = (ebp_1 & 0x200);
                    return 1;

                case 6: /* MoveToObject  — guid + Position + MovementParameters + runRate */
                case 7: /* MoveToPosition — Position + MovementParameters + runRate     */
                case 8: /* TurnToObject  — guid + heading + MovementParameters          */
                case 9: /* TurnToHeading — MovementParameters                            */
                    // each delegates to MoveToManager::* (out of scope here)
            }
        }
    }
    return 0;
}
```

**Reads from wire (case 0 only):**

- 2 bytes — `movement_type` (0=RawCommand, 6/7/8/9=MoveTo variants).
- 2 bytes — initial currentStyle (16-bit MotionCommand low → expanded
  to full uint32 via `command_ids[]` lookup).
- `InterpretedMotionState::UnPack` (see §5) consumes the rest.

**Writes to MotionInterp:**

- If the new style differs, `DoMotion(style, default_params)` runs
  immediately — this is how stance changes (Combat ↔ Peace) occur.
- Then `move_to_interpreted_state` bulk-applies the unpacked state
  (see §7).
- `standing_longjump` flag set from the high bit (0x200) of
  movement_type.

**Note:** Type 0 is what the ACE relay produces for nearly every
locomotion event. Types 6–9 are server-controlled MoveTo's (e.g.
"NPC walks to point X"). The MoveTo branches end up calling
`MoveToManager::MoveToObject`/`...Position`/`TurnToHeading` which is
its own state machine — out of scope for this doc.

---

## 5. InterpretedMotionState::UnPack — flag-driven field reader

**Function:** `InterpretedMotionState::UnPack`
**Address:** `0x0051f400`
**Lines:** 294360–294523.

This reads a single `uint32_t` flag word and conditionally unpacks 13
fields. **This is exactly the format ACE writes when relaying.**

Verbatim core (294360–294492):

```c
0051f400  int32_t __thiscall InterpretedMotionState::UnPack(this, arg2, arg3)
{
    InterpretedMotionState::Destroy(this);                // clear actions list

    uint32_t edx;
    if (arg3 < 4) edx = arg3;
    else {
        edx = *(uint32_t*)(*arg2);                        // FLAGS uint32
        *arg2 += 4;
    }

    if ((edx & 0x01) == 0)  this->current_style = 0x8000003d;          // NonCombat
    else { read uint16, expand via command_ids[] → current_style }

    if ((edx & 0x02) == 0)  this->forward_command = 0x41000003;        // Ready
    else { read uint16, expand → forward_command }

    if ((edx & 0x08) == 0)  this->sidestep_command = 0;
    else { read uint16, expand → sidestep_command }

    if ((edx & 0x20) == 0)  this->turn_command = 0;
    else { read uint16, expand → turn_command }

    if ((edx & 0x04) == 0)  this->forward_speed  = 1.0f;
    else  { this->forward_speed  = *(float*)(*arg2); *arg2 += 4; }

    if ((edx & 0x10) == 0)  this->sidestep_speed = 1.0f;
    else  { this->sidestep_speed = *(float*)(*arg2); *arg2 += 4; }

    if ((edx & 0x40) == 0)  this->turn_speed     = 1.0f;
    else  { this->turn_speed     = *(float*)(*arg2); *arg2 += 4; }

    int32_t i_4 = (edx >> 7) & 0x1f;                                  // action count (5 bits)
    while (i_4-- > 0) {
        // each action: uint16 motion → command_ids[], uint32 speed,
        // uint16 stamp+autonomous bit (0x7fff stamp; 0x8000 autonomous)
        InterpretedMotionState::AddAction(this, motion, speed, stamp, autonomous);
    }

    align_ptr_to_4();
    return 1;
}
```

**Key facts (this is THE definitive flag layout):**

| Bit | Field | When CLEAR |
|----|----|----|
| 0x01 | current_style | defaults to NonCombat (0x8000003d) |
| 0x02 | forward_command | defaults to Ready (0x41000003) |
| 0x04 | forward_speed | defaults to 1.0f |
| 0x08 | sidestep_command | defaults to 0 |
| 0x10 | sidestep_speed | defaults to 1.0f |
| 0x20 | turn_command | defaults to 0 |
| 0x40 | turn_speed | defaults to 1.0f |
| 0x80–0x800 | action count (5 bits) | 0 |

**Crucial corollary for the L.3 port:** when ACE omits a field on the
wire (e.g. doesn't set bit 0x02 because forward_command was
"Invalid" — its idle), the decompiled UnPack DEFAULTS that field to
the table-default value (Ready / 0 / 1.0f). This is **NOT** "preserve
previous." It's "reset to the per-axis default." That's why a stop
broadcast looks like an UM with all command bits cleared.

The wire's `forward_command = 0` (clear bit 0x02) IS the stop signal.
The unpacker maps it to Ready.

---

## 6. command_ids[] — 16-bit → 32-bit motion expansion

`command_ids[]` is a static lookup table that takes the 16-bit
MotionCommand low word and returns the full uint32 (with class byte
reattached). This is how a wire `0x0007` (RunForward low) becomes
`0x44000007` (RunForward full). acdream's
`MotionCommandResolver.ReconstructFullCommand` is the equivalent.

---

## 7. CMotionInterp::move_to_interpreted_state

**Function:** `CMotionInterp::move_to_interpreted_state`
**Address:** `0x005289c0`
**Lines:** 305936–305992.

Verbatim:

```c
005289c0  int32_t __thiscall CMotionInterp::move_to_interpreted_state(this, arg2)
{
    if (physics_obj == 0) return 0;

    this->raw_state.current_style = arg2->current_style;
    CPhysicsObj::interrupt_current_movement(physics_obj);
    uint32_t eax_2 = motion_allows_jump(this, this->interpreted_state.forward_command);
    int32_t esi_1 = -eax_2;
    InterpretedMotionState::copy_movement_from(&this->interpreted_state, arg2);   // ← bulk copy
    apply_current_movement(this, 1, -((esi_1 - esi_1)));                          // cancelMoveTo=1, allowJump=stillAllowed

    MovementParameters var_2c;
    MovementParameters::MovementParameters(&var_2c);

    for (LListData* i = arg2->actions.head_; i != 0; i = i->llist_next)
    {
        // 15-bit action stamp comparison (wrap-aware via 0x7fff)
        int32_t actStamp  = *(int32_t*)((char*)i + 0xc) & 0x7fff;
        int32_t serverStamp = this->server_action_stamp & 0x7fff;
        int32_t delta = abs(actStamp - serverStamp);
        bool isNewer = (delta <= 0x3fff) ? (serverStamp < actStamp) : (actStamp < serverStamp);

        if (isNewer)
        {
            // gate: only fire actions that came from the network (autonomous=0)
            // when this is a player; for NPCs always fire.
            if (weenie_obj == 0 || weenie_obj->vtable->IsThePlayer() == 0
                                || *(int32_t*)((char*)i + 0x10) == 0 /*autonomous bit*/)
            {
                this->server_action_stamp = *(int32_t*)((char*)i + 0xc);
                var_2c.action_stamp = *(int32_t*)((char*)i + 8);
                // var_28 |= 0x1000 = ModifyInterpretedState
                CMotionInterp::DoInterpretedMotion(this, *(int32_t*)((char*)i + 4), &var_2c);
            }
        }
    }
    return 1;
}
```

**Critical facts:**

1. **`copy_movement_from` is UNCONDITIONAL bulk copy** (lines
   293301–293311) — every field of InterpretedState is overwritten:
   `current_style`, `forward_command`, `forward_speed`,
   `sidestep_command`, `sidestep_speed`, `turn_command`, `turn_speed`.
   No filter by stance change, no diff, no per-axis gate. Whatever
   the wire said (post-defaults from UnPack) is now the body's state.

2. After the copy, **`apply_current_movement(cancelMoveTo=true, allowJump)`**
   re-runs the full state machine. This is where the body's velocity
   gets re-derived from the new InterpretedState (see §8).

3. The actions list is iterated separately with stamp-wrap protection
   so we don't replay actions we already saw, and we skip player-self
   echoes that we ourselves originated (autonomous=true).

---

## 8. apply_current_movement → apply_interpreted_movement

**Function:** `CMotionInterp::apply_interpreted_movement`
**Address:** `0x00528600`
**Lines:** 305713–305788.

```c
00528600  void apply_interpreted_movement(this, arg2 /*cancelMoveTo*/, arg3 /*allowJump*/)
{
    if (physics_obj != 0)
    {
        MovementParameters var_2c;
        MovementParameters::MovementParameters(&var_2c);

        // If forward is RunForward, cache the speed as MyRunRate
        if (this->interpreted_state.forward_command == 0x44000007)
            this->my_run_rate = this->interpreted_state.forward_speed;

        // Re-fire DoInterpretedMotion(currentStyle) — re-applies stance
        DoInterpretedMotion(this, this->interpreted_state.current_style, &var_2c);

        if (contact_allows_move(this->interpreted_state.forward_command) == 0)
        {
            // Body is airborne / dead — force Falling
            DoInterpretedMotion(this, 0x40000015 /*Falling*/, &var_2c);
        }
        else
        {
            if (this->standing_longjump != 0)
            {
                DoInterpretedMotion(this, 0x41000003 /*Ready*/, &var_2c);
                StopInterpretedMotion(this, 0x6500000f /*SideStepRight*/, &var_2c);
            }
            else
            {
                // FORWARD axis
                var_2c.speed = this->interpreted_state.forward_speed;
                DoInterpretedMotion(this, this->interpreted_state.forward_command, &var_2c);

                // SIDESTEP axis
                if (this->interpreted_state.sidestep_command == 0)
                    StopInterpretedMotion(this, 0x6500000f /*SideStepRight*/, &var_2c);
                else
                {
                    var_2c.speed = this->interpreted_state.sidestep_speed;
                    DoInterpretedMotion(this, this->interpreted_state.sidestep_command, &var_2c);
                }
            }
        }

        // TURN axis
        if (this->interpreted_state.turn_command != 0) {
            var_2c.speed = this->interpreted_state.turn_speed;
            DoInterpretedMotion(this, this->interpreted_state.turn_command, &var_2c);
            return;
        }
        // No turn — explicit stop
        uint32_t eax_10 = CPhysicsObj::StopInterpretedMotion(physics_obj, 0x6500000d /*TurnRight*/, &var_2c);
        if (eax_10 == 0) {
            add_to_queue(this, var_c, 0x41000003, 0);
            // remove TurnRight from action queue
        }
    }
}
```

**This is the per-tick re-apply.** Every time the wire delivers a new
state (or we land, or we leave ground), this function fires
`DoInterpretedMotion` for each axis (forward, sidestep, turn) so the
physics body re-derives velocity. Velocity comes from
`get_state_velocity` which lives inside `DoInterpretedMotion` →
`CPhysicsObj::DoInterpretedMotion` (not shown in detail here, but
the chain runs through `set_local_velocity`).

---

## 9. CMotionInterp::DoMotion (raw command path)

**Function:** `CMotionInterp::DoMotion`
**Address:** `0x00528d20`
**Lines:** 306159–306217.

```c
00528d20  uint32_t DoMotion(this, arg2 /*motion*/, arg3 /*MovementParameters*/)
{
    if (physics_obj == 0) return 8;

    uint32_t ebp = arg2;
    // ... copy struct fields locally ...

    if (params->__inner0.byte1 < 0)                       // CancelMoveTo bit
        CPhysicsObj::interrupt_current_movement(physics_obj);

    if ((params->__inner0.byte1 & 8) != 0)                // SetHoldKey bit
        SetHoldKey(this, params->hold_key_to_apply, ((__inner0 >> 0xf) & 1));

    adjust_motion(this, &arg2, &speed, params->hold_key_to_apply);

    if (this->interpreted_state.current_style != 0x8000003d /*NonCombat*/) {
        if (ebp == 0x41000012 /*Crouch*/)  return 0x3f;   // CantCrouchInCombat
        if (ebp == 0x41000013 /*Sit*/)     return 0x40;
        if (ebp == 0x41000014 /*Sleep*/)   return 0x41;
        if ((ebp & 0x2000000) != 0)        return 0x42;   // CantChatEmoteInCombat
    }

    if ((ebp & 0x10000000 /*Action*/) != 0
            && InterpretedMotionState::GetNumActions(&this->interpreted_state) >= 6)
        return 0x45;                                       // TooManyActions

    uint32_t result = DoInterpretedMotion(this, arg2, &var_2c);

    if (result == 0 && (params->__inner0.byte1 & 0x20 /*ModifyRawState*/) != 0)
        RawMotionState::ApplyMotion(&this->raw_state, ebp, arg3);

    return result;
}
```

**Behavior:**

- `adjust_motion` (see §10) folds HoldKey + sign-flipped commands.
- Combat-style guards reject Crouch/Sit/Sleep/ChatEmote.
- Action-class commands are queued, max 6 outstanding.
- All work delegates to `DoInterpretedMotion` (§11).
- If caller asked, the raw state is also updated via
  `RawMotionState::ApplyMotion`.

---

## 10. CMotionInterp::adjust_motion

**Function:** `CMotionInterp::adjust_motion`
**Address:** `0x00528010`
**Lines:** 305343–305400.

This is **the canonical sign-flipping / hold-key application function**.
Verbatim core:

```c
00528010  void adjust_motion(this, uint32_t* motion, float* speed, HoldKey holdKey)
{
    if (weenie_obj != 0 && weenie_obj->IsCreature() == 0) return;

    uint32_t cmd = *motion;

    if (cmd == 0x6500000e /*SideStepLeft*/) {
        *motion = 0x6500000f /*SideStepRight*/;
        *speed *= -1.0f;
    }
    else if (cmd == 0x65000010 /*TurnLeft???*/) {  // really an alias
        *motion = 0x6500000f;
        *speed *= -1.0f;
    }
    else if (cmd == 0x45000006 /*WalkBackward*/) {
        *motion = 0x45000005 /*WalkForward*/;
        *speed *= -0.65f;                           // BackwardsFactor
    }
    // (RunForward 0x44000007 falls through unchanged)

    // Sidestep gets its own scale factor
    if (*motion == 0x6500000f /*SideStepRight*/) {
        *speed = (3.12f / 1.25f) * 0.5f * (*speed);  // = 1.248
    }

    if (holdKey == HoldKey_Invalid)
        holdKey = this->raw_state.current_holdkey;

    if (holdKey == HoldKey_Run)
        apply_run_to_command(this, motion, speed);
}
```

**Mappings produced by adjust_motion:**

| Input motion | Input speed | Output motion | Output speed |
|---|---|---|---|
| WalkForward | s | WalkForward | s |
| WalkBackward | s | WalkForward | -0.65 × s |
| TurnLeft | s | TurnRight | -s |
| SideStepLeft | s | SideStepRight | -s |
| SideStepRight (final) | s | SideStepRight | 1.248 × s |
| RunForward | s | RunForward | s |

Then if HoldKey == Run, `apply_run_to_command` fires.

---

## 11. CMotionInterp::apply_run_to_command

**Function:** `CMotionInterp::apply_run_to_command`
**Address:** `0x00527be0`
**Lines:** 305062–305123.

```c
00527be0  void apply_run_to_command(this, uint32_t* motion, float* speed)
{
    long double speedMod;

    if (weenie_obj != 0) {
        if (weenie_obj->InqRunRate(&speedMod) != 0) {
            // speedMod taken from weenie InqRunRate output
        } else {
            speedMod = (long double)this->my_run_rate;
        }
    } else {
        speedMod = 1.0L;
    }

    uint32_t cmd = *motion;

    if (cmd == 0x45000005 /*WalkForward*/) {
        if (*speed > 0.0f)
            *motion = 0x44000007 /*RunForward*/;          // PROMOTION
        *speed = (float)(speedMod * (*speed));
        return;
    }

    if (cmd == 0x6500000d /*TurnRight*/) {
        *speed = (float)(1.5f * (*speed));                // RunTurnFactor
        return;
    }

    if (cmd == 0x6500000f /*SideStepRight*/) {
        speedMod *= (long double)*speed;
        *speed = (float)speedMod;
        if (fabsl(speedMod) > 3.0L) {                     // MaxSidestepAnimRate
            *speed = (speedMod > 0) ? 3.0f : -3.0f;
        }
    }
}
```

**Critical asymmetry — the speed > 0.0 gate:**

```c
if (*speed > 0.0f)
    *motion = 0x44000007 /*RunForward*/;
```

This is the line that prevents `WalkBackward + HoldKey.Run` from
becoming `RunBackward`. After `adjust_motion` flips WalkBackward →
WalkForward with negative speed, this gate keeps the motion as
WalkForward (because speed ≤ 0) and the speed multiplication still
applies the runRate.

So sign-flipped backward arrives at `get_state_velocity` as:
- `forward_command = WalkForward (0x45000005)`
- `forward_speed = -0.65 × runRate` (negative)

Then `get_state_velocity` (next section) hits the WalkForward branch
and produces a NEGATIVE `velocity.Y` — the body moves backward at
walk-pace × 65% × runRate.

---

## 12. CMotionInterp::get_state_velocity

**Function:** `CMotionInterp::get_state_velocity`
**Address:** `0x00527d50`
**Lines:** 305160–305204.

```c
00527d50  void get_state_velocity(this, AC1Legacy::Vector3* out)
{
    long double vx;
    if (this->interpreted_state.sidestep_command != 0x6500000f)
        vx = 0.0L;
    else
        vx = 1.25L * (long double)this->interpreted_state.sidestep_speed;
    out->x = (float)vx;

    long double vy;
    uint32_t fwd = this->interpreted_state.forward_command;
    if (fwd == 0x45000005 /*WalkForward*/)
        vy = 3.12L * (long double)this->interpreted_state.forward_speed;
    else if (fwd == 0x44000007 /*RunForward*/)
        vy = 4.0L  * (long double)this->interpreted_state.forward_speed;
    else
        vy = 0.0L;
    out->y = (float)vy;

    out->z = 0.0f;

    // Cap to maxSpeed = 4.0 * runRate
    long double rate = this->my_run_rate; /* or InqRunRate */
    long double len = sqrtl(vx*vx + vy*vy + 0.0L);
    if (len > 4.0L * rate) {
        long double scale = (4.0L * rate) / len;
        out->x *= (float)scale;
        out->y *= (float)scale;
    }
}
```

**Hard-coded constants (these match ACE 1:1):**

- `WalkAnimSpeed = 3.12 m/s`
- `RunAnimSpeed = 4.0 m/s`
- `SidestepAnimSpeed = 1.25 m/s`
- `MaxSidestepAnimRate = 3.0` (clamp inside apply_run_to_command)
- `BackwardsFactor = 0.65`
- `RunTurnFactor = 1.5`

Velocity output is body-local: X = strafe (right positive),
Y = forward (forward positive), Z = 0. Z gets composed by gravity /
LeaveGround in CPhysicsObj.

---

## 13. CMotionInterp::contact_allows_move

**Function:** `CMotionInterp::contact_allows_move`
**Address:** `0x00528240`
**Lines:** 305471–305505.

```c
00528240  int32_t contact_allows_move(this, uint32_t motion)
{
    if (physics_obj != 0) {
        if (motion > 0x40000015) {
            if (motion >= 0x6500000d && motion <= 0x6500000e)  // TurnRight..TurnLeft
                return 1;                                      // turns always allowed
        } else if (motion == 0x40000015 /*Falling*/ || motion == 0x40000011 /*Dead*/) {
            return 1;
        }

        if (weenie_obj != 0 && weenie_obj->IsCreature() == 0)  // non-creatures (chess pieces, etc)
            return 1;

        if (physics_obj == 0 || (physics_obj->state & 0x4 /*Gravity*/) == 0)
            return 1;                                           // no gravity → always

        uint8_t ts = physics_obj->transient_state;
        if ((ts & 1 /*Contact*/) != 0 && (ts & 2 /*OnWalkable*/) != 0)
            return 1;                                           // grounded
    }
    return 0;                                                   // airborne creature on gravity-affected body
}
```

Used by `DoInterpretedMotion` to decide whether a motion can take
effect right now or must be deferred.

---

## 14. CMotionInterp::DoInterpretedMotion (the leaf)

**Function:** `CMotionInterp::DoInterpretedMotion`
**Address:** `0x00528360`
**Lines:** 305575–305631.

```c
00528360  uint32_t DoInterpretedMotion(this, motion, params)
{
    if (physics_obj == 0) return 8;
    uint32_t result;

    if (contact_allows_move(this, motion) != 0) {
        if (this->standing_longjump != 0
            && (motion == 0x45000005 /*Walk*/ || motion == 0x44000007 /*Run*/ || motion == 0x6500000f /*SideStep*/))
        {
            // skip the engine-side action; just touch InterpretedState if asked
            if (params->__inner0.byte1 & 0x40 /*ModifyInterpretedState*/)
                InterpretedMotionState::ApplyMotion(&this->interpreted_state, motion, params);
            result = 0;
        }
        else {
            if (motion == 0x40000011 /*Dead*/)
                CPhysicsObj::RemoveLinkAnimations(this->physics_obj);

            result = CPhysicsObj::DoInterpretedMotion(this->physics_obj, motion, params);
            if (result == 0) {
                uint32_t jumpErr;
                if ((params->__inner0 & 0x20000) == 0) {
                    jumpErr = motion_allows_jump(this, motion);
                    if (jumpErr == 0 && (motion & 0x10000000 /*Action*/) == 0)
                        jumpErr = motion_allows_jump(this, this->interpreted_state.forward_command);
                } else {
                    jumpErr = 0x48; /*disable*/
                }
                add_to_queue(this, params->context_id, motion, jumpErr);
                if (params->__inner0.byte1 & 0x40 /*ModifyInterpretedState*/)
                    InterpretedMotionState::ApplyMotion(&this->interpreted_state, motion, params);
            }
        }
    }
    else if ((motion & 0x10000000 /*Action*/) == 0) {
        if (params->__inner0.byte1 & 0x40 /*ModifyInterpretedState*/)
            InterpretedMotionState::ApplyMotion(&this->interpreted_state, motion, params);
        result = 0;
    }
    else result = 0x24 /*YouCantJumpWhileInTheAir*/;

    if (physics_obj != 0 && physics_obj->cell == 0)
        CPhysicsObj::RemoveLinkAnimations(physics_obj);

    return result;
}
```

This is **the function `apply_interpreted_movement` calls 3× per UM**
(once each for forward/sidestep/turn axes). Note:

- The actual physics velocity push is inside
  `CPhysicsObj::DoInterpretedMotion` — that's where
  `set_local_velocity(get_state_velocity(...))` happens.
- `InterpretedMotionState::ApplyMotion` updates the InterpretedState
  ONLY when the params flag 0x40 (ModifyInterpretedState) is set.
  In `apply_interpreted_movement`, params is a fresh
  `MovementParameters()` with default flags — and the default has
  ModifyInterpretedState=0. So per-axis re-application is purely a
  PHYSICS push; the state itself stays as `copy_movement_from`
  bulk-loaded it.

---

## 15. InterpretedMotionState::ApplyMotion

**Function:** `InterpretedMotionState::ApplyMotion`
**Address:** `0x0051ea40`
**Lines:** 293531–293564.

```c
0051ea40  void ApplyMotion(this, uint32_t motion, params)
{
    if (motion == 0x6500000d /*TurnRight*/) {
        this->turn_command = 0x6500000d;
        this->turn_speed   = params->speed;
        return;
    }
    if (motion == 0x6500000f /*SideStepRight*/) {
        this->sidestep_command = 0x6500000f;
        this->sidestep_speed   = params->speed;
        return;
    }
    if ((motion & 0x40000000) != 0) {                     // any 0x4xxxxxxx (Walk/Run/Stand/Falling/Dead/etc)
        this->forward_command = motion;
        this->forward_speed   = params->speed;
        return;
    }
    if (motion < 0) {                                     // 0x8xxxxxxx — style change
        this->forward_command = 0x41000003 /*Ready*/;
        this->current_style   = motion;
        return;
    }
    if ((motion & 0x10000000 /*Action*/) != 0) {
        AddAction(this, motion, params->speed, params->action_stamp,
                  (params->__inner0 >> 0xc) & 1 /*autonomous bit*/);
    }
}
```

**This drives the state machine when the engine is *generating*
motion locally (e.g. local player, MoveTo manager).** It is NOT what
the wire-driven path uses — the wire path uses
`copy_movement_from` (§7).

This is the function ACE's `InterpretedMotionState.ApplyMotion`
mirrors verbatim.

---

## 16. Outbound: CommandInterpreter::SendMovementEvent

**Function:** `CommandInterpreter::SendMovementEvent`
**Address:** `0x006b4680`
**Lines:** 700274–700312.

```c
006b4680  void SendMovementEvent(this)
{
    CPhysicsObj* player = this->player;
    if (player != 0 && this->smartbox != 0
                    && CPhysicsObj::InqRawMotionState(player) != 0)
    {
        if (this->autonomy_level != 0)                    // CLIENT IS IN CONTROL
        {
            uint16_t instTs = player->update_times[8];
            int32_t  ctlTs  = player->update_times[4];
            int32_t  teleTs = player->update_times[5];
            int32_t  forceTs= player->update_times[6];
            CMotionInterp* mi = CPhysicsObj::get_minterp(player);
            // contact = (Contact && OnWalkable)
            int32_t contact = (player->transient_state & 1) && (player->transient_state & 2);

            MoveToStatePack pkt;
            MoveToStatePack::MoveToStatePack(&pkt,
                CPhysicsObj::InqRawMotionState(player),   // RAW state, not interpreted
                &player->m_position,
                contact,
                mi->standing_longjump,
                instTs, teleTs, ctlTs, forceTs);

            this->vtable->SendMoveToStateEvent(&pkt);     // → 0xF61C wire
            this->last_sent_position_time = Timer::cur_time;
        }
    }
}
```

**Critical facts:**

- The **OUTBOUND packet (0xF61C MoveToState) uses the RawMotionState**,
  not the InterpretedMotionState. RawState carries the player's
  literal input (e.g. `forward_command = WalkForward`,
  `forward_holdkey = Run`) — the server (or observer) does the
  promotion via its own `apply_raw_movement` → `adjust_motion` chain.
- `RawMotionState::Pack` (`0x0051ed10`, lines 293761–293980) packs a
  flag word with bits matching different fields than the interpreted
  one: 0x01=holdkey, 0x02=style, 0x04=fwd_cmd, 0x08=fwd_holdkey,
  0x10=fwd_speed≠1.0, 0x20=side_cmd, 0x40=side_holdkey, 0x80=side_speed≠1,
  0x100=turn_cmd, 0x200=turn_holdkey, 0x400=turn_speed≠1, then 5-bit
  action count starting at 0x800.

This means the outbound flag layout is **different** from the
inbound InterpretedMotionState layout (different bit positions, plus
holdkey fields). When the local player presses W:
- `raw_state.forward_command = WalkForward`
- `raw_state.forward_holdkey = Run` (because shift not held)
- `raw_state.forward_speed = 1.0` (so flag 0x10 is CLEAR)

When the local player **releases W** (stops walking forward):
- `raw_state.forward_command = Ready (0x41000003)` — the Ready
  default → **flag 0x04 is CLEARED**
- `raw_state.forward_speed = 1.0` → flag 0x10 CLEARED
- (HoldKey may still be Run from the toggle, so flag 0x08 may be set)

So a STOP is **the absence of forward_command on the wire**, plus
the absence of forward_speed. It's encoded as "both flag bits clear,
implicit defaults Ready/1.0."

---

## 17. SendDoMovementEvent — slash-command only

**Function:** `ACCmdInterp::SendDoMovementEvent`
**Address:** `0x0058b230`
**Lines:** 405442–405455.

```c
0058b230  int32_t SendDoMovementEvent(this, motion, speed, holdKey) {
    return CM_Movement::Event_DoMovementCommand(motion, speed, holdKey);
}
0058b250  int32_t SendStopMovementEvent(this, motion, holdKey) {
    return CM_Movement::Event_StopMovementCommand(motion, holdKey);
}
```

These are the **single-action** outbound messages used by slash
commands and macros (e.g. `/say`, `/use`). The cdb live trace from
2026-05-01 confirmed `SendDoMovementEvent` is NOT in the WASD path —
WASD always goes through `SendMovementEvent` → MoveToState
(§16). The DoMovement / StopMovement events are for one-shot motion
commands only.

---

## Answers to the critical questions

### Q: When the local actor stops (releases W), what UM does retail SEND outbound?

**A retail-format 0xF61C MoveToState packet.** The packet's
RawMotionState has `forward_command = Ready (0x41000003)` and
`forward_speed = 1.0`. Both flag bits 0x04 and 0x10 in the
RawMotionState's flag word are CLEARED. HoldKey may remain set.

**There is no separate "stop motion" packet on the WASD path.** The
release of W simply produces another full MoveToState whose raw
state shows Ready+1.0. ACE's relay then re-emits this as a 0xF74C
UpdateMotion to nearby observers, with the InterpretedMotionState's
flag 0x02 cleared (no forward_command field).

### Q: When observer receives that UM, what does CMotionInterp::DoMotion do?

The observer's `MovementManager::unpack_movement` reads movement_type=0
(RawCommand), then `InterpretedMotionState::UnPack` runs (§5). With
the wire's flag 0x02 clear, **forward_command defaults to Ready**.
Flag 0x04 clear → forward_speed defaults to 1.0. Flag 0x08 clear →
sidestep_command = 0. Flag 0x20 clear → turn_command = 0.

Then `MovementManager::move_to_interpreted_state` →
`CMotionInterp::move_to_interpreted_state` (§7) runs:
1. `InterpretedMotionState::copy_movement_from` bulk-copies the
   defaults into the body's interpreted state.
2. `apply_current_movement` → `apply_interpreted_movement` (§8) fires
   `DoInterpretedMotion(Ready, ...)` for forward,
   `StopInterpretedMotion(SideStepRight)` for sidestep, and
   `StopInterpretedMotion(TurnRight)` for turn.
3. `get_state_velocity` returns (0, 0, 0) because forward_command is
   Ready (matches neither WalkForward nor RunForward).
4. `set_local_velocity(0, 0, 0)` — body stops moving.

**Note `DoMotion` itself is NOT called here.** The wire-driven
relay path uses `move_to_interpreted_state`, not `DoMotion`.
`DoMotion` is the LOCAL command path (e.g. `MoveToManager`,
slash commands, animation hooks).

### Q: Does retail observer also have a "stop signal" path via UpdatePosition (separate from UM)?

**No, not for the stop semantics.** UpdatePosition (0xF748) is for
position teleports / heartbeat re-syncs and goes through
`SmartBox::UnpackPositionEvent` (357185, line 357181 case 0xf748).
It does NOT touch InterpretedMotionState. Position can move the body
in space, but the locomotion command (Walk/Run/Ready) is purely
UM-driven.

That said, if a player is moving and stops, the next AutonomousPosition
(0xF749/0xF75A) heartbeat from the server will keep the position
matching, but it's the UM that delivers the Ready transition.

The local prediction layer (`SmartBox::QueueBlobForObject`) holds an
inbound UM if the entity is not yet known — but once known, every
inbound 0xF74C is processed by UnPack → move_to_interpreted_state in
order.

### Q: How does sign-flipped backward (WalkForward + ForwardSpeed = -1) get processed?

**Receiver side (UM observer / DoMotion local):**

1. `InterpretedMotionState::UnPack` reads
   `forward_command = 0x45000005 (WalkForward)` and
   `forward_speed = -1.0f` verbatim from the wire.
2. `move_to_interpreted_state` → `copy_movement_from` writes those
   into InterpretedState unchanged.
3. `apply_interpreted_movement` calls
   `DoInterpretedMotion(WalkForward, params{speed=-1.0})`.
4. `DoInterpretedMotion` → `CPhysicsObj::DoInterpretedMotion` →
   `get_state_velocity`:
   - WalkForward branch hits, `velocity.Y = 3.12 × -1.0 = -3.12 m/s`.
5. `set_local_velocity` pushes a NEGATIVE Y velocity → body translates
   backward in body-local frame.

Critically: `adjust_motion` is **NOT** called on the receive path for
sign-flipped backward. It was already called at the SENDER (typically
the originating client's local `DoMotion`). Once the wire has
`WalkForward + speed=-1.0`, that's the canonical form. ApplyMotion
and copy_movement_from simply copy it.

**On the SEND side**, the local `DoMotion(WalkBackward, +1.0)`:
1. `adjust_motion` flips: motion → WalkForward, speed → -0.65 ×
   BackwardsFactor.
2. If HoldKey == Run, `apply_run_to_command` checks `speed > 0` —
   FALSE — so the motion stays WalkForward (no promotion to
   RunForward), but speed gets multiplied by speedMod (runRate).
3. Final: motion=WalkForward, speed = -0.65 × runRate.
4. `RawMotionState::ApplyMotion` writes that back (when ModifyRawState
   bit set), so the next outbound MoveToState carries
   `forward_command=WalkForward, forward_speed=-0.65×runRate`.

### Q: What's the difference between apply_run_to_command and DoInterpretedMotion?

| Aspect | apply_run_to_command (305062) | DoInterpretedMotion (305575) |
|---|---|---|
| Purpose | **Modifier**: rewrite motion+speed for HoldKey.Run | **Action**: fire physics velocity push + queue + state update |
| Inputs | `motion*, speed*` (in/out), uses my_run_rate | `motion, MovementParameters` |
| Side effects | NONE (pure rewrite via ref params) | Updates InterpretedState (if flag set), enqueues motion, calls `CPhysicsObj::DoInterpretedMotion` (which calls `set_local_velocity`) |
| Promotes WalkForward → RunForward | YES (when speed > 0) | NO |
| Applies speedMod | YES (multiplies speed by runRate) | NO |
| Called from | `adjust_motion` (305388) when holdKey == Run | `DoMotion` (306211), `move_to_interpreted_state` (305983), `apply_interpreted_movement` (305744) |

`apply_run_to_command` is a **command-rewriter** that runs once at
input time. `DoInterpretedMotion` is the **executor** that fires
many times per UM (once per forward/sidestep/turn axis).

---

## Cross-reference with ACE's MotionInterp

ACE's `references/ACE/Source/ACE.Server/Physics/Animation/MotionInterp.cs`
mirrors retail with high fidelity:

| ACE method | Retail equivalent | Lines (retail) | Differences |
|---|---|---|---|
| `DoMotion` (l.112) | `CMotionInterp::DoMotion` | 306159 | Identical structure. Uses `ModifyRawState` flag from `MovementParameters`. |
| `DoInterpretedMotion` (l.51) | same name | 305575 | Identical. |
| `StopMotion` (l.367) | same | 305674 | Identical. |
| `StopInterpretedMotion` (l.329) | same | 305635 | Identical. |
| `StopCompletely` (l.301) | same | 305208 | Identical including `forward_command=Ready, speed=1, side=0, turn=0`. |
| `adjust_motion` (l.394) | same | 305343 | Identical. ACE uses `BackwardsFactor=-1` (?? check) — retail `-0.65`. ACE source shows `speed *= -BackwardsFactor` with a separate constant declaration; need to confirm value. |
| `apply_run_to_command` (l.525) | same | 305062 | Identical mappings. ACE `MaxSidestepAnimRate=3.0f` matches retail. |
| `contact_allows_move` (l.584) | same | 305471 | Identical. |
| `get_state_velocity` (l.678) | same | 305160 | Identical. ACE uses `WalkAnimSpeed=3.12, RunAnimSpeed=4.0, SidestepAnimSpeed=1.25` matching retail. |
| `apply_interpreted_movement` (l.440) | same | 305713 | Identical re-application of forward → sidestep → turn. |
| `move_to_interpreted_state` (l.789) | same | 305936 | Identical. ACE's stamp comparison logic matches the 15-bit wrap. |

ACE's port is faithful. The only deviations seen so far are in the
`apply_raw_movement` path which ACE uses for autonomous (player-self)
echoes, but this isn't on the L.3 critical path.

---

## Cross-reference with acdream

### `src/AcDream.App/Rendering/GameWindow.cs::OnLiveMotionUpdated` (l.2591)

**This is acdream's UM handler. It DIVERGES from retail in important
structural ways.**

acdream's path:
1. Pull `update.MotionState.Stance`, `ForwardCommand`, `ForwardSpeed`
   etc. directly from the parsed wire packet.
2. For non-self entities, **directly mutate**
   `remoteMot.Motion.InterpretedState.ForwardCommand = fullMotion`
   and `.ForwardSpeed = speedMod` (l.2860, 2868). This is
   acdream's equivalent of `copy_movement_from` — but only for
   ForwardCommand+ForwardSpeed, NOT a full bulk copy.
3. SideStep + Turn axes are handled separately via
   `remoteMot.Motion.DoInterpretedMotion(...)` /
   `StopInterpretedMotion(...)` (l.3073, 3079, 3109, 3121).
4. The animation sequencer is driven by a separately computed
   `animCycle` with its own priority logic
   (Forward → Sidestep → Turn → Ready) at l.2918–2952.

### Divergences from retail:

1. **No bulk `copy_movement_from`.** acdream only copies
   ForwardCommand+ForwardSpeed when those wire bits change. Retail
   always copies all 7 fields. Consequence: on a stop UM (no command
   bits set), acdream's parser produces command=null and speed=null;
   the assignment at l.2860 only fires when ForwardCommand changed.
   **It's possible for InterpretedState fields to retain stale
   values across stop UMs** if the parser logic doesn't normalize
   absence to "Ready/1.0." (Need to audit
   `WorldSession.EntityMotionUpdate.MotionState` — does it default
   on absence the same way `InterpretedMotionState::UnPack` does?
   Per CLAUDE.md memory entry on Phase L.X, the wire parser had
   bits wrong before — flag mapping is now correct.)

2. **No per-axis `apply_interpreted_movement` re-fire.** Retail's
   `apply_interpreted_movement` re-runs `DoInterpretedMotion` for
   each axis on every state change, which calls
   `CPhysicsObj::DoInterpretedMotion` and ultimately
   `get_state_velocity` → `set_local_velocity`. acdream's port skips
   this re-fire — it relies on per-tick logic in `TickAnimations` to
   pick up the InterpretedState change next frame. This is the
   "staircase" issue noted in CLAUDE.md.

3. **MotionInterpreter.cs `DoMotion` does not match retail.**
   acdream's `MotionInterpreter.DoMotion` (l.381–395) just records
   RawState and forwards to `DoInterpretedMotion(motion, speed,
   modifyInterpretedState:true)`. Retail's `DoMotion` calls
   `adjust_motion` first, then `DoInterpretedMotion`, then
   conditionally `RawMotionState::ApplyMotion`. The acdream version
   skips the `adjust_motion` call — meaning a local
   `DoMotion(WalkBackward, +1.0)` would NOT get sign-flipped to
   `WalkForward + -0.65`. (For the L.3 receiver path this doesn't
   matter because the wire already carries the post-adjust form;
   for the local-player command path it does matter and is a separate
   bug.)

4. **`get_state_velocity` (MotionInterpreter.cs l.587)** is faithful
   to retail except it adds an Option-B path that reads from
   `GetCycleVelocity` (the sequencer's MotionData.Velocity) when
   available — overriding the hardcoded `RunAnimSpeed=4.0` constant
   with the dat-baked velocity. This is a deliberate enhancement
   for non-humanoid creatures (different MotionData scales) and is
   noted in the comment block. It's safe because the max-speed clamp
   below still uses `RunAnimSpeed × runRate`.

5. **`StopInterpretedMotion` (MotionInterpreter.cs l.460)** does NOT
   re-run `apply_interpreted_movement`. It only edits InterpretedState
   then calls `apply_current_movement(false, false)` — which itself
   doesn't re-fire per-axis like retail does. This matches the
   retail single-stop semantics, but combined with #2 above it means
   a single sidestep-clear UM doesn't immediately push zero X velocity
   to the body.

### Files to compare side-by-side during the L.3 port:

- `src/AcDream.Core/Physics/MotionInterpreter.cs` — the executor
- `src/AcDream.App/Rendering/GameWindow.cs::OnLiveMotionUpdated` — the
  receiver glue
- `src/AcDream.Core/Net/WorldSession.cs` (search for
  `EntityMotionUpdate`) — the wire parser
- `references/ACE/Source/ACE.Server/Physics/Animation/MotionInterp.cs`
  — ACE's mirror
- `docs/research/named-retail/acclient_2013_pseudo_c.txt`:305062–306268
  — retail source of truth.
