acdream/docs/research/2026-05-10-phase-m-opcode-matrix.md
Erik c7021d8645 docs(phase-m): sharpen Phase M into design spec + opcode coverage matrix
Captures Phase M (Network Stack Conformance) as a fully-formed phase
ready to be picked up later. Three deliverables:

1. Design spec at docs/superpowers/specs/2026-05-10-phase-m-network-stack-design.md
   (~700 lines, 8 sections):
   - Bar C completeness target ("wireable on demand"): every wire opcode
     a 2013 EoR retail client receives or sends gets a parser/builder +
     golden-vector test + typed event in the new layered stack.
   - Three-layer architecture: INetTransport / IReliableSession /
     IGameProtocol, with WorldSession as a thin behavior consumer.
     Concrete C# interface signatures, sub-component decomposition.
   - Worktree-branch big-bang migration on claude/phase-m-network-stack;
     weekly rebase cadence; single --no-ff merge ships the phase.
   - Per-sub-phase entry/exit gates, conformance test plan (golden vectors
     + live capture replay + live ACE smoke), 10-row risk register, scope-
     cut order if calendar compresses.
   - Cost: 256 hours / ~6.4 weeks single-developer; 4-6 weeks calendar
     with subagent parallelization on M.1 + M.6.

2. Opcode coverage matrix at docs/research/2026-05-10-phase-m-opcode-matrix.md
   (~284 rows across 5 sections):
   - Section 1: 22 transport flags (14 implemented).
   - Section 2: 12 optional-header fields (10 partial).
   - Section 3: 51 top-level GameMessages (21 implemented).
   - Section 4: 103 GameEvent sub-opcodes inside 0xF7B0 (27 parsed,
     26 wired).
   - Section 5: 96 GameAction sub-opcodes inside 0xF7B1 (24 built,
     8 with live callers).
   - Roll-up: ~34% complete by raw opcode count. Biggest single
     unblocking step is wiring the 16 dead builders in section 5
     (Phase B.4 surface — Use / UseWithTarget / Allegiance / Inventory
     / Social / Cast / Appraise).
   - Sources cited per row: holtburger (629695a), ACE, named retail
     decomp, acdream current state.
   - Produced by 4 parallel research agents (one per class). Spot-check
     pass owed before M.1 closes.

3. Roadmap update: Phase M section trimmed to summary + status + pointer
   to the spec; the previously-tracked M.0 Tier 1 quick-wins are folded
   into M.3 / M.4 / M.6 per the spec; M.1 retained as the matrix
   construction sub-lane with status note.

Why this shape: the user goal is a complete, layered, testable network
stack that can be wired in as gameplay phases need it — independent of
whether each opcode is yet hooked to game state. The matrix is the
source of truth for "done"; the spec is the architecture the matrix
implements against; the roadmap is the index that points at both.

Decisions captured during the design discussion (in case they need
revisiting):
- Bar C ("wireable on demand") chosen over Bar A (holtburger parity)
  or Bar B (named-retail completeness).
- Three layers (INetTransport / IReliableSession / IGameProtocol)
  chosen over holtburger's two-layer split.
- Big-bang on a feature branch (worktree) chosen over strangler
  pattern; preserves live-ACE testing on main throughout the phase.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 19:22:49 +02:00

543 lines
60 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Phase M — Network Opcode Coverage Matrix
**Date:** 2026-05-10
**Status:** Initial population complete (4 parallel research agents). Spot-check pass + intentional-divergence ratification owed before M.1 closes.
**Companion spec:** [`docs/superpowers/specs/2026-05-10-phase-m-network-stack-design.md`](../superpowers/specs/2026-05-10-phase-m-network-stack-design.md)
**Companion study:** [`docs/research/2026-05-10-holtburger-network-stack-study.md`](2026-05-10-holtburger-network-stack-study.md)
This matrix is the **source of truth for Phase M completeness**. Every row defines: what the opcode is, who currently sends/receives it across our three reference sources, what acdream does today, and what Phase M must do. The spec's M.6 work plan reduces to "for every row where `acdream today``Phase M target`, implement the delta and add tests."
---
## Roll-up
| Section | In-scope | Acdream today | Phase M delta |
|---------|----------|---------------|---------------|
| 1 — Transport flags | 22 | 14 parse / 5 build | 8 |
| 2 — Optional-header fields | 12 | 10 partial | builder + decoder gaps |
| 3 — GameMessage opcodes (top-level) | 51 | 21 implemented | 30 |
| 4 — GameEvent sub-opcodes (inside 0xF7B0) | 103 | 27 parsed / 26 wired | 76 new (~50 deferable to gameplay phases) |
| 5 — GameAction sub-opcodes (inside 0xF7B1) | 96 | 24 built / 8 live callers | 72 new + 16 dead-builder wirings |
| **Total** | **~284** | **~96** | **~190** |
Roughly **34% complete by raw opcode count.** The biggest single Phase-M unblocking step is wiring the 16 dead builders in section 5 (Phase B.4 surface — Use / UseWithTarget / Allegiance / Inventory / Social / Cast / Appraise / etc.).
---
## Cell-value vocabulary
| Code | Meaning |
|------|---------|
| `P` | Parses inbound |
| `B` | Builds outbound |
| `PB` | Parses + builds (both directions) |
| `W` | Wired — typed handler exists AND state is updated by it |
| `H` | (ACE only) Server has a handler that processes this client-sent opcode |
| `` | Not implemented |
| `N/A` | Not applicable for this side (e.g., server-only message in ACE column) |
| `?` | Could not determine — needs verification |
**Phase M target column:**
| Target | Meaning |
|--------|---------|
| `PB+W` | Must parse, build (if outbound), wire to typed event by phase end |
| `PB` | Must parse + build, no wiring required |
| `P+W` | Inbound only, must parse + dispatch typed event |
| `B+W` | Outbound only, must build + have a live caller |
| `B` | Build only, no live caller required (typed for future use) |
| `defer:<phase>` | Explicitly deferred to a named gameplay phase |
| `skip:<reason>` | Out of scope, with justification |
---
## Section 1 — Transport flags
In-scope: 22. Implemented in acdream: 14 (parse path + 5 build path). Phase M target delta: 8 (4 inbound parse gaps to wire, 4 outbound builders, plus 6 to retire/skip-justify).
| Code | Direction | Name | Named-retail symbol | Holtburger | ACE | acdream today | Phase M target | Notes |
|---|---|---|---|---|---|---|---|---|
| `0x00000000` | N/A | None | | N/A | N/A | N/A | N/A | Identity flag value [^t-a] |
| `0x00000001` | both | Retransmission | | P (set on retx) | PB+W | | PB+W | We never echo/honor this bit [^t-b] |
| `0x00000002` | both | EncryptedChecksum | `FlowQueue::EncryptChecksum` | PB | PB+W | PB+W | PB+W | Codec covers in/out + ISAAC |
| `0x00000004` | both | BlobFragments | `MessageFragment` group | PB+W | PB+W | PB+W | PB+W | Fragment list parsed/built |
| `0x00000100` | inbound | ServerSwitch | `ClientNet::HandleServerSwitch` | P (size-skip) | PB | P (size-skip) | P+W | Handler missing; just consumes 8 bytes |
| `0x00000200` | inbound | LogonServerAddr | | | | | defer:M2 | Login-server bounce; no client logic yet [^t-c] |
| `0x00000400` | inbound | EmptyHeader1 | `CEmptyHeader<0x400,2>` | | | | skip:dead-flag | Retail struct exists, never sent |
| `0x00000800` | inbound | Referral | `ClientNet::HandleReferral` | | B only (server) | | defer:M2 | Server-only path until login bounce |
| `0x00001000` | both | RequestRetransmit | `FlowQueue::TransmitNaks` | PB+W | PB+W | P (size-skip) | PB+W | NAK list parsed but ignored [^t-d] |
| `0x00002000` | both | RejectRetransmit | `FlowQueue::EnqueueEmptyAck` | P+W | PB | P (size-skip) | P+W | Inbound only (server tells us "no") |
| `0x00004000` | both | AckSequence | `FlowQueue::EnqueueAcks` | PB+W | PB+W | PB+W | PB+W | Per-packet ack pump shipped |
| `0x00008000` | both | Disconnect | `Client::Disconnect` | | P+W | B only | P+W | Inbound parse-and-tear-down missing [^t-e] |
| `0x00010000` | outbound | LoginRequest | `ClientNet::SendLoginRequest` | B | P+W | B | B | Auth-only, parsed by server [^t-f] |
| `0x00020000` | inbound | WorldLoginRequest | `CEmptyHeader<0x20000,1>` | | P (8 bytes) | P (size-skip) | P (size-skip) | Server-only on relay [^t-g] |
| `0x00040000` | inbound | ConnectRequest | `ClientNet::HandleConnectionRequest` | P+W | B | P+W | P+W | Handshake oracle, ISAAC seeded |
| `0x00080000` | outbound | ConnectResponse | `ClientNet::SendConnectAck` | B | P+W | B | B | 8-byte cookie echo |
| `0x00100000` | inbound | NetError | `NetError::UnPack` | | | | P+W | Drop session + surface error to UI |
| `0x00200000` | inbound | NetErrorDisconnect | `NetError::UnPack` | | P+W | | P+W | Same parse, hard-disconnect variant |
| `0x00400000` | inbound | CICMDCommand | | P (size-skip) | P+W | P (size-skip) | defer:M3 | Server-debug only; not honored by retail clients |
| `0x01000000` | inbound | TimeSync | `ClientNet::HandleTimeSynch` | P+W | P+W | P+W | P+W | Drives `WorldTimeService` |
| `0x02000000` | inbound | EchoRequest | `CEchoRequestHeader::CreateFromData` | P+W (mirrors out) | P+W | P (no reply) | PB+W | Must build EchoResponse mirror [^t-h] |
| `0x04000000` | outbound | EchoResponse | | B | B | | B | Reply path for incoming EchoRequest |
| `0x08000000` | both | Flow | `FlowQueue::TransmitNewPackets` | P (size-skip) | P+W | P (size-skip) | defer:M2 | Throttle hint; safe to ignore until M2 |
**Footnotes:**
[^t-a]: The `None=0` value isn't a wire bit, but it's in our enum so callers can default-initialize headers — keep it.
[^t-b]: ACE sets `Retransmission` when re-sending a cached packet; clients should accept it as informational. We currently treat the bit as a no-op (works because we don't dedupe on it).
[^t-c]: A login-server-side handshake step; only relevant when ACE adds login-bounce, which it doesn't today.
[^t-d]: We need to actually retransmit on inbound NAK and need to send NAKs for our own missing inbound. M3 reliability-core phase.
[^t-e]: Inbound `Disconnect` must close the session cleanly and notify upper layers; right now the connection just times out on client side too.
[^t-f]: `LoginRequest` is a server-decode case but our codec consumes it on encode for hashing.
[^t-g]: Retail server uses this for world-server entry confirmation; the holtburger ref has no parse, ACE writer-side is `Pack`. Our consumer just skips 8 bytes for hashing.
[^t-h]: Servers do periodically EchoRequest to the client; we must mirror the 4-byte client-time as an `EchoResponse` per `FlowQueue::DequeueAck` semantics.
---
## Section 2 — Optional-header fields
In-scope: 12. Implemented in acdream: 12 of 12 sized-skip; 6 of 12 surface decoded fields. Phase M target delta: needs (a) builders for the ones we only parse, (b) ConnectRequest + EchoRequest builder paths for symmetric tests, (c) golden-vector test file.
| Code | Direction | Name | Named-retail symbol | Holtburger | ACE | acdream today | Phase M target | Notes |
|---|---|---|---|---|---|---|---|---|
| `0x100` | inbound | ServerSwitch (8 bytes) | `UCServerSwitchStruct` | P (skip) | PB | P (skip) | P (decode)+W | Decode `serverIp:u32, port:u16, pad:u16` [^o-a] |
| `0x1000` | inbound | RequestRetransmit (4+N\*4) | `FlowQueue::TransmitNaks` | PB | PB | P (parsed list) | PB+W | List stored; build path missing |
| `0x2000` | inbound | RejectRetransmit (4+N\*4) | `FlowQueue::CompileEmptyAcks` | P | PB | P (size-skip) | P (decode)+W | List currently consumed without storage |
| `0x4000` | both | AckSequence (4 bytes) | `FlowQueue::EnqueueAcks` | PB | PB | PB | PB | Stored as `AckSequence:u32` |
| `0x10000` | outbound | LoginRequest (rest of pkt) | `ClientNet::SendLoginRequest` | B | P (full) | B (via `LoginRequest.Build`) | B | Variable-length tail; raw bytes hashed |
| `0x20000` | inbound | WorldLoginRequest (8 bytes) | `CEmptyHeader<0x20000,1>` | | P (8B peek) | P (size-skip) | P (decode)+W | Decode purpose unknown, store raw |
| `0x40000` | inbound | ConnectRequest (32 bytes) | `CConnectHeader` | P+W | B (server) | P+W | PB | We need encode path for round-trip tests |
| `0x80000` | outbound | ConnectResponse (8 bytes) | | B | P (8B peek) | B | PB | Decode on inbound test fixtures |
| `0x400000` | inbound | CICMDCommand (8 bytes) | | P (skip) | P (8B) | P (size-skip) | defer:M3 | Decode + handler deferred |
| `0x1000000` | inbound | TimeSync (8 bytes) | `CTimeSyncHeader` | P+W | P+W | P+W | PB | Add build for symmetry; double LE |
| `0x2000000` | inbound | EchoRequest (4 bytes) | `CEchoRequestHeader` | P+W | P+W | P (no reply) | PB+W | Wire to `SendEchoResponse` builder |
| `0x8000000` | both | Flow (6 bytes) | `UCFlowStruct` | P (skip) | P+W | P (decode) | defer:M2 | `FlowBytes:u32, FlowInterval:u16` decoded |
**Footnotes:**
[^o-a]: ServerSwitch struct layout per retail `UCServerSwitchStruct` — confirmed via named-retail symbol `?CreateFromData@?$COnePrimHeader@$0BAA@$0GA@UCServerSwitchStruct@@@@`. M3 needs the IP/port to actually re-target the socket; today we'd silently drop traffic from a relocated server.
**Cross-cutting Phase M deliverables for sections 1+2:**
1. **Goldens fixture file**`tests/AcDream.Core.Net.Tests/Packets/PacketHeaderOptionalTests.cs` does not exist; only indirect coverage via `PacketCodecTests` and `ConnectRequestTests`. M needs one fixture per non-skip flag covering parse + build symmetry.
2. **Typed events** — currently the only `WorldSession`-side flag-driven event is `ServerTimeUpdated` (from `TimeSync`). Phase M target adds: `ServerSwitchRequested(ip, port)`, `ServerDisconnect(reason)`, `ServerNetError(NetErrorCode, message)`, `EchoRequested(clientTime)` (internal), `RetransmitRequested(seqs)`, `RetransmitRejected(seqs)`.
3. **`PacketHeaderOptional` storage gaps** — `RejectRetransmit` list is consumed but discarded; `WorldLoginRequest` 8-byte body is skipped; `CICMDCommand` 8-byte body is skipped; `ConnectResponse` 8-byte cookie is decoded only inside `Connect()`'s send path, not on inbound parse. M target: lift each into a typed property on `PacketHeaderOptional`.
4. **Builder-side parity**`PacketHeaderOptional.Parse` exists; there is no `PacketHeaderOptional.Build` — every outbound flag's body bytes are hand-rolled at the call site (`SendAck`, `Connect`, `Dispose`). Phase M should add a single `Build(PacketHeaderFlags, body fields)` to mirror parse.
---
## Section 3 — GameMessage opcodes (top-level)
In-scope: 51. Implemented in acdream: 21. Phase M target delta: 30.
| Code | Direction | Name | Named-retail symbol | Holtburger | ACE | acdream today | Phase M target | Notes |
|------|-----------|------|---------------------|------------|-----|---------------|----------------|-------|
| 0x0000 | both | None | | PB | N/A | | skip:heartbeat-only | Internal/heartbeat sentinel |
| 0x0024 | inbound | InventoryRemoveObject | | P | B | | P+W | Out of bubble or destroyed |
| 0x0197 | inbound | SetStackSize | | P | B | | P+W | Container stack size delta |
| 0x019E | inbound | PlayerKilled | | PB | B | P+W | P+W | victim+killer broadcast |
| 0x01E0 | inbound | EmoteText | `CM_Communication::DispatchUI_HearEmote` | PB | B | P+W | P+W | Server-driven 3rd-person emote |
| 0x01E2 | inbound | SoulEmote | `CM_Communication::DispatchUI_HearSoulEmote` | PB | B | P+W | P+W | Complex emote w/ animation |
| 0x02BB | inbound | HearSpeech | `ClientCommunicationSystem::Handle_Communication__HearSpeech` | PB | B | P+W | P+W | Local chat |
| 0x02BC | inbound | HearRangedSpeech | `ClientCommunicationSystem::Handle_Communication__HearRangedSpeech` | PB | B | P+W | P+W | Shouts; same parser as 0x02BB |
| 0x02CD | inbound | PrivateUpdatePropertyInt | `ClientObjMaintSystem::Handle_Qualities__PrivateUpdateInt` | PB | B | | P+W | Owner-only int property |
| 0x02CE | inbound | PublicUpdatePropertyInt | | PB | B | | P+W | Broadcast int property |
| 0x02CF | inbound | PrivateUpdatePropertyInt64 | | PB | B | | P+W | Owner-only int64 |
| 0x02D0 | inbound | PublicUpdatePropertyInt64 | | PB | B | | P+W | Broadcast int64 |
| 0x02D1 | inbound | PrivateUpdatePropertyBool | `ClientObjMaintSystem::Handle_Qualities__PrivateUpdateBool` | PB | B | | P+W | Owner-only bool |
| 0x02D2 | inbound | PublicUpdatePropertyBool | | PB | B | | P+W | Broadcast bool |
| 0x02D3 | inbound | PrivateUpdatePropertyFloat | `ClientObjMaintSystem::Handle_Qualities__PrivateUpdateFloat` | PB | B | | P+W | Owner-only float |
| 0x02D4 | inbound | PublicUpdatePropertyFloat | | PB | B | | P+W | Broadcast float |
| 0x02D5 | inbound | PrivateUpdatePropertyString | | PB | B | | P+W | Owner-only string |
| 0x02D6 | inbound | PublicUpdatePropertyString | | PB | B | | P+W | Broadcast string |
| 0x02D7 | inbound | PrivateUpdatePropertyDataID | | PB | B | | P+W | Owner-only DataID |
| 0x02D8 | inbound | PublicUpdatePropertyDataID | | PB | B | | P+W | Broadcast DataID |
| 0x02D9 | inbound | PrivateUpdatePropertyInstanceID | `CM_Qualities::DispatchUI_PrivateUpdateInstanceID` | PB | B | | P+W | Owner-only InstanceID |
| 0x02DA | inbound | PublicUpdateInstanceID | | PB | B | | P+W | Broadcast InstanceID |
| 0x02DB | inbound | PrivateUpdatePosition | `CM_Qualities::DispatchUI_PrivateUpdatePosition` | PB | B | | defer:F.x | Owner-only position; redundant with 0xF748 |
| 0x02DC | inbound | PublicUpdatePosition | | PB | B | | defer:F.x | Public position; redundant with 0xF748 |
| 0x02DD | inbound | PrivateUpdateSkill | | PB | B | | P+W | Owner-only skill XP |
| 0x02DE | inbound | PublicUpdateSkill | | PB | B | | P+W | Public skill |
| 0x02DF | inbound | PrivateUpdateSkillLevel | | PB | B | | P+W | Owner-only skill base level |
| 0x02E0 | inbound | PublicUpdateSkillLevel | | PB | B | | P+W | Public skill base level |
| 0x02E3 | inbound | PrivateUpdateAttribute | `ClientObjMaintSystem::Handle_Qualities__PrivateUpdateAttribute` | PB | B | | P+W | Strength/Stamina/etc base |
| 0x02E4 | inbound | PublicUpdateAttribute | | PB | B | | P+W | Public attribute |
| 0x02E7 | inbound | PrivateUpdateVital | | PB | B | P+W | P+W | Max HP/Stam/Mana — vitals panel |
| 0x02E8 | inbound | PublicUpdateVital | | PB | B | | P+W | Public vital |
| 0x02E9 | inbound | PrivateUpdateAttribute2ndLevel | `ClientObjMaintSystem::Handle_Qualities__PrivateUpdateAttribute2ndLevel` | PB [^m-1] | B | P+W | P+W | Current-only vital delta |
| 0xEA60 | inbound | AdminEnvirons | `CPlayerSystem::Handle_Admin__Environs` | | B | P+W | P+W | Fog presets / sound cues |
| 0xF625 | inbound | ObjDescEvent | `SmartBox::HandleObjDescEvent` | PB | B | P+W | P+W | Per-entity appearance update |
| 0xF643 | inbound | CharacterCreateResponse | | PB | B | | defer:char-creation | Char-creation flow not yet built |
| 0xF653 | outbound | CharacterLogOff | | PB | P | B | PB+W | Sent on Dispose; ACE accepts |
| 0xF655 | both | CharacterDelete | | PB | P | | defer:char-mgmt | Char-management UI deferred |
| 0xF656 | outbound | CharacterCreate | | PB | P | | defer:char-creation | Char-creation flow not yet built |
| 0xF657 | outbound | CharacterEnterWorld | `CM_Login::SendNotice_BeginEnterWorld` [^m-2] | PB | P | B | PB+W | Built; sent during handshake |
| 0xF658 | inbound | CharacterList | `CPlayerSystem::Handle_Login__CharacterSet` | PB | B | P+W | P+W | Login char picker |
| 0xF659 | inbound | CharacterError | `CPlayerSystem::Handle_CharacterError` | PB | B | | P+W | Login/restore failures |
| 0xF6EA | both | ForceObjectDescSend | | PB | P | | defer:F.x | Server requests client re-send ObjDesc; rare |
| 0xF745 | inbound | CreateObject (ObjectCreate) | `SmartBox::HandleCreateObject` | PB | B | P+W | P+W | Spawn entity in bubble |
| 0xF746 | inbound | PlayerCreate | `SmartBox::HandleCreatePlayer` | PB | B | P+W [^m-3] | P+W | Triggers LoginComplete |
| 0xF747 | inbound | DeleteObject (ObjectDelete) | `SmartBox::HandleDeleteObject` | PB | B | P+W | P+W | Despawn |
| 0xF748 | inbound | UpdatePosition | `CM_Qualities::DispatchUI_UpdatePosition` | PB | B | P+W | P+W | Periodic position sync |
| 0xF749 | inbound | ParentEvent | `SmartBox::HandleParentEvent` | PB | B | | P+W | Equip/wield parent change |
| 0xF74A | inbound | PickupEvent | `SmartBox::HandlePickupEvent` | PB | B | | P+W | Pickup confirmation |
| 0xF74B | inbound | SetState | `SmartBox::HandleSetState` | PB | B | | P+W | Door open/close, container state |
| 0xF74C | inbound | UpdateMotion (Motion) | | PB | B | P+W | P+W | Animation cycle change |
| 0xF74E | inbound | VectorUpdate | `SmartBox::HandleVectorUpdate` | PB | B | P+W | P+W | Remote jump velocity, missile arc |
| 0xF750 | inbound | Sound | `SmartBox::HandleSoundEvent` | PB | B | | P+W | Positional sound trigger |
| 0xF751 | inbound | PlayerTeleport | `SmartBox::HandlePlayerTeleport` | PB | B | P+W | P+W | Portal/teleport screen |
| 0xF752 | inbound | AutonomyLevel | `CommandInterpreter::SetAutonomyLevel` | P [^m-4] | | | P+W | Server tells client physics-trust level |
| 0xF753 | both | AutonomousPosition | `CM_Movement::Event_AutonomousPosition` | PB | | B | PB+W | Outbound built; inbound parser missing |
| 0xF754 | inbound | PlayScript (PlayScriptId) | `SmartBox::HandlePlayScriptID` | | | P+W [^m-5] | P+W | Inline parser; lightning, spell FX, emotes |
| 0xF755 | inbound | PlayEffect | | PB | B | | P+W | Particle/visual scripts; ACE uses for PlayScript wrapper |
| 0xF7B0 | inbound | GameEvent (envelope) | | PB | B | P+W | P+W | Envelope for sub-opcodes (see §4) |
| 0xF7B1 | outbound | GameAction (envelope) | | PB | P | B+W | PB+W | Envelope for sub-opcodes (see §5) |
| 0xF7C1 | inbound | AccountBanned | | | B | | defer:F.x | ACE-only, rarely seen |
| 0xF7C8 | outbound | CharacterEnterWorldRequest | | PB | P | B | PB+W | Built; sent before 0xF657 |
| 0xF7CC | both | GetServerVersion | `Proto_UI::SendAdminGetServerVersion` | | P | | defer:F.x | Admin-only |
| 0xF7CD | both | FriendsOld | | | P | | defer:F.x | Obsolete; ACE drops it |
| 0xF7D9 | outbound | CharacterRestore | | PB | P | | defer:char-mgmt | Char-management UI deferred |
| 0xF7DB | inbound | UpdateObject | `SmartBox::HandleUpdateObject` | PB | B | | P+W | Heavy re-send of object visual+physics |
| 0xF7DC | inbound | AccountBoot | `CPlayerSystem::Handle_AccountBooted` | PB | B | | P+W | Kicked from server |
| 0xF7DE | both | TurbineChat | `CCommunicationSystem::IsUsingTurbineChat` | PB | PB [^m-6] | PB+W | PB+W | Global community chat |
| 0xF7DF | inbound | CharacterEnterWorldServerReady | | P [^m-7] | B | P+W [^m-8] | P+W | Handshake gate during enter-world |
| 0xF7E0 | inbound | ServerMessage | | PB | B | P+W | P+W | System message / announcements |
| 0xF7E1 | inbound | ServerName | `ECM_Login::SendNotice_WorldName` | PB | B | | P+W | Shard name during login |
| 0xF7E2 | both | DDD_DataMessage | | | | | defer:dat-streaming | DDD download channel (we ship dats locally) |
| 0xF7E3 | both | DDD_RequestDataMessage | | | P | | defer:dat-streaming | Client requests dat data |
| 0xF7E4 | both | DDD_ErrorMessage | | | | | defer:dat-streaming | DDD error channel |
| 0xF7E5 | inbound | DDD_Interrogation | `DDD_InterrogationMessage::Serialize` | PB [^m-9] | B | P+W | P+W | Server asks "what dat versions?" |
| 0xF7E6 | outbound | DDD_InterrogationResponse | | PB | P | B | PB+W | Built; sent in response to 0xF7E5 |
| 0xF7E7 | both | DDD_BeginDDD | | | | | defer:dat-streaming | DDD start |
| 0xF7E8 | both | DDD_BeginPullDDD | | | | | defer:dat-streaming | DDD pull start |
| 0xF7E9 | both | DDD_IterationData | | | | | defer:dat-streaming | DDD chunk iteration |
| 0xF7EA | inbound | DDD_EndDDD | | | P | | defer:dat-streaming | DDD end signal |
**Footnotes:**
[^m-1]: ACE calls 0x02E9 `PrivateUpdateAttribute2ndLevel`; holtburger calls it `PrivateUpdateVitalCurrent` (current-only delta).
[^m-2]: Retail-side trigger of the enter-world flow; the wire opcode 0xF657 is constructed from the request.
[^m-3]: PlayerCreate fires LoginComplete when guid matches own char; CreateObject body is parsed for the player too.
[^m-4]: AutonomyLevel is in holtburger's `GameMessage` enum + unpack/pack, but its enum value (0xF752) is mapped via opcode dispatch.
[^m-5]: 0xF754 PlayScript is parsed inline in `WorldSession.cs:850` (no dedicated `Messages/PlayScript.cs`); routed to `PlayScriptReceived` event for VFX runtime.
[^m-6]: ACE handles inbound TurbineChat via `TurbineChatHandler` and emits outbound via `GameMessageTurbineChat`, hence both directions.
[^m-7]: CharacterEnterWorldServerReady is unit variant in holtburger (no payload); only an opcode marker.
[^m-8]: acdream uses 0xF7DF as a handshake gate (`WorldSession.cs:495`), no dedicated parser file.
[^m-9]: DddInterrogation in holtburger is a unit variant — opcode marker only, no payload to parse.
**Caveats and unknowns:**
- `0xF7C1 AccountBanned` is in ACE's enum + has a `GameMessageAccountBanned.cs`, but holtburger has it commented out. Marked `defer` since the channel exists in retail but rarely fires.
- `0xF7CC GetServerVersion`, `0xF7CD FriendsOld`: ACE has handlers for them (i.e. accepts them inbound from a client that sends them), but no acdream sends them today. Listed as `defer`.
- `0xF619 PositionAndMovement`: holtburger documents this as a "ghost" opcode (defined but never emitted by ACE/retail). Excluded from the table — confirmed dead code per holtburger comment + grep on ACE shows no `Writer.Write` site.
- `0xF754 PlayScriptId` vs `0xF755 PlayEffect`: ACE has the `Script.cs` GameMessage tagged with `PlayEffect (0xF755)`, while retail's `SmartBox::HandlePlayScriptID` is the 0xF754 handler. acdream's inline parser at `WorldSession.cs:850` reads `[u32 opcode][u32 guid][u32 scriptId]` matching the 0xF754 layout.
---
## Section 4 — GameEvent sub-opcodes (inside 0xF7B0 envelope)
In-scope: 103. Implemented (parsed) in acdream today: 27. Wired (`W`) in acdream today: 26. Phase M target delta: 76 new parsers + ~50 deferred to later phases.
All rows are `inbound` direction (GameEvents are server→client only).
| Code | Direction | Name | Named-retail symbol | Holtburger | ACE | acdream today | Phase M target | Notes |
|---|---|---|---|---|---|---|---|---|
| 0x0003 | inbound | AllegianceUpdateAborted | `ClientAllegianceSystem::Handle_Allegiance__AllegianceUpdateAborted` | | W | | defer:Allegiance | scope deferred — no allegiance UI yet |
| 0x0004 | inbound | PopupString | `ClientCommunicationSystem::Handle_Communication__PopUpString` | W | W | W | W | modal text → ChatLog.OnPopup |
| 0x0013 | inbound | PlayerDescription | `CPlayerSystem::Handle_PlayerDescription` | W | W | W | W | full local-player snapshot at login [^e-a] |
| 0x0020 | inbound | AllegianceUpdate | `ClientAllegianceSystem::Handle_Allegiance__AllegianceUpdate` | | W | | defer:Allegiance | needs CAllegianceProfile parser |
| 0x0021 | inbound | FriendsListUpdate | `CM_Social::SendNotice_UpdateFriendsList` | | W | | P+W | FriendDataList; small parser, high UX value |
| 0x0022 | inbound | InventoryPutObjInContainer | (CM_Inventory) | W | W | W | W | (item, container, slot) — items.MoveItem |
| 0x0023 | inbound | WieldObject | (CM_Inventory) | W | W | W | W | server-driven equip |
| 0x0029 | inbound | CharacterTitle | `CM_Social::SendNotice_AddCharacterTitle` | | W | | defer:Social | gmCharacterTitleUI |
| 0x002B | inbound | UpdateTitle | `CM_Social::SendNotice_SetDisplayCharacterTitle` | | W | | defer:Social | titles UI not yet built |
| 0x0052 | inbound | CloseGroundContainer | (gmInventoryUI) | W | W | P | P+W | parser exists, needs ItemRepository wiring |
| 0x0062 | inbound | ApproachVendor | (CM_Vendor) | W | W | | defer:VendorPanel | needs VendorProfile + ItemProfile list parser |
| 0x0075 | inbound | StartBarber | `ClientUISystem::Handle_Character__StartBarber` | | W | | defer:Barber | gmBarberUI not yet built |
| 0x00A0 | inbound | InventoryServerSaveFailed | (CM_Inventory) | W | W | P | P+W | parser exists; needs revert hook |
| 0x00A3 | inbound | FellowshipQuit | `ClientFellowshipSystem::Handle_Fellowship__Quit` | W | W | | defer:Fellowship | scope deferred — no fellowship state |
| 0x00A4 | inbound | FellowshipDismiss | `ClientFellowshipSystem::Handle_Fellowship__Dismiss` | W | W | | defer:Fellowship | scope deferred |
| 0x00B4 | inbound | BookDataResponse | `CM_Writing::Event_BookData` | W | W | | defer:Books | gmBookUI not yet built |
| 0x00B5 | inbound | BookModifyPageResponse | `CM_Writing::Event_BookModifyPage` | | W | | defer:Books | |
| 0x00B6 | inbound | BookAddPageResponse | `CM_Writing::SendNotice_BookAddPageResponse` | | W | | defer:Books | |
| 0x00B7 | inbound | BookDeletePageResponse | `CM_Writing::SendNotice_BookDeletePageResponse` | | W | | defer:Books | |
| 0x00B8 | inbound | BookPageDataResponse | `CM_Writing::SendNotice_BookPageDataResponse` | W | W | | defer:Books | |
| 0x00C3 | inbound | GetInscriptionResponse | | | W | | defer:Books | inscription on caster items |
| 0x00C9 | inbound | IdentifyObjectResponse | `ClientUISystem::Handle_Item__AppraiseDone` [^e-b] | W | W | W | W | AppraiseInfoParser feeds ItemRepository |
| 0x0147 | inbound | ChannelBroadcast | `ClientCommunicationSystem::Handle_Communication__ChannelBroadcast` | W | W | W | W | (channelId, sender, msg) → ChatLog |
| 0x0148 | inbound | ChannelList | `ClientCommunicationSystem::Handle_Communication__ChannelList` | | W | | P+W | PackableList<PStringBase>; admin/list response |
| 0x0149 | inbound | ChannelIndex | `ClientCommunicationSystem::Handle_Communication__ChannelIndex` | | W | | P+W | PackableList<PStringBase> |
| 0x0196 | inbound | ViewContents | `ClientUISystem::OnViewContents` | W | W | | P+W | server view of remote container — needed for sidepacks |
| 0x019A | inbound | InventoryPutObjectIn3D | (CM_Inventory) | W | W | P | P+W | parser exists; needs spawn-into-world wiring |
| 0x01A7 | inbound | AttackDone | | W | W | W | W | combat seq complete |
| 0x01A8 | inbound | MagicRemoveSpell | `ClientMagicSystem::Handle_Magic__RemoveSpell` | W | W | W | W | spell removed from spellbook |
| 0x01AC | inbound | VictimNotification | `ClientCombatSystem::HandleVictimNotificationEvent` | W | W | W | W | death msg for victim |
| 0x01AD | inbound | KillerNotification | `ClientCombatSystem::HandleKillerNotificationEvent` | W | W | W | W | death msg for killer |
| 0x01B1 | inbound | AttackerNotification | `ClientCombatSystem::HandleAttackerNotificationEvent` | W | W | W | W | "you hit X" |
| 0x01B2 | inbound | DefenderNotification | `ClientCombatSystem::HandleDefenderNotificationEvent` | W | W | W | W | "X hit you" |
| 0x01B3 | inbound | EvasionAttackerNotification | `ClientCombatSystem::HandleEvasionAttackerNotificationEvent` | W | W | W | W | "X evaded" |
| 0x01B4 | inbound | EvasionDefenderNotification | `ClientCombatSystem::HandleEvasionDefenderNotificationEvent` | W | W | W | W | "you evaded X" |
| 0x01B8 | inbound | CombatCommenceAttack | | W | W | W | W | empty payload |
| 0x01C0 | inbound | UpdateHealth | `CM_Combat::SendNotice_UpdateObjectHealth` | W | W | W | W | (guid, healthPct) → CombatState |
| 0x01C3 | inbound | QueryAgeResponse | `ClientCommunicationSystem::Handle_Character__QueryAgeResponse` | | W | | P | small string parser; chat panel display |
| 0x01C7 | inbound | UseDone | `ClientUISystem::Handle_Item__UseDone` | W | W | P | P+W | parser exists; needs InteractionState wiring |
| 0x01C8 | inbound | AllegianceUpdateDone | | | W | | defer:Allegiance | |
| 0x01C9 | inbound | FellowshipFellowUpdateDone | `ClientFellowshipSystem::Handle_Fellowship__FellowUpdateDone` | W | W | | defer:Fellowship | empty payload |
| 0x01CA | inbound | FellowshipFellowStatsDone | `ClientFellowshipSystem::Handle_Fellowship__FellowStatsDone` | W | W | | defer:Fellowship | empty payload |
| 0x01CB | inbound | ItemAppraiseDone | `ClientUISystem::Handle_Item__AppraiseDone` | | W | | P | post-IdentifyObjectResponse signal |
| 0x01E2 | inbound | Emote | `ClientCommunicationSystem::Handle_Communication__HearEmote` [^e-c] | | W | | P | "*X waves*" — chat broadcast |
| 0x01EA | inbound | PingResponse | `ClientUISystem::Handle_Character__ReturnPing` | W | W | P | P+W | parser exists; needs latency/heartbeat wiring |
| 0x01F4 | inbound | SetSquelchDB | `ClientCommunicationSystem::Handle_Communication__SetSquelchDB` | | W | | defer:SquelchUI | SquelchDB blob; ignore-list state |
| 0x01FD | inbound | RegisterTrade | `ClientTradeSystem::Handle_Trade__Recv_RegisterTrade` | W | W | | defer:TradePanel | (guid, accepterGuid, ackTimer) |
| 0x01FE | inbound | OpenTrade | `ClientTradeSystem::Handle_Trade__Recv_OpenTrade` | W | W | | defer:TradePanel | initiator guid |
| 0x01FF | inbound | CloseTrade | `ClientTradeSystem::Handle_Trade__Recv_CloseTrade` | W | W | | defer:TradePanel | closer guid |
| 0x0200 | inbound | AddToTrade | `ClientTradeSystem::Handle_Trade__Recv_AddToTrade` | W | W | P | defer:TradePanel | parser exists; needs TradeState |
| 0x0201 | inbound | RemoveFromTrade | `ClientTradeSystem::Handle_Trade__Recv_RemoveFromTrade` | | W | | defer:TradePanel | (initiatorGuid, itemGuid) |
| 0x0202 | inbound | AcceptTrade | `ClientTradeSystem::Handle_Trade__Recv_AcceptTrade` | W | W | P | defer:TradePanel | parser exists |
| 0x0203 | inbound | DeclineTrade | `ClientTradeSystem::Handle_Trade__Recv_DeclineTrade` | W | W | | defer:TradePanel | initiator guid |
| 0x0205 | inbound | ResetTrade | `ClientTradeSystem::Handle_Trade__Recv_ResetTrade` | W | W | | defer:TradePanel | reset to-trade list |
| 0x0207 | inbound | TradeFailure | `ClientTradeSystem::Handle_Trade__Recv_TradeFailure` | W | W | P | defer:TradePanel | parser exists |
| 0x0208 | inbound | ClearTradeAcceptance | `ClientTradeSystem::Handle_Trade__Recv_ClearTradeAcceptance` | W | W | | defer:TradePanel | empty payload |
| 0x021D | inbound | HouseProfile | `ClientHousingSystem::Handle_House__Recv_HouseProfile` | | W | | defer:Housing | HouseProfile blob |
| 0x0225 | inbound | HouseData | `ClientHousingSystem::Handle_House__Recv_HouseData` | | W | | defer:Housing | HouseData blob |
| 0x0226 | inbound | HouseStatus | `ClientHousingSystem::Handle_House__Recv_HouseStatus` | | W | | defer:Housing | scalar status code |
| 0x0227 | inbound | UpdateRentTime | `ClientHousingSystem::Handle_House__Recv_UpdateRentTime` | | W | | defer:Housing | i32 timestamp |
| 0x0228 | inbound | UpdateRentPayment | `ClientHousingSystem::Handle_House__Recv_UpdateRentPayment` | | W | | defer:Housing | HousePaymentList |
| 0x0248 | inbound | HouseUpdateRestrictions | `ClientHousingSystem::Handle_House__Recv_UpdateRestrictions` | | W | | defer:Housing | RestrictionDB blob |
| 0x0257 | inbound | UpdateHAR | `ClientHousingSystem::Handle_House__Recv_UpdateHAR` | | W | | defer:Housing | HAR blob |
| 0x0259 | inbound | HouseTransaction | `ClientHousingSystem::Handle_House__Recv_HouseTransaction` | | W | | defer:Housing | scalar txn code |
| 0x0264 | inbound | QueryItemManaResponse | `ClientUISystem::Handle_Item__QueryItemManaResponse` | W | W | P | P+W | parser exists; needs ItemRepository wiring |
| 0x0271 | inbound | AvailableHouses | `ClientHousingSystem::Handle_House__Recv_AvailableHouses` | | W | | defer:Housing | PackableList<u32> + flag |
| 0x0274 | inbound | CharacterConfirmationRequest | `ClientUISystem::Handle_Character__ConfirmationRequest` | W | W | P | P+W | parser exists; needs modal-confirm wiring |
| 0x0276 | inbound | CharacterConfirmationDone | `ClientUISystem::Handle_Character__ConfirmationDone` | W | W | | P+W | (type, contextId); confirms client ACK |
| 0x027A | inbound | AllegianceLoginNotification | `ClientAllegianceSystem::Handle_Allegiance__AllegianceLoginNotificationEvent` | | W | | defer:Allegiance | (guid, login/logout flag) |
| 0x027C | inbound | AllegianceInfoResponse | `ClientAllegianceSystem::Handle_Allegiance__AllegianceInfoResponseEvent` | | W | | defer:Allegiance | CAllegianceProfile |
| 0x0281 | inbound | JoinGameResponse | `ClientMiniGameSystem::Handle_Game__Recv_JoinGameResponse` | | W | | defer:MiniGame | chess/dice/etc — minimal value |
| 0x0282 | inbound | StartGame | `ClientMiniGameSystem::Handle_Game__Recv_StartGame` | W | W | | defer:MiniGame | empty payload |
| 0x0283 | inbound | MoveResponse | `ClientMiniGameSystem::Handle_Game__Recv_MoveResponse` | | W | | defer:MiniGame | minigame move ack |
| 0x0284 | inbound | OpponentTurn | `ClientMiniGameSystem::Handle_Game__Recv_OpponentTurn` | | W | | defer:MiniGame | GameMoveData blob |
| 0x0285 | inbound | OpponentStalemate | `ClientMiniGameSystem::Handle_Game__Recv_OppenentStalemateState` | | W | | defer:MiniGame | typo preserved (retail name) |
| 0x028A | inbound | WeenieError | `ClientCommunicationSystem::Handle_Communication__WeenieError` | W | W | W | W | error code → ChatLog.OnWeenieError |
| 0x028B | inbound | WeenieErrorWithString | `ClientCommunicationSystem::Handle_Communication__WeenieErrorWithString` | W | W | W | W | (code, interp) → ChatLog |
| 0x028C | inbound | GameOver | `ClientMiniGameSystem::Handle_Game__Recv_GameOver` | | W | | defer:MiniGame | (gameId, winner) |
| 0x0295 | inbound | SetTurbineChatChannels | `ClientCommunicationSystem::Handle_Communication__Recv_ChatRoomTracker` [^e-d] | W | W | W | W | per-room ids → TurbineChatState |
| 0x02AE | inbound | AdminQueryPluginList | (admin tooling) | | W | | skip:admin-only | server-admin path; not retail-emitted to player |
| 0x02B1 | inbound | AdminQueryPlugin | | | W | | skip:admin-only | |
| 0x02B3 | inbound | AdminQueryPluginResponse | | | W | | skip:admin-only | |
| 0x02B4 | inbound | SalvageOperationsResult | `ClientUISystem::Handle_Inventory__Recv_SalvageOperationsResultData` | | W | | defer:SalvageUI | SalvageOperationsResultData blob |
| 0x02BD | inbound | Tell | (CM_Communication) | W | W | W | W | direct whisper → ChatLog |
| 0x02BE | inbound | FellowshipFullUpdate | `ClientFellowshipSystem::Handle_Fellowship__FullUpdate` | W | W | | defer:Fellowship | CFellowship blob |
| 0x02BF | inbound | FellowshipDisband | `ClientFellowshipSystem::Handle_Fellowship__Disband` | W | W | | defer:Fellowship | empty payload |
| 0x02C0 | inbound | FellowshipUpdateFellow | `ClientFellowshipSystem::Handle_Fellowship__UpdateFellow` | W | W | | defer:Fellowship | (memberGuid, Fellow, flag) |
| 0x02C1 | inbound | MagicUpdateSpell | `ClientMagicSystem::Handle_Magic__UpdateSpell` | W | W | W | W | learned spellId → Spellbook |
| 0x02C2 | inbound | MagicUpdateEnchantment | `ClientMagicSystem::Handle_Magic__UpdateEnchantment` | W | W | W | W | Enchantment blob → Spellbook |
| 0x02C3 | inbound | MagicRemoveEnchantment | `ClientMagicSystem::Handle_Magic__RemoveEnchantment` | W | W | W | W | (layerId, spellId) |
| 0x02C4 | inbound | MagicUpdateMultipleEnchantments | `ClientMagicSystem::Handle_Magic__UpdateMultipleEnchantments` | W | W | | P+W | PackableList<Enchantment> |
| 0x02C5 | inbound | MagicRemoveMultipleEnchantments | `ClientMagicSystem::Handle_Magic__RemoveMultipleEnchantments` | W | W | | P+W | PackableList<u32> |
| 0x02C6 | inbound | MagicPurgeEnchantments | `ClientMagicSystem::Handle_Magic__PurgeEnchantments` | W | W | W | W | empty payload → Spellbook.OnPurgeAll |
| 0x02C7 | inbound | MagicDispelEnchantment | `ClientMagicSystem::Handle_Magic__DispelEnchantment` | W | W | W | W | shared parser w/ MagicRemoveEnchantment |
| 0x02C8 | inbound | MagicDispelMultipleEnchantments | `ClientMagicSystem::Handle_Magic__DispelMultipleEnchantments` | W | W | | P+W | PackableList<u32> |
| 0x02C9 | inbound | PortalStormBrewing | `ClientUISystem::Handle_Misc__PortalStormBrewing` | | W | | P+W | float intensity → ChatLog system message |
| 0x02CA | inbound | PortalStormImminent | `ClientUISystem::Handle_Misc__PortalStormImminent` | | W | | P+W | float intensity |
| 0x02CB | inbound | PortalStorm | `ClientUISystem::Handle_Misc__PortalStorm` | | W | | P+W | empty payload — actual storm trigger |
| 0x02CC | inbound | PortalStormSubsided | `ClientUISystem::Handle_Misc__PortalStormSubsided` | | W | | P+W | empty payload |
| 0x02EB | inbound | CommunicationTransientString | `ClientCommunicationSystem::Handle_Communication__TransientString` | W | W | W | W | (msg, chatType) → ChatLog system msg |
| 0x0312 | inbound | MagicPurgeBadEnchantments | `ClientMagicSystem::Handle_Magic__PurgeBadEnchantments` | W | W | | P+W | empty payload |
| 0x0314 | inbound | SendClientContractTrackerTable | `ClientUISystem::Handle_Social__SendClientContractTrackerTable` | | W | | defer:Quests | CContractTrackerTable blob |
| 0x0315 | inbound | SendClientContractTracker | `ClientUISystem::Handle_Social__SendClientContractTracker` | | W | | defer:Quests | (CContractTracker, flag, flag) |
**Footnotes:**
[^e-a]: PlayerDescription has its own dedicated parser (`PlayerDescriptionParser.TryParse`) rather than living in `GameEvents.cs`. Wires into `LocalPlayerState` (vitals 7/8/9), `Spellbook` (learned spells + enchantments), `ItemRepository` (inventory + equipped), and the `onSkillsUpdated` callback (Run/Jump skills for movement).
[^e-b]: IdentifyObjectResponse uses `AppraiseInfoParser.TryParse` (separate file) rather than the simple header-only parser in `GameEvents.cs`. Returns full property bundle (int / int64 / bool / float / string / DID tables) plus SpellBook list. The retail handler `Handle_Item__AppraiseDone` (0x01CB) is the post-arrival completion signal, not the data carrier itself.
[^e-c]: 0x01E2 Emote sub-opcode is distinct from `HearEmote` (top-level GameMessage 0x02BC); the sub-opcode form is documented in ACE's `GameEventType.cs` but the named-retail decomp doesn't expose a dedicated handler — likely re-routed through the chat broadcast path.
[^e-d]: Named retail's `Recv_ChatRoomTracker` is the underlying handler symbol; ACE/Holtburger renamed to `SetTurbineChatChannels` for clarity. Same wire payload (per-room session ids for General/Trade/LFG/Roleplay/Society/Olthoi/Allegiance).
---
## Section 5 — GameAction sub-opcodes (inside 0xF7B1 envelope)
In-scope: 96. Implemented (built) in acdream: 24. Live callers in acdream: 8. Phase M target delta: 72 new builders + golden-vector tests.
All rows are `outbound` direction (GameActions are client→server only).
| Code | Direction | Name | Named-retail symbol | Holtburger | ACE | acdream today | Phase M target | Notes |
|------|-----------|------|---------------------|------------|-----|---------------|----------------|-------|
| 0x0005 | outbound | SetSingleCharacterOption | | W | H | | B | Per-option toggle; sibling of 0x01A1 bitmap |
| 0x0008 | outbound | TargetedMeleeAttack | `CM_Combat::Event_TargetedMeleeAttack` | W | H | W | B+W | Wired in WorldSession.SendMeleeAttack |
| 0x000A | outbound | TargetedMissileAttack | `CM_Combat::Event_TargetedMissileAttack` | W | H | W | B+W | Wired in WorldSession.SendMissileAttack |
| 0x000F | outbound | SetAfkMode | `CM_Communication::Event_SetAFKMode` | | H | | B | Toggle AFK |
| 0x0010 | outbound | SetAfkMessage | `CM_Communication::Event_SetAFKMessage` | | H | | B | Custom AFK string |
| 0x0015 | outbound | Talk | `CM_Communication::Event_Talk` | W | H | W | B+W | Wired in WorldSession.SendTalk |
| 0x0017 | outbound | RemoveFriend | `CM_Social::Event_RemoveFriend` | | H | | B | Friends list mutation |
| 0x0018 | outbound | AddFriend | `CM_Social::Event_AddFriend` | | H | | B | Friends list mutation |
| 0x0019 | outbound | PutItemInContainer | `CM_Inventory::Event_PutItemInContainer` | W | H | | B | Inventory move; high priority |
| 0x001A | outbound | GetAndWieldItem | `CM_Inventory::Event_GetAndWieldItem` | W | H | | B | Equip item |
| 0x001B | outbound | DropItem | `CM_Inventory::Event_DropItem` | W | H | | B | Drop to ground |
| 0x001D | outbound | SwearAllegiance | `CM_Allegiance::Event_SwearAllegiance` | W | H | B | B+W | AllegianceRequests dead [^a-1] |
| 0x001E | outbound | BreakAllegiance | `CM_Allegiance::Event_BreakAllegiance` | W | H | B | B+W | AllegianceRequests dead [^a-1] |
| 0x001F | outbound | AllegianceUpdateRequest | | | H | | B | Refresh allegiance tree |
| 0x0025 | outbound | RemoveAllFriends | | | H | | B | Clear friends list |
| 0x0026 | outbound | TeleToPklArena | | W | H | | B | PK-lite arena recall |
| 0x0027 | outbound | TeleToPkArena | | | H | | B | PK arena recall |
| 0x002C | outbound | TitleSet | | | H | | B | Equip title |
| 0x0030 | outbound | QueryAllegianceName | `CM_Allegiance::Event_QueryAllegianceName` | | H | | B | |
| 0x0031 | outbound | ClearAllegianceName | `CM_Allegiance::Event_ClearAllegianceName` | | H | | B | Officer-only |
| 0x0032 | outbound | TalkDirect | `CM_Communication::Event_TalkDirect` | | H | | B | Targeted /say (rarely used) |
| 0x0033 | outbound | SetAllegianceName | `CM_Allegiance::Event_SetAllegianceName` | | H | | B | Monarch-only |
| 0x0035 | outbound | UseWithTarget | `CM_Inventory::Event_UseWithTargetEvent` | W | H | B | B+W | InteractRequests dead [^a-1] |
| 0x0036 | outbound | Use | `CM_Inventory::Event_UseEvent` | W | H | B | B+W | InteractRequests dead [^a-1] |
| 0x003B | outbound | SetAllegianceOfficer | `CM_Allegiance::Event_SetAllegianceOfficer` | | H | | B | |
| 0x003C | outbound | SetAllegianceOfficerTitle | `CM_Allegiance::Event_SetAllegianceOfficerTitle` | | H | | B | |
| 0x003D | outbound | ListAllegianceOfficerTitles | `CM_Allegiance::Event_ListAllegianceOfficerTitles` | | H | | B | |
| 0x003E | outbound | ClearAllegianceOfficerTitles | `CM_Allegiance::Event_ClearAllegianceOfficerTitles` | | H | | B | |
| 0x003F | outbound | DoAllegianceLockAction | `CM_Allegiance::Event_DoAllegianceLockAction` | | H | | B | Lock recruitment |
| 0x0040 | outbound | SetAllegianceApprovedVassal | `CM_Allegiance::Event_SetAllegianceApprovedVassal` | | | | B | |
| 0x0041 | outbound | AllegianceChatGag | `CM_Allegiance::Event_AllegianceChatGag` | | H | | B | |
| 0x0042 | outbound | DoAllegianceHouseAction | `CM_Allegiance::Event_DoAllegianceHouseAction` | | H | | B | |
| 0x0044 | outbound | RaiseVital | | W | H | B | B+W | CharacterActions builder dead [^a-1] |
| 0x0045 | outbound | RaiseAttribute | | W | H | B | B+W | CharacterActions builder dead [^a-1] |
| 0x0046 | outbound | RaiseSkill | | W | H | B | B+W | CharacterActions builder dead [^a-1] |
| 0x0047 | outbound | TrainSkill | `CM_Train::Event_TrainSkill` | W | H | B | B+W | CharacterActions builder dead [^a-1] |
| 0x0048 | outbound | CastUntargetedSpell | `CM_Magic::Event_CastUntargetedSpell` | W | H | B | B+W | CastSpellRequest builder dead [^a-1] |
| 0x004A | outbound | CastTargetedSpell | `CM_Magic::Event_CastTargetedSpell` | W | H | B | B+W | CastSpellRequest builder dead [^a-1] |
| 0x0053 | outbound | ChangeCombatMode | `CM_Combat::Event_ChangeCombatMode` | W | H | W | B+W | Wired in WorldSession.SendChangeCombatMode |
| 0x0054 | outbound | StackableMerge | `CM_Inventory::Event_StackableMerge` | W | H | B | B+W | InventoryActions builder dead [^a-1] |
| 0x0055 | outbound | StackableSplitToContainer | `CM_Inventory::Event_StackableSplitToContainer` | W | H | B | B+W | InventoryActions builder dead [^a-1] |
| 0x0056 | outbound | StackableSplitTo3D | `CM_Inventory::Event_StackableSplitTo3D` | | H | B | B+W | InventoryActions builder dead [^a-1] |
| 0x0058 | outbound | ModifyCharacterSquelch | `CM_Communication::Event_ModifyCharacterSquelch` | | H | | B | Mute one player |
| 0x0059 | outbound | ModifyAccountSquelch | `CM_Communication::Event_ModifyAccountSquelch` | | H | | B | Mute account |
| 0x005B | outbound | ModifyGlobalSquelch | `CM_Communication::Event_ModifyGlobalSquelch` | | H | | B | Mute pattern |
| 0x005D | outbound | Tell | | W | H | W | B+W | Wired in WorldSession.SendTell [^a-2] |
| 0x005F | outbound | Buy | `CM_Vendor::Event_Buy` | W | H | | B | Vendor purchase |
| 0x0060 | outbound | Sell | `CM_Vendor::Event_Sell` | W | H | | B | Vendor sell |
| 0x0063 | outbound | TeleToLifestone | `CM_Character::Event_TeleToLifestone` | W | H | B | B+W | InteractRequests builder dead [^a-1] |
| 0x00A1 | outbound | LoginComplete | `CM_Character::Event_LoginCompleteNotification` | W | H | W | B+W | Wired in GameWindow.cs:4423 |
| 0x00A2 | outbound | FellowshipCreate | `CM_Fellowship::Event_Create` | W | H | B | B+W | SocialActions builder dead [^a-1] |
| 0x00A3 | outbound | FellowshipQuit | `CM_Fellowship::Event_Quit` | W | H | B | B+W | SocialActions builder dead [^a-1] |
| 0x00A4 | outbound | FellowshipDismiss | `CM_Fellowship::Event_Dismiss` | W | H | B | B+W | SocialActions builder dead [^a-1] |
| 0x00A5 | outbound | FellowshipRecruit | `CM_Fellowship::Event_Recruit` | W | H | B | B+W | SocialActions builder dead [^a-1] |
| 0x00A6 | outbound | FellowshipUpdateRequest | `CM_Fellowship::Event_UpdateRequest` | W | H | B | B+W | SocialActions builder dead [^a-1] |
| 0x00AA | outbound | BookData | `CM_Writing::Event_BookData` | | H | | B | Open book contents |
| 0x00AB | outbound | BookModifyPage | `CM_Writing::Event_BookModifyPage` | | H | | B | Edit page text |
| 0x00AC | outbound | BookAddPage | `CM_Writing::Event_BookAddPage` | | H | | B | |
| 0x00AD | outbound | BookDeletePage | `CM_Writing::Event_BookDeletePage` | | H | | B | |
| 0x00AE | outbound | BookPageData | `CM_Writing::Event_BookPageData` | W | | | B | Read one page |
| 0x00B1 | outbound | TeleToPoi | | | | B | B | InventoryActions builder dead; ACE handler unclear [^a-1][^a-3] |
| 0x00BF | outbound | SetInscription | `CM_Writing::Event_SetInscription` | | | | B | Inscribe item |
| 0x00C8 | outbound | IdentifyObject | `CM_Item::Event_Appraise` | W | H | B | B+W | AppraiseRequest builder dead [^a-1] |
| 0x00CD | outbound | GiveObjectRequest | `CM_Inventory::Event_GiveObjectRequest` | W | H | B | B+W | InventoryActions builder dead [^a-1] |
| 0x00D6 | outbound | AdvocateTeleport | | | H | | B | GM-only teleport |
| 0x0140 | outbound | AbuseLogRequest | `CM_Character::Event_AbuseLogRequest` | | | | B | Player report tool |
| 0x0145 | outbound | AddChannel | `CM_Communication::Event_ChannelList` | | H | B | B+W | SocialActions builder dead [^a-1][^a-4] |
| 0x0146 | outbound | RemoveChannel | | | H | B | B+W | SocialActions builder dead [^a-1] |
| 0x0147 | outbound | ChatChannel | `CM_Communication::Event_ChannelBroadcast` | W | H | W | B+W | Wired in WorldSession.SendChannel; same code as inbound 0x0147 [^a-5] |
| 0x0148 | outbound | ListChannels | | | | | B | |
| 0x0149 | outbound | IndexChannels | `CM_Communication::Event_ChannelIndex` | | | | B | |
| 0x0195 | outbound | NoLongerViewingContents | `CM_Inventory::Event_NoLongerViewingContents` | W | H | | B | Container UI close |
| 0x019B | outbound | StackableSplitToWield | `CM_Inventory::Event_StackableSplitToWield` | W | H | B | B+W | InventoryActions builder dead [^a-1] |
| 0x019C | outbound | AddShortcut | `CM_Character::Event_AddShortCut` | | H | B | B+W | InventoryActions builder dead [^a-1] |
| 0x019D | outbound | RemoveShortcut | `CM_Character::Event_RemoveShortCut` | | H | B | B+W | InventoryActions builder dead [^a-1] |
| 0x01A1 | outbound | SetCharacterOptions | | | H | B | B+W | SocialActions builder dead [^a-1] |
| 0x01A8 | outbound | RemoveSpellC2S | `CM_Magic::Event_RemoveSpell` | | H | | B | Self-cancel buff |
| 0x01B7 | outbound | CancelAttack | `CM_Combat::Event_CancelAttack` | W | H | W | B+W | Wired in WorldSession.SendCancelAttack |
| 0x01BF | outbound | QueryHealth | `CM_Combat::Event_QueryHealth` | W | H | B | B+W | SocialActions builder dead [^a-1] |
| 0x01C2 | outbound | QueryAge | `CM_Character::Event_QueryAge` | | H | | B | |
| 0x01C4 | outbound | QueryBirth | `CM_Character::Event_QueryBirth` | | H | | B | |
| 0x01DF | outbound | Emote | `CM_Communication::Event_Emote` | W | H | | B | Custom /e text |
| 0x01E1 | outbound | SoulEmote | `CM_Communication::Event_SoulEmote` | W | H | | B | /soulemote |
| 0x01E3 | outbound | AddSpellFavorite | `CM_Character::Event_AddSpellFavorite` | | H | | B | Spellbook pin |
| 0x01E4 | outbound | RemoveSpellFavorite | `CM_Character::Event_RemoveSpellFavorite` | | | | B | Spellbook unpin |
| 0x01E9 | outbound | PingRequest | | W | H | B | B+W | SocialActions builder dead; keepalive [^a-1] |
| 0x01F6 | outbound | OpenTradeNegotiations | `CM_Trade::Event_OpenTradeNegotiations` | W | H | | B | Begin trade |
| 0x01F7 | outbound | CloseTradeNegotiations | `CM_Trade::Event_CloseTradeNegotiations` | W | H | | B | Cancel trade |
| 0x01F8 | outbound | AddToTrade | `CM_Trade::Event_AddToTrade` | W | H | | B | Add item to trade |
| 0x01FA | outbound | AcceptTrade | `CM_Trade::Event_AcceptTrade` | W | H | | B | Confirm trade |
| 0x01FB | outbound | DeclineTrade | `CM_Trade::Event_DeclineTrade` | W | H | | B | Reject trade |
| 0x0204 | outbound | ResetTrade | `CM_Trade::Event_ResetTrade` | W | H | | B | Clear pending items |
| 0x0216 | outbound | ClearPlayerConsentList | `CM_Character::Event_ClearPlayerConsentList` | | H | | B | Resurrection consent |
| 0x0217 | outbound | DisplayPlayerConsentList | `CM_Character::Event_DisplayPlayerConsentList` | | H | | B | |
| 0x0218 | outbound | RemoveFromPlayerConsentList | `CM_Character::Event_RemoveFromPlayerConsentList` | | | | B | |
| 0x0219 | outbound | AddPlayerPermission | `CM_Character::Event_AddPlayerPermission` | W | H | | B | Storage / consent perm |
| 0x021A | outbound | RemovePlayerPermission | `CM_Character::Event_RemovePlayerPermission` | W | H | | B | |
| 0x021C | outbound | BuyHouse | `CM_House::Event_BuyHouse` | | H | | defer:Phase Q | Housing — out of M baseline scope |
| 0x021E | outbound | HouseQuery | | | H | | defer:Phase Q | Housing |
| 0x021F | outbound | AbandonHouse | `CM_House::Event_AbandonHouse` | | H | | defer:Phase Q | Housing |
| 0x0221 | outbound | RentHouse | `CM_House::Event_RentHouse` | | | | defer:Phase Q | Housing |
| 0x0224 | outbound | SetDesiredComponentLevel | | | | | B | Component-buy preference |
| 0x0245 | outbound | AddPermanentGuest | `CM_House::Event_AddPermanentGuest_Event` | | H | | defer:Phase Q | Housing |
| 0x0246 | outbound | RemovePermanentGuest | `CM_House::Event_RemovePermanentGuest_Event` | | H | | defer:Phase Q | Housing |
| 0x0247 | outbound | SetOpenHouseStatus | `CM_House::Event_SetOpenHouseStatus_Event` | | H | | defer:Phase Q | Housing |
| 0x0249 | outbound | ChangeStoragePermission | `CM_House::Event_ChangeStoragePermission_Event` | | H | | defer:Phase Q | Housing |
| 0x024A | outbound | BootSpecificHouseGuest | `CM_House::Event_BootSpecificHouseGuest_Event` | | H | | defer:Phase Q | Housing |
| 0x024C | outbound | RemoveAllStoragePermission | `CM_House::Event_RemoveAllStoragePermission` | | H | | defer:Phase Q | Housing |
| 0x024D | outbound | RequestFullGuestList | `CM_House::Event_RequestFullGuestList_Event` | | | | defer:Phase Q | Housing |
| 0x0254 | outbound | SetMotd | `CM_Allegiance::Event_SetMotd` | | | | B | Allegiance message-of-the-day |
| 0x0255 | outbound | QueryMotd | `CM_Allegiance::Event_QueryMotd` | | | | B | |
| 0x0256 | outbound | ClearMotd | `CM_Allegiance::Event_ClearMotd` | | H | | B | |
| 0x0258 | outbound | QueryLord | `CM_House::Event_QueryLord` | | | | defer:Phase Q | Housing |
| 0x025C | outbound | AddAllStoragePermission | `CM_House::Event_AddAllStoragePermission` | | | | defer:Phase Q | Housing |
| 0x025E | outbound | RemoveAllPermanentGuests | `CM_House::Event_RemoveAllPermanentGuests_Event` | | H | | defer:Phase Q | Housing |
| 0x025F | outbound | BootEveryone | `CM_House::Event_BootEveryone_Event` | | H | | defer:Phase Q | Housing |
| 0x0262 | outbound | TeleToHouse | `CM_House::Event_TeleToHouse_Event` | | | | defer:Phase Q | Housing |
| 0x0263 | outbound | QueryItemMana | `CM_Item::Event_QueryItemMana` | W | H | | B | Mana-meter check |
| 0x0266 | outbound | SetHooksVisibility | `CM_House::Event_SetHooksVisibility` | | H | | defer:Phase Q | Housing |
| 0x0267 | outbound | ModifyAllegianceGuestPermission | `CM_House::Event_ModifyAllegianceGuestPermission` | | | | defer:Phase Q | Housing |
| 0x0268 | outbound | ModifyAllegianceStoragePermission | `CM_House::Event_ModifyAllegianceStoragePermission` | | | | defer:Phase Q | Housing |
| 0x0269 | outbound | ChessJoin | | | H | | skip:minigame | Chess |
| 0x026A | outbound | ChessQuit | | | H | | skip:minigame | Chess |
| 0x026B | outbound | ChessMove | | | H | | skip:minigame | Chess |
| 0x026D | outbound | ChessMovePass | | | H | | skip:minigame | Chess |
| 0x026E | outbound | ChessStalemate | | | H | | skip:minigame | Chess |
| 0x0270 | outbound | ListAvailableHouses | `CM_House::Event_ListAvailableHouses` | | | | defer:Phase Q | Housing |
| 0x0275 | outbound | ConfirmationResponse | `CM_Character::Event_ConfirmationResponse` | W | H | | B | Yes/No popups |
| 0x0277 | outbound | BreakAllegianceBoot | `CM_Allegiance::Event_BreakAllegianceBoot` | | H | | B | Officer kick |
| 0x0278 | outbound | TeleToMansion | `CM_House::Event_TeleToMansion_Event` | W | | | defer:Phase Q | Housing recall |
| 0x0279 | outbound | Suicide | `CM_Character::Event_Suicide` | W | | | B | /suicide cmd |
| 0x027B | outbound | AllegianceInfoRequest | `CM_Allegiance::Event_AllegianceInfoRequest` | | H | | B | Tree info |
| 0x027D | outbound | CreateTinkeringTool / SalvageItemsWith | `CM_Inventory::Event_CreateTinkeringTool` | W | H | | B | Salvage UI [^a-6] |
| 0x0286 | outbound | SpellbookFilter | `CM_Character::Event_SpellbookFilterEvent` | | | | B | School filter |
| 0x028D | outbound | TeleToMarketPlace | | W | | | B | MP recall |
| 0x028F | outbound | EnterPkLite | | W | | | B | PK-lite toggle |
| 0x0290 | outbound | FellowshipAssignNewLeader | `CM_Fellowship::Event_AssignNewLeader` | W | H | | B | |
| 0x0291 | outbound | FellowshipChangeOpenness | `CM_Fellowship::Event_ChangeFellowOpeness` | | H | | B | |
| 0x02A0 | outbound | AllegianceChatBoot | `CM_Allegiance::Event_AllegianceChatBoot` | | | | B | Officer chat-mute |
| 0x02A1 | outbound | AddAllegianceBan | `CM_Allegiance::Event_AddAllegianceBan` | | H | | B | |
| 0x02A2 | outbound | RemoveAllegianceBan | `CM_Allegiance::Event_RemoveAllegianceBan` | | | | B | |
| 0x02A3 | outbound | ListAllegianceBans | `CM_Allegiance::Event_ListAllegianceBans` | | | | B | |
| 0x02A5 | outbound | RemoveAllegianceOfficer | `CM_Allegiance::Event_RemoveAllegianceOfficer` | | H | | B | |
| 0x02A6 | outbound | ListAllegianceOfficers | `CM_Allegiance::Event_ListAllegianceOfficers` | | | | B | |
| 0x02A7 | outbound | ClearAllegianceOfficers | `CM_Allegiance::Event_ClearAllegianceOfficers` | | | | B | |
| 0x02AB | outbound | RecallAllegianceHometown | `CM_Allegiance::Event_RecallAllegianceHometown` | | | | B | Bind to monarch lifestone |
| 0x02AF | outbound | QueryPluginListResponse | `CM_Admin::Event_QueryPluginListResponse` | | | | skip:plugin-c2s | Decal-era plugin probe |
| 0x02B2 | outbound | QueryPluginResponse | `CM_Admin::Event_QueryPluginResponse` | | | | skip:plugin-c2s | Decal-era plugin probe |
| 0x0311 | outbound | FinishBarber | `CM_Character::Event_FinishBarber` | | H | | B | Char appearance commit |
| 0x0316 | outbound | AbandonContract | `CM_Social::Event_AbandonContract` | | H | | B | Drop quest |
**Footnotes:**
[^a-1]: "Builder dead" = the byte-array builder is implemented in `src/AcDream.Core.Net/Messages/<file>.cs` but no caller in `src/AcDream.App/` or a `WorldSession.Send*` wrapper invokes it. Phase M wires these to game-state actions (UI clicks, command bus, key bindings) and adds golden-vector tests against holtburger fixtures.
[^a-2]: ACE's wire field order for Tell is `message FIRST then target` (see `ChatRequests.BuildTell` doc comment). Sept-2013 PDB has no `Event_Tell` symbol — it routes through `CM_Communication::Event_TalkDirectByName` plus a server-side rename.
[^a-3]: TeleToPoi (0x00B1) is listed in `InventoryActions.cs` but not in ACE's `GameActionType` enum. Cross-reference holtburger to confirm; may be a dead-letter opcode that retail's vendored 2013 ACE branch dropped. Verify before shipping the test vector.
[^a-4]: AddChannel (0x0145) — named-retail's matching symbol is `Event_ChannelList` (0x0148 according to retail enum), so the symbol mapping is approximate; AddChannel in pseudo-C may be unsymbolicated. Confirm by greping `acclient_2013_pseudo_c.txt` before publishing.
[^a-5]: 0x0147 ChannelBroadcast is the same numeric code in both directions (outbound GameAction = client sends to channel; inbound GameEvent = server broadcasts to channel members). Listed under outbound here per Section-5 scope; inbound version is in §4.
[^a-6]: ACE GameActionType lists 0x027D as `CreateTinkeringTool`; holtburger names the same opcode `SalvageItemsWith`. Both behaviors funnel through the salvage UI in retail. Either name is acceptable in acdream; pick one and leave the other as an alias constant.
---
## Source attribution
- **Holtburger** — `references/holtburger/` at `629695a` (2026-05-10). Primary client-behavior oracle.
- **ACE** — `references/ACE/Source/ACE.Server/Network/`. Server-side authority for GameMessages, GameEvents, GameActions, and accept rules.
- **Named retail decomp** — `docs/research/named-retail/` (Sept 2013 EoR PDB + Binary Ninja pseudo-C). Wire-format ground truth for the 2013 client.
- **acdream current state** — `src/AcDream.Core.Net/` and `src/AcDream.App/`. Inventoried by parallel agents on 2026-05-10.
## Caveats
This is the **initial population**, produced by four parallel research agents (one per opcode class) on 2026-05-10. Spot-check pass + intentional-divergence ratification is owed before M.1 closes. Specifically:
- A handful of named-retail symbol citations are tentative (marked in footnotes); spot-check by greping `acclient_2013_pseudo_c.txt` and `symbols.json`.
- Holtburger / ACE / acdream cells were determined by reading the actual code (not guessing); when an agent couldn't determine a value, it used `?`. The `?` cells need a follow-up read.
- "Dead builder" calls (rows where acdream `B` but Phase M target is `B+W`) are based on a grep for `WorldSession.Send*` patterns and `worldSession.Send` calls in `src/AcDream.App/`. Edge cases (call sites in test code, command-bus indirection) may have been missed.
- Total opcode count in scope (~284) is approximate; deduplication of cross-section codes (e.g., 0x0147 in §4 and §5) is tracked in footnotes but the headline count treats them as distinct rows.
This matrix lives on as a long-term reference. Phase M.6 implementation tracks progress against it; gameplay phases consuming Phase M will reference the rows they wire as part of their phase acceptance.