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>
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
LifestoneRecallas0x10000150. - Current ACE names
LifestoneRecallas0x10000153. - Local DAT
MotionTablelinks 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.txtcommand_ids[0x198]at0x007c73e8- command-name table around
0x008041ec..0x0080444c
- ACE current master:
https://raw.githubusercontent.com/ACEmulator/ACE/master/Source/ACE.Entity/Enum/MotionCommand.cshttps://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
MotionTablerecords withChorizite.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 = 0x1000010eSkillHealOther = 0x1000010f- duplicate/commented legacy slots around
0x010f..0x0111 SnowAngelState = 0x43000118
2013 retail instead has:
SkillHealSelf = 0x0900010ENextMonster = 0x0900010FPreviousMonster = 0x09000110ClosestMonster = 0x09000111NextPlayer = 0x09000112PreviousPlayer = 0x09000113ClosestPlayer = 0x09000114SnowAngelState = 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
/lswire0x0153currently reconstructs to0x10000153, which is good for ACE and local DATs. - 2013 retail lifestone wire
0x0150would currently reconstruct asScanHorizonchat emote, notLifestoneRecall. - The override comment in
MotionCommandResolveris wrong: the mismatch does not start atAllegianceHometownRecall; it starts earlier aroundSnowAngelState. - The override range
0x016E..0x0197maps0x016E/0x016F/0x0170to 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:
-
Retail2013CommandCatalog- For proving decomp behavior and reproducing the 2013 client command table.
- Useful for old-client conformance tests.
-
AceModernCommandCatalog- For reconstructing ACE
InterpretedMotionStatewireu16commands into the full 32-bit motion commands that match local DAT motion tables. - Should be the runtime default while talking to ACE.
- For reconstructing 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.