acdream/docs/research/2026-07-11-local-combat-motion-pseudocode.md
Erik 9958458318 fix(combat): apply server attack motions locally
Route accepted non-autonomous local UpdateMotion state through retail's wholesale interpreted-motion funnel, so ACE-selected melee and missile actions use the normal motion queue and action-stamp gate. Share nullable wire conversion with remotes and remove the local direct command replay.

Co-Authored-By: Codex <codex@openai.com>
2026-07-11 13:34:55 +02:00

81 lines
3.2 KiB
Markdown

# Local combat UpdateMotion — retail receive pseudocode
Date: 2026-07-11
## Question
Who chooses and plays the local player's melee or missile attack motion?
## Named-retail oracle
- `ClientCombatSystem::ExecuteAttack` at `0x0056BB70` sends the targeted
melee/missile request and updates combat-repeat state. It does not choose a
concrete swing command and does not play an animation locally.
- `CPhysics::SetObjectMovement` at `0x00509690` rejects a local autonomous
echo, but accepts a newer non-autonomous movement event, stores
`last_move_was_autonomous = false`, calls `unpack_movement`, then returns 1
so `CommandInterpreter::LoseControlToServer` runs.
- `MovementManager::unpack_movement` at `0x00524440`, case 0, unpacks an
`InterpretedMotionState`, calls `move_to_interpreted_state`, then applies the
sticky-object and standing-long-jump trailers.
- `CMotionInterp::move_to_interpreted_state` at `0x005289C0` copies the state
wholesale, reapplies style/forward/side/turn, and dispatches fresh command
list entries under the 15-bit `server_action_stamp` gate. The player skips
only command entries whose autonomous bit is set.
## ACE cross-reference
ACE's `Player_Melee.DoSwingMotion` selects the actual maneuver from the combat
table, constructs a `Motion`, marks the target sticky, and broadcasts it.
Missile attacks similarly enqueue the server-selected aim action, followed by
reload and Ready. `MovementData.IsAutonomous` documents the wire split as
client-initiated `true`, server-initiated `false`.
## Retail pseudocode
```text
on local attack input:
send TargetedMeleeAttack or TargetedMissileAttack
do not invent or play a swing locally
on UpdateMotion for local player:
apply movement and server-control sequence gates
if event.isAutonomous:
drop the entire event
player.lastMoveWasAutonomous = false
interrupt current move
unstick
apply outer style if it changed
if movementType is MoveTo/TurnTo:
route to MoveToManager
return
if movementType == InterpretedMotionState:
ims = constructor defaults (NonCombat, Ready, speeds 1)
unpack every present field over ims
move_to_interpreted_state(ims)
copy every axis wholesale
apply style, forward, sidestep, turn
for each fresh command-list entry:
if local player and entry.autonomous: skip
dispatch entry through the motion table
apply sticky target trailer
write standingLongJump, including false
command interpreter loses control to server
next local input edge takes autonomous control back
```
## acdream correction
The local branch already had the autonomous-event rejection and the
interrupt/style/MoveTo routing, but deliberately skipped the mt-0 wholesale
copy. It then replayed `Commands[]` directly through
`AnimationCommandRouter`, bypassing retail's action-stamp and local-autonomous
gates. The correction routes local mt-0 through the existing
`MotionInterpreter.MoveToInterpretedState` and persistent motion-table sink,
exactly like remotes, and removes the direct replay. A shared App-layer factory
now owns nullable-wire-to-constructor-default conversion for both branches.