# Retail client slash-command registry — complete enumeration + acdream audit Date: 2026-08-09 Status: RESEARCH ONLY. No production code was changed. Oracle: `docs/research/named-retail/acclient_2013_pseudo_c.txt` (Sept 2013 EoR build) plus byte-level decode of the PDB-paired binary `C:\Users\erikn\Downloads\acclient.exe` (v11.4186, CodeView GUID `9e847e2f-777c-4bd9-886c-22256bb87f32`). Every verb string in this document was read out of `.rdata` at the exact `push imm32` operand feeding `PStringBase::PStringBase` inside the registration loop — none were inferred from Binary Ninja's symbolic rendering, which mislabels several of the pooled single-character strings as wide-string slices (`&*U"fvpca"[4]`) and two as vtable fields. Extends `docs/research/2026-07-13-retail-client-command-families-pseudocode.md`, which already proved out the families acdream ported (lifestone/marketplace/ arena/age/birth/framerate/lockui/die/loc/corpse/clear/UI-layout/friends/afk/ consent/squelch/filter/emote/fillcomps). Those pseudocode blocks are not repeated here. --- ## 1. Registry mechanism There is **one** command table and **two** functions that populate it: `ClientCommunicationSystem::m_hashCommands`, an `IntrusiveHashTable>, CmdHashData*>` sized to 100 buckets in the ctor at `0x0058555D`. `ClientCommunicationSystem::InitializeCommands @ 0x00581970` (spanning to `0x00585520`) registers **116** entries; `CmdHashData::CmdHashData @ 0x0056ED30` stores `+0x00` verb, `+0x08` `func` (`uint8_t (*)(this, int argc, char** argv)`), `+0x0C` unused-in-practice, `+0x10` `help` (`uint8_t (*)(this, HelpType, char const*, PStringBase*)`), `+0x14` unused. `ClientCommunicationSystem::StartupTurbineChatSystem @ 0x0057EFB0` runs only when the server enables Turbine Chat; it **removes** the `a` entry and adds 15 entries (`a`, `guild`, `gu`, `general`, `cg`, `trade`, `ct`, `lfg`, `clfg`, `roleplay`, `crp`, `society`, `soc`, `olthoi`, `o`), i.e. 14 net-new verbs. Nothing else ever calls `IntrusiveHashTable<…CmdHashData*>::add` — verified by grepping every call site (18 in `0x0057xxxx`, all inside `StartupTurbineChatSystem` plus the two hash-table helpers themselves; 116 in `0x0058xxxx`, all inside `InitializeCommands`). Dispatch is `ClientCommunicationSystem::OnChatCommand @ 0x00581320` → `ClientCommunicationSystem::DoCommand @ 0x0057E2E0`. `OnChatCommand` switches on `firstChar - 0x2F`: `'/'` (case 0) is **rewritten in place to `'@'`** and falls into `DoCommand`; `'@'` (case 0x11) enters `DoCommand` directly; `':'` and `';'` (cases 0x0B/0x0C) have their first character replaced with a space and the whole line prefixed with the literal `"@emote"` before `DoCommand` — that is retail's emote shorthand. Anything else routes to `PublicChat` / talk-focus. `DoCommand` splits the line on `" \t"` (`PSUtils::FindAllWords`, delimiter set at `0x007E0F5C`), takes `words[0].substring(1)` as the verb, **right-trims `','`** from it (trim char set `0x0079452C`, `trim(left=0, right=1)`) so `@f, hello` works, then looks the verb up. If the entry is absent **or its `func` is NULL**, it calls `ClientCommunicationSystem::DoChannelCommand @ 0x005774A0`, which tries `ChannelSystem::GetChannelID @ 0x005CF1F0` on the verb and, on a hit, sends `CM_Communication::Event_ChannelBroadcast(id, joinedArgs)`. Only if *that* also fails does the client fall back to `CM_Communication::Event_Talk` with the **original `@`-prefixed line** — which is exactly the passthrough ACE relies on (`references/ACE/Source/ACE.Server/Network/GameAction/Actions/GameActionTalk.cs:21`, `if (message.StartsWith("@"))` → `CommandManager`). A registered handler that returns 0 raises `HandleFailureEvent(0x26, "")`. --- ## 2. Complete retail command table One row per handler. `A` in the Turbine column = the row exists only after `StartupTurbineChatSystem`. acdream paths are relative to the repo root. ### 2.1 Help / group nodes | Verb | Aliases | Handler @addr | Args | Behavior | acdream status | |---|---|---|---|---|---| | `help` | `?` | `DoHelp @ 0x0057F9E0` | `[command\|group]` | No args: prints the group index + "Note: You may substitute a forward slash (/) for the at symbol (@)." With an arg: looks the verb up and calls its `help` fn. | **PARTIAL** — `ChatCommandRouter.TryHandleLocalPresentationCommand` (`src/AcDream.UI.Abstractions/Panels/Chat/ChatCommandRouter.cs:96`) prints one flat blob for `/help`, `/?`, `/h`. No `@help `, no group topics. `/h` is an acdream invention (not a retail verb). | | `commands` | — | *(NULL func)*, help `HelpAllGroup @ 0x0057E7F0` | — | Help-topic node only. Typing it alone falls through to channel lookup then server. | MISSING | | `allegiances` | — | *(NULL func)*, `HelpAllegiancesGroup @ 0x0057D4C0` | — | Help topic. | MISSING | | `channels` | — | *(NULL func)*, `HelpChannelsGroup @ 0x005773E0` | — | Help topic. | MISSING | | `chatting` | — | *(NULL func)*, `HelpChattingGroup @ 0x0057B1A0` | — | Help topic. | MISSING | | `death` | — | *(NULL func)*, `HelpDeathGroup @ 0x0057B8B0` | — | Help topic. | MISSING | | `status` | — | *(NULL func)*, `HelpStatusGroup @ 0x0057C410` | — | Help topic. | MISSING | | `text` | — | *(NULL func)*, `HelpTextGroup @ 0x0057C6C0` | — | Help topic. | MISSING | ### 2.2 Local chat routing | Verb | Aliases | Handler @addr | Args | Behavior | acdream status | |---|---|---|---|---|---| | `say` | `s` | `DoSay @ 0x00581240` | `` | Joins + trims args, `PublicChat`. Empty → "You must specify the text you wish to say." | IMPLEMENTED-EQUIVALENT — `ChatInputParser` `SayAliases = {"/say","/s"}` (`ChatInputParser.cs:41`). Not a catalog command; routed as `SendChatCmd(Say)`. | | `tell` | `t`, `send`, `whisper`, `w` | `DoTell @ 0x00577E40` | `, ` | Splits on the **first comma**; name = left, text = right. Sets `SetLastTelleeName`, sends `CM_Communication::Event_TalkDirectByName`. Retail help is explicit: "you must put a comma after the character's name." | **PARTIAL** — `ChatInputParser.TellAliases = {"/tell","/t"}` only; `send`/`whisper`/`w` MISSING. acdream splits on **whitespace first** and merely trims a trailing comma off the target (`ChatInputParser.cs:208-220`), so a multi-word name (`/t Lord Gnarly, hi`) resolves the wrong target where retail resolves it correctly. | | `reply` | `r`, `rp` | `DoReply @ 0x00577910` | `` | Sends `Event_TalkDirect` to `gmCCommunicationSystem::GetLastTeller()` (a **guid**, not a name). No teller → "Someone must @tell you first!" | **PARTIAL/DIVERGENT** — `ReplyAliases = {"/reply","/r"}`; `rp` is MISSING **and is bound to Roleplay in acdream** (`ChatInputParser.cs:78`). acdream replies by *name*, retail by guid. | | `mr` | — | **NULL func**, help `HelpReply @ 0x00577A50` | `` | Documented as "reply to the last person who @m'd you (monarchs only)" but the 2013 build registers it with a **null function pointer** (verified at `0x00583041`: `xor edi,edi; xor ebp,ebp` before the ctor call — arg3 is 0). It therefore falls through to channel lookup (miss) and is sent to the server as text. A retail defect, not an acdream one. | MISSING (and **do not** implement client-side — retail does not) | | `pr` | — | **NULL func**, help `HelpReply` | `` | Same as `mr` (verified at `0x005830C1`). | MISSING (same note) | | `retell` | `rt` | `DoReTell @ 0x00577BD0` | `` | Re-sends to the last person **you** tell'd. | **PARTIAL** — `RetellAliases = {"/retell"}`; `rt` MISSING. | | `chat` | — | `DoChatToggle @ 0x0056FAD0` | `on\|off` | `on` → `Event_ModifyGlobalSquelch(remove, 2)`; `off` → `Event_ModifyGlobalSquelch(add, 2)` — i.e. it is a global **Speech** filter, not a separate flag. | MISSING (cheap: acdream already has `ModifyGlobalSquelch`) | | `notell` | — | `DoNoTell @ 0x0056FBD0` | `on\|off` | Same mechanism with message type **3 (Tell)**. | MISSING (same) | | `join` | — | `DoJoinChat @ 0x0056F510` | `` | Sets the matching `PlayerModule::SetHear*Chat(true)` Turbine-room flag. | MISSING | | `leave` | — | `DoLeaveChat @ 0x0056F7F0` | same tags | Clears the matching flag. | MISSING | | `index` | — | `DoChannelIndex @ 0x0056E640` | — | `CM_Communication::Event_ChannelIndex()`. ACE: `GameActionType.IndexChannels`. | MISSING | | `clist` | — | `DoChannelList @ 0x0057A9B0` | `` | `GetChannelID` then `Event_ChannelList(id)`; bad tag → WeenieError `0x422`; no arg → "Please specify the channel name." ACE: `ListChannels`. | MISSING | | `on` | — | `DoChannelOn @ 0x0057AA80` | `` | `Event_AddToChannel(id)`. ACE: `AddChannel`. | MISSING | | `off` | — | `DoChannelOff @ 0x0057AB50` | `` | `Event_RemoveFromChannel(id)`. ACE: `RemoveChannel`. | MISSING | | `title` | — | `DoTitle @ 0x0057A640` | `` | Sets the popup chat window's title. Pure local UI. | MISSING | | `log` | — | `DoSetOutput @ 0x0057E4F0` | `[filename]` | Toggles chat-to-file logging; appends `.txt`; "Copying chat to %s. Run command again with no arguments to turn off logging." | MISSING | | `clear` | — | `DoClear @ 0x0056E600` | `[all]` | IMPLEMENTED | **IMPLEMENTED** — `RetailClientCommandCatalog.cs:115`, `ClientCommandController.cs:165`. | | `filter` / `unfilter` | — | `DoFilter @ 0x0057C860` / `DoUnFilter @ 0x0057C880` | `-` | IMPLEMENTED | **IMPLEMENTED** — `ClientCommandController.ExecuteGlobalFilter`. | | `messagetypes` | `message_types`, `msgtypes`, `msg_types` | `DoMessageTypes @ 0x0057A010` | — | Lists squelch/filter categories. | **PARTIAL** — only `messagetypes` registered (`RetailClientCommandCatalog.cs:251`); the three underscore/short aliases are MISSING. | | `loadfile` | — | `DoLoadFile @ 0x00581870` / `LoadFile @ 0x00581710` | `` | `fopen` the file, then feed every line back through `OnChatCommand` after `MakeLoadFileVariableSubstitutions`. A script player. No arg → "You must provide a file name."; open failure → "Cannot open file %hs". | MISSING (see §3 risk note) | ### 2.3 Chat channels (`DoStupidChannelHack @ 0x0057B130` → `DoChannelCommand`) All of these are one handler: no args → "You must specify the text you wish to say."; otherwise `ChannelSystem::GetChannelID(verb)` then `CM_Communication::Event_ChannelBroadcast(id, text)`. | Channel | Registered verbs | Channel id | acdream status | |---|---|---|---| | Allegiance | `a`, `ab` (also `allegiance`/`all` **as a command**, see §2.4) | `0x02000000` | `/a` IMPLEMENTED-EQUIVALENT (`ChatInputParser.cs:63`). `ab` MISSING. **`/allegiance` is bound to the Allegiance channel in acdream but is a COMMAND in retail** — divergence. | | Co-vassals | `co-vassals`, `covassals`, `covassal`, `c` | `0x01000000` | `covassals` OK; `c`, `covassal`, `co-vassals` MISSING. acdream's `/cv` is an invention. | | Monarch | `monarch`, `m` | `0x4000` | IMPLEMENTED-EQUIVALENT | | Patron | `patron`, `p` | `0x2000` | IMPLEMENTED-EQUIVALENT | | Vassals | `vassals`, `vassal`, `v` | `0x1000` | `vassals`, `v` OK; `vassal` MISSING. | | Fellowship | `fellowship`, `fellows`, `fellow`, `f`, `group`, `g`, `party` | `0x800` | **DIVERGENT** — retail's `g` and `group` and `party` are **Fellowship**. acdream maps `/g` to **General** (`ChatInputParser.cs:57`). `fellows`, `group`, `party` MISSING. | `ChannelSystem::GetChannelID` also resolves 22 tags that are **not** in the command table. Because `DoCommand` falls through to `DoChannelCommand` for any unregistered verb, these still work as channel broadcasts: | Tags | Channel id | |---|---| | `av`, `av1`, `advocate`, `advocate1` | `0x08` | | `av2`, `advocate2` | `0x10` | | `av3`, `advocate3` | `0x20` | | `abuse` | `0x01` | | `ad`, `admin` | `0x02` | | `au`, `audit` | `0x04` | | `sent`, `sentinel` | `0x200` | | `celestialhand`, `celhan` | `0x08000000` | | `eldrytchweb`, `eldweb` | `0x10000000` | | `radiantblood`, `radblo` | `0x20000000` | | `ol` (and `olthoi` pre-Turbine) | `0x40000000` | | `help` | `0x400` — **explicitly rejected** by `DoChannelCommand` at `0x005774FD` (`id == 0 || id == 0x400` → return 0) | All 22 are SERVER-PASSTHROUGH in acdream today (they reach ACE as `@abuse …` text rather than a channel broadcast). ### 2.4 Turbine Chat (registered only by `StartupTurbineChatSystem @ 0x0057EFB0`) Each sends `ClientCommunicationSystem::SendTurbineChat @ 0x0057DB10` with the matching `ChatTypeEnum` and the player's `PlayerModule::Hear*Chat` gate. | Verb | Aliases | Handler @addr | acdream status | |---|---|---|---| | `a` | `guild`, `gu` | `DoTurbineChat_Allegiance @ 0x0057EBA0` | `/a` routed as a plain Allegiance channel message; `guild`/`gu` MISSING | | `general` | `cg` | `DoTurbineChat_General @ 0x0057EC50` | `general` OK; `cg` MISSING. acdream's `/gen` is an invention. | | `trade` | `ct` | `DoTurbineChat_Trade @ 0x0057ECE0` | `trade` OK; `ct` MISSING. `/tr` is an invention. | | `lfg` | `clfg` | `DoTurbineChat_LFG @ 0x0057ED70` | `lfg` OK; `clfg` MISSING. `/lookingforgroup` is an invention. | | `roleplay` | `crp` | `DoTurbineChat_Roleplay @ 0x0057EE00` | `roleplay` OK; `crp` MISSING. `/role` and `/rp` are inventions (`/rp` collides with retail's reply alias). | | `society` | `soc` | `DoTurbineChat_Society @ 0x0057EF20` | `society` OK; `soc` MISSING | | `olthoi` | `o` | `DoTurbineChat_Olthoi @ 0x0057EE90` | `olthoi` OK; `o` MISSING | ### 2.5 Allegiance management | Verb | Aliases | Handler @addr | Args | Behavior | acdream status | |---|---|---|---|---|---| | `allegiance` | `all` | `DoAllegiance @ 0x0057D5A0` | ` [args]` | Subcommand dispatcher. Verbs read out of the handler: `boot` (`DoAllegianceBoot @ 0x0057AEF0`), `info` (`DoAllegianceInfo @ 0x00576560`), `chat`/`ch` (`DoAllegianceChat @ 0x00575CB0`), `broadcast`/`br` (`DoAllegianceBroadcast @ 0x005761F0`), `ban` (`DoAllegianceBan @ 0x005762A0`), `officer` (`DoAllegianceOfficer @ 0x00576650`), `title` (`DoAllegianceOfficerTitle @ 0x00576A10`), `hometown`/`ho` (`DoAllegianceHometown @ 0x0056EF10`), `motd` (`DoMotd @ 0x00577150`), `name` (`DoAllegianceName @ 0x00576C80`), `lock` (`DoAllegianceLock @ 0x00576E70`), `house` (`DoAllegianceHouse @ 0x0056EF70`). | MISSING (and `/allegiance` currently mis-bound as a chat channel) | | `ab` | — | `DoAllegianceBroadcast @ 0x005761F0` | `` | Monarch broadcast to the whole allegiance. | MISSING | | `alh` | `ah` | `DoAllegianceHometown @ 0x0056EF10` | — | ACE: `GameActionType.RecallAllegianceHometown`. | MISSING | | `motd` | — | `DoMotd @ 0x00577150` | `[set \|clear]` | Displays, sets (monarch-only), or clears the allegiance MOTD. | MISSING | | `speaker` | — | `DoSpeaker @ 0x0057DAB0` | — | Prints exactly "This command is no longer in use, please see @allegiance officer.\n". Pure local. | MISSING (trivial) | ### 2.5b Housing | Verb | Aliases | Handler @addr | Args | Behavior | acdream status | |---|---|---|---|---|---| | `house` | `hou` | `DoHouse @ 0x00580860` | ` [args]` | Subcommand verbs read out of the handler: `open`, `close`, `recall`/`re`, `mansion_recall`/`alleg_recall`/`ma`, `storage` (`DoHouseStorage @ 0x00579560`), `remove`, `boot` (`DoHouseBoot @ 0x00579970`), `boot_all`, `remove_all`, `guest` (`DoHouseGuests @ 0x005791A0`), `abandon`, `available`, `hooks`, `on`, `off`. | **PARTIAL** — `RetailClientCommandCatalog.TryMatch` (`RetailClientCommandCatalog.cs:279-301`) recognizes only `recall`, `mansion_recall`, `alleg_recall`. `hou`, `re`, `ma` and all 12 other subcommands are MISSING, **and the catalog swallows them locally** with an invalid-args message instead of letting them reach ACE. | | `hor` | `hr` | `DoHouseRecall @ 0x00570450` | — | IMPLEMENTED | **IMPLEMENTED** | | `hom` | `hoa` | `DoMansionRecall @ 0x005704B0` | — | IMPLEMENTED | **IMPLEMENTED** | | `hslist` | — | `DoHouseAvailableList @ 0x00570510` | `` | Lists available houses. ACE: `GameActionType.ListAvailableHouses`. | MISSING | ### 2.6 Death / recall / PK | Verb | Aliases | Handler @addr | acdream status | |---|---|---|---| | `lifestone` | `lif`, `ls` | `DoLifestone @ 0x0056FC70` | **IMPLEMENTED** | | `marketplace` | `mar`, `mp` | `DoMarketplace @ 0x0056FCE0` | **IMPLEMENTED** | | `pkarena` | `pka` | `DoPKArena @ 0x005788D0` | **IMPLEMENTED** | | `pklarena` | `pla` | `DoPKLArena @ 0x005789D0` | **IMPLEMENTED** | | `pklite` | `pkl` | `DoPKLite @ 0x0057A490` | **PARTIAL** — `pkl` alias MISSING (`RetailClientCommandCatalog.cs:218` registers only `pklite`) | | `die` | — | `DoDie @ 0x00580050` | **IMPLEMENTED** | | `corpse` | `cor` | `DoCorpse @ 0x00578220` | **IMPLEMENTED** | | `consent` | — | `DoConsent @ 0x0057DDA0` | **IMPLEMENTED** | | `permit` | — | `DoPermit @ 0x005785A0` | `add ` / `remove ` — grants/revokes corpse-loot permission. ACE: `AddPlayerPermission` / `RemovePlayerPermission`. **MISSING** | ### 2.7 Status / display | Verb | Aliases | Handler @addr | Args | Behavior | acdream status | |---|---|---|---|---|---| | `age` | — | `DoAge @ 0x0057C5A0` | — | **IMPLEMENTED** | | `birth` | — | `DoBirth @ 0x0056E5F0` | — | **IMPLEMENTED** | | `day` | — | `DoDay @ 0x005706F0` | — | Toggles `LScape::m_fAlwaysDaylight` via `LScape::SetDay`, persists with `PlayerModule::SetPersistentAtDay`, echoes "Let there be light!" when enabling. Pure local. | MISSING | | `endurance` | — | `DoEndurance @ 0x0057C5F0` | — | Prints a fixed paragraph beginning "The endurance attribute has a nu…". Pure local text. | MISSING (trivial) | | `framerate` | — | `DoFrameRate @ 0x005707D0` | — | **IMPLEMENTED** | | `loc` | — | `DoLoc @ 0x0057A250` | — | **IMPLEMENTED** | | `version` | — | `DoVersion @ 0x0057E1B0` | — | Prints "Client version %s\n"; additionally prints "Using Turbine Chat.\n" when `IsUsingTurbineChat()`, and extra lines when `PlayerIsPSR()`. | **PARTIAL** — `ClientCommandController.cs:136` prints the version line only. | | `render` | — | `DoRenderOption @ 0x0057E120` | `[options]` | Forwards to `SmartBox::HandleRenderOption`, prints its two out-strings. Pure local dev/render toggle. | MISSING | ### 2.8 Interface layout, emotes, social, components | Verb | Aliases | Handler @addr | acdream status | |---|---|---|---| | `saveui` / `loadui` / `saveautoui` / `loadautoui` / `lockui` | — | `0x0056FFF0` / `0x00570150` / `0x005702B0` / `0x00570330` / `0x005703B0` | **IMPLEMENTED** | | `emote` | `e`, `em`, `me` | `DoEmote @ 0x00578AD0` | **IMPLEMENTED** | | `emotes` | — | `DoEmoteList @ 0x0057BB30` | **IMPLEMENTED** | | `afk` | — | `DoAFK @ 0x0057B3F0` | **IMPLEMENTED** | | `friends` / `friends_add` / `friends_remove` | — | `0x0057BC00` / `0x00578FB0` / `0x00579080` | **IMPLEMENTED** | | `squelch` / `unsquelch` | — | `0x0057BF50` / `0x0057C070` | **IMPLEMENTED** | | `fillcomps` | — | `DoFillComponents @ 0x0056FD50` | **IMPLEMENTED** | ### 2.9 Prefixes (not verbs) | Prefix | Site | Behavior | acdream status | |---|---|---|---| | `/` | `OnChatCommand @ 0x00581431` | Rewritten to `@`, then dispatched. | **IMPLEMENTED** — `RetailClientCommandCatalog.TryMatch` accepts both (`RetailClientCommandCatalog.cs:267`). | | `@` | `OnChatCommand @ 0x00581444` | Dispatched directly. | **IMPLEMENTED** | | `:` , `;` | `OnChatCommand @ 0x0058144D` | First char replaced with a space, line prefixed with `"@emote"`, then dispatched — `:waves` ≡ `@emote waves`. | **MISSING** | | trailing `,` on the verb | `DoCommand @ 0x0057E3CD` | Right-trimmed, so `@f, hi` ≡ `@f hi`. | **MISSING** — acdream's verb token keeps the comma, so `/f, hi` does not match any verb and is shipped to ACE as `@f,`. | --- ## 3. Prioritized missing-command list Effort key: **L** = pure-local (no wire message); **W-have** = wire path already exists in acdream; **W-new** = needs a new game action. ### Tier 1 — correctness bugs in what already ships (do these first) 1. **`/g` is bound to General; retail binds it to Fellowship** (`0x800`). `ChatInputParser.cs:57`. One-line fix, but it silently sends fellowship chatter to a global channel today. **L** 2. **`/rp` is bound to Roleplay; retail binds it to `reply`.** `ChatInputParser.cs:78`. Same class of bug (a private reply becomes a global broadcast). **L** 3. **`/allegiance ` is bound to the Allegiance channel; retail's `allegiance`/`all` is the allegiance *management command*.** Channel verbs are `a`/`ab`/`guild`/`gu`. **L** 4. **`/house ` is swallowed locally.** `RetailClientCommandCatalog.cs:288-296` returns a match with `HasValidArguments:false` for every unrecognized subcommand, so `@house open`, `@house guest add X`, `@house abandon` never reach ACE. Until `DoHouse` is ported, unrecognized `house` subcommands must fall through to `SendServerCommandCmd`. **L** 5. **Verb-trailing-comma trim.** `@f, hi` / `@t Bob, hi` — retail right-trims `,` off the verb in `DoCommand`. **L** 6. **`/tell` splits on the first comma, not the first space.** `ChatInputParser.cs:208`. Multi-word names break today. **L** ### Tier 2 — pure-local commands, no wire work 7. `:` / `;` emote prefixes (`OnChatCommand` case 0x0B/0x0C). **L** 8. `@day` — daylight toggle + `SetPersistentAtDay`. **L** 9. `@endurance` — fixed help paragraph. **L** 10. `@speaker` — fixed deprecation line. **L** 11. `@title ` — chat window title. **L** 12. `@log [file]` — chat-to-file logging. **L** 13. `@version` — add the "Using Turbine Chat." line. **L** 14. `@render` — `SmartBox::HandleRenderOption` equivalent (acdream has no SmartBox; map to the existing quality/debug toggles or leave as a documented divergence). **L** 15. `@help ` / `@help ` + the 7 group nodes (`commands`, `allegiances`, `channels`, `chatting`, `death`, `status`, `text`). The exact retail help strings for all 57 `Help*` functions are recoverable from the binary — see §4 for the extraction recipe. **L** 16. **Missing aliases on already-implemented commands** (one dictionary edit each): `pkl`, `hou`, `message_types`, `msgtypes`, `msg_types`, `rt`, `send`, `whisper`, `w`, `vassal`, `covassal`, `co-vassals`, `c`, `fellows`, `group`, `party`, `guild`, `gu`, `cg`, `ct`, `clfg`, `crp`, `soc`, `o`, `ab`. Also delete the non-retail inventions `gen`, `cv`, `lookingforgroup`, `tr`, `role`, `h`. **L** ### Tier 3 — wire messages acdream already has 17. `@chat on|off` and `@notell on|off` — both are `ModifyGlobalSquelch` with message types 2 and 3. acdream already wires `ModifyGlobalSquelch` for `/filter`. **W-have** 18. `@join` / `@leave ` — set `PlayerModule::SetHear*Chat`; these ride the existing character-options path. **W-have** ### Tier 4 — new game actions (ACE-side handlers all exist) 19. `@permit add|remove ` — ACE `AddPlayerPermission` / `RemovePlayerPermission`. **W-new** 20. `@hslist ` — ACE `ListAvailableHouses`. **W-new** 21. `@index` / `@clist` / `@on` / `@off` — ACE `IndexChannels`, `ListChannels`, `AddChannel`, `RemoveChannel`. **W-new** (four small parameterless/one-dword actions) 22. `@allegiance ` — the 12-subcommand dispatcher. ACE handlers exist for every one (`AllegianceInfoRequest`, `AllegianceChatBoot`, `AddAllegianceBan`, `SetAllegianceOfficer`, `SetAllegianceOfficerTitle`, `RecallAllegianceHometown`, `SetAllegianceName`, `DoAllegianceLockAction`, `DoAllegianceHouseAction`, …). Largest single item; deserves its own slice. **W-new** 23. `@house ` — 15 subcommands; ACE handlers exist (`SetOpenHouseStatus`, `ChangeStoragePermission`, `BootSpecificHouseGuest`, `HouseBootAll`, `AbandonHouse`, `HouseQuery`, `ModifyAllegianceGuestPermission`, …). **W-new** 24. `@motd [set|clear]` — allegiance MOTD. **W-new** 25. `@ab` — allegiance broadcast. **W-new** 26. `@alh` / `@ah` — `RecallAllegianceHometown`. **W-new** 27. Turbine-chat verbs as *Turbine* sends (`SendTurbineChat`) rather than plain `ChannelBroadcast`. acdream already ships the 0xF7DE TurbineChat path, so this is a routing decision, not new wire work. **W-have** ### Explicitly do NOT implement - `@mr`, `@pr` — registered with a **null function pointer** in the 2013 build. Retail ships them as help text only; the command itself falls through to the server. Implementing them client-side would be a divergence. - The 22 fallback-only channel tags (`abuse`, `admin`, `audit`, `advocate*`, `sentinel`, `celhan`, `eldweb`, `radblo`, `ol`, …) are GM/faction channels. They are reachable in retail only through `DoChannelCommand`'s fallback; the cheapest faithful port is to add the `GetChannelID` table and let the existing unknown-verb path consult it before falling back to `SendServerCommandCmd`. - `@loadfile` — a client-side script player that re-enters `OnChatCommand` for every line of an arbitrary file. Faithful, but it is a scripting surface; acdream already has a designed plugin API for that, so porting `@loadfile` should be an explicit product decision, not a parity checkbox. --- ## 4. Counts | Measure | Count | |---|---| | Verbs registered by `InitializeCommands @ 0x00581970` | **116** | | Verbs added by `StartupTurbineChatSystem @ 0x0057EFB0` | 15 (14 net-new; `a` is replaced) | | **Total registered verbs** | **130** | | …of which have a **null** `func` (help-only nodes) | 9 (`commands`, `allegiances`, `channels`, `chatting`, `death`, `status`, `text`, `mr`, `pr`) | | Distinct handler functions behind those verbs | 68 (61 base + 7 Turbine) | | Additional verbs reachable via the `GetChannelID` fallback (unregistered) | 22 | | **Total client-parsed verbs** | **152** | | Prefixes with special parsing | 4 (`/`, `@`, `:`, `;`) | acdream, against the 130 registered verbs. Every verb is in exactly one row, so the column sums to 130. | Status | Verbs | Where | |---|---|---| | IMPLEMENTED — typed `ExecuteClientCommandCmd` | **45** (33 `ClientCommandId` values) | `RetailClientCommandCatalog.cs` + `ClientCommandController.cs` | | PARTIAL via typed catalog | **1** — `house` (3 of 15 subcommands, and it swallows the rest) | `RetailClientCommandCatalog.cs:279` | | IMPLEMENTED-EQUIVALENT — chat alias → `SendChatCmd` | **21** | `ChatInputParser.cs` | | PARTIAL via chat alias (argument shape wrong) | **3** — `tell` (space-split, not comma-split), `reply` (by name, not guid), `retell` | `ChatInputParser.cs` | | PARTIAL via local presentation | **2** — `help`, `?` (flat blob; no per-command or group help) | `ChatCommandRouter.cs:96` | | DIVERGENT — bound, wrong target | **3** — `g`→General (retail Fellowship), `rp`→Roleplay (retail reply), `allegiance`→channel (retail command) | `ChatInputParser.cs` | | **MISSING** | **55** | — | | **Total registered** | **130** | | Plus **22** unregistered `GetChannelID` fallback tags, all MISSING → **77** client-parsed verbs unimplemented out of 152. Reading note: the §2 tables mark `messagetypes`, `pklite` and `version` PARTIAL because an *alias* or an *output line* is missing. The counts above are per-verb, so those three verbs sit in IMPLEMENTED while their missing aliases (`message_types`, `msgtypes`, `msg_types`, `pkl`) are counted as four separate MISSING verbs. Everything not in the table above reaches ACE correctly as `SendServerCommandCmd` → Talk (`ChatCommandRouter.cs:62`), which matches retail's own final fallback (`DoCommand` → `Event_Talk`) except that retail consults the channel-tag table first. acdream verbs with **no retail counterpart** (candidates for removal): `/h`, `/gen`, `/cv`, `/lookingforgroup`, `/tr`, `/role`. --- ## 5. Reproduction recipes Verb table (authoritative, resolves BN's mislabeled pooled strings): ``` # scan InitializeCommands for `push imm32` operands that point at short # ASCII strings in .rdata — one per registered verb, in registration order python pushes.py 581970 585520 # 116 verbs python pushes.py 57efb0 57f9e0 # 15 Turbine verbs ``` Handler pairing: `sed -n '396604,399712p' acclient_2013_pseudo_c.txt` and read the `CmdHashData::CmdHashData(ptr, &name, , nullptr)` call (later blocks) or the `*(esi + 8) = ` / `*(esi + 0x10) = ` stores (first ~22 blocks — BN renders the same code two different ways). Exact retail help text for all 57 `Help*` functions: scan each `ClientCommunicationSystem::Help*` symbol's byte extent for `push imm32` into `.rdata` and read the C string. This recovered, verbatim, every usage line and group listing quoted in §2 (e.g. the `@tell` comma requirement, the `@afk msg` 192-character limit, the `@hslist` house types). `ChannelSystem::GetChannelID @ 0x005CF1F0`: alternate `push imm32` (tag) and `mov eax, imm32` (channel id) down the function; the id immediately precedes the next tag group. Cross-checks performed: ACE `GameActionTalk.cs:21` (the `@`-passthrough contract) and the `GameActionType` enumeration (confirms server-side handlers exist for every Tier-4 item). holtburger's `commands.rs` was consulted and **rejected as an oracle for aliases** — it binds `/g` to Allegiance and `/p` to Fellowship, which matches neither retail nor acdream.