acdream/docs/research/2026-08-09-chat-retail-interface-text.md
Erik e306c979ae docs: Campaign CH R1/R2/R4 research + ledger correction (CH1 = 172c6f9a)
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>
2026-08-09 15:25:29 +02:00

58 KiB
Raw Blame History

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 SpewBoxgmSpewBoxUI @ 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 filterif (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 0x186A5AddTextToScroll
ClientCommunicationSystem::RecvNotice_DisplayWeenieError 0x0057E700 notice 0x186B7HandleFailureEvent
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::MakeDialogInCurrentUIa 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:

  1. Plugin veto. If allowPluginFilter != 0 and the plugin API is ready, call IACPlugin::OnChatWindowText(bstr, type, &eat). If the plugin sets eat, the message is dropped entirely — it reaches neither chat nor spew. (0x00563C7D0x00563CB6.)
  2. Trim trailing whitespace.
  3. Censor. If PlayerModule::FilterLanguage(), explode on spaces and replace any word failing TabooTableAdaptor::CheckCensorsW with ****.
  4. Branch on type (0x00563DE6):
    • type == 0x1Askip the timestamp, skip the chat log file, jump straight to the broadcast.
    • otherwise → prepend %#H:%M:%S if PlayerModule::DisplayTimeStamps(), and fprintf the line to ClientSystem::s_pLogFile if a chat log is open.
  5. 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 ignores windowId entirely.
  • 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 @ 0x00571990 builds each one inline (PStringBase<unsigned short>::PStringBase<unsigned short>(&var, u"…")) or via PStringBase<unsigned short>::sprintf(&s, u"The %s cannot be used …") with the 0x028B string parameter substituted.
  • The 11 movement/jump refusals are process-lifetime globals initialised by static ctors at 0x00708F000x00709180 (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 0x004D5EB10x004D5EE6 resized to the ListBox's width, then RecalculateGlyphList, then resized again to the computed scrollable height (word wrap)
Dedupe 0x004D5EF60x004D5F91 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:

  1. Line lifetime / fade curve. acclient never raises element message 0x10000003. I searched every BroadcastElementMessage / ForwardElementMessage call site in the whole 66 MB listing: the only element message id above 0x10000000 that acclient itself raises is 0x10000004 (0x004F0EC5, a stat-type element). UIElement / UIRegion / ElementDesc / LayoutDesc expose no Duration / Lifetime / Fade / Expire member at all. The timeout and any fade are owned by keystone.dll or by the authored ElementDesc behaviour of layout 0x10000012 element 0x1000004A. Resolution path: dump that LayoutDesc from client_local_English.dat, or set a cdb breakpoint on gmSpewBoxUI::ListenToElementMessage (0x004D57C0) in a live retail client and time the deltas between a message appearing and its removal.
  2. Screen position and extent. Authored in whatever LayoutDesc declares an element of class 0x10000016. The natural host is the main game view (gmSmartBoxUI, LayoutDesc 0x2100000F) but this was not confirmed — no dumped layout in docs/research/retail-ui/ mentions it. Resolution path: enumerate LayoutDescs and look for element type 0x10000016.
  3. 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: BuildChatColorLookupTable writes to ChatInterface::m_chatLog, a different element tree entirely.
  4. 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 0x1A row 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 0x20 and 0x21 are real and coloured. ACE's ChatMessageType stops at 0x1F — 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 0x00708F000x00709180. 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 0x0056AFE3cant_jump_position (0x48); 0x0056AFD7cant_jump_load (0x49); 0x0056AFCBcant_jump_in_air (fallback)
ClientCombatSystem::DoJump 0x0056B110 0x0056B29Acant_jump_in_air (0x24); 0x0056B27Ecant_jump_position (0x48); 0x0056B262cant_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 familyCommandInterpreter::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 family0x004C4575, 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 0x1ASpewBox
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 (0x000x21) 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 the UseDone handler.

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-2514var jumpResult = _motion.jump(...), if (jumpResult == WeenieError.None) { ...launch... }, no else. On refusal the controller resets _jumpCharging / _jumpExtent and 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.


Layered per docs/architecture/acdream-architecture.md and the Code Structure Rules.

7.1 AcDream.Core — the type space and the strings

  1. AcDream.Core/Chat/TextMessageType.cs — a uint-backed enum mirroring the client's 0x000x21 space, not ACE's truncated 0x000x1F. Names from ACE's ChatMessageType where they exist; SpewBox = 0x1A for the one retail leaves unnamed; explicit placeholders for 0x20/0x21 (coloured in retail, unnamed in every server-side oracle). This retires the raw 0x1Au literals already scattered through InteractionRetainedUiComposition and SessionPlayerComposition.
  2. Extend WeenieErrorMessages into the full retail table: (id) → (template, TextMessageType). The TextMessageType column is the routing decision, taken verbatim from HandleFailureEvent — the appendix below is the transcription. Keep _/{0} interpolation for the 0x028B parameter. Fold WeenieErrorText.cs into it (it is a 4-entry duplicate).
  3. ClientTextRefusals — the 11 client-local literals from §4.1 as named constants, so the jump/posture sites and the HandleFailureEvent table share one string exactly as retail does.

7.2 AcDream.Core / AcDream.Runtime — the chokepoint and the sinks

  1. One router, the direct analogue of ClientSystem::AddTextToScroll: AddText(string text, TextMessageType type, uint windowId = 0). It owns: trim → (future) plugin veto → branch: type == SpewBox bypasses timestamp and log-file, everything else does not → publish one event. Given J4.1, the natural owner is RuntimeCommunicationState (src/AcDream.Runtime/...), which already owns the canonical transcript. It should expose two borrowed views: the existing chat transcript and a new SpewBoxState.
  2. SpewBoxState in 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-1 on 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).
  3. Rewire the producers: GameEventWiring 0x028A/0x028B → look up (template, type) → router. New 0x02EB handler → router with type = SpewBox. LiveSessionEventRouter:268 (0xF7E0) → router with the wire chatType instead of Chat.OnSystemMessage.
  4. Wire the local refusals. PlayerMovementController.cs:2473 and :2484-2514 currently drop WeenieError values on the floor. Give both an else that calls the router with the matching string at TextMessageType.SpewBox. Same for the posture/emote family if/when CommandInterpreter::MovePlayer is ported.

7.3 AcDream.UI.Abstractions — the contract

  1. A SpewBoxVM snapshot (ordered lines + remaining lifetime) beside the existing ChatVM, per Code Structure Rule 3. Panels must not reach into Runtime.

7.4 AcDream.App — presentation

  1. A SpewBoxController next to PortalWaitNoticeController, using the same proven pattern: full-width UiText block, ClickThrough, high ZOrder, newest line at the top. PortalWaitNoticeController is the template to copy — it is already the right kind of object, it just holds one slot instead of a bounded list.
  2. 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.
  • MaxConcurrentItems uses the code default (1) rather than the authored DAT value — risk: bursts of refusals collapse to one visible line where retail shows N.
  • 0x0004 PopUpString continues to route to chat rather than a modal dialog.

8. Open questions / next steps

  1. Which LayoutDesc hosts the SpewBox? Enumerate LayoutDescs in client_local_English.dat for an element of type 0x10000016. That yields position, extent, and the ListBox's 0x10000028 max-items property.
  2. What is the line lifetime? Two options, both cheap: (a) dump layout enum 0x10000012 element 0x1000004A and read the authored behaviour; (b) attach cdb to live retail with a breakpoint on gmSpewBoxUI::ListenToElementMessage @ 0x004D57C0 and on gmSpewBoxUI::RecvNotice_DisplayFinalStringInfo @ 0x004D60A0, then spam You can't jump while in the air and diff the timestamps. Option (b) also answers "does it fade or does it pop?" if the item's alpha is sampled.
  3. What is 0x81C4D8? The one chat colour not yet read (types 0x04/0x0B). Trivial to grab in the same cdb session (dd 0x81c4d8 L4).
  4. Re-read the five truncated posture/emote literals from the binary before committing them.
  5. Should the plugin OnChatWindowText veto 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:

  • 0x1ASpewBox (transient on-screen), 119 ids
  • 0x00 → chat, default/broadcast colour (green), 162 ids
  • 0x07 → chat, Magic channel (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
0x1B0x1E unnamed in ACE blue-grey
0x1F AdminTell yellow
0x20 0x21 absent from ACE coloured by the client (blue-grey / orange)