# L.2g S2 — inbound CMotionInterp funnel: verbatim pseudocode Date: 2026-07-02. Oracle: named decomp (line refs into `docs/research/named-retail/acclient_2013_pseudo_c.txt`) + the LIVE cdb trace of a retail observer (`l2g-observer-trace.log`, breakpoint script `tools/cdb/l2g-observer.cdb`) which confirmed the exact runtime chain and per-UM `DoInterpretedMotion` order: **style → forward → sidestep(-stop) → turn(-stop)**, applied wholesale the tick the message arrives. Empty-UM semantics (settled): `InterpretedMotionState::UnPack` (0x0051f400, 294360) decodes absent fields to ctor defaults — style `0x8000003D`, forward `0x41000003 Ready`, speeds `1.0`, sidestep/turn `0` — so a flags=0 UM is a retail-verbatim FULL STOP. No special-casing. (The wire varies with the DRIVER's client state; both explicit-walk and empty variants are handled by the same wholesale apply. `RawMotionState::Pack` 0x0051ed10 confirmed pure static-default-diff — outbound L.2b port stays as-is.) ## 1. MovementManager::unpack_movement — case 0 (0x00524440, 300563) ``` unpack_movement(blob): if minterp == null or physics_obj == null: return 0 physics_obj.interrupt_current_movement() physics_obj.unstick_from_object() u16 header = read_u16() # low byte: movement type; high byte: motionFlags u16 styleIdx = read_u16() # outer style, command_ids[] index style = command_ids[styleIdx] if minterp.get_current_style() != style: # "GetPinVersion" in BN minterp.DoMotion(style, default_params) # style applied ONLY on change switch header.low_byte: case 0: # InterpretedMotionState ims = InterpretedMotionState() # ctor defaults (see above) ims.UnPack(blob) # absent fields keep defaults stickyGuid = (header & 0x100) ? read_u32() : 0 MovementManager.move_to_interpreted_state(ims) if stickyGuid: physics_obj.stick_to_object(stickyGuid) minterp.standing_longjump = (header & 0x200) return 1 case 6/7: MoveTo… (existing acdream path, keep) case 8/9: TurnTo… (S6) default: return 0 ``` ## 2. CMotionInterp::move_to_interpreted_state (0x005289c0, 305936) ``` move_to_interpreted_state(ims): if physics_obj == null: return 0 raw_state.current_style = ims.current_style physics_obj.interrupt_current_movement() jumpAllowed = motion_allows_jump(interpreted_state.forward_command) # OLD state! interpreted_state.copy_movement_from(ims) # FLAT overwrite (0x0051e750) apply_current_movement(force=1, jumpAllowed) for action in ims.actions: # MotionItem list # 15-bit wraparound stamp gate vs server_action_stamp (305953-305971) if newer_15bit(action.stamp, server_action_stamp): if weenie is player and action.autonomous: skip # local echo guard server_action_stamp = action.stamp DoInterpretedMotion(action.command, params(speed=action.speed)) return 1 ``` ## 3. CMotionInterp::apply_current_movement (0x00528870, 305838) ``` apply_current_movement(force, jumpAllowed): if physics_obj == null or !initted: return if (weenie==null or weenie.IsThePlayer()) and physics_obj.movement_is_autonomous(): apply_raw_movement(force, jumpAllowed) # LOCAL player (already ported, D6) else: apply_interpreted_movement(force, jumpAllowed) # REMOTES — this port ``` ## 4. CMotionInterp::apply_interpreted_movement (0x00528600, 305713) ``` apply_interpreted_movement(force, jumpAllowed): if physics_obj == null: return if interpreted_state.forward_command == RunForward (0x44000007): my_run_rate = interpreted_state.forward_speed # cache server run rate DoInterpretedMotion(interpreted_state.current_style, {}) # stance if !contact_allows_move(interpreted_state.forward_command): DoInterpretedMotion(0x40000015 Falling, {}) elif standing_longjump: DoInterpretedMotion(0x41000003 Ready, {}) StopInterpretedMotion(0x6500000F SideStep…, {}) else: DoInterpretedMotion(interpreted_state.forward_command, {speed: interpreted_state.forward_speed}) if interpreted_state.sidestep_command == 0: StopInterpretedMotion(0x6500000F, {}) else: DoInterpretedMotion(interpreted_state.sidestep_command, {speed: interpreted_state.sidestep_speed}) if interpreted_state.turn_command != 0: DoInterpretedMotion(interpreted_state.turn_command, {speed: interpreted_state.turn_speed}) return # early — no idle-stop this call if StopInterpretedMotion(0x6500000D Turn…, {}) == 0: add_to_queue(ctx=0, Ready, tick) # idle bookkeeping (S3 wires fully) ``` Live-trace confirmation (actor minterp 18e8b0f8): per UM exactly `[DIM] 0x8000003D` then `[DIM] ` (0x45000005 / 0x41000003 / 0x44000007) then sidestep/turn stops (0x6500000F / 0x6500000D) — order verbatim. ## 5. CMotionInterp::DoInterpretedMotion (0x00528360, 305575) ``` DoInterpretedMotion(motion, params): if physics_obj == null: return 8 if contact_allows_move(motion): if standing_longjump and motion in jump-set: goto apply_only if motion == 0x40000011: physics_obj.RemoveLinkAnimations() result = physics_obj.DoInterpretedMotion(motion, params) # → CPartArray → MotionTableManager::PerformMovement # → CMotionTable::GetObjectSequence ≙ AnimationSequencer.SetCycle if result == 0: add_to_queue(params.context_id, motion, jumpAllowed) # pending_motions (S3) if params.flags & 0x40: interpreted_state.ApplyMotion(motion, params) elif (motion & 0x10000000) == 0: apply_only: if params.flags & 0x40: interpreted_state.ApplyMotion(motion, params) result = 0 else: result = 0x24 if physics_obj != null and physics_obj.cell == 0: physics_obj.RemoveLinkAnimations() return result ``` ## acdream mapping (surgical) - **New in `MotionInterpreter` (Core.Physics):** `MoveToInterpretedState(ims)`, `ApplyInterpretedMovement()`, using the EXISTING `DoInterpretedMotion` / `StopInterpretedMotion` (extended to retail semantics above) with the sequencer as the `GetObjectSequence` backend. `InterpretedMotionStateData` = a plain struct mirroring the ctor defaults; built from `UpdateMotion.Parsed` (absent wire fields → defaults — the parser already yields nullables; the CONVERSION applies the defaults). - **`OnLiveMotionUpdated` remote SubState branch collapses** to: build ims → `remoteMot.Motion.MoveToInterpretedState(ims, sequencer)`. Style-on-change at the unpack level (today's `fullStyle` preserve-current logic maps to `get_current_style()` compare). PRESERVE deliberately: K-fix17 airborne cycle guard, HasCycle fallback chain, MoveTo (case 6/7) seeding, overlay (Action-class) routing — these live in the DIM backend, not deleted. - **Stop** rides the same path: empty UM → ims defaults → fwd=Ready → apply → sequencer Ready + `get_state_velocity`→0 → body velocity zero. The acdream-invented 300 ms stop-detection window + UP-near-zero StopCompletely become dead code to remove in the same slice (DEV-3). - **`my_run_rate` caching** per remote (apply_interpreted_movement head). - Tests first: fake-sequencer call-order tests per UM shape (empty, walk, run@2.85, run+turn, action list, stale action stamp), then live smoke. ## Live-trace answer to the S0-open question Retail observer applies EVERY accepted UM wholesale — including empties as full stops. "Correct behavior" (user-verified during the trace) comes from this + the chase, not from any inference. acdream's S2 target = exactly this.