# Combat Animation Planner Pseudocode

## Sources

- Retail `ClientCombatSystem::ExecuteAttack` (`0x0056BB70`): sends
  targeted melee or missile attack intent and records pending response state.
  It does not choose a local swing animation.
- Retail `ClientCombatSystem::HandleCommenceAttackEvent` (`0x0056AD20`):
  starts/updates power-bar and busy UI state. The event carries no
  `MotionCommand`.
- Retail command-name table around `0x00803F34`: combat commands include
  `Twitch1..4`, `StaggerBackward`, `StaggerForward`, `ThrustMed`,
  `SlashHigh`, `Shoot`, `AttackHigh1`, and later offhand/multistrike
  commands.
- ACE `Player_Melee.DoSwingMotion` and `GetSwingAnimation`: server chooses
  a swing from `CombatManeuverTable.GetMotion(...)` and broadcasts the
  selected `MotionCommand` with `UpdateMotion`.
- ACE `CombatManeuverTable.GetMotion`: indexes `(stance, attack height,
  attack type)` to one or more motion commands; power level chooses between
  multiple entries.

## Retail Rule

Combat GameEvents are state/UI notifications. Motion state is the animation
authority.

## Pseudocode

```text
PlanForEvent(event):
    return None

PlanFromWireCommand(wireCommand, speed):
    fullCommand = MotionCommandResolver.ReconstructFullCommand(wireCommand)
    return PlanFromFullCommand(fullCommand, speed)

PlanFromFullCommand(fullCommand, speed):
    kind = ClassifyMotionCommand(fullCommand)
    if kind is None:
        return None

    routeKind = AnimationCommandRouter.Classify(fullCommand)
    return Plan(kind, routeKind, fullCommand, speed)

ClassifyMotionCommand(fullCommand):
    if command is a combat stance:
        return CombatStance
    if command is a thrust/slash/backhand/offhand/multistrike motion:
        return MeleeSwing
    if command is Shoot, MissileAttack*, or Reload:
        return MissileAttack
    if command is AttackHigh/Med/Low 1..6:
        return CreatureAttack
    if command is CastSpell, UseMagicStaff, or UseMagicWand:
        return SpellCast
    if command is Twitch*, Stagger*, FallDown, or Sanctuary:
        return HitReaction
    if command is Dead:
        return Death
    return None
```

## Maneuver Selection Pseudocode

```text
SelectMotion(table, stance, attackHeight, attackType, powerLevel,
             isThrustSlashWeapon):
    candidates = []
    for maneuver in table.CombatManeuvers:
        if maneuver.Style == stance
           and maneuver.AttackHeight == attackHeight
           and maneuver.AttackType == attackType:
            candidates.append(maneuver.Motion)

    if candidates is empty:
        return None

    subdivision = isThrustSlashWeapon ? 0.66 : 0.33

    if candidates.Count > 1 and powerLevel < subdivision:
        motion = candidates[1]
    else:
        motion = candidates[0]

    return motion
```

This matches ACE `CombatManeuverTable.GetMotion` plus
`Player_Melee.GetSwingAnimation`. The `prevMotion` parameter is present in
ACE's table API but the current ACE implementation does not use it; the
power threshold chooses between multiple entries.

## Named Retail Motion IDs

`DatReaderWriter.Enums.MotionCommand` is shifted by three entries starting
at `AllegianceHometownRecall`. Named retail command tables are:

- `command_ids` table lines 1017626-1017658:
  `0x016E..0x0197 -> 0x1000016E..0x10000197`.
- command-name table lines 1068272-1068313:
  `OffhandSlashHigh = 0x10000170`, `AttackLow6 = 0x1000018B`,
  `PunchFastLow = 0x1000018E`, etc.

`MotionCommandResolver` therefore overrides that range after building the
DRW reflection table, otherwise offhand and late unarmed attack actions
resolve as UI/mappable commands and never reach `PlayAction`.

## Implementation Note

The next table-driven layer can use `DatReaderWriter.DBObjs.CombatTable`
and `DatReaderWriter.Types.CombatManeuver` directly. acdream already
references `Chorizite.DatReaderWriter`; the missing live-state piece is a
named `CombatTable` data-id on player/creature state.
