# 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.