re: the reactive-chatter rule table behind ORDOR_SQUADRON_EXTENDED

864 records = 144 rule tables per language pack x 6.  One schema for all
9216 event records; MessageCount*2 == positional count with zero mismatches.
Named 136/144 by two independent routes that agree as sets.  2388/2405
message ids join the settled sound-cue table.

Corrects squadron-orders.md: the executable misspells all four SQUADRON
entries of the 0x820AEEB0 enum as ORDOR_, and the disc data matches.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PMRJjbxLqZtsb5Vb7KunPE
This commit is contained in:
Claude (auto)
2026-08-27 16:07:17 +00:00
parent d1368c12db
commit 2c2af7ad78
6 changed files with 783 additions and 5 deletions

View File

@@ -0,0 +1,167 @@
# 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<NN>.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_<lang>.pak`:
| name | role |
|---|---|
| `message\PresetMessage_<who>.tbl` | the **rule** table — this document |
| `message\PresetMessage_<who>_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_<lang>.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
211, 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<NN>.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_<who>[_<NN>]_S<stage>-<phase>.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<NN>.tbl` exists for **22 of the 28 shipped stages**. The six
missing are exactly **S18S23, 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.

View File

@@ -57,11 +57,21 @@ as 🟡 rather than levelled up to match the other three.
## 🔴 A different enum that is NOT this
`sub_82320B48` selects a voice line from
`ORDER_{WINGMAN,SQUAD,SQUADRON}_{FORMATION,ATTACK,COVER,EXTENDED}`
(`0x820AEEB0`…). The names are tempting — ATTACK and COVER look like they should
map onto the order classes — but there is **no evidence** they index the order
types, and it is a UI/comms axis. Not connected; do not conflate them.
`sub_82320B48` selects a voice line from twelve strings at `0x820AEEB0`…. Note
the spelling, which is the game's own and matters because it is the join key:
the WINGMAN and SQUAD names are `ORDER_`, and **all four SQUADRON names are
misspelled `ORDOR_`** — `ORDOR_SQUADRON_{FORMATION,ATTACK,COVER,EXTENDED}`. (An
earlier revision of this document rendered all twelve as `ORDER_`.)
The names are tempting — ATTACK and COVER look like they should map onto the
order classes — but there is **no evidence** they index the order types, and it
is a UI/comms axis. Not connected; do not conflate them.
**✅ Settled 2026-08-27, and the warning above holds.** All twelve are record
names in the reactive-chatter rule table, carrying the same misspelling on the
data side — see [`preset-message-rules.md`](preset-message-rules.md). They are
the twelve *events* "a squadron order was issued", each with its own lines,
priority and interval. Triggers for barks, not order types.
## 🟡 Not settled