Commits the command-registry, interface-text (SpewBox), and side-channels-vs-ACE research docs (R3 color-table landed with CH1). Corrects the CH1 ledger SHA the implementer recorded pre-amend. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
58 KiB
Retail's transient on-screen "interface text" — the SpewBox
Date: 2026-08-09
Status: RESEARCH ONLY. No production code changed.
Oracle: docs/research/named-retail/acclient_2013_pseudo_c.txt (Sept 2013 EoR
build, PDB-named), docs/research/named-retail/symbols.json,
docs/research/named-retail/acclient.h, plus byte-level string recovery from the
PDB-paired binary C:\Users\erikn\Downloads\acclient.exe (v11.4186, CodeView GUID
9e847e2f-777c-4bd9-886c-22256bb87f32). Server-side cross-check against
references/ACE/ and references/holtburger/.
TL;DR
The system is called the SpewBox — gmSpewBoxUI @ 0x004D5A30.
The routing rule is one line: ClientSystem::AddTextToScroll(text, type, ...)
broadcasts one notice to every text sink, and the sinks self-select — the SpewBox
takes type == 0x1A and nothing else, while every ChatInterface window is
constructed with a 64-bit type filter that has bit 26 (1 << 0x1A) cleared, so
0x1A is exactly the type the chat window refuses and the SpewBox accepts.
23 client-raised local refusal sites were found (11 distinct message strings),
none of which involve a server round-trip. All of them use type 0x1A.
1. System identification
1.1 The display element
| Symbol | Address | Role |
|---|---|---|
gmSpewBoxUI::gmSpewBoxUI |
0x004D5A30 |
ctor; derives from UIElement_Field + NoticeHandler |
gmSpewBoxUI::Create |
0x004D5C30 |
factory (operator new(0x610)) |
gmSpewBoxUI::Register |
0x004D5DD0 |
UIElement::RegisterElementClass(0x10000016, gmSpewBoxUI::Create) |
gmSpewBoxUI::GetUIElementType |
0x004D5AA0 |
returns 0x10000016 |
gmSpewBoxUI::PostInit |
0x004D5AB0 |
binds the child ListBox, reads max-items, registers for notice 0x186B6 and global message 3 |
gmSpewBoxUI::RecvNotice_DisplayFinalStringInfo |
0x004D60A0 |
the type filter — if (arg2 == 0x1A) m_spewBoxPending.AddToEnd(str) |
gmSpewBoxUI::Update |
0x004D5DF0 |
drains the pending queue into the ListBox |
gmSpewBoxUI::ListenToGlobalMessage |
0x004D6090 |
if (msg == 3) Update() |
gmSpewBoxUI::ListenToElementMessage |
0x004D57C0 |
if (idElement == 0x1000004A && idMessage == 0x10000003) DeleteItem() — the expiry hook |
gmSpewBoxUI::~gmSpewBoxUI |
0x004D5BD0 |
unregisters |
It is registered alongside the rest of the HUD in the element-class registration
block at 0x0047A4A6 (gmClient init), between gmSmartBoxUI::Register() and the
gmFloaty*UI family.
1.2 The router
| Symbol | Address | Role |
|---|---|---|
ClientSystem::AddTextToScroll(PStringBase<wchar>, uint type, uint8 allowPluginFilter, uint windowId) |
0x00563C50 |
the single chokepoint for all player-visible text |
ClientSystem::AddTextToScroll(PStringBase<char>, ...) |
0x004C2420 |
narrow-string overload → widens → above |
ClientSystem::AddTextToScroll(char const*, ...) |
0x00487FC0 |
literal overload → widens → above |
ECM_UI::SendNotice_DisplayFinalStringInfo |
0x00692550 |
broadcast to notice id 0x186B6 |
ECM_UI::SendNotice_DisplayStringInfo |
0x006925B0 |
broadcast to notice id 0x186A5 |
ECM_UI::SendNotice_DisplayWeenieError |
0x00692600 |
broadcast to notice id 0x186B7 |
ClientCommunicationSystem::RecvNotice_DisplayStringInfo |
0x0056E890 |
notice 0x186A5 → AddTextToScroll |
ClientCommunicationSystem::RecvNotice_DisplayWeenieError |
0x0057E700 |
notice 0x186B7 → HandleFailureEvent |
ClientCommunicationSystem::HandleFailureEvent(uint errorId, PStringBase<wchar> param) |
0x00571990 |
the error-id → text + destination switch (339 cases) |
ChatInterface::RecvNotice_DisplayFinalStringInfo |
0x004F4640 |
the chat-window sink |
ChatInterface::TypeIsActive |
0x004F2F10 |
(m_llTextTypeFilter >> type) & 1 |
ChatInterface::ChatInterface (ctor) |
0x004F4550 |
sets the default filter — see §2.2 |
ChatInterface::BuildChatColorLookupTable |
0x004F31C0 |
per-type chat colors |
1.3 The wire entry points
| Symbol | Address | Opcode | Behaviour |
|---|---|---|---|
ClientCommunicationSystem::Handle_Communication__TextboxString |
0x0057D3A0 |
0xF7E0 (ServerMessage) |
squelch check, then AddTextToScroll(text, wireChatType, 1, 0) — the wire type decides the destination |
ClientCommunicationSystem::Handle_Communication__TransientString |
0x0057D460 |
0x02EB (GameEvent) |
AddTextToScroll(text, 0x1A, 1, 0) — hardcoded to the SpewBox |
ClientCommunicationSystem::Handle_Communication__PopUpString |
0x0057FE80 |
0x0004 (GameEvent) |
builds a PropertyCollection and calls DialogFactory::MakeDialogInCurrentUI — a modal dialog, neither chat nor spew |
ClientCommunicationSystem::RecvNotice_DisplayWeenieError |
0x0057E700 |
0x028A / 0x028B |
→ HandleFailureEvent → per-id destination |
Dispatch table sites: 0x0055CA1F (0xF7E0), 0x0055C581 (0x02EB),
0x0055B0BD (event 0x0004), all inside the UIQueueManager message switch that
begins at 0x0055B000.
2. The routing model
2.1 One broadcast, self-selecting sinks
ClientSystem::AddTextToScroll @ 0x00563C50 does, in order:
- Plugin veto. If
allowPluginFilter != 0and the plugin API is ready, callIACPlugin::OnChatWindowText(bstr, type, &eat). If the plugin setseat, the message is dropped entirely — it reaches neither chat nor spew. (0x00563C7D–0x00563CB6.) - Trim trailing whitespace.
- Censor. If
PlayerModule::FilterLanguage(), explode on spaces and replace any word failingTabooTableAdaptor::CheckCensorsWwith****. - Branch on type (
0x00563DE6):type == 0x1A→ skip the timestamp, skip the chat log file, jump straight to the broadcast.- otherwise → prepend
%#H:%M:%SifPlayerModule::DisplayTimeStamps(), andfprintfthe line toClientSystem::s_pLogFileif a chat log is open.
- Broadcast
ECM_UI::SendNotice_DisplayFinalStringInfo(type, mainStr, prefixStr, windowId).
That last call goes to every registered handler of notice 0x186B6. There are
exactly two kinds of subscriber:
gmSpewBoxUI(0x004D60A0):if (type == 0x1A) enqueue. Nothing else, and it ignoreswindowIdentirely.ChatInterface(0x004F4640), one per chat window:if (windowId == this->m_eWindowID) -> append else if (windowId == 0 && TypeIsActive(type)) -> append else -> ignore
So the routing rule is a type filter on the receiver side, not a switch on the sender side. There is no "destination" field anywhere in the data.
The fact that type 0x1A is skipped for timestamping and chat-log-file writing
(step 4) is the client author's own statement that 0x1A is not chat.
2.2 Why 0x1A never appears in the chat window
ChatInterface::ChatInterface @ 0x004F4550:
0x004F45B8 this->m_llTextTypeFilter = 0xFFFFFFFF; // low dword
0x004F45BE ((uint32*)&m_llTextTypeFilter)[1] = 0xFFFFFFFF; // high dword
0x004F45F3 this->m_llTextTypeFilter &= 0xFBFFFFFF; // clear bit 26
0xFBFFFFFF = ~0x04000000 = ~(1 << 26) = ~(1 << 0x1A).
Every chat window is born with every text type enabled except 0x1A. That is
the whole mechanism. TypeIsActive @ 0x004F2F10 is just
(m_llTextTypeFilter >> type) & 1.
The filter is subsequently overwritten from a saved UI bitfield property
(InqBitfield64 at 0x004F3109 and 0x004F3984), so in principle a chat window
could be configured to show 0x1A — but the shipped chat-options UI does not offer
it, which is why ACE's ChatMessageType.cs:255-259 concluded "Client doesn't
display it" and commented x1A out. That conclusion is wrong and worth
recording: the client does display 0x1A, just not in the chat scroll.
2.3 The complete destination model
| Destination | Owner | Trigger |
|---|---|---|
| Chat scroll (one or more windows) | ChatInterface @ 0x004F4640 |
AddTextToScroll with type != 0x1A, or with windowId == this window |
| SpewBox (transient screen text) | gmSpewBoxUI @ 0x004D60A0 |
AddTextToScroll with type == 0x1A |
| Modal dialog | DialogFactory::MakeDialogInCurrentUI |
Handle_Communication__PopUpString (event 0x0004) only |
| Chat log file | ClientSystem::s_pLogFile |
AddTextToScroll with type != 0x1A |
| Plugin sink / veto | IACPlugin::OnChatWindowText |
every AddTextToScroll with allowPluginFilter != 0 |
| (dropped) | — | plugin sets the eat out-param |
Note that a type == 0x1A message with a non-zero windowId lands in both
the SpewBox and that specific chat window. This is exactly what slash-command
output does: ClientCommunicationSystem emits its command responses as
AddTextToScroll(text, 0x1A, 1, this->m_idCurrentCommandSource) (~40 sites from
0x0056EF3B through 0x005707FB), so a /-command's reply appears on screen
and is echoed into the window you typed it in.
2.4 Where the strings come from
Not from client_local_English.dat. Every player-visible error string in this
path is a wide-char literal compiled into acclient.exe:
HandleFailureEvent@0x00571990builds each one inline (PStringBase<unsigned short>::PStringBase<unsigned short>(&var, u"…")) or viaPStringBase<unsigned short>::sprintf(&s, u"The %s cannot be used …")with the0x028Bstring parameter substituted.- The 11 movement/jump refusals are process-lifetime globals initialised by static
ctors at
0x00708F00–0x00709180(see §4).
The DAT StringTable machinery (StringInfo::SetTableEnum,
StringInfo::SetStringIDandTableEnum) exists and is used for UI chrome —
option labels, tooltips, command aliases — but the failure-event text is hardcoded.
StringInfo::SetLiteralValue is what the failure path uses.
This matters for the port: we do not need a DAT string table to reach parity on
this feature. A C# table keyed by WeenieError id is exactly what retail does.
3. Presentation parameters
3.1 What acclient owns (portable, measured)
From gmSpewBoxUI::PostInit @ 0x004D5AB0 and gmSpewBoxUI::Update @ 0x004D5DF0:
| Behaviour | Evidence | Value |
|---|---|---|
| Backing widget | 0x004D5AD7 |
a UIElement_ListBox found by GetChildRecursive(0x10000049) |
| Mouse | 0x004D5AC4, 0x004D5AFE |
the SpewBox and its ListBox are both SetMouseVisible(0) — click-through |
| Background | 0x004D5ABB |
SetShouldEraseBackground(1) |
| Max concurrent lines | 0x004D5B34 |
ListBox property 0x10000028; defaults to 1 if the property is absent or unreadable |
| Per-line widget | 0x004D5E42 |
CreateChildElementByEnum(parent=null, layoutEnum=0x10000012, elementId=0x1000004A) — a DAT-authored UIElement_Text template |
| Text preprocessing | 0x004D5E9C |
trim(leading=0, trailing=1, whitespace) — trailing whitespace stripped |
| Sizing | 0x004D5EB1–0x004D5EE6 |
resized to the ListBox's width, then RecalculateGlyphList, then resized again to the computed scrollable height (word wrap) |
| Dedupe | 0x004D5EF6–0x004D5F91 |
if the current item 0 has byte-identical text, that older item is deleted first. A repeated message refreshes in place instead of stacking. |
| Insertion | 0x004D5F9F |
InsertItem(item, 0) — newest at the top |
| Overflow | 0x004D5FB6 |
if count > m_maxConcurrentItems, DeleteItem(count - 1) — oldest drops off |
| Scroll | 0x004D601D |
ScrollToShow(0) after a batch |
| Drain cadence | 0x004D5BA6, 0x0045CFFB |
global message 3, broadcast once per UI tick from UIElementManager::UseTime @ 0x0045CFD0 |
| Expiry hook | 0x004D57D7 |
the SpewBox deletes an item when it receives element message 0x10000003 from element id 0x1000004A |
The queue is a SmartArray<StringInfo,1> m_spewBoxPending; RecvNotice_* only
enqueues, Update only drains. Enqueue and display are decoupled by one frame.
3.2 PRESENTATION-UNKNOWN (keystone / DAT-owned)
These could not be established from acclient and must not be guessed:
- Line lifetime / fade curve. acclient never raises element message
0x10000003. I searched everyBroadcastElementMessage/ForwardElementMessagecall site in the whole 66 MB listing: the only element message id above0x10000000that acclient itself raises is0x10000004(0x004F0EC5, a stat-type element).UIElement/UIRegion/ElementDesc/LayoutDescexpose noDuration/Lifetime/Fade/Expiremember at all. The timeout and any fade are owned by keystone.dll or by the authoredElementDescbehaviour of layout0x10000012element0x1000004A. Resolution path: dump that LayoutDesc fromclient_local_English.dat, or set a cdb breakpoint ongmSpewBoxUI::ListenToElementMessage(0x004D57C0) in a live retail client and time the deltas between a message appearing and its removal. - Screen position and extent. Authored in whatever LayoutDesc declares an
element of class
0x10000016. The natural host is the main game view (gmSmartBoxUI, LayoutDesc0x2100000F) but this was not confirmed — no dumped layout indocs/research/retail-ui/mentions it. Resolution path: enumerate LayoutDescs and look for element type0x10000016. - Font, size, justification, colour of the SpewBox line. All from the same DAT
template. In particular, the SpewBox does not use the chat colour table:
BuildChatColorLookupTablewrites toChatInterface::m_chatLog, a different element tree entirely. - Max concurrent items in the shipped layout. The code reads ListBox property
0x10000028; the authored value is DAT data. The code default is 1.
3.3 The chat-window colour table (adjacent, for completeness)
ChatInterface::BuildChatColorLookupTable @ 0x004F31C0 assigns
RGBAColor constants to text types. Colour values below are from
claude-memory/reference_retail_chat_colors.md (dumped live via cdb, 2026-06-16).
| Type(s) | Colour symbol | Addr | RGB |
|---|---|---|---|
| default (all) | colorGreen |
0x81C578 |
0.500, 1.000, 0.498 |
0x02 |
colorWhite |
0x81C4B8 |
1, 1, 1 |
0x03 0x0A 0x13 0x1F |
(yellow, unnamed) | 0x81C4C8 |
1, 1, 0.247 |
0x04 0x0B |
(unnamed, not yet read) | 0x81C4D8 |
— |
0x05 |
colorBrightPurple |
0x81C4E8 |
1, 0.498, 1 |
0x06 0x0F 0x15 |
colorDarkRed |
0x81C4F8 |
1, 0.247, 0.247 |
0x07 0x11 |
colorLightBlue |
0x81C518 |
0.247, 0.749, 1 |
0x08 0x09 |
colorPink |
0x81C528 |
1, 0.588, 0.588 |
0x0C |
colorGrey |
0x81C558 |
0.824, 0.824, 0.784 |
0x0D |
colorCyan |
0x81C538 |
0.247, 0.863, 0.863 |
0x0E 0x1B 0x1C 0x1D 0x1E 0x20 |
colorBlueGrey |
0x81C548 |
0.706, 0.863, 0.941 |
0x12 0x21 |
(orange, unnamed) | 0x81C568 |
0.933, 0.573, 0.118 |
0x16 |
colorLightRed |
0x81C508 |
0.960, 0.459, 0.447 |
0x1A |
colorBrightRed |
0x81C4A8 |
1, 0, 0 |
Two things fall out of this table:
- The
0x1Arow exists purely for the case where a user manually enables the filter bit. It is not the SpewBox's colour. Do not port it as such. - Types
0x20and0x21are real and coloured. ACE'sChatMessageTypestops at0x1F— the client's text-type space is wider than the server-side enum.
4. Client-raised local errors (no server round-trip)
Retail refuses several actions locally and prints the refusal itself. All of them
land on type 0x1A.
4.1 The message globals
Static ctors at 0x00708F00–0x00709180. Strings recovered verbatim from the
binary (the pseudo-C truncates at 33 chars).
| Global | Full text | Used? |
|---|---|---|
cant_jump_position |
You can't jump from this position |
yes (3 sites) |
cant_jump_in_air |
You can't jump while in the air |
yes (3 sites) |
cant_jump_load |
You're too loaded down to jump |
yes (3 sites) |
cant_jump_stamina |
You're too tired to jump! |
dead in this build |
cant_jump_recent |
You've jumped too recently! |
dead in this build |
too_tired |
You are too tired to move! |
yes (1 site) |
cant_sit_combat |
You can't sit down while in combat * |
yes (1 site) |
cant_lie_down_combat |
You can't lie down while in combat * |
yes (1 site) |
cant_crouch_combat |
You can't crouch while in combat * |
yes (1 site) |
cant_emote_combat |
You can't use chat emotes in combat * |
yes (1 site) |
cant_emote_position |
You can't use chat emotes from this position * |
yes (1 site) |
* these five were length-truncated in the listing; the prefixes are exact, the tails are the obvious completion and should be re-read from the binary before being committed as literals.
4.2 Raise sites
Jump family — the source of the codes is CMotionInterp and they are
WeenieError ids, the same numbering the server uses.
| Function | Addr | Codes it produces |
|---|---|---|
CMotionInterp::charge_jump |
0x005281C0 |
0x49 if CWeenieObject::CanJump(jump_extent) fails; 0x48 if forward_command is a disallowed posture; 0 otherwise |
CMotionInterp::jump_is_allowed |
0x005282B0 |
0x24 if not on the ground; 0x47 if fully constrained or out of stamina; else defers to jump_charge_is_allowed / motion_allows_jump |
| Consumer | Addr | Sites |
|---|---|---|
ClientCombatSystem::CommenceJump |
0x0056AF90 |
0x0056AFE3 → cant_jump_position (0x48); 0x0056AFD7 → cant_jump_load (0x49); 0x0056AFCB → cant_jump_in_air (fallback) |
ClientCombatSystem::DoJump |
0x0056B110 |
0x0056B29A → cant_jump_in_air (0x24); 0x0056B27E → cant_jump_position (0x48); 0x0056B262 → cant_jump_load (0x49) |
ClientCommunicationSystem::HandleFailureEvent |
0x00571990 |
0x00571DA1 (0x24), 0x00571D73 (0x48), 0x00571D8A (0x49) — the same three globals, reused for the server-sent ids |
That last row is the important one: retail reuses one string table for locally-detected and server-reported failures. The client-local path is a latency optimisation over the server's own answer, not a separate feature.
Movement / posture / emote family — CommandInterpreter::MovePlayer @
0x006B3F40, switching on CPhysicsObj::DoMotion's return:
| Code | Addr | Message | Emit |
|---|---|---|---|
0x3E |
0x006B43E4 |
too_tired |
ECM_UI::SendNotice_DisplayStringInfo(0x1A, ...) |
0x3F |
0x006B4366 |
cant_crouch_combat |
same |
0x40 |
0x006B43A4 |
cant_sit_combat |
same |
0x41 |
0x006B43C4 |
cant_lie_down_combat |
same |
0x42 |
0x006B43F6 |
cant_emote_combat |
same |
0x44 |
0x006B4419 |
cant_emote_position |
same |
These take the SendNotice_DisplayStringInfo path (notice 0x186A5) rather than
calling AddTextToScroll directly, but
ClientCommunicationSystem::RecvNotice_DisplayStringInfo @ 0x0056E890 immediately
forwards to AddTextToScroll(str, 0x1A, 1, 0), so the outcome is identical.
Vendor family — 0x004C4575, AddTextToScroll("You need an open vendor.", 0x1A, 1, 0).
Total: 23 client-raised sites, 6 enclosing functions, 11 distinct strings.
4.3 What is not client-raised
Retail does not locally generate "You are too encumbered to carry that!" —
0x2A arrives from the server as WeenieError and is turned into text by
HandleFailureEvent. Likewise spell fizzle (0x0402) is server-sent. ACE confirms
this shape: Player_Inventory.cs sends the encumbrance message as
GameEventCommunicationTransientString (0x02EB) rather than as a WeenieError
at all, and Player_Magic.cs:918 sends SendWeenieError(YourSpellFizzled).
5. What the server sends (ACE cross-check)
| Opcode | Class | Payload | Client destination |
|---|---|---|---|
0xF7E0 ServerMessage |
GameMessageSystemChat |
string16L text, u32 chatType |
AddTextToScroll(text, chatType, 1, 0) — chat or spew depending on the type |
GameEvent 0x02EB CommunicationTransientString |
GameEventCommunicationTransientString |
string16L text only, no type field |
hardcoded 0x1A → SpewBox |
GameEvent 0x028A WeenieError |
GameEventWeenieError |
u32 errorId |
HandleFailureEvent → per-id (see appendix) |
GameEvent 0x028B WeenieErrorWithString |
GameEventWeenieErrorWithString |
u32 errorId, string16L param |
same, with %s substitution |
GameEvent 0x0004 PopUpString |
— | string16L text |
DialogFactory::MakeDialogInCurrentUI → modal dialog |
ACE never resolves a WeenieError id to text — it always writes the bare u32
(references/ACE/Source/ACE.Server/Network/GameEvent/Events/GameEventWeenieError.cs).
The client owns every error string. 0x48 = YouCantJumpFromThisPosition,
0x49 = CantJumpLoadedDown (ACE marks the latter "client side only", consistent
with our charge_jump finding).
references/holtburger/ is not a useful oracle here: it flattens 0x02EB into
a plain system chat line
(crates/holtburger-core/src/client/messages.rs:268-275) and has no transient
destination at all. It is a useful oracle for id→text: its hand-written
format_weenie_error table (crates/holtburger-core/src/errors.rs) covers ~60
ids, and its is_actually_weenie_error() allowlist (errors.rs:302-315) correctly
notes that several "errors" are success notices.
6. acdream gap list
Verified against the worktree at .claude/worktrees/eloquent-hugle-42119e.
6.1 Routing
| Retail | acdream today | Gap |
|---|---|---|
One AddTextToScroll(text, type, ...) chokepoint feeding N self-selecting sinks |
GameEventWiring.cs:223/228 calls chat.OnWeenieError(...); LiveSessionEventRouter.cs:268 calls Chat.OnSystemMessage(text, chatType) |
No chokepoint, no sink model. Every producer writes directly into ChatLog. |
| Destination decided by text type on the receiver | ChatLog is the only destination |
The whole transient destination is missing. |
Text type 0x1A = SpewBox |
wire chatType is parsed and stored in ChatEntry.ChannelId but never read for display; colour comes solely from the 9-value ChatKind enum (ChatWindowController.cs:542-555) |
The discriminator we need is on the wire, captured, and then thrown away. |
| 34-value text-type space (0x00–0x21) | no enum mirroring it — ChatKind (9 buckets), TurbineChat.ChatType (rooms), ChatChannelKind (outbound) are all different axes |
Missing enum. Raw 0x1Au literals already appear at InteractionRetainedUiComposition.cs:348/418/756/771 and SessionPlayerComposition.cs:1128 with no name. |
0x02EB CommunicationTransientString → always spew |
not wired at all | Missing message. |
0x0004 PopUpString → modal dialog |
GameEventWiring.cs:126 chat.OnPopup(...) → ChatLog |
Wrong destination (retail opens a dialog). Out of scope for this port but worth a register row. |
Plugin veto hook OnChatWindowText(text, type, &eat) |
none | Missing; note it for the plugin API. |
Timestamp + chat-log-file suppressed for 0x1A |
n/a | Falls out of the port if the sink split is done right. |
6.2 Presentation
PortalWaitNoticeController (src/AcDream.App/UI/PortalWaitNoticeController.cs) is
the closest existing thing: a single centred full-screen UiText, ClickThrough,
ZOrder = int.MaxValue, ported from gmSmartBoxUI::UseTime. It is a single
overwrite-only slot with no queue, no timeout, no fade — structurally the right
shape but missing every SpewBox behaviour (bounded queue, newest-on-top, dedupe
against the newest, per-line expiry).
TextRenderer + BitmapFont (src/AcDream.App/Rendering/) are a 2D screen-space
quad batcher and an ASCII atlas — primitives with no message concept.
DebugVM.ToastKind/AddToast is a 25-deep ring rendered inside the ImGui dev
panel only (DebugPanel.cs:88), explicitly documented as "no on-screen flash".
There is no spew-box panel, controller, or element id anywhere in src/ — a
tree-wide grep for Spew returns zero hits.
6.3 Strings
Two unrelated hardcoded maps exist and neither covers the SpewBox set:
src/AcDream.Core/Chat/WeenieErrorMessages.cs— ~30 no-param + ~28 with-string templates, fallback"WeenieError 0x{code:X4}".src/AcDream.Core.Net/Messages/WeenieErrorText.cs— 4 codes, used only by theUseDonehandler.
Neither has 0x0048 or 0x0049. A server-sent 0x48 renders today as the literal
string WeenieError 0x0048. Retail has 339 ids in its switch.
6.4 Client-raised errors — the sharpest gap
src/AcDream.Core/Physics/MotionInterpreter.cs already computes the right codes:
JumpChargeIsAllowed (:1762-1773), ChargeJump (:1827-1851, an explicit port
of CMotionInterp::charge_jump @ 0x005281C0), JumpIsAllowedSharedGate
(:2052-2070). They are unit-tested
(tests/AcDream.Core.Tests/Physics/MotionInterpreterJumpFamilyTests.cs).
They are then discarded:
src/AcDream.Runtime/Gameplay/PlayerMovementController.cs:2473—_motion.ChargeJump();with the return value not assigned to anything.PlayerMovementController.cs:2484-2514—var jumpResult = _motion.jump(...),if (jumpResult == WeenieError.None) { ...launch... }, noelse. On refusal the controller resets_jumpCharging/_jumpExtentand returns silently.
So the player sees the power bar drain and nothing happen. No code path in acdream
carries a locally-produced WeenieError to any display surface — the only
WeenieError → text conversions are triggered by inbound wire events.
7. Recommended port shape
Layered per docs/architecture/acdream-architecture.md and the Code Structure Rules.
7.1 AcDream.Core — the type space and the strings
AcDream.Core/Chat/TextMessageType.cs— auint-backed enum mirroring the client's 0x00–0x21 space, not ACE's truncated 0x00–0x1F. Names from ACE'sChatMessageTypewhere they exist;SpewBox = 0x1Afor the one retail leaves unnamed; explicit placeholders for0x20/0x21(coloured in retail, unnamed in every server-side oracle). This retires the raw0x1Auliterals already scattered throughInteractionRetainedUiCompositionandSessionPlayerComposition.- Extend
WeenieErrorMessagesinto the full retail table:(id) → (template, TextMessageType). TheTextMessageTypecolumn is the routing decision, taken verbatim fromHandleFailureEvent— the appendix below is the transcription. Keep_/{0}interpolation for the0x028Bparameter. FoldWeenieErrorText.csinto it (it is a 4-entry duplicate). ClientTextRefusals— the 11 client-local literals from §4.1 as named constants, so the jump/posture sites and theHandleFailureEventtable share one string exactly as retail does.
7.2 AcDream.Core / AcDream.Runtime — the chokepoint and the sinks
- One router, the direct analogue of
ClientSystem::AddTextToScroll:AddText(string text, TextMessageType type, uint windowId = 0). It owns: trim → (future) plugin veto → branch:type == SpewBoxbypasses timestamp and log-file, everything else does not → publish one event. Given J4.1, the natural owner isRuntimeCommunicationState(src/AcDream.Runtime/...), which already owns the canonical transcript. It should expose two borrowed views: the existing chat transcript and a newSpewBoxState. SpewBoxStatein Runtime — pure state, no presentation:- pending queue drained once per tick (retail's global message 3),
MaxConcurrentItems(retail code default 1; the shipped value is DAT data — see the open question in §8),- insert at index 0,
- dedupe against index 0 only (identical text deletes the older entry first),
- drop index
count-1on overflow, - per-entry expiry timestamp. Retail's expiry lives in keystone; until it is measured, this is a divergence needing a register row (see §7.5).
- Rewire the producers:
GameEventWiring0x028A/0x028B→ look up(template, type)→ router. New0x02EBhandler → router withtype = SpewBox.LiveSessionEventRouter:268(0xF7E0) → router with the wirechatTypeinstead ofChat.OnSystemMessage. - Wire the local refusals.
PlayerMovementController.cs:2473and:2484-2514currently dropWeenieErrorvalues on the floor. Give both anelsethat calls the router with the matching string atTextMessageType.SpewBox. Same for the posture/emote family if/whenCommandInterpreter::MovePlayeris ported.
7.3 AcDream.UI.Abstractions — the contract
- A
SpewBoxVMsnapshot (ordered lines + remaining lifetime) beside the existingChatVM, per Code Structure Rule 3. Panels must not reach into Runtime.
7.4 AcDream.App — presentation
- A
SpewBoxControllernext toPortalWaitNoticeController, using the same proven pattern: full-widthUiTextblock,ClickThrough, highZOrder, newest line at the top.PortalWaitNoticeControlleris the template to copy — it is already the right kind of object, it just holds one slot instead of a bounded list. - Do not reuse the chat colour table for it. The SpewBox colour is DAT-owned; until the layout is dumped, pick a placeholder and put a register row on it.
7.5 Divergence-register rows this port must add
Per the mandatory bookkeeping rule, the following are deviations at the moment of
landing and each needs a row in
docs/architecture/retail-divergence-register.md in the same commit:
- SpewBox line lifetime / fade curve is invented, not measured (keystone-owned) — risk: lines linger or vanish visibly faster/slower than retail.
- SpewBox screen position / font / colour are invented until the LayoutDesc is dumped — risk: text in the wrong place or the wrong colour.
MaxConcurrentItemsuses the code default (1) rather than the authored DAT value — risk: bursts of refusals collapse to one visible line where retail shows N.0x0004 PopUpStringcontinues to route to chat rather than a modal dialog.
8. Open questions / next steps
- Which LayoutDesc hosts the SpewBox? Enumerate LayoutDescs in
client_local_English.datfor an element of type0x10000016. That yields position, extent, and the ListBox's0x10000028max-items property. - What is the line lifetime? Two options, both cheap:
(a) dump layout enum
0x10000012element0x1000004Aand read the authored behaviour; (b) attach cdb to live retail with a breakpoint ongmSpewBoxUI::ListenToElementMessage@0x004D57C0and ongmSpewBoxUI::RecvNotice_DisplayFinalStringInfo@0x004D60A0, then spamYou can't jump while in the airand diff the timestamps. Option (b) also answers "does it fade or does it pop?" if the item's alpha is sampled. - What is
0x81C4D8? The one chat colour not yet read (types0x04/0x0B). Trivial to grab in the same cdb session (dd 0x81c4d8 L4). - Re-read the five truncated posture/emote literals from the binary before committing them.
- Should the plugin
OnChatWindowTextveto hook be part of the acdream plugin API? Retail lets a plugin suppress any line before it reaches any sink.
Appendix A — HandleFailureEvent routing table
Transcribed from ClientCommunicationSystem::HandleFailureEvent @ 0x00571990
(339 cases). Type is the literal argument passed to
ClientSystem::AddTextToScroll, i.e. the routing decision:
0x1A→ SpewBox (transient on-screen), 119 ids0x00→ chat, default/broadcast colour (green), 162 ids0x07→ chat,Magicchannel (light blue), 58 ids
%s is the 0x028B string parameter. Strings are the full binary literals where
recovery was unambiguous; [AMBIG n] marks a truncated prefix that matched n
candidates in the binary (the shortest is shown) and must be re-read before use.
| Error id | Type | Text |
|---|---|---|
0x017 |
0x1A | You failed to go to non-combat mode. |
0x01D |
0x1A | You're too busy! |
0x01E |
0x1A | You must control both objects! |
0x020 |
0x1A | You must control both objects! |
0x023 |
0x1A | Unable to move to object! |
0x024 |
0x1A | (no literal — uses a shared string global; see §4.1) |
0x026 |
0x1A | That is not a valid command. |
0x028 |
0x1A | The item is under someone else's control! |
0x029 |
0x1A | You cannot pick that up! |
0x02A |
0x1A | You are too encumbered to carry that! |
0x02B |
0x00 | cannot carry anymore.\n |
0x036 |
0x1A | Action cancelled! |
0x037 |
0x1A | Unable to move to object! |
0x038 |
0x1A | Unable to move to object! |
0x039 |
0x1A | Unable to move to object! |
0x03A |
0x1A | You can't do that... you're dead! |
0x03D |
0x1A | You charged too far! |
0x03E |
0x1A | You are too tired to do that! |
0x048 |
0x1A | (no literal — uses a shared string global; see §4.1) |
0x049 |
0x1A | (no literal — uses a shared string global; see §4.1) |
0x04A |
0x00 | Ack! You killed yourself!\n |
0x04D |
0x1A | Invalid PK status! |
0x04E |
0x07 | You fail to affect %s because you cannot affect anyone! [AMBIG 4] |
0x050 |
0x07 | You fail to affect %s because beneficial spells do not affect %s! |
0x051 |
0x07 | You fail to affect %s because you cannot affect anyone! [AMBIG 4] |
0x052 |
0x07 | You fail to affect %s because %s is not a player killer! |
0x053 |
0x07 | You fail to affect %s because you cannot affect anyone! [AMBIG 4] |
0x054 |
0x07 | You fail to affect %s because you cannot affect anyone! [AMBIG 4] |
0x3EF |
0x00 | is not accepting gifts right now. |
0x3F1 |
0x1A | You failed to go to non-combat mode. |
0x3F7 |
0x1A | You are too fatigued to attack! |
0x3F8 |
0x1A | You are out of ammunition! |
0x3F9 |
0x1A | Your missile attack misfired! |
0x3FA |
0x1A | You've attempted an impossible spell path! |
0x3FE |
0x1A | You don't know that spell! |
0x3FF |
0x1A | Incorrect target type |
0x400 |
0x1A | You don't have all the components for this spell. |
0x401 |
0x1A | You don't have enough Mana to cast this spell. |
0x402 |
0x07 | Your spell fizzled.\n |
0x403 |
0x1A | Your spell's target is missing! |
0x404 |
0x1A | Your projectile spell mislaunched! |
0x407 |
0x1A | Your spell cannot be cast outside |
0x40A |
0x1A | You are unprepared to cast a spell |
0x40B |
0x1A | You've already sworn your Allegiance |
0x40C |
0x1A | You don't have enough experience available to swear Allegiance |
0x413 |
0x1A | %s is already one of your followers |
0x414 |
0x1A | You are not in an allegiance! |
0x416 |
0x1A | %s cannot have any more Vassals |
0x41D |
0x1A | You must be the leader of a Fellowship |
0x41E |
0x1A | Your Fellowship is full |
0x41F |
0x1A | That Fellowship name is not permitted |
0x422 |
0x1A | That channel doesn't exist. |
0x423 |
0x1A | You can't use that channel. |
0x424 |
0x1A | You're already on that channel. |
0x425 |
0x1A | You're not currently on that channel. |
0x427 |
0x1A | You cannot merge different stacks! |
0x428 |
0x1A | You cannot merge enchanted items! |
0x429 |
0x1A | You must control at least one stack! |
0x432 |
0x1A | Your craft attempt fails. |
0x433 |
0x1A | Your craft attempt fails. |
0x434 |
0x1A | Given that number of items, you cannot craft anything. |
0x435 |
0x1A | Your craft attempt fails. |
0x437 |
0x1A | Either you or one of the items involved does not pass the requirements for this craft interaction. |
0x438 |
0x1A | You do not have all the neccessary items. |
0x439 |
0x1A | Not all the items are avaliable. |
0x43A |
0x1A | You must be at rest in peace mode to do trade skills. |
0x43B |
0x1A | You are not trained in that trade skill. |
0x43C |
0x1A | Your hands must be free. |
0x43D |
0x07 | You cannot link to that portal!\n |
0x43E |
0x00 | You have solved this quest too recently! |
0x43F |
0x00 | You have solved this quest too many times! |
0x445 |
0x00 | This item requires you to complete a specific quest before you can pick it up! |
0x45C |
0x07 | Player killers may not interact with that portal! |
0x45D |
0x07 | Non-player killers may not interact with that portal! |
0x45E |
0x1A | You do not own a house! |
0x45F |
0x1A | You do not own a house! |
0x466 |
0x07 | You must purchase Asheron's Call -- Dark Majesty to use this function. [AMBIG 6] |
0x469 |
0x00 | You have used all the hooks you are allowed to use for this house. |
0x46A |
0x00 | doesn't know what to do with th… |
0x474 |
0x07 | You must complete a quest to interact with that portal. |
0x47F |
0x1A | You must own a house to use this command. |
0x480 |
0x1A | Your monarch does not own a mansion or a villa! |
0x481 |
0x1A | Your monarch does not own a mansion or a villa! |
0x482 |
0x1A | Your monarch has closed the mansion to the Allegiance. |
0x488 |
0x00 | You must be above level %s to purchase this dwelling. |
0x489 |
0x00 | You must be at or below level %s to purchase this dwelling. |
0x48B |
0x00 | You must be above allegiance rank %s to purchase this dwelling. |
0x48C |
0x00 | You must be at or below allegiance rank %s to purchase this dwelling. |
0x48E |
0x1A | Your offer of Allegiance has been ignored. |
0x48F |
0x1A | You are already involved in something! |
0x490 |
0x1A | You must be a monarch to use this command. |
0x491 |
0x1A | You must specify a character to boot. [AMBIG 2] |
0x492 |
0x1A | You can't boot yourself! |
0x493 |
0x1A | That character does not exist. |
0x494 |
0x1A | That person is not a member of your Allegiance! |
0x495 |
0x1A | No patron from which to break! |
0x496 |
0x00 | Your Allegiance has been dissolved! |
0x497 |
0x00 | Your patron's Allegiance to you has been broken! |
0x498 |
0x1A | You have moved too far! |
0x499 |
0x1A | That is not a valid destination! |
0x49A |
0x1A | You must purchase Asheron's Call -- Dark Majesty to use this function. [AMBIG 6] |
0x49B |
0x07 | You fail to link with the lifestone! |
0x49C |
0x07 | You wandered too far to link with the lifestone! |
0x49D |
0x07 | You successfully link with the lifestone! |
0x49E |
0x07 | You must have linked with a lifestone in order to recall to it! |
0x49F |
0x07 | You fail to recall to the lifestone! |
0x4A0 |
0x07 | You fail to link with the portal! |
0x4A1 |
0x07 | You successfully link with the portal! |
0x4A2 |
0x07 | You fail to recall to the portal! |
0x4A3 |
0x07 | You must have linked with a portal in order to summon it! [AMBIG 2] |
0x4A4 |
0x07 | You fail to summon the portal!\n |
0x4A5 |
0x07 | You must have linked with a portal in order to summon it! [AMBIG 2] |
0x4A6 |
0x07 | You fail to teleport!\n |
0x4A7 |
0x07 | You have been teleported too recently! |
0x4A8 |
0x07 | You must be an Advocate to interact with that portal. |
0x4AA |
0x07 | Players may not interact with that portal. |
0x4AB |
0x07 | You are not powerful enough to interact with that portal! |
0x4AC |
0x07 | You are too powerful to interact with that portal! |
0x4AD |
0x07 | You cannot recall to that portal! |
0x4AE |
0x07 | You cannot summon that portal!\n |
0x4AF |
0x1A | The lock is already unlocked. |
0x4B0 |
0x1A | You can't lock or unlock that! |
0x4B1 |
0x1A | You can't lock or unlock what is open! |
0x4B2 |
0x00 | The key doesn't fit this lock.\n |
0x4B3 |
0x1A | The lock has been used too recently. |
0x4B4 |
0x1A | You aren't trained in lockpicking! |
0x4B5 |
0x1A | You must specify a character to boot. [AMBIG 2] |
0x4B6 |
0x1A | Please use the allegiance panel to view your own information. |
0x4B7 |
0x1A | You have used that command too recently. |
0x4B8 |
0x00 | You do not own that salvage tool! |
0x4B9 |
0x00 | You do not own that salvage tool! |
0x4BA |
0x00 | You do not own that salvage tool! |
0x4BD |
0x00 | You do not own that salvage tool! |
0x4BE |
0x00 | You do not own that item!\n |
0x4BF |
0x1A | The %s was not suitable for salvaging. |
0x4C0 |
0x1A | The %s contains the wrong material. |
0x4C1 |
0x00 | The material cannot be created.\n |
0x4C2 |
0x00 | The list of items you are attempting to salvage is invalid. |
0x4C3 |
0x00 | You cannot salvage items that you are trading! |
0x4C4 |
0x07 | You must be a guest in this house to interact with that portal. |
0x4C5 |
0x1A | Your Allegiance Rank is too low to use that item's magic. |
0x4C6 |
0x1A | You must be %s to use that item's magic. |
0x4C7 |
0x1A | Your Arcane Lore skill is too low to use that item's magic. |
0x4C8 |
0x1A | That item doesn't have enough Mana. |
0x4C9 |
0x1A | Your %s is too low to use that item's magic. |
0x4CA |
0x1A | Only %s may use that item's magic. |
0x4CB |
0x1A | You must have %s specialized to use that item's magic. |
0x4CC |
0x07 | You have been involved in a player killer battle too recently to do that! |
0x4CE |
0x00 | is too busy to accept gifts right now. |
0x4CF |
0x00 | cannot accept stacked objects. … |
0x4D0 |
0x00 | You have failed to alter your skill. |
0x4D1 |
0x00 | Your %s skill must be trained, not untrained or specialized, in order to be altered in this way! |
0x4D2 |
0x00 | You do not have enough skill credits to specialize your %s skill. |
0x4D3 |
0x00 | You have too many available experience points to be able to absorb the experience points from your %s skill. Please spend some of your experience points and try again. |
0x4D4 |
0x00 | Your %s skill is already untrained! |
0x4D5 |
0x00 | You are currently wielding items which require a certain level of %s. Your %s skill cannot be lowered while you are wielding these items. Please remove these items and try again. [AMBIG 2] |
0x4D6 |
0x00 | You have succeeded in specializing your %s skill! |
0x4D7 |
0x00 | You have succeeded in lowering your %s skill from specialized to trained! |
0x4D8 |
0x00 | You have succeeded in untraining your %s skill! |
0x4D9 |
0x00 | Although you cannot untrain your %s skill, you have succeeded in recovering all the experience you had invested in it. |
0x4DA |
0x00 | You have too many credits invested in specialized skills already! Before you can specialize your %s skill, you will need to unspecialize some other skill. |
0x4DD |
0x00 | You have failed to alter your attributes. |
0x4DE |
0x00 | (no literal — uses a shared string global; see §4.1) |
0x4DF |
0x00 | (no literal — uses a shared string global; see §4.1) |
0x4E0 |
0x00 | You are currently wielding items which require a certain level of %s. Your %s skill cannot be lowered while you are wielding these items. Please remove these items and try again. [AMBIG 2] |
0x4E1 |
0x00 | You have succeeded in transferring your attributes! |
0x4E2 |
0x00 | This hook is a duplicated housing object. You may not add items to a duplicated housing object. Please empty the hook and allow it to reset. |
0x4E3 |
0x00 | That item is of the wrong type to be placed on this hook. |
0x4E4 |
0x00 | This chest is a duplicated housing object. You may not add items to a duplicated housing object. Please empty everything -- including backpacks -- out of the chest and allow the chest to reset. |
0x4E5 |
0x00 | This hook was a duplicated housing object. Since it is now empty, it will be deleted momentarily. Once it is gone, it is safe to use the other, non-duplicated hook that is here. |
0x4E6 |
0x00 | This chest was a duplicated housing object. Since it is now empty, it will be deleted momentarily. Once it is gone, it is safe to use the other, non-duplicated chest that is here. |
0x4E7 |
0x00 | You cannot swear allegiance to anyone because you own a monarch-only house. Please abandon your house and try again. |
0x4E9 |
0x00 | The %s cannot be used while on a hook and only the owner may open the hook. [AMBIG 2] |
0x4EA |
0x00 | The %s can only be used while on a hook. |
0x4EB |
0x1A | You can't do that while in the air! |
0x4EC |
0x00 | You cannot modify your player killer status while you are recovering from a PK death. |
0x4ED |
0x00 | Advocates may not change their player killer status! |
0x4EE |
0x00 | Your level is too low to change your player killer status with this object. |
0x4EF |
0x00 | Your level is too high to change your player killer status with this object. |
0x4F0 |
0x00 | You feel a harsh dissonance, and you sense that an act of killing you have committed recently is interfering with the conversion. |
0x4F1 |
0x00 | Bael'Zharon's power flows through you again. You are once more a player killer. |
0x4F2 |
0x00 | Bael'Zharon has granted you respite after your moment of weakness. You are temporarily no longer a player killer. |
0x4F3 |
0x07 | Lite Player Killers may not interact with that portal! |
0x4F4 |
0x07 | %s fails to affect you because $… |
0x4F5 |
0x07 | %s fails to affect you because y… |
0x4F6 |
0x07 | %s fails to affect you because %… |
0x4F7 |
0x07 | fails to affect you because you… |
0x4F8 |
0x07 | fails to affect you because you… |
0x4F9 |
0x07 | fails to affect you across a ho… |
0x4FA |
0x07 | is an invalid target.\n |
0x4FB |
0x07 | You are an invalid target for the spell of %s. |
0x4FC |
0x1A | You aren't trained in healing! |
0x4FD |
0x1A | You don't own that healing kit! |
0x4FE |
0x1A | You can't heal that! |
0x4FF |
0x1A | is already at full health! |
0x500 |
0x1A | You aren't ready to heal! |
0x501 |
0x1A | You can only use Healing Kits on player characters. |
0x502 |
0x07 | The Lifestone's magic protects you from the attack! |
0x503 |
0x07 | The portal's residual energy protects you from the attack! |
0x504 |
0x00 | You are enveloped in a feeling of warmth as you are brought back into the protection of the Light. You are once again a Non-Player Killer. |
0x505 |
0x1A | You're too close to your sanctuary! |
0x506 |
0x1A | You can't do that -- you're trading! |
0x507 |
0x00 | Only Non-Player Killers may enter PK Lite. Please see @help pklite for more details about this command. |
0x508 |
0x00 | A cold wind touches your heart. You are now a Player Killer Lite. |
0x509 |
0x07 | has no appropriate targets equi… |
0x50A |
0x07 | You have no appropriate targets equipped for %s's spell. |
0x50B |
0x00 | is now an open fellowship; anyo… |
0x50C |
0x00 | is now a closed fellowship.\n |
0x50D |
0x00 | is now the leader of this fello… |
0x50E |
0x00 | You have passed leadership of the fellowship to %s |
0x50F |
0x1A | You do not belong to a Fellowship. |
0x510 |
0x00 | You may not hook any more %s on your house. You already have the maximum number of %s hooked or you are not permitted to hook any on your type of house. |
0x512 |
0x00 | You are now using the maximum number of hooks. You cannot use another hook until you take an item off one of your hooks. |
0x513 |
0x00 | You are no longer using the maximum number of hooks. You may again add items to your hooks. |
0x514 |
0x00 | You now have the maximum number of %s hooked. You cannot hook any additional %s until you remove one or more from your house. |
0x515 |
0x00 | You no longer have the maximum number of %s hooked. You may hook additional %s. |
0x516 |
0x00 | You are not permitted to use that hook. |
0x517 |
0x00 | is not close enough to your lev… |
0x518 |
0x00 | cannot be recruited into the fe… |
0x519 |
0x00 | The fellowship is locked, you were not added to the fellowship. |
0x51A |
0x1A | Only the original owner may use that item's magic. |
0x51B |
0x00 | You have entered the %s channel. |
0x51C |
0x00 | You have left the %s channel.\n |
0x51E |
0x00 | will not receive your message, please use urgent assistance to speak with an in-game representative |
0x51F |
0x1A | Message Blocked: %s |
0x520 |
0x00 | You cannot add anymore people to the list of players that you can hear. |
0x521 |
0x00 | has been added to the list of p… |
0x522 |
0x00 | has been removed from the list … |
0x523 |
0x00 | You are now deaf to player's screams. |
0x524 |
0x00 | You can hear all players once again. |
0x525 |
0x00 | You fail to remove %s from your loud list. |
0x526 |
0x1A | You chicken out. |
0x527 |
0x1A | You cannot posssibly succeed. |
0x528 |
0x00 | The fellowship is locked; you cannot open locked fellowships. |
0x529 |
0x1A | Trade Complete! |
0x52A |
0x1A | That is not a salvaging tool. |
0x52B |
0x1A | That person is not available now. |
0x52C |
0x00 | You are now snooping on %s.\n |
0x52D |
0x00 | You are no longer snooping on %s. |
0x52E |
0x00 | You fail to snoop on %s.\n |
0x52F |
0x00 | %s attempted to snoop on you.\n |
0x530 |
0x00 | %s is already being snooped on, … |
0x531 |
0x00 | %s is in limbo and cannot receive your message. |
0x532 |
0x00 | You must wait 30 days after purchasing a house before you may purchase another with any character on the same account. This applies to all housing except apartments. |
0x533 |
0x00 | You have been booted from your allegiance chat room. Use "@allegiance chat on" to rejoin. (%s). |
0x534 |
0x00 | %s has been booted from the alle… |
0x535 |
0x00 | You do not have the authority within your allegiance to do that. |
0x536 |
0x00 | The account of %s is already banned from the allegiance. |
0x537 |
0x00 | The account of %s is not banned from the allegiance. |
0x538 |
0x00 | The account of %s was not unbanned from the allegiance. |
0x539 |
0x00 | The account of %s has been banned from the allegiance. |
0x53A |
0x00 | The account of %s is no longer banned from the allegiance. |
0x53B |
0x00 | Banned Characters: |
0x53E |
0x00 | %s is banned from the allegiance… |
0x53F |
0x00 | You are banned from %s's allegiance! |
0x540 |
0x00 | You have the maximum number of accounts banned.! |
0x541 |
0x00 | %s is now an allegiance officer.… |
0x542 |
0x00 | An unspecified error occurred while attempting to set %s as an allegiance officer. [AMBIG 2] |
0x543 |
0x00 | %s is no longer an allegiance of… |
0x544 |
0x00 | An unspecified error occurred while attempting to set %s as an allegiance officer. [AMBIG 2] |
0x545 |
0x00 | You already have the maximum number of allegiance officers. You must remove some before you add any more. |
0x546 |
0x00 | Your allegiance officers have been cleared. |
0x547 |
0x00 | You must wait %s before communicating again! |
0x548 |
0x00 | You cannot join any chat channels while gagged. |
0x549 |
0x00 | Your allegiance officer status has been modified. You now hold the position of: %s. |
0x54A |
0x00 | You are no longer an allegiance officer. |
0x54B |
0x00 | %s is already an allegiance offi… |
0x54C |
0x00 | Your allegiance does not have a hometown. |
0x54D |
0x1A | The %s is currently in use.\n |
0x54E |
0x00 | The hook does not contain a usable item. Use the '@house hooks on'command to make the hook openable. [AMBIG 2] |
0x54F |
0x00 | The hook does not contain a usable item. Use the '@house hooks on'command to make the hook openable. [AMBIG 2] |
0x550 |
0x1A | Out of Range! |
0x551 |
0x00 | You are not listening to the %s channel! |
0x552 |
0x1A | You must purchase Asheron's Call -- Dark Majesty to use this function. [AMBIG 6] |
0x553 |
0x1A | You must purchase Asheron's Call -- Dark Majesty to use this function. [AMBIG 6] |
0x554 |
0x1A | You must purchase Asheron's Call -- Dark Majesty to use this function. [AMBIG 6] |
0x555 |
0x1A | You must purchase Asheron's Call -- Dark Majesty to use this function. [AMBIG 6] |
0x556 |
0x00 | You have failed to complete the augmentation. |
0x557 |
0x00 | You have used this augmentation too many times already. |
0x558 |
0x00 | You have used augmentations of this type too many times already. |
0x559 |
0x00 | You do not have enough unspent experience available to purchase this augmentation. |
0x55A |
0x00 | (no literal — uses a shared string global; see §4.1) |
0x55B |
0x00 | Congratulations! You have succeeded in acquiring the %s augmentation. |
0x55C |
0x00 | Although your augmentation will not allow you to untrain your %s skill, you have succeeded in recovering all the experience you had invested in it. |
0x55D |
0x00 | You must exit the Training Academy before that command will be available to you. |
0x55E |
0x00 | (no literal — uses a shared string global; see §4.1) |
0x55F |
0x00 | Only Player Killer characters may use this command! |
0x560 |
0x00 | Only Player Killer Lite characters may use this command! |
0x561 |
0x1A | You may only have a maximum of 50 friends at once. If you wish to add more friends, you must first remove some. |
0x562 |
0x00 | %s is already on your friends li… |
0x563 |
0x00 | That character is not on your friends list! |
0x564 |
0x00 | Only the character who owns the house may use this command. |
0x565 |
0x00 | That allegiance name is invalid because it is empty. Please use the @allegiance name clear command to clear your allegiance name. |
0x566 |
0x00 | That allegiance name is too long. Please choose another name. |
0x567 |
0x00 | That allegiance name contains illegal characters. Please choose another name using only letters, spaces, - and '. |
0x568 |
0x00 | That allegiance name is not appropriate. Please choose another name. |
0x569 |
0x00 | That allegiance name is already in use. Please choose another name. |
0x56A |
0x00 | You may only change your allegiance name once every 24 hours. You may change your allegiance name again in %s. |
0x56B |
0x00 | Your allegiance name has been cleared. |
0x56C |
0x00 | That is already the name of your allegiance! |
0x56D |
0x00 | %s is the monarch and cannot be … |
0x56E |
0x00 | That level of allegiance officer is now known as: %s. |
0x56F |
0x00 | That is an invalid officer level. |
0x570 |
0x00 | That allegiance officer title is not appropriate. |
0x571 |
0x00 | That allegiance name is too long. Please choose another name. |
0x572 |
0x00 | All of your allegiance officer titles have been cleared. |
0x573 |
0x00 | That allegiance title contains illegal characters. Please choose another name using only letters, spaces, - and '. |
0x574 |
0x00 | Your allegiance is currently: %s. |
0x575 |
0x00 | Your allegiance is now: %s.\n |
0x576 |
0x00 | You may not accept the offer of allegiance from %s because your allegiance is locked. |
0x577 |
0x00 | You may not swear allegiance at this time because the allegiance of %s is locked. |
0x578 |
0x00 | You have pre-approved %s to join your allegiance. |
0x579 |
0x00 | You have not pre-approved any vassals to join your allegiance. |
0x57A |
0x00 | %s is already a member of your a… |
0x57B |
0x00 | %s has been pre-approved to join… |
0x57C |
0x00 | You have cleared the pre-approved vassal for your allegiance. |
0x57D |
0x00 | That character is already gagged! |
0x57E |
0x00 | That character is not currently gagged! |
0x57F |
0x00 | Your allegiance chat privileges have been restored. [AMBIG 3] |
0x580 |
0x00 | %s is now temporarily unable to … |
0x581 |
0x00 | Your allegiance chat privileges have been restored. [AMBIG 3] |
0x582 |
0x00 | Your allegiance chat privileges have been restored. [AMBIG 3] |
0x583 |
0x00 | You have restored allegiance chat privileges to %s. |
0x584 |
0x1A | You cannot pick up more of that item! |
0x585 |
0x1A | You are restricted to clothes and armor created for your race. |
0x586 |
0x1A | That item was specifically created for another race. |
0x587 |
0x07 | Olthoi cannot interact with that! |
0x588 |
0x07 | Olthoi cannot use regular lifestones! Asheron would not allow it! |
0x589 |
0x07 | The vendor looks at you in horror! |
0x58A |
0x00 | %s cowers from you!\n |
0x58B |
0x07 | As a mindless engine of destruction an Olthoi cannot join a fellowship! |
0x58C |
0x07 | The Olthoi only have an allegiance to the Olthoi Queen! |
0x58D |
0x07 | You cannot use that item!\n |
0x58E |
0x07 | This person will not interact with you! |
0x58F |
0x07 | Only Olthoi may pass through this portal! |
0x590 |
0x07 | Olthoi may not pass through this portal! |
0x591 |
0x07 | You may not pass through this portal while Vitae weakens you! |
0x592 |
0x07 | This character must be two weeks old or have been created on an account at least two weeks old to use this portal! |
0x593 |
0x07 | Olthoi characters can only use Lifestone and PK Arena recalls! |
Appendix B — text types seen in this build
| Type | ACE ChatMessageType |
Notes |
|---|---|---|
0x00 |
Broadcast |
default colour (green) |
0x01 |
AllChannels |
|
0x02 |
Speech |
white |
0x03 |
Tell |
yellow |
0x04 |
OutgoingTell |
|
0x05 |
System |
bright purple |
0x06 |
Combat |
dark red |
0x07 |
Magic |
light blue — the spell/portal failure family |
0x08 0x09 |
Channel / ChannelSend |
pink |
0x0A 0x0B |
Social / SocialSend |
|
0x0C |
Emote |
grey |
0x0D |
Advancement |
cyan |
0x0E |
Abuse |
|
0x0F |
Help |
dark red |
0x10 |
Appraisal |
|
0x11 |
Spellcasting |
light blue |
0x12 |
Allegiance |
orange |
0x13 |
Fellowship |
yellow |
0x14 |
WorldBroadcast |
|
0x15 0x16 |
CombatEnemy / CombatSelf |
|
0x17 |
Recall |
|
0x18 0x19 |
Craft / Salvaging |
|
0x1A |
commented out in ACE | SpewBox |
0x1B–0x1E |
unnamed in ACE | blue-grey |
0x1F |
AdminTell |
yellow |
0x20 0x21 |
absent from ACE | coloured by the client (blue-grey / orange) |