acdream/docs/research/2026-06-26-ace-vs-2013-motion-command-gap.md
Erik 33ad231d18 docs(L.2b/L.1b): movement wire-parity slice design + 2026-06-26 audits
Imports the two 2026-06-26 movement/animation retail-parity research docs
(audit D1-D12 + ACE-vs-2013 command-catalog gap) and adds the design spec
for the first implementation slice: dual command catalog (AceModern runtime
default + full-extraction Retail2013 conformance) + outbound wire parity
(RawMotionState default-difference packing D1, contact|standingLongjump
trailing byte D3, retail JumpPack layout D4). Decomp-verbatim, tests-first;
D6 motion-interpreter input-state construction explicitly deferred.

Spec: docs/superpowers/specs/2026-06-30-movement-wire-parity-design.md
Phases: L.2b (movement wire/contact authority) + L.1b (command router).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 21:34:33 +02:00

6.4 KiB

ACE vs 2013 Retail Motion Command Gap

Date: 2026-06-26

Why this matters

The movement/animation parity work cannot use one command catalog blindly.

The 2013 acclient.exe decomp has a command_ids[0x198] table and a matching command-name table, but ACE's MotionCommand enum is based on a later client catalog. The local DAT motion tables also contain later-client command keys. If AcDream reconstructs incoming ACE wire commands through the 2013 table only, some server-triggered animations will disappear.

The concrete example is lifestone recall:

  • 2013 retail decomp names LifestoneRecall as 0x10000150.
  • Current ACE names LifestoneRecall as 0x10000153.
  • Local DAT MotionTable links include both values, but many later recall/offhand commands only exist under the ACE-shifted value.

Sources checked

  • 2013 retail decomp:
    • docs/research/named-retail/acclient_2013_pseudo_c.txt
    • command_ids[0x198] at 0x007c73e8
    • command-name table around 0x008041ec..0x0080444c
  • ACE current master:
    • https://raw.githubusercontent.com/ACEmulator/ACE/master/Source/ACE.Entity/Enum/MotionCommand.cs
    • https://raw.githubusercontent.com/ACEmulator/ACE/master/Source/ACE.Entity/Enum/CommandMasks.cs
  • Local DATs:
    • C:\Users\erikn\Documents\Asheron's Call\client_portal.dat
    • scanned 436 MotionTable records with Chorizite.DatReaderWriter 2.1.7
  • Current AcDream resolver:
    • src/AcDream.Core/Physics/MotionCommandResolver.cs

Catalog mismatch

Parsed inventory:

  • 2013 retail command names parsed: 406
  • ACE command names parsed: 409
  • Common names: 400
  • Common names with different values: 130

The important mismatch is a contiguous low-word +3 shift beginning after the targeting UI block:

Name 2013 retail ACE current
SnowAngelState 0x43000115 0x43000118
MeditateState 0x43000119 0x4300011C
Pickup5 0x40000133 0x40000136
HouseRecall 0x10000137 0x1000013A
SitState 0x4300013A 0x4300013D
HaveASeat 0x1300014F 0x13000152
LifestoneRecall 0x10000150 0x10000153
MarketplaceRecall 0x10000163 0x10000166
AllegianceHometownRecall 0x1000016E 0x10000171
PKArenaRecall 0x1000016F 0x10000172
OffhandSlashHigh 0x10000170 0x10000173

ACE's enum comments show the branch point:

  • SkillHealSelf = 0x1000010e
  • SkillHealOther = 0x1000010f
  • duplicate/commented legacy slots around 0x010f..0x0111
  • SnowAngelState = 0x43000118

2013 retail instead has:

  • SkillHealSelf = 0x0900010E
  • NextMonster = 0x0900010F
  • PreviousMonster = 0x09000110
  • ClosestMonster = 0x09000111
  • NextPlayer = 0x09000112
  • PreviousPlayer = 0x09000113
  • ClosestPlayer = 0x09000114
  • SnowAngelState = 0x43000115

So this is a later-client catalog divergence, not just a single bad enum value.

MotionTable availability

I scanned all 436 local DAT MotionTable records for the selected 2013 and ACE values. Counts below are link target hits; recall/action commands are stored in Links, not Cycles.

Command 2013 value hits ACE value hits Interpretation
HouseRecall 317 24 both exist; the old value is very common
LifestoneRecall 28 19 both exist; ACE /ls value can animate
MarketplaceRecall 0 19 only ACE-shifted value exists
AllegianceHometownRecall 0 19 only ACE-shifted value exists
PKArenaRecall 0 18 only ACE-shifted value exists
OffhandSlashHigh 0 31 only ACE-shifted value exists

This means the local DATs are not pure 2013-command-table data. They include later-client action keys that match ACE/DatReaderWriter.

Current AcDream resolver behavior

Current MotionCommandResolver.ReconstructFullCommand results:

Wire low Current full command
0x0137 0x40000137
0x013A 0x1000013A
0x0150 0x13000150
0x0153 0x10000153
0x0163 0x09000163
0x0166 0x10000166
0x016E 0x1000016E
0x016F 0x1000016F
0x0170 0x10000170
0x0171 0x10000171
0x0172 0x10000172
0x0173 0x10000173

Implications:

  • ACE /ls wire 0x0153 currently reconstructs to 0x10000153, which is good for ACE and local DATs.
  • 2013 retail lifestone wire 0x0150 would currently reconstruct as ScanHorizon chat emote, not LifestoneRecall.
  • The override comment in MotionCommandResolver is wrong: the mismatch does not start at AllegianceHometownRecall; it starts earlier around SnowAngelState.
  • The override range 0x016E..0x0197 maps 0x016E/0x016F/0x0170 to action-class commands even though ACE names those low words as UI commands. This probably does not hurt normal ACE animation broadcasts, but it is not a clean catalog model.

Recommendation

Do not replace ACE/DatReaderWriter reconstruction with 2013 command_ids globally.

Use two catalogs explicitly:

  1. Retail2013CommandCatalog

    • For proving decomp behavior and reproducing the 2013 client command table.
    • Useful for old-client conformance tests.
  2. AceModernCommandCatalog

    • For reconstructing ACE InterpretedMotionState wire u16 commands into the full 32-bit motion commands that match local DAT motion tables.
    • Should be the runtime default while talking to ACE.

Then add a resolver mode/test matrix:

  • ACE mode:
    • 0x0153 -> 0x10000153 (LifestoneRecall)
    • 0x0166 -> 0x10000166 (MarketplaceRecall)
    • 0x0171 -> 0x10000171 (AllegianceHometownRecall)
    • 0x0173 -> 0x10000173 (OffhandSlashHigh)
  • 2013 retail mode:
    • 0x0150 -> 0x10000150 (LifestoneRecall)
    • 0x0163 -> 0x10000163 (MarketplaceRecall)
    • 0x016E -> 0x1000016E (AllegianceHometownRecall)
    • 0x0170 -> 0x10000170 (OffhandSlashHigh)

Animation lookup should be tested against the DAT motion tables too, not just enum names. A command is visually usable only if the entity's MotionTable has a Links or Modifiers entry for that full command.

Bottom line

For ACE interop, the lifestone recall animation is not missing from the local DAT motion tables. It is missing only if we force ACE's wire 0x0153 through the 2013 retail command catalog.

The gap is real and broader than lifestone recall: the later-client/ACE command catalog diverges from the 2013 decomp for 130 common names, and several important animations only exist under the ACE-shifted IDs in the local DATs.