# The reactive-chatter rule table **✅ Settled 2026-08-27** (static; regenerator `tools/re-capture/preset_messages.py`, artefact [`data/preset-messages.txt`](../data/preset-messages.txt)). The corpus already knew *that* `PresetMessage_*` is reactive combat chatter — [`ixud-localised-text.md`](ixud-localised-text.md) names the IXUD family ("the bulk: reactive combat chatter keyed by speaker class") and [`mission-phase-deployment.md`](../mission-phase-deployment.md) found the link that reaches it (`message\UnitMessageSet_S.tbl` gives every crew a `PresetMessage_Phase1/2/3`). What neither says is **what is inside the table** — which events exist, what governs when a line fires, and how a line reaches a recording. That is this document. ## Two files per set A chatter set is a **pair** of IDXD objects in each `dat/GP_MAIN_GAME_.pak`: | name | role | |---|---| | `message\PresetMessage_.tbl` | the **rule** table — this document | | `message\PresetMessage__msg.tbl` | the **message** table | The `_msg` half is *not* new: it is exactly the record layout [`cutscene-message-table.md`](cutscene-message-table.md) settled for `MSG_DEMO_*` — a `Generic` count record plus `Message_NNN` records with `ID` / `PageCount` and 9 positional fields per page. What that document does not say is that the same layout carries **137 more objects per language pack** for in-mission chatter, against the 32 cutscene ones it counted. The control run confirms the split cleanly: of the 273 `PresetMessage_*` names that hit an IDXD entry, **136 are rule tables and 137 are `Generic`+`Message_NNN` message tables** (3 of those are `Generic`-only, i.e. empty). ## The rule table **144 per language pack, 864 across the six `GP_MAIN_GAME_.pak`** — one identical count in each, and `ORDOR_SQUADRON_EXTENDED` (the misspelling is the game's, see below) is the largest single record name on the disc for exactly that reason. Every one of the 144 has the **same 65 records**: 64 event records plus a `Sperkers` (sic) roster. One record-name tuple, no variants. ### Event records All 9 216 event records (144 × 64) share **one** named-field schema, with no exceptions: ``` EffectiveTime Interval Priority Probability MessageCount IntervalFluctuation Pattern ``` After the seven named fields come the record's positional payload — [a record's unnamed positional fields are its payload] — in **pairs**: (message id, Yes/No) e.g. ('MSG_BIRD_020', 'No') `MessageCount` is the number of pairs, and that identity is **exact**: `MessageCount · 2 == positional-field count` in **9 216 of 9 216** records, zero mismatches. That is what pins the pairing. The second element of each pair is `No` 8 940 times and `Yes` 388 times; its meaning is not settled. ### The 64 events The full census with per-event table counts is in the artefact. The shape of that census is itself informative: * **Player-facing events are voiced only by the wingman tables.** Every `LOCKON_1..12`, `LOCKON_UP`, `LOCKON_DOWN`, `PLAYER_HP_LESS_10/30/50` and `PLAYER_AMMO_LESS_00/10/40` record carries lines in **exactly 13** of the 144 tables — the same 13 as `ORDER_WINGMAN_{FORMATION,ATTACK,COVER,EXTENDED}`. * The squad/squadron order events carry lines in **7** tables each. * Generic combat events are near-universal: `DAMAGED_BY_PLAYER_GREATER_50` 128, `DAMAGED` 124, `HP_LESS_10` and `HP_LESS_50` 121 each. * **Five events are declared everywhere and populated nowhere** — `DAMAGED_BY_PLAYER_50_30`, `HP_LESS_30`, `RETURNED`, `TALK`, `TM_TO_PLAYER` have `MessageCount 0` in all 144 tables. They are slots the shipped data never uses. Note the near-miss pairs that are *not* redundant: `HP_LESS_*` (the speaker's own hull) versus `PLAYER_HP_LESS_*`, and `AVOIDED_BY_PLAYER` versus `AVOIDED_PLAYER_ATTACK`. The `_PLAYER` half of each pair is the smaller population. ### `Sperkers` The last record is a roster of speaker names as **field names** with empty values — `CharacterELLEN`, `CharacterADANCAMAEL`, … **49 distinct speakers** across the 144 tables. Most tables name one speaker (84 of 144); the rest name 2–11, and three name none. ## The join to audio is the settled cue table The pair's first element is a message id, and it resolves through the cue index [`sound-cue-table.md`](sound-cue-table.md) already documents — `MSG_` becomes `VOICE_`, then `SOUNDS` in `dat/tables.pak` gives the numeric id and `FILES` the `.slb` bank. **2 388 of 2 405 distinct message ids resolve (99.3 %).** The 17 that do not are characterised, not swept up: 8 are ordinary four-letter-family ids (`MSG_ADAN_569/668/788/789`, `MSG_ADPL_045/046/047`, `MSG_RHIN_341`) and 9 are `MSG_VOICE_*`, seven of them the consecutive run `MSG_VOICE_D_443`…`_449`. A consecutive run with no recording reads as content cut late. All 17 are listed in the artefact. ⚠️ **The wrinkle in that doc is real and I walked into it.** Rewriting `MSG_` to `VOICE_` unconditionally gives `VOICE_VOICE_D_443`, which resolved only 2 353 of 2 405 and would have been written up as "52 lines with no recording", with 44 of them a naming artefact. `sound-cue-table.md` states the exception explicitly — a name that already carries `VOICE_` keeps the one it has — and applying it moves the count to 2 388. Recorded here because the same trap is one line of code away in any port. ## Naming, by two independent routes that agree The rule tables are pak entries keyed by `name_hash`, so they had to be named before any of this could be read. 1. **Harvested strings** — every `PresetMessage_*` string appearing anywhere in any IDXD object on the disc, hashed under the `message\` prefix: **136/144**. 2. **Declarations** — the `PresetMessage_Phase1/2/3` values of the 22 `message\UnitMessageSet_S.tbl` tables: **136/144**. The two routes name **the same set** (verified as a set identity, not a matching count). Names are of the form `PresetMessage_[_]_S-.tbl`, e.g. `PresetMessage_Ellen_03_S03-3.tbl`, `PresetMessage_ADAN_Camael.tbl` — so a set is scoped to a character *and* a stage-phase, which is what makes 144 of them. ### Residual, characterised * **8 rule tables stay unnamed** and — this is the point — they are unnamed by *both* routes: nothing on the disc declares them. Their hashes are in the artefact. * `UnitMessageSet_S.tbl` exists for **22 of the 28 shipped stages**. The six missing are exactly **S18–S23, the tutorials**, which is the same set that has no `AIParams_SNN.tbl` ([`challenge-mission-gate.md`](challenge-mission-gate.md)). So the 8 undeclared tables are plausibly tutorial chatter, but that is a reading — no tutorial `UnitMessageSet` exists to confirm it. * The 22 stage tables declare **137** distinct `PresetMessage` sets against 136 rule tables resolved, so one declared set has no rule table under the `message\` prefix. ## ✅ Correction to `squadron-orders.md` That document renders the voice-line enum at `0x820AEEB0` as `ORDER_{WINGMAN,SQUAD,SQUADRON}_{FORMATION,ATTACK,COVER,EXTENDED}`. The executable's own strings are **not** uniform: the WINGMAN and SQUAD names are `ORDER_`, and all four SQUADRON names are misspelled **`ORDOR_`**. The disc data carries the identical misspelling, which is how the two sides were joined. That document's 🔴 warning — that this enum is a UI/comms axis and is **not** the order-class enum — is **confirmed and now explained**: all twelve names are record names in this chatter rule table, i.e. they are the twelve *events* "a squadron order was issued", each with its own lines, priority and interval. They are triggers for barks, not order types. ## 🟡 Not settled * What `EffectiveTime`, `Interval`, `IntervalFluctuation`, `Priority`, `Probability` and `Pattern` mean numerically — they are read here as a schema, not measured against the running game. * The `Yes`/`No` second element of each pair. * Which of the 49 speakers is a wingman — the "13 tables" partition above is a count, not a verified roster.