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>
This commit is contained in:
parent
8ec0944a1c
commit
9958458318
6 changed files with 275 additions and 135 deletions
81
docs/research/2026-07-11-local-combat-motion-pseudocode.md
Normal file
81
docs/research/2026-07-11-local-combat-motion-pseudocode.md
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
# 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.
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue