The user set the goal: complete retail parity for the chat system AND the chat
GUI, not just the green clickable name that started the review. Plan restated
around that bar, with a definition of done — every retail behaviour either
implemented or carrying a divergence-register row, every user-visible surface
covered by a test, and the digest/ISSUES describing reality.
Slices regrouped into A (the tagged-text capability, a strict chain where
nothing is visible until A4), B (system behaviours), C (GUI), D (hygiene), plus
the research still owed before specific slices and what is deliberately out of
scope.
Four items joined the plan that the original six lanes did not own, because
they fell between lanes:
- the /r, /t, /tell text-replacement macro (the commands work; retail's
VISIBLE expansion to "@tell {LastTeller}, " does not exist)
- FilterLanguage, which is a decorative toggle: we store it, ship the bit and
show it in Options, and never actually filter anything
- the plain-text session chat log retail writes and we do not
- the option-gated timestamp prefix
Also corrects the CH3 command-registry research note. Its "acdream status"
columns are from before slice CH4 and list 13 verbs as MISSING that have all
since been added — cg, soc, o, co-vassals, fellows, group, party, vassal, ab,
guild, ct, clfg, crp — and its DIVERGENT row for /g is likewise stale: acdream
maps /g to Fellowship, matching retail, confirmed against the live client
today. The retail side of that document is still the authority; only the
columns describing us were wrong. They misled this session's investigation,
which is exactly why the banner says to verify against ChatInputParser.cs.
Nothing implemented.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
29 KiB
Retail client slash-command registry — complete enumeration + acdream audit
CORRECTION 2026-08-21 — the "acdream status" columns below are STALE. They record the state on 2026-08-09, BEFORE slice CH4 landed. Every verb this document calls MISSING has since been added and verified present in
ChatInputParser.ChannelVerbs:cg,soc,o,co-vassals,covassal,c,fellows,group,party,vassal,ab,guild,gu,ct,clfg,crp. The DIVERGENT row forgwas also corrected — acdream now maps/gto Fellowship, matching retail, confirmed against the live retail client on 2026-08-21.The RETAIL side of this document (the registered verbs, handler addresses and channel ids) remains accurate and is still the authority. Only the columns describing what acdream does are out of date; verify against
ChatInputParser.csbefore trusting them.
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<char>::PStringBase<char> 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<CaseInsensitiveStringBase<PStringBase<char>>, 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<char>*)), +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 <command>, 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 |
<text> |
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 |
<name>, <text> |
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 |
<text> |
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 |
<text> |
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 |
<text> |
Same as mr (verified at 0x005830C1). |
MISSING (same note) |
retell |
rt |
DoReTell @ 0x00577BD0 |
<text> |
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 |
<Allegiance|General|Trade|LFG|Roleplay|Society|Soc> |
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 |
<channel> |
GetChannelID then Event_ChannelList(id); bad tag → WeenieError 0x422; no arg → "Please specify the channel name." ACE: ListChannels. |
MISSING |
on |
— | DoChannelOn @ 0x0057AA80 |
<channel> |
Event_AddToChannel(id). ACE: AddChannel. |
MISSING |
off |
— | DoChannelOff @ 0x0057AB50 |
<channel> |
Event_RemoveFromChannel(id). ACE: RemoveChannel. |
MISSING |
title |
— | DoTitle @ 0x0057A640 |
<text> |
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 |
-<type> |
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 |
<file> |
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 |
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 |
<sub> [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 |
<text> |
Monarch broadcast to the whole allegiance. | MISSING |
alh |
ah |
DoAllegianceHometown @ 0x0056EF10 |
— | ACE: GameActionType.RecallAllegianceHometown. |
MISSING |
motd |
— | DoMotd @ 0x00577150 |
[set <text>|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 |
<sub> [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 |
<apartment|cottage|villa|mansion> |
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 <name> / remove <name> — 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)
/gis 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/rpis bound to Roleplay; retail binds it toreply.ChatInputParser.cs:78. Same class of bug (a private reply becomes a global broadcast). L/allegiance <text>is bound to the Allegiance channel; retail'sallegiance/allis the allegiance management command. Channel verbs area/ab/guild/gu. L/house <anything-but-recall>is swallowed locally.RetailClientCommandCatalog.cs:288-296returns a match withHasValidArguments:falsefor every unrecognized subcommand, so@house open,@house guest add X,@house abandonnever reach ACE. UntilDoHouseis ported, unrecognizedhousesubcommands must fall through toSendServerCommandCmd. L- Verb-trailing-comma trim.
@f, hi/@t Bob, hi— retail right-trims,off the verb inDoCommand. L /tellsplits 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
:/;emote prefixes (OnChatCommandcase 0x0B/0x0C). L@day— daylight toggle +SetPersistentAtDay. L@endurance— fixed help paragraph. L@speaker— fixed deprecation line. L@title <text>— chat window title. L@log [file]— chat-to-file logging. L@version— add the "Using Turbine Chat." line. L@render—SmartBox::HandleRenderOptionequivalent (acdream has no SmartBox; map to the existing quality/debug toggles or leave as a documented divergence). L@help <command>/@help <group>+ the 7 group nodes (commands,allegiances,channels,chatting,death,status,text). The exact retail help strings for all 57Help*functions are recoverable from the binary — see §4 for the extraction recipe. L- 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 inventionsgen,cv,lookingforgroup,tr,role,h. L
Tier 3 — wire messages acdream already has
@chat on|offand@notell on|off— both areModifyGlobalSquelchwith message types 2 and 3. acdream already wiresModifyGlobalSquelchfor/filter. W-have@join/@leave <room>— setPlayerModule::SetHear*Chat; these ride the existing character-options path. W-have
Tier 4 — new game actions (ACE-side handlers all exist)
@permit add|remove <name>— ACEAddPlayerPermission/RemovePlayerPermission. W-new@hslist <type>— ACEListAvailableHouses. W-new@index/@clist/@on/@off— ACEIndexChannels,ListChannels,AddChannel,RemoveChannel. W-new (four small parameterless/one-dword actions)@allegiance <sub>— 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@house <sub>— 15 subcommands; ACE handlers exist (SetOpenHouseStatus,ChangeStoragePermission,BootSpecificHouseGuest,HouseBootAll,AbandonHouse,HouseQuery,ModifyAllegianceGuestPermission, …). W-new@motd [set|clear]— allegiance MOTD. W-new@ab— allegiance broadcast. W-new@alh/@ah—RecallAllegianceHometown. W-new- Turbine-chat verbs as Turbine sends (
SendTurbineChat) rather than plainChannelBroadcast. 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 throughDoChannelCommand's fallback; the cheapest faithful port is to add theGetChannelIDtable and let the existing unknown-verb path consult it before falling back toSendServerCommandCmd. @loadfile— a client-side script player that re-entersOnChatCommandfor every line of an arbitrary file. Faithful, but it is a scripting surface; acdream already has a designed plugin API for that, so porting@loadfileshould 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, <func>, nullptr) call (later blocks)
or the *(esi + 8) = <func> / *(esi + 0x10) = <help> 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.