# R10 — Quest System, NPC Dialogs, and Emote Scripts

**Status:** Deep-dive research, 2026-04-18. Author: R10 research agent.
**Scope:** Everything that connects a player talking to an NPC with a change
in the player's persistent state — quest flags, quest timers, emote scripts,
item turn-ins, dialog text, and the client-side rendering of all of the
above.

Every AC-specific behavior in this document is anchored to one of:

- `docs/research/decompiled/` — decompiled retail client (ground truth for
  client-side behavior)
- `references/ACE/Source/ACE.Server/` — ACEmulator server (authority on
  server-side quest / emote evaluation and on the wire protocol the server
  sends to the client)
- `references/DatReaderWriter/` + `references/ACE/Source/ACE.DatLoader/` —
  the dat files the client reads (`ChatEmoteData`, `Contract`,
  `ContractTable`)
- `references/holtburger/crates/holtburger-protocol/` — Rust reference
  client's packed/unpacked byte layouts (cross-check on the wire format
  for `Tell`, `HearSpeech`, `EmoteText`, `SoulEmote`, `PopupString`)

The short summary: AC has no "quest system" in the sense a modern MMO has.
Quests are a *convention on top of emote scripts*. Every "quest" that
exists in retail is expressed as a tree of emote actions hanging off
weenies (NPCs, items, portals), together with entries in a world-wide
`Quest` table (min delta / max solves) and a per-character quest registry
(last completed time, number of completions). The "quest tracker" UI is
not an independent quest system; it is a presentation layer over the
`Contract` dat table, which in turn points at plain quest-flag names that
the emote engine reads and writes. Everything comes back to the emote
engine.

---

## 1. Quest flag system

### 1.1 Per-character storage

A character's quest state is a set of `CharacterPropertiesQuestRegistry`
rows (see
`references/ACE/Source/ACE.Database/Models/Shard/CharacterPropertiesQuestRegistry.cs`):

```csharp
public partial class CharacterPropertiesQuestRegistry
{
    public uint CharacterId { get; set; }       // character GUID
    public string QuestName { get; set; }       // e.g. "arwicemayortalk"
    public uint LastTimeCompleted { get; set; } // Unix time (seconds)
    public int NumTimesCompleted { get; set; }  // solve count
}
```

Key observations:

- **One row per (character, questName).** "Quest flag" in retail
  terminology means a row in this table. An NPC checks "does the player
  have quest X?" by asking "does a row with questName=X exist for this
  char?"
- **`NumTimesCompleted` is overloaded.** For a simple stage flag it is
  treated as a 0/non-zero presence test. For a kill-task or kill-counter
  (e.g. "GolemKillTask@#kt"), it's a literal count. For a "bit-field"
  quest flag, the integer is a 32-bit bitmask and emotes manipulate
  specific bits via `SetQuestBitsOn` / `SetQuestBitsOff` / `InqQuestBitsOn`
  / `InqQuestBitsOff` (see section 3). That's the "BitField (bool quest
  flags)" notion — a single `NumTimesCompleted` row can store up to 32
  boolean sub-flags.
- **`LastTimeCompleted`** drives the "quest stamp" — timestamp + cooldown.
  Combined with the world-quest-table `MinDelta`, it answers "can the
  player solve this quest again yet?"

### 1.2 World quest table (server-only)

The world (not per-character) has a `Quest` table
(`references/ACE/Source/ACE.Database/Models/World/Quest.cs`):

```csharp
public partial class Quest
{
    public uint Id { get; set; }
    public string Name { get; set; }        // e.g. "arwicemayortalk"
    public uint MinDelta { get; set; }      // seconds between solves
    public int MaxSolves { get; set; }      // -1 = unlimited, >0 = cap
    public string Message { get; set; }     // unused in live
}
```

Only the server needs this table. The client is never shown `MinDelta`
or `MaxSolves` directly — it sees them via the emote-engine
substitutions `%tqt` (time-to-solve), `%tqm` (max solves), `%tqc`
(current solves) baked into NPC dialog strings (see section 3.4
"string replacement"), and via the Contract-tracker UI (section 9).

### 1.3 What the client actually stores

**The retail client never stores quest flags.** Quest state lives entirely
server-side. The client only learns about a quest:

- Indirectly, via NPC dialog strings (Tell/Say/TextDirect) whose text has
  been server-side-formatted with `%tqc`/`%tqt` etc.
- Directly, via the Contract tracker messages
  (`SendClientContractTrackerTable` 0x0314 and
  `SendClientContractTracker` 0x0315). These carry a ContractId + stage +
  time-when-done / time-when-repeats; they *do not* send the raw quest
  flag name or completion count. See section 9.
- As error toasts — e.g. "You have solved this quest too recently!"
  (decompiled at `chunk_00570000.c:1629`, game-event case `0x43e`). This
  is a generic error string, the client does not know the quest name
  that was involved.

So: for acdream, we do not need a client-side QuestRegistry store.
We need: (a) a Contract tracker store (short-lived, server pushes full
state), and (b) a generic chat log that can render Tell/Say/EmoteText
and the embedded `<Tell:IIDString:...>NAME<\Tell>` clickable-name markup.

### 1.4 Encoding in the save blob

"Save blob" here means the shard-database row, not a client-side file.
The client has no save file for quest state. Server-side, see the
QuestSQLWriter at
`references/ACE/Source/ACE.Database/SQLFormatters/World/QuestSQLWriter.cs`
for world quests and plain EF models for per-character quests. acdream's
server (Phase 7+) would follow ACE's shape.

---

## 2. Quest manager — opcodes and wire events

The server never sends a "quest updated" push to the client in the
generic case. Instead, two categories of server → client message carry
quest-related information:

### 2.1 Generic "game event" errors

When a quest-gated action fails, the server sends a `GameEventWeenieError`
(opcode 0x028A) or `GameEventInventoryServerSaveFailed` (opcode 0x00A0)
with a WeenieError code. The client maps the code to a hard-coded wide
string and displays it in chat. The decompiled client's dispatcher is in
`chunk_00570000.c` around line 1500–2900. Selected cases:

| Code   | String                                                                       | Source |
|--------|------------------------------------------------------------------------------|--------|
| 0x043E | `You have solved this quest too recently!\n`                                 | `chunk_00570000.c:1629` |
| 0x043F | `You have solved this quest too many times!\n`                               | `chunk_00570000.c:1633` |
| 0x0445 | `This item requires you to complete a specific quest before you can pick it up!\n` | `chunk_00570000.c:1637` |
| 0x0474 | `You must complete a quest to interact with that portal.\n`                  | `chunk_00570000.c:1673` |
| 0x0555 | `You must purchase Asheron's Call -- Throne of Destiny to access this quest.`| `chunk_00570000.c:2811` |

These are purely display. There is no structured "quest state" in them.

### 2.2 Contract tracker events (the real "quest tracker" UI)

Two opcodes in the `GameEventType` enum
(`references/ACE/Source/ACE.Server/Network/GameEvent/GameEventType.cs`):

- `SendClientContractTrackerTable = 0x0314` — full replacement of the
  tracker panel. Body is a packed list of ContractTracker structs.
- `SendClientContractTracker = 0x0315` — single contract update
  (added / changed / deleted). Body: one ContractTracker struct +
  `DeleteContract` bool + `SetAsDisplayContract` bool.

Structure written by server (`ContractTracker.Write` at
`references/ACE/Source/ACE.Server/Network/Structure/ContractTracker.cs:137`):

```text
uint32  Version              // Contract.Version from dat
uint32  ContractId           // key into ContractTable
uint32  Stage                // ContractStage enum, see below
double  TimeWhenDone         // seconds until current cooldown ends
double  TimeWhenRepeats      // seconds until repeat cooldown ends
```

`ContractStage`:

```csharp
Available           = 0x1,
InProgress          = 0x2,
DoneOrPendingRepeat = 0x3,
ProgressCounter     = 0x4,   // add N for N progress steps done
```

The ContractTracker is the client's only structured view of any
quest-like state. It does NOT contain the quest flag name itself — the
flag name is in the Contract dat (see section 9), keyed by ContractId.
The client looks up the Contract in `ContractTable`, reads
`QuestflagStarted`, `QuestflagFinished`, etc., and uses those to
decorate the UI. It does not read or write quest flags directly.

### 2.3 Quest flag write notifications

There is no such opcode. Quest flags are written server-side in
`EmoteManager.ExecuteEmote` via `QuestManager.Update` /
`SetQuestCompletions` / `Erase` etc. The only notification the client
receives is:

1. A contract tracker refresh (if the changed quest flag is referenced
   by a Contract in the dat).
2. Any Tell / Say / DirectBroadcast / Sound emote action that happened
   to be sequenced after the quest write — this is how NPCs "confirm"
   a quest step to the player.

This is why the retail client feels so non-MMO: the server tells the
player "you've done it" by scripting the NPC to say "Here is your
reward, friend," not by emitting a structured quest-progress event.

---

## 3. Emote system — the retail mini-language

An "emote" in AC retail terminology is **not** a chat action like `/wave`.
It's a scripted response a weenie (NPC, item, portal, generator) can run
when some trigger fires. An **emote set** is an ordered list of
**emote actions** tagged with a **category** (the trigger) and optional
filters (quest name, vendor type, weenie class ID, motion style, health
threshold).

Player-visible chat-command emotes like `/wave` are a separate thing —
they are handled by `GameActionEmote` (opcode 0x01E0) and
`GameActionSoulEmote`, which simply broadcast `GameMessageEmoteText` /
`GameMessageSoulEmote` to everyone in range. No scripting involved.

### 3.1 EmoteCategory — the trigger types (39 values)

From `references/ACE/Source/ACE.Entity/Enum/EmoteCategory.cs`:

| Value | Name                        | When it fires |
|-------|-----------------------------|---------------|
| 0     | Invalid                     | — |
| 1     | Refuse                      | NPC "examines" an item given to it but doesn't consume it |
| 2     | Vendor                      | Vendor window lifecycle events (Open, Close, etc.) |
| 3     | Death                       | The weenie is killed |
| 4     | Portal                      | Player activates a portal (post travel) |
| 5     | HeartBeat                   | Periodic tick (ambient speech, homesickness) |
| 6     | Give                        | Player gives the NPC an item (quest turn-in) |
| 7     | Use                         | Player right-clicks / uses the weenie |
| 8     | Activation                  | Chained Activate from a switch/lever |
| 9     | Generation                  | A generator spawns something |
| 10    | PickUp                      | Item is picked up |
| 11    | Drop                        | Item is dropped |
| 12    | QuestSuccess                | InqQuest / InqMyQuest / InqFellowQuest → true |
| 13    | QuestFailure                | same → false |
| 14    | Taunt                       | Combat alert chatter |
| 15    | WoundedTaunt                | Taunt filtered by health range |
| 16    | KillTaunt                   | Fires when this creature kills a player |
| 17    | NewEnemy                    | First acquires a player target |
| 18    | Scream                      | Panic chatter |
| 19    | Homesick                    | Displaced from home |
| 20    | ReceiveCritical             | Took a crit hit |
| 21    | ResistSpell                 | Resisted a magic attack |
| 22    | TestSuccess                 | Inq*Stat passed |
| 23    | TestFailure                 | Inq*Stat failed |
| 24    | HearChat                    | Nearby player spoke a keyword |
| 25    | Wield                       | Item equipped |
| 26    | UnWield                     | Item unequipped |
| 27    | EventSuccess                | InqEvent → started |
| 28    | EventFailure                | InqEvent → not started |
| 29    | TestNoQuality               | Inq*Stat on a stat the target doesn't have |
| 30    | QuestNoFellow               | InqFellowQuest when player has no fellowship |
| 31    | TestNoFellow                | same semantics for arbitrary tests |
| 32    | GotoSet                     | Named sub-routine target for `Goto` |
| 33    | NumFellowsSuccess           | InqFellowNum passed |
| 34    | NumFellowsFailure           | InqFellowNum failed |
| 35    | NumCharacterTitlesSuccess   | InqNumCharacterTitles passed |
| 36    | NumCharacterTitlesFailure   | same failed |
| 37    | ReceiveLocalSignal          | LocalSignal broadcast received |
| 38    | ReceiveTalkDirect           | Someone used /tell keyword on this NPC |

### 3.2 EmoteType — the action types (122 values)

From `references/ACE/Source/ACE.Entity/Enum/EmoteType.cs`. This is the
instruction-set of the emote mini-language. Grouped by purpose:

**Speech & UI output:**
- `Say (8)` — NPC speaks, broadcast to all nearby (HearSpeech / HearRangedSpeech)
- `Tell (10)` — NPC whispers directly to the player (GameEventTell 0x02BD)
- `Act (1)` — "acting" text, broadcast system chat ("Bob waves at you")
- `TextDirect (13)` — direct broadcast to player's chat window (GameMessageSystemChat)
- `DirectBroadcast (18)` — same as TextDirect, reliable
- `LocalBroadcast (17)` — broadcast in area
- `WorldBroadcast (16)` — broadcast to all players on server
- `FellowBroadcast (65)` — broadcast to player's fellowship
- `TellFellow (64)` — Tell delivered to all fellowship members
- `AdminSpam (26)` — log-channel broadcast
- `BLog (25)` — server log (not client-visible)
- `PopUp (68)` — modal popup window (GameEventPopupString 0x0004)
- `Sound (9)` — play a sound effect (GameMessageSound)
- `PhysScript (7)` — particle / visual effect (GameMessagePlayParticleEffect)

**Motion & movement:**
- `Motion (5)` — play animation on this NPC
- `ForceMotion (52)` — play animation on the player target
- `Move (6)` — walk to home-relative offset
- `MoveHome (4)` — walk to home position
- `MoveToPos (87)` — walk to absolute position
- `Turn (11)` — turn to heading
- `TurnToTarget (12)` — face the player
- `ResetHomePosition (57)` — snap home to current
- `SetSanctuaryPosition (63)` — store player's /recall target

**Quest flag ops (the core of quest scripting):**
- `UpdateQuest (20)` / `UpdateMyQuest (79)` — add quest if new, stamp if old, branch on success
- `InqQuest (21)` / `InqMyQuest (80)` / `InqFellowQuest (58)` — branch on "has and is on cooldown"
- `StampQuest (22)` / `StampMyQuest (81)` / `StampFellowQuest (61)` — update LastTimeCompleted (unconditional)
- `IncrementQuest (33)` / `IncrementMyQuest (85)` — NumTimesCompleted += amount
- `DecrementQuest (32)` / `DecrementMyQuest (84)` — NumTimesCompleted -= amount
- `EraseQuest (31)` / `EraseMyQuest (83)` — delete the quest row entirely
- `SetQuestCompletions (70)` / `SetMyQuestCompletions (86)` — overwrite NumTimesCompleted
- `InqQuestSolves (30)` / `InqMyQuestSolves (82)` — branch on N in [min, max]
- `InqQuestBitsOn (102)` / `InqQuestBitsOff (103)` / `InqMyQuestBitsOn (104)` / `InqMyQuestBitsOff (105)` — branch on bitmask
- `SetQuestBitsOn (106)` / `SetQuestBitsOff (107)` / `SetMyQuestBitsOn (108)` / `SetMyQuestBitsOff (109)` — OR / AND-NOT bits
- `InqFellowNum (59)` — branch on fellowship size
- `UpdateFellowQuest (60)` — fellowship-wide UpdateQuest

The `MyQuest` variants target the creature executing the emote (NPC-local
state); the non-`My` variants target the player interacting with it.
This is how a single statue can "remember" that it has been touched by
this specific player without flooding the central quest table.

**Stat inquiries (all branch into TestSuccess / TestFailure / TestNoQuality):**
- `InqIntStat (36)`, `InqInt64Stat (114)`, `InqFloatStat (37)`, `InqBoolStat (35)`, `InqStringStat (38)`
- `InqAttributeStat (39)` / `InqRawAttributeStat (40)`
- `InqSecondaryAttributeStat (41)` / `InqRawSecondaryAttributeStat (42)` (vitals)
- `InqSkillStat (43)` / `InqRawSkillStat (44)` / `InqSkillTrained (45)` / `InqSkillSpecialized (46)`
- `InqNumCharacterTitles (71)`
- `InqPackSpace (89)` — branch on free inventory slots
- `InqOwnsItems (76)` — branch on "player has WCID × N"
- `InqContractsFull (121)`
- `InqEvent (51)` — branch on world event started
- `InqYesNo (75)` — raise a confirmation dialog (section 5)

**Stat writes:**
- `SetBoolStat (69)`, `SetIntStat (53)`, `SetInt64Stat (115)`, `SetFloatStat (118)`
- `IncrementIntStat (54)` / `DecrementIntStat (55)`

**Awards:**
- `AwardXP (2)`, `AwardNoShareXP (62)`, `AwardLevelProportionalXP (49)`
- `AwardSkillXP (28)`, `AwardSkillPoints (29)`, `AwardLevelProportionalSkillXP (50)`
- `AwardLuminance (113)` / `SpendLuminance (112)`
- `AwardTrainingCredits (47)`
- `AddCharacterTitle (34)`
- `TeachSpell (27)` — add a spell to player's spellbook
- `AddContract (119)` / `RemoveContract (120)` — add/remove a quest-tracker entry

**Actions on the weenie itself:**
- `Generate (72)` — trigger a generator to spawn
- `CreateTreasure (56)` — spawn loot into player's pack
- `Give (3)` — give a specific WCID item to the player
- `TakeItems (74)` — consume items from player's pack
- `DeleteSelf (77)` — remove the weenie
- `KillSelf (78)` — smite the weenie
- `OpenMe (116)` / `CloseMe (117)` — container/door
- `Activate (15)` — chain-activate linked object
- `CastSpell (14)` / `CastSpellInstant (19)` / `PetCastSpellOnOwner (73)`

**Teleport / positioning:**
- `Goto (67)` — jump to named sub-set within same emote-set (sub-routine call)
- `StartEvent (23)` / `StopEvent (24)` — world event control
- `LocalSignal (88)` — broadcast signal that other weenies' `ReceiveLocalSignal` emotes can listen for
- `TeleportSelf (100)` — NPC teleports (unused in retail)
- `TeleportTarget (99)` — player teleports
- `InflictVitaePenalty (48)` / `RemoveVitaePenalty (90)`

**Character appearance (barber):**
- `SetEyeTexture (91)` / `SetEyePalette (92)` / `SetNoseTexture (93)` / `SetNosePalette (94)` /
  `SetMouthTexture (95)` / `SetMouthPalette (96)` / `SetHeadObject (97)` / `SetHeadPalette (98)`
- `StartBarber (101)` — open the barber UI
- `SetAltRacialSkills (111)` — for race-change flows

**Misc:**
- `LockFellow (66)` — lock fellowship roster
- `UntrainSkill (110)`
- `Invalid (0)` — no-op
- `Enlightenment (9001)` — ACE-custom (not retail)

### 3.3 PropertiesEmote / PropertiesEmoteAction shape

Source:
`references/ACE/Source/ACE.Entity/Models/PropertiesEmote.cs` and
`PropertiesEmoteAction.cs`. Schema:

```csharp
PropertiesEmote {
    EmoteCategory Category;
    float         Probability;     // 0..1, roll per set
    uint?         WeenieClassId;   // filter for Give/Refuse/Taunt
    MotionStance? Style;           // filter for HeartBeat
    MotionCommand? Substyle;
    string        Quest;           // filter — only fire if quest flag matches
    VendorType?   VendorType;      // filter for Vendor category
    float?        MinHealth;       // filter for WoundedTaunt
    float?        MaxHealth;
    List<PropertiesEmoteAction> PropertiesEmoteAction;  // the script
}

PropertiesEmoteAction {
    uint     Type;              // EmoteType
    float    Delay;             // pre-delay seconds
    float    Extent;            // motion/speech parameter
    MotionCommand? Motion;
    string   Message;           // speech text, quest name, or emote category for Goto
    string   TestString;        // for InqStringStat, InqYesNo prompt text
    int?/long?/double? Min/Max; // Inq ranges
    int?     Stat;              // PropertyInt/etc. key for Inq/Set
    int?     Amount;            // integer arg
    long?    Amount64;          // 64-bit arg
    double?  Percent;
    int?     SpellId;
    PlayScript? PScript;
    Sound?   Sound;
    uint?    WeenieClassId;     // for Give/TakeItems
    int?     StackSize;
    int?     Palette;           // for Give
    float?   Shade;
    uint?    ObjCellId;         // for TeleportTarget/MoveToPos/SetSanctuaryPosition
    float?   OriginX/Y/Z, AnglesX/Y/Z/W;
    ...
}
```

### 3.4 String replacement — `%n`, `%s`, `%tqt`, etc.

From `EmoteManager.Replace` (line 1777 of EmoteManager.cs):

| Token    | Replaced with |
|----------|---------------|
| `%n` / `%mn` | sender name (this NPC) |
| `%s` / `%tn` | target name (the player) |
| `%ml` / `%tl` | sender / target level |
| `%mt` / `%tt` | sender / target Template property |
| `%mh` / `%th` | sender / target heritage name |
| `%tqt` | time-until-next-solve for the active quest (target) |
| `%CDtime` | same (LSD custom) |
| `%fqt` | fellowship time-until-next-solve |
| `%tqm` | target max-solves |
| `%tqc` | target current-solves |
| `%mqt` | source (NPC) quest-cooldown |

These let the content author write NPC speech like `"I said no, %s. Come
back in %tqt."` and have it rendered dynamically per-player. This is the
only structured quest-state information that ever actually reaches the
client — baked into speech text.

### 3.5 Emote execution model

Flow: `ExecuteEmoteSet(category, quest, target)` → `GetEmoteSet(...)`
picks a random set (filtered by category + quest + vendor + wcid + style,
weighted by Probability) → `Enqueue(set, target, 0)` runs the action list
in order. Each action can return a delay; the next action is scheduled
after that delay + its own pre-delay. Branching emotes (Inq*, Update*,
Goto) recursively call `ExecuteEmoteSet` on a new category
(`TestSuccess`/`TestFailure`/`QuestSuccess`/`QuestFailure`/`GotoSet`),
which is how conditionals and subroutines are expressed.

Busy-state protection: `EmoteManager.IsBusy` prevents a second trigger
from starting while one is running; `Nested` counts recursion depth;
`Nested > 75` + self-referential emote aborts with a log error (infinite
loop detection). The 75-deep cap is why some retail quests bail out
silently when misconfigured.

---

## 4. NPC dialog flow

### 4.1 Click the NPC

On the client side, right-clicking an NPC or selecting it and hitting Use
runs the Use code at `chunk_00580000.c:5890+`. That function eventually
emits a `GameActionUseRequest` (opcode 0x0035 per ACE's
`GameActionType.cs`). On the server, that lands in the creature's
`OnUse` handler, which calls `EmoteManager.OnUse(player)` — which in turn
calls `ExecuteEmoteSet(EmoteCategory.Use, null, player)` (see
`EmoteManager.cs:1916`).

### 4.2 The server evaluates a Use emote set

`GetEmoteSet(EmoteCategory.Use, ...)` picks one of the NPC's Use sets
(possibly filtered by quest flag — e.g. "only show first-meeting speech
to players who don't have `metArwicMayor`"), rolling against
`Probability`. The picked set's action list is enqueued.

Typical first-meeting conversation script:

```
EmoteSet {Category=Use, Quest=null, Probability=1.0}
  Act:             "%n turns to %s."
  TurnToTarget
  Motion:          Bow
  Tell:            "Hello, %s. I don't believe I've had the pleasure. I am Mayor Arwic."
  UpdateQuest:     "metArwicMayor"
```

After this fires, the NPC has "remembered" this player. A second Use set
(filtered `Quest="metArwicMayor"`) holds the follow-up dialog.

### 4.3 Dialog rendering on the client

The server sends each Say/Tell/TextDirect as its own wire message. The
client has no dialog-box widget — it simply appends each line to the chat
window with the right `ChatMessageType` colour. The "NPC dialog window"
you remember from playing AC is in fact a chat pane, filtered on NPC
messages.

Visible NPC speech goes through the `<Tell:IIDString:ID:name>NAME<\Tell>`
clickable-name markup when the sender is a player (GUID range
`0x50000001 – 0x6FFFFFFF`). For an NPC (GUID outside that range) the
format is plain `%s tells you, "%s"\n` with no clickable link
(`chunk_00570000.c:1190-1195`):

```c
if (sender_guid < 0x50000001 || sender_guid > 0x6FFFFFFF) {
    FUN_00402710(&out, "%s tells you, \"%s\"\n", name, message);
} else {
    FUN_00402710(&out, "<Tell:IIDString:%d:%s>%s<\\Tell> tells you, \"%s\"\n",
                 sender_guid, name, name, message);
}
```

This means in acdream we render NPC dialog as normal chat lines with
colour = `ChatMessageType.Tell` (0x03 — yellow) and no clickable name.
For player-to-player tells we emit the clickable span.

### 4.4 Busy-state & client behaviour

While the NPC's EmoteManager is running a set, all further Use and Give
attempts are rejected server-side (`Player_Inventory.cs:3404` for Give,
similar for Use) with `WeenieErrorWithString.AiRefuseItemDuringEmote`.
The client shows "<NPC> is busy right now.". That's the only "dialog
window modal" indication — a soft refusal, not a UI lock.

---

## 5. Use / Appraise triggers

- **Use** (`EmoteCategory.Use`) — fires on `OnUse`. Any weenie with a Use
  emote set is "talkable"/"interactable". Items with Use sets are
  single-use consumables that can trigger scripts (e.g. "use this book
  to start a quest").
- **Appraise** — technically the *client* action; the server responds
  with `GameEventIdentifyObjectResponse` (0x00C9). There is no
  EmoteCategory.Appraise. If you want an NPC to react to being appraised,
  the closest pattern is to key off a `HearChat` emote with the NPC's
  own name — not reliable — or to cheat and trigger from an `InqYesNo`.
  Retail quests do not depend on appraisal.
- **Activation** (`EmoteCategory.Activation`) — fires when a switch is
  flipped, chained via `OnActivate`. Used by quest puzzle switches.
- **Portal** (`EmoteCategory.Portal`) — fires after a portal teleport
  succeeds. Used for warning/greeting text post-travel.
- **HearChat** (`EmoteCategory.HearChat`) — fires when a nearby player
  speaks a keyword. Matched by literal string match on `Quest` field.
  This is how "say the magic word to the door" puzzles work.
- **ReceiveTalkDirect** (`EmoteCategory.ReceiveTalkDirect`) — fires when
  a player sends a `/tell` to this NPC containing a keyword. This is the
  "text-input quest" pattern.

---

## 6. Quest turn-in: dragging an item onto an NPC

Wire path:

1. Client sends `GameActionGiveObjectRequest`
   (`GameActionType.GiveObjectRequest` ≈ 0x00EA). Payload:
   `uint32 targetGuid, uint32 objectGuid, int32 amount`.
   Source:
   `references/ACE/Source/ACE.Server/Network/GameAction/Actions/GameActionGiveObjectRequest.cs`.
2. Server routes to `Player.HandleActionGiveObjectRequest`
   (`Player_Inventory.cs:3190`). Validates the player isn't busy / teleporting
   / trading the item, locates both objects, and walks the player to the
   target via `CreateMoveToChain`.
3. On arrival, calls `GiveObjectToNPC(target, item, ...)`
   (`Player_Inventory.cs:3380`).
4. Target looks for a matching emote set:

    ```csharp
    var refuseItem = EmoteManager.GetEmoteSet(EmoteCategory.Refuse, null, null, item.WeenieClassId);
    var giveItem   = EmoteManager.GetEmoteSet(EmoteCategory.Give,   null, null, item.WeenieClassId);
    ```

    (Note: these sets are filtered by `WeenieClassId` — the emote "matches"
    the item's WCID exactly. Generic "I accept anything" is expressed via
    the `AiAcceptEverything` bool property on the weenie.)

5. If Give emote matches: remove the item from the player's inventory,
   send chat: `"You give <NPC> <item>."`, broadcast a `ReceiveItem`
   sound on the NPC, and run the emote set (`ExecuteEmoteSet(emoteResult, this)`).
   The emote set typically contains: `StampQuest`, `AwardXP`, `Give`
   (return reward), `Tell` ("Many thanks, %s."), etc.
6. If Refuse emote matches: NPC "examines" the item (it is returned),
   chat: `"You allow <NPC> to examine your <item>."`, and the Refuse
   emote runs — typically just a `Tell` explaining why it's not
   accepted. This is how NPCs react to items that are "close but not
   quite" quest items.
7. If neither: `WeenieError.TradeAiDoesntWant` — generic "not accepting
   gifts right now."

Critical point: **the NPC's Give emote is the quest turn-in.** There is
no separate "quest turn-in" handler. `StampQuest`/`UpdateQuest` inside
the Give emote writes the quest flag; `AwardXP`/`Give` distributes the
reward; a final `Tell` gives narrative closure. The sequence is not
transactional — if the server crashes between steps, quest state can
become inconsistent. Retail accepted this.

---

## 7. Branching dialogs

NPCs with multiple stages of a questline expose this by having multiple
Use emote sets, each filtered by `Quest`. `GetEmoteSet` iterates the
NPC's PropertiesEmote list, filters by category + quest + vendor + wcid
+ style/substyle/health, then applies `Probability`-weighted random
selection.

Typical multi-stage pattern:

```
EmoteSet {Use, Quest=null, Prob=1.0}                     // first meet
  Tell "Hello, traveller. Talk to me about the %s ring."
  UpdateQuest "arwicMayorQuest"                          // phase 1 stamp

EmoteSet {Use, Quest="arwicMayorQuest", Prob=1.0}        // post-first-meet
  InqQuest "arwicMayorQuestDone"                         // has player done the task?
    ↓ QuestSuccess set  →  Tell "You're back! Good, good."
    ↓ QuestFailure set  →  Tell "Have you not found it yet?"

EmoteSet {Give, WCID=<ring>, Prob=1.0}                   // turn-in
  StampQuest "arwicMayorQuestDone"
  Tell "Excellent! Here is your reward."
  AwardXP 50000
  Give <reward WCID>
```

A single NPC can have dozens of Use/Give sets — the emote engine is the
entire dialog tree. In principle content authors could use `Goto` to
build larger branching trees, and `InqYesNo` to add confirmation popups,
but retail kept it simple: Use emotes were almost always straight-line
scripts.

---

## 8. Random / ambient emotes

NPCs play ambient speech via `HeartBeat` (category 5) emote sets. These
tick periodically (`EmoteManager.HeartBeat` at `EmoteManager.cs:1904`)
while the NPC isn't actively engaged. Implementation filters by current
motion stance/substyle so that e.g. a sitting NPC only plays sitting
emotes. Retail shopkeepers have multi-line HeartBeat sets — `Say`
followed by a `PhysScript` gesture — rolled against Probability.

`HeartBeat` does not fire on player characters (early-return in
`HeartBeat()` method). It does not fire on "awake" creatures (those with
an active combat target); `WoundedTaunt`, `Taunt`, etc. take over in
combat.

`ReceiveCritical`, `ResistSpell`, `NewEnemy`, `Scream`, `Homesick` are
similar background triggers, each driven server-side from the relevant
combat/movement event handlers.

---

## 9. Quest Tracker UI panel

The retail client has a **Contract Tracker** panel (not a "Quest
Tracker"). It displays a list of Contracts, each showing: name,
description, progress, and timers. Contracts are entries in the
ContractTable dat file (`DatReaderWriter.Types.Contract`):

Fields:

```
uint32   Version
uint32   ContractId
string   ContractName           // e.g. "Aerlinthe Recall Ring Quest"
string   Description            // long-form text
string   DescriptionProgress    // "You have defeated N of M..."
string   NameNPCStart           // NPC that offers the quest
string   NameNPCEnd             // NPC that takes turn-in
string   QuestflagStamped       // flag set by first-meeting stamp
string   QuestflagStarted       // flag set by acceptance
string   QuestflagFinished      // flag set by completion (terminates)
string   QuestflagProgress      // flag whose NumTimesCompleted is the counter
string   QuestflagTimer         // flag whose cooldown feeds TimeWhenDone
string   QuestflagRepeatTime    // flag whose cooldown feeds TimeWhenRepeats
Position LocationNPCStart
Position LocationNPCEnd
Position LocationQuestArea
```

The server-side `ContractTracker` struct
(`references/ACE/Source/ACE.Server/Network/Structure/ContractTracker.cs`)
builds the live state: it looks up each of the Questflag* names in the
player's QuestRegistry, translates that into a Stage + two doubles
(`TimeWhenDone`, `TimeWhenRepeats`), and writes the packed struct into
`SendClientContractTracker` (0x0315) or `SendClientContractTrackerTable`
(0x0314).

On the client, the panel:

- Looks up the Contract by ContractId in the ContractTable dat.
- Renders `ContractName` as the list item title.
- Renders `Description` (or `DescriptionProgress` if `Stage == ProgressCounter + N`) as the body.
- Shows a timer if `TimeWhenDone > 0` or `TimeWhenRepeats > 0`.
- Clicking the NPC-name rows lets the player see NPC location on the in-game map.

The panel is additive/replacement: a single `SendClientContractTracker`
message with `DeleteContract=true` removes an entry; otherwise the
entry's state is updated. Full refresh arrives via
`SendClientContractTrackerTable` (e.g. after login).

The client learns about *new* Contracts via the `AddContract` emote
action (EmoteType 119), which server-side writes a
`CharacterPropertiesContractRegistry` row and pushes a
`SendClientContractTracker` to the client.

---

## 10. Wire messages — byte layouts

All strings are String16-L: `uint16 length` then UTF-8 bytes, then
padded to 4-byte alignment. All integers and floats are little-endian.
Where noted, the "Game Event" wrapper adds a leading `uint32 event_type`
before the payload described below.

### 10.1 Server → client

**GameEventTell (0x02BD)** — NPC tell / player tell:

```
string16L  message
string16L  senderName
uint32     senderID        // GUID
uint32     targetID        // GUID
uint32     chatType        // ChatMessageType
uint32     reserved        // always 0, observed in retail pcaps
```

ACE: `GameEventTell.cs`. holtburger: `TellEventData`
(`crates/holtburger-protocol/src/messages/chat/events.rs:7`).

**GameEventPopupString (0x0004)** — modal popup window:

```
string16L  message
```

ACE: `GameEventPopupString.cs`.

**GameMessageHearSpeech (0x02BB)** — NPC says something locally:

```
string16L  message
string16L  senderName
uint32     senderID
uint32     chatType
```

**GameMessageHearRangedSpeech (0x02BC)** — Say with explicit range:

```
string16L  message
string16L  senderName
uint32     senderID
float32    range
uint32     chatType
```

**GameMessageEmoteText (0x01E0)** — acting text ("NPC waves"):

```
uint32     senderID
string16L  senderName
string16L  emoteText
```

**GameMessageSoulEmote (0x01E2)** — soul-emote (out-of-character tone):

```
uint32     senderID
string16L  senderName
string16L  emoteText
```

**GameMessageSystemChat (server message frame 0xF7E0)** — plain text to chat:

```
string16L  message
int32      chatMessageType
```

**GameEventWeenieError (0x028A)** — typed error:

```
uint32     errorCode         // WeenieError enum (e.g. 0x43E YouHaveSolvedThisQuestTooRecently)
```

**GameEventInventoryServerSaveFailed (0x00A0)** — item-related error:

```
uint32     itemGuid
uint32     errorCode
```

**GameEventConfirmationRequest (0x0274)** — yes/no dialog:

```
uint32     confirmationType
uint32     context          // server-chosen correlation id
string16L  prompt
```

**GameEventSendClientContractTracker (0x0315)** — single contract update:

```
uint32     version
uint32     contractId
uint32     stage            // ContractStage
double     timeWhenDone
double     timeWhenRepeats
uint32     deleteContract   // bool (0/1)
uint32     setAsDisplayContract
```

**GameEventSendClientContractTrackerTable (0x0314)** — bulk refresh:

```
uint32     numContracts
ContractManager.Write(...)  // packed list; see Structure/ContractManager.cs
```

The inner loop writes each ContractTracker without the trailing two
bools (`ContractTracker.Write` variant at ContractTracker.cs:137).

### 10.2 Client → server

**GameActionEmote (0x01E0-ish, see GameActionType.Emote)** — `/emote` chat command:

```
string16L  emoteText
```

Source: `GameActionEmote.cs`. The server re-broadcasts via
`GameMessageEmoteText` to everyone in LocalBroadcastRange.

**GameActionSoulEmote (GameActionType.SoulEmote)** — `/soul` chat command:

```
string16L  emoteText
```

**GameActionGiveObjectRequest (GameActionType.GiveObjectRequest)** — drag item to NPC:

```
uint32     targetGuid
uint32     objectGuid
int32      amount
```

**GameActionTellRequest (GameActionType.TellRequest)** — player `/tell` to another player or NPC:

```
string16L  message
string16L  targetName
```

(Server route: TellFromTargetNameHandler.cs.) For NPC tells (quest
keyword dialogs), this is what triggers `EmoteCategory.ReceiveTalkDirect`
emotes on the NPC.

**GameActionConfirmationResponse (GameActionType.ConfirmationResponse)** — yes/no reply:

```
uint32     confirmationType
uint32     context
uint32     response      // 0=no, 1=yes
```

### 10.3 Where these fit in the decompiled dispatch table

`chunk_00550000.c` around line 10700 is the client's incoming game-event
switch. Each opcode maps to a concrete handler function:

- 0x01E0 EmoteText → `FUN_006a5a20`
- 0x02BB HearSpeech → handler (not shown; in same table)
- 0x02BC HearRangedSpeech → handler
- 0x02BD Tell → `FUN_006a5920` (chunk_006A0000.c:4991)
- 0x0314 ContractTrackerTable → handler in same switch
- 0x0315 ContractTracker → handler in same switch

Cross-referencing holtburger's `events.rs` gives clean packed layouts
for most of these; ACE's `Source/ACE.Server/Network/GameEvent/Events/`
gives write-side layouts. The two agree on all byte offsets.

---

## 11. Port plan — C# classes for acdream

Target layer: `AcDream.Core` (state + DTOs) + `AcDream.App/UI` (panels) +
`AcDream.Core.Net/Messages` (wire).

### 11.1 Core types

```csharp
// AcDream.Core/Quests/QuestState.cs
// Client-side mirror of what we know about one quest's state from server
// pushes. We only track it if a Contract references it.
public sealed record QuestState(
    string QuestName,
    uint   LastTimeCompleted,   // Unix seconds; 0 = never
    int    NumTimesCompleted,
    uint   MinDelta,            // from world Quest table snapshot if known
    int    MaxSolves);

// AcDream.Core/Quests/ContractTracker.cs
public enum ContractStage { Available = 1, InProgress = 2, DoneOrPendingRepeat = 3, ProgressCounter = 4 }

public sealed record ContractEntry(
    uint ContractId,
    uint Version,
    ContractStage Stage,
    double TimeWhenDone,
    double TimeWhenRepeats);

public interface IContractTracker : IReadOnlyCollection<ContractEntry>
{
    event Action<ContractEntry> Added;
    event Action<ContractEntry> Updated;
    event Action<uint>          Removed;
    event Action                BulkReplaced;
}

public sealed class ContractTracker : IContractTracker { /* standard observable dict */ }
```

### 11.2 Emote mini-language types

For acdream, the emote engine runs server-side. The client only needs
to decode the *outputs* (Tell / Say / EmoteText / Popup / Confirmation /
ContractTracker / WeenieError). But because acdream must support plugin
scripting (per `project_plugin_requirement.md`), we port the full
EmoteType + EmoteCategory enums now so plugins can inspect and inject
into the chat stream with the same vocabulary the server uses.

```csharp
// AcDream.Core/Emotes/EmoteCategory.cs  — same 39 values as EmoteCategory.cs (section 3.1)
// AcDream.Core/Emotes/EmoteType.cs      — same 122 values as EmoteType.cs (section 3.2)

// AcDream.Core/Emotes/EmoteAction.cs
public sealed record EmoteAction(
    EmoteType Type,
    float     Delay,
    float     Extent,
    int?      Motion,
    string?   Message,
    string?   TestString,
    int?      Min, int? Max,
    long?     Min64, long? Max64,
    double?   MinDbl, double? MaxDbl,
    int?      Stat,
    int?      Amount,
    long?     Amount64,
    double?   Percent,
    int?      SpellId,
    uint?     WeenieClassId,
    int?      StackSize,
    int?      Palette,
    float?    Shade,
    uint?     ObjCellId,
    float?    OriginX, float? OriginY, float? OriginZ,
    float?    AnglesX, float? AnglesY, float? AnglesZ, float? AnglesW);

// AcDream.Core/Emotes/EmoteSet.cs
public sealed record EmoteSet(
    EmoteCategory   Category,
    float           Probability,
    uint?           WeenieClassId,
    string?         Quest,
    int?            Style,
    int?            Substyle,
    float?          MinHealth, float? MaxHealth,
    IReadOnlyList<EmoteAction> Actions);

// AcDream.Core/Emotes/EmoteScript.cs
// Server-driven only on the client side; acdream plugin code can inspect
// but not override. Plugin hooks:
//   - IEmoteObserver.OnEmoteReceived(source, EmoteSet)  // from server log stream
//   - IEmoteObserver.OnChatLine(ChatLine)               // rendered form
public interface IEmoteScriptService
{
    IObservable<ChatLine> ChatLines { get; }  // every rendered line
    IObservable<PopupRequest> Popups { get; }
}
```

### 11.3 Trigger / action enums reused as strict enums

`EmoteCategory` (0..38) and `EmoteType` (0..121 + `Enlightenment=9001`)
as plain C# enums. This gives plugins type-safe switches and the Roslyn
analyzer can flag missing-case issues if we use exhaustive switches.

### 11.4 UI panels

```csharp
// AcDream.App/UI/Panels/ChatPanel.cs
//   - Appends ChatLine records with colour by ChatMessageType
//   - Renders <Tell:IIDString:ID:name>NAME<\Tell> spans as
//     clickable "click to /tell" links
//   - Filters / tabs (All / Speech / Fellowship / etc.)
//   - This is where NPC dialog ends up — not a separate dialog window

// AcDream.App/UI/Panels/PopupDialogPanel.cs
//   - Listens to PopupRequest stream
//   - Modal single-text popup with OK
//   - Used for EmoteType.PopUp (wire: 0x0004)

// AcDream.App/UI/Panels/YesNoDialogPanel.cs
//   - Listens to ConfirmationRequest stream (0x0274)
//   - Yes / No buttons post back GameActionConfirmationResponse
//   - Used for InqYesNo and other confirmation flows
//   - The context uint32 must round-trip unchanged

// AcDream.App/UI/Panels/ContractTrackerPanel.cs
//   - Bound to IContractTracker
//   - Row per ContractEntry
//     - Lookup dat Contract by ContractId
//     - Title: Contract.ContractName
//     - Body: Description or DescriptionProgress
//     - Timer: TimeWhenDone (countdown) or TimeWhenRepeats (cooldown)
//   - Click NPC name → show Contract.LocationNPCStart/End on in-game map
```

### 11.5 Wire decoders

```csharp
// AcDream.Core.Net/Messages/GameEventTellMessage.cs
internal static class GameEventTellMessage {
    public static TellDto Decode(ref DatReader r) => new(
        message:    r.ReadString16L(),
        senderName: r.ReadString16L(),
        senderId:   r.ReadUInt32(),
        targetId:   r.ReadUInt32(),
        chatType:   (ChatMessageType)r.ReadUInt32(),
        reserved:   r.ReadUInt32());
}

// Repeat shape for: HearSpeech (02BB), HearRangedSpeech (02BC),
// EmoteText (01E0), SoulEmote (01E2), PopupString (0004),
// WeenieError (028A), WeenieErrorWithString (028B),
// ConfirmationRequest (0274), ConfirmationDone (0276),
// ContractTrackerTable (0314), ContractTracker (0315),
// SystemChat via 0xF7E0 frame.
```

Cross-reference holtburger's `messages/chat/events.rs` and
`messages/chat/types.rs` for field order; copy its pack/unpack test
vectors into `AcDream.Core.Net.Tests` as conformance tests — this is the
only reliable way to catch byte-order / padding regressions before they
hit live traffic.

### 11.6 Plugin API surface

From `project_plugin_requirement.md`: the plugin API must expose
"game state through well-defined interfaces." For R10 that means:

```csharp
public interface IQuestState
{
    // For quests known to the client through Contracts (NOT a general
    // oracle — the client only knows about Contracts).
    IContractTracker Contracts { get; }

    // Emitted ChatLines with sender/target GUIDs + ChatMessageType —
    // lets plugins regex-match dialog for e.g. macro automation.
    IObservable<ChatLine> ChatStream { get; }

    IObservable<PopupRequest> PopupStream { get; }

    // Lets plugins send /tell (including to NPCs, which is how
    // HearChat / ReceiveTalkDirect quests are solved).
    Task SendTellAsync(string targetName, string message, CancellationToken ct = default);

    // Lets plugins drag an item to an NPC (a.k.a. "give").
    Task GiveItemAsync(uint targetGuid, uint itemGuid, int amount, CancellationToken ct = default);

    // Use / right-click a weenie.
    Task UseAsync(uint targetGuid, CancellationToken ct = default);

    // Respond to a YesNo popup.
    Task RespondConfirmationAsync(uint confirmationType, uint context, bool yes, CancellationToken ct = default);
}
```

This is enough for plugin-author-driven quest automation without
exposing raw packet sends.

### 11.7 Minimum viable acdream deliverables (phase R10)

1. `AcDream.Core/Emotes/EmoteCategory.cs` + `EmoteType.cs` as typed enums.
2. `AcDream.Core/Quests/ContractEntry.cs` + `ContractTracker.cs` with events.
3. `AcDream.Core.Net/Messages/*` decoders for opcodes listed in section
   10.1, matched to holtburger test fixtures.
4. `AcDream.App/UI/Panels/ChatPanel.cs` renders all of:
   NPC Tell / player Tell / HearSpeech / HearRangedSpeech / EmoteText /
   SoulEmote / SystemChat / channel broadcasts, with the
   `<Tell:IIDString:...>` markup parsed into clickable spans.
5. `AcDream.App/UI/Panels/PopupDialogPanel.cs` for PopupString
   (simple ack dialog).
6. `AcDream.App/UI/Panels/YesNoDialogPanel.cs` for ConfirmationRequest
   (context round-tripping).
7. `AcDream.App/UI/Panels/ContractTrackerPanel.cs` renders
   ContractTracker + dat-lookup of Contract records.
8. Plugin interfaces from 11.6 exposed via `IGameState`.

That lets plugins drive quest automation and lets players play every
retail quest that exists, because every retail quest IS an emote script
playing through Tell + Give + (optionally) ContractTracker.

---

## 12. Where this will go wrong (and where it won't)

Subtle bugs to watch in the port:

1. **`%tqt` not rendering** — if the server's string-substitution path
   doesn't get a live QuestManager handle, `%tqt` stays literal and
   players see the placeholder in chat. On the client side this is
   purely cosmetic; just escape `%` properly when parsing incoming
   strings — do not attempt to format them yourself. The server
   delivers pre-formatted text.
2. **ConfirmationRequest context round-trip** — if the client's
   ConfirmationResponse drops the server-chosen `context` uint32, the
   server will reject it silently and the emote chain will stall. Always
   echo exactly what the server sent.
3. **Contract stage = `ProgressCounter + N`** — note this is ADD, not
   replace. `Stage` arrives as (4 + progress). Panel decoder must split
   `stage % 4 == 0 && stage >= 4` as progress vs 1/2/3 stages.
4. **Tell markup parsing** — the string `<Tell:IIDString:0x%08x:name>`
   can appear inside the Say/Tell `message` field, not just in chat
   framing. Every string field that comes from an NPC speech emote is
   subject to it. Parse it as a rich-text span while rendering, not as
   markup in the pipeline.
5. **Sender GUID range `0x50000001 – 0x6FFFFFFF`** — this is the retail
   player-GUID range. Only wrap speech in `<Tell>` markup when the
   sender is in this range. NPCs use other GUID ranges (typically
   landblock-local). `chunk_00570000.c:1189-1195` is the reference.
6. **Emote category filtering by `Quest`** — some NPCs have fifty Use
   emote sets, each with a different `Quest` value. The client never
   sees the filtering; only the one winning emote fires. But plugin
   observers that snoop on the chat stream will not be able to reverse-
   engineer which set fired unless the NPC's sequence is distinctive
   enough. Document this limitation in the plugin API.
7. **Infinite-loop protection** — server aborts nested > 75. If a retail
   NPC has a looping emote set, the loop terminates silently in
   `Enqueue`. Plugins should not rely on seeing the full sequence if
   they observe this abort pattern (rare but possible).
8. **HearChat / ReceiveTalkDirect keywords** — the emote set's `Quest`
   field is a case-insensitive literal match against the player's chat
   message. This is the ONLY way to trigger NPC reactions to arbitrary
   player speech. Document clearly for plugin authors.

Places this port is NOT load-bearing on acdream:

- We do NOT implement QuestManager, EmoteManager, EventManager,
  ContractManager, or the world Quest table on the client. Those are
  server responsibilities.
- We do NOT evaluate emote scripts on the client. We only *observe*
  their output.
- We do NOT store CharacterPropertiesQuestRegistry client-side.
  Everything we display comes from server pushes.

---

## 13. Cross-references & file pointers

**Decompiled retail client:**

- `docs/research/decompiled/chunk_00550000.c:10700-10880` — incoming
  game-event dispatcher switch; shows 0x2BD (Tell), 0x1E0 (EmoteText),
  0x2BB/2BC (HearSpeech), 0x314/0x315 (ContractTracker) handler
  functions.
- `docs/research/decompiled/chunk_006A0000.c:4991` (`FUN_006a5920`) —
  Tell (0x2BD) receive-side dispatch.
- `docs/research/decompiled/chunk_006A0000.c:5036` (`FUN_006a5a20`) —
  EmoteText (0x1E0) receive-side dispatch.
- `docs/research/decompiled/chunk_00570000.c:1165-1205` — text
  rendering for Tell, including `<Tell:IIDString>` markup and the
  player-GUID range test `0x50000001 – 0x6FFFFFFF`.
- `docs/research/decompiled/chunk_00570000.c:740-810` — same markup
  used for allegiance / fellowship / vassal tell variants.
- `docs/research/decompiled/chunk_00570000.c:1500-2900` — game event
  error-code → wide-string lookup table. Contains all the quest-related
  error strings (0x43E "solved too recently", 0x43F "too many times",
  0x445 "requires quest to pick up", 0x474 "must complete quest to use
  portal").
- `docs/research/decompiled/chunk_00580000.c:481-505` — client
  chat-command handler; routes `:` / `;` prefix input to `@emote`
  command.
- `docs/research/decompiled/chunk_00580000.c:2095-2170` — chat command
  registration for `emote` / `emotes`.
- `docs/research/decompiled/chunk_00580000.c:5936-5962` — "Select your
  target before using the %s" path; client-side Use pipeline.

**ACE server (wire + server-side quest evaluation):**

- `references/ACE/Source/ACE.Entity/Enum/EmoteType.cs` — the 122 values.
- `references/ACE/Source/ACE.Entity/Enum/EmoteCategory.cs` — the 39 values.
- `references/ACE/Source/ACE.Entity/Enum/ChatMessageType.cs` — colour
  codes for every chat line.
- `references/ACE/Source/ACE.Entity/Enum/ConfirmationType.cs` —
  Yes_No (7), SwearAllegiance (1), AlterSkill (2), etc.
- `references/ACE/Source/ACE.Entity/Models/PropertiesEmote.cs` — set
  shape.
- `references/ACE/Source/ACE.Entity/Models/PropertiesEmoteAction.cs` —
  action shape.
- `references/ACE/Source/ACE.Server/Managers/QuestManager.cs` —
  HasQuest, CanSolve, IsMaxSolves, Increment, Decrement, Stamp, Erase,
  EraseAll, HasQuestBits/SetQuestBits, HandleKillTask, HandleSolveError,
  HandlePortalQuestError.
- `references/ACE/Source/ACE.Server/WorldObjects/Managers/EmoteManager.cs` —
  the mini-language interpreter (1740+ lines).
- `references/ACE/Source/ACE.Server/Network/GameEvent/GameEventType.cs` —
  opcode list including 0x0004 PopupString, 0x028A WeenieError,
  0x02BD Tell, 0x0274 CharacterConfirmationRequest,
  0x0314 SendClientContractTrackerTable, 0x0315 SendClientContractTracker.
- `references/ACE/Source/ACE.Server/Network/GameMessages/GameMessageOpcode.cs` —
  0x01E0 EmoteText, 0x01E2 SoulEmote, 0x02BB HearSpeech,
  0x02BC HearRangedSpeech.
- `references/ACE/Source/ACE.Server/Network/GameEvent/Events/GameEventTell.cs`
  — server-side Tell packer.
- `references/ACE/Source/ACE.Server/Network/GameEvent/Events/GameEventPopupString.cs` —
  PopupString packer.
- `references/ACE/Source/ACE.Server/Network/GameEvent/Events/GameEventConfirmationRequest.cs` —
  confirmation request packer.
- `references/ACE/Source/ACE.Server/Network/GameEvent/Events/GameEventSendClientContractTracker.cs`
  and `.../GameEventSendClientContractTrackerTable.cs` — tracker
  packers.
- `references/ACE/Source/ACE.Server/Network/GameMessages/Messages/GameMessageHearSpeech.cs`
  / `GameMessageHearRangedSpeech.cs` / `GameMessageEmoteText.cs` /
  `GameMessageSoulEmote.cs` — speech packers.
- `references/ACE/Source/ACE.Server/Network/GameAction/Actions/GameActionEmote.cs`
  / `GameActionSoulEmote.cs` / `GameActionGiveObjectRequest.cs` —
  client → server action handlers.
- `references/ACE/Source/ACE.Server/WorldObjects/Player_Inventory.cs:3190-3478` —
  `HandleActionGiveObjectRequest` and `GiveObjectToNPC` flow.
- `references/ACE/Source/ACE.Server/Network/Structure/ContractTracker.cs` —
  ContractStage enum and packed struct layout.
- `references/ACE/Source/ACE.Database/Models/Shard/CharacterPropertiesQuestRegistry.cs` —
  per-character row.
- `references/ACE/Source/ACE.Database/Models/World/Quest.cs` —
  world quest table.

**DatReaderWriter (what the dat files contain):**

- `references/DatReaderWriter/DatReaderWriter/Generated/Types/Contract.generated.cs` —
  Contract struct (Quest flag name pointers).
- `references/DatReaderWriter/DatReaderWriter/Generated/Types/ChatEmoteData.generated.cs` —
  per-chat-emote dat strings ("MyEmote" = what you see, "OtherEmote" =
  what others see).
- `references/DatReaderWriter/DatReaderWriter/Generated/DBObjs/ContractTable.generated.cs` —
  Contract → ContractTable keyed dict in portal.dat.
- `references/ACE/Source/ACE.DatLoader/Entity/Contract.cs` — ACE's
  reader for the same (byte-compatible).
- `references/ACE/Source/ACE.DatLoader/Entity/ChatEmoteData.cs` —
  ACE's reader for ChatEmoteData.

**Holtburger (Rust client wire behaviour):**

- `references/holtburger/crates/holtburger-protocol/src/messages/chat/events.rs` —
  TellEventData, PopupStringEventData, ChannelBroadcastEventData with
  byte-level pack/unpack impls (ground truth for wire format).
- `references/holtburger/crates/holtburger-protocol/src/messages/chat/types.rs` —
  HearSpeechData, HearRangedSpeechData, ServerMessageData,
  EmoteTextData, SoulEmoteData.
- `references/holtburger/apps/holtburger-cli/src/pages/game/panels/chat.rs` —
  rendering of speech/tell/emote in a terminal (demonstrates the Tell
  markup parsing).

---

## 14. Appendix — EmoteType numeric index

For cross-referencing against decompiled type-dispatch constants. From
`EmoteType.cs` (section 3.2 names, for quick lookup without flipping
windows):

```
  0 Invalid           28 AwardSkillXP         57 ResetHomePosition     86 SetMyQuestCompletions
  1 Act               29 AwardSkillPoints     58 InqFellowQuest         87 MoveToPos
  2 AwardXP           30 InqQuestSolves       59 InqFellowNum           88 LocalSignal
  3 Give              31 EraseQuest           60 UpdateFellowQuest      89 InqPackSpace
  4 MoveHome          32 DecrementQuest       61 StampFellowQuest       90 RemoveVitaePenalty
  5 Motion            33 IncrementQuest       62 AwardNoShareXP         91 SetEyeTexture
  6 Move              34 AddCharacterTitle    63 SetSanctuaryPosition   92 SetEyePalette
  7 PhysScript        35 InqBoolStat          64 TellFellow             93 SetNoseTexture
  8 Say               36 InqIntStat           65 FellowBroadcast        94 SetNosePalette
  9 Sound             37 InqFloatStat         66 LockFellow             95 SetMouthTexture
 10 Tell              38 InqStringStat        67 Goto                   96 SetMouthPalette
 11 Turn              39 InqAttributeStat     68 PopUp                  97 SetHeadObject
 12 TurnToTarget      40 InqRawAttributeStat  69 SetBoolStat            98 SetHeadPalette
 13 TextDirect        41 InqSecondaryAttr     70 SetQuestCompletions    99 TeleportTarget
 14 CastSpell         42 InqRawSecondary      71 InqNumCharacterTitles 100 TeleportSelf
 15 Activate          43 InqSkillStat         72 Generate              101 StartBarber
 16 WorldBroadcast    44 InqRawSkillStat      73 PetCastSpellOnOwner   102 InqQuestBitsOn
 17 LocalBroadcast    45 InqSkillTrained      74 TakeItems             103 InqQuestBitsOff
 18 DirectBroadcast   46 InqSkillSpecialized  75 InqYesNo              104 InqMyQuestBitsOn
 19 CastSpellInstant  47 AwardTrainingCredits 76 InqOwnsItems          105 InqMyQuestBitsOff
 20 UpdateQuest       48 InflictVitaePenalty  77 DeleteSelf            106 SetQuestBitsOn
 21 InqQuest          49 AwardLevelPropXP     78 KillSelf              107 SetQuestBitsOff
 22 StampQuest        50 AwardLevelPropSkill  79 UpdateMyQuest         108 SetMyQuestBitsOn
 23 StartEvent        51 InqEvent             80 InqMyQuest            109 SetMyQuestBitsOff
 24 StopEvent         52 ForceMotion          81 StampMyQuest          110 UntrainSkill
 25 BLog              53 SetIntStat           82 InqMyQuestSolves      111 SetAltRacialSkills
 26 AdminSpam         54 IncrementIntStat     83 EraseMyQuest          112 SpendLuminance
 27 TeachSpell        55 DecrementIntStat     84 DecrementMyQuest      113 AwardLuminance
                      56 CreateTreasure       85 IncrementMyQuest      114 InqInt64Stat
                                                                      115 SetInt64Stat
                                                                      116 OpenMe
                                                                      117 CloseMe
                                                                      118 SetFloatStat
                                                                      119 AddContract
                                                                      120 RemoveContract
                                                                      121 InqContractsFull
                                                                     9001 Enlightenment (ACE-custom)
```

---

**End of R10 deep-dive. Approximate word count: ~5,500.**
