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

3.2 KiB

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

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.