This repository has been archived on 2026-09-16. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
Syplheed-Reborn/docs/re/structures/preset-message-rules.md
Claude (auto) b710c9b9f0 re: Interval and IntervalFluctuation are the cooldown; the chatter system closes
sub_82210C38 retires a line and is the only chatter function reading the rule's
+20/+24 as words: node[+16] = Fluctuation * rand01 + Interval frames, state
0x20.  Interval is a floor, Fluctuation a uniform additive jitter.

The queue node IS the cooldown timer -- the tick counts +16 down and only then
frees the slot, so the already-queued bail in sub_82210670 and the cooldown are
one mechanism: a speaker cannot repeat an event until its node expires.  A
lingering node still holds one of the 128 pool slots.

Also: +20 on the node is a ducking level, raised while a higher-priority line
plays and released on retirement.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PMRJjbxLqZtsb5Vb7KunPE
2026-08-27 17:28:57 +00:00

27 KiB
Raw Blame History

The reactive-chatter rule table

Settled 2026-08-27 (static; regenerator tools/re-capture/preset_messages.py, artefact data/preset-messages.txt).

The corpus already knew that PresetMessage_* is reactive combat chatter — ixud-localised-text.md names the IXUD family ("the bulk: reactive combat chatter keyed by speaker class") and 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 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 nowhereDAMAGED_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 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 — five routes, and all 144 land

The rule tables are pak entries keyed by name_hash, so they had to be named before any of this could be read. Five routes, each an independent transform:

route source named
1 any PresetMessage_* string appearing in any IDXD object, under message\ 136/144
2 the PresetMessage_Phase1/2/3 values of the 22 UnitMessageSet_S<NN>.tbl 136/144
3 strip _msg from a message-table name to get its rule table's name 137/144
4 predicted from the Sperkers roster (below) 6/144
5 sweep of the naming grammar over the 86 <who> tokens routes 14 expose 1/144
union 144/144

Routes 1 and 2 name the same set (checked as a set identity, not a matching count). Route 3 is a strict superset of route 1 and hits no entry that is not a rule table. Every one of the 144 has its _msg companion present.

Names are of the form PresetMessage_<who>[_<NN>][_S<stage>-<phase>].tbl, e.g. PresetMessage_Ellen_03_S03-3.tbl, PresetMessage_ADAN_Camael.tbl — a set is scoped to a character and a stage-phase, which is what makes 144 of them.

The roster predicts the name

Route 4 is the interesting one, because the prediction came first. Among the tables already named, the two-speaker ones expose a transparent rule:

<FACTION>_Fleet<X><Y>   speaks (<FACTION>OP<Y>, <FACTION><X>)
TCAF_..Ship<X>          speaks (ADANPL<X>,      TCAF<X>)

TCAF_Fleet{AA,AB,BA,CA,CB} and TCAF_17thFleet{AA,AB,BA} and TCAF_17thFleetShipC are named; six gaps in those series were predicted from the six unnamed tables' rosters and then hashed:

roster predicted name result
TCAFOPB, TCAFB TCAF_FleetBB 0x59d48b31
TCAFOPB, TCAFB TCAF_17thFleetBB 0x9d29575d
TCAFOPA, TCAFC TCAF_17thFleetCA 0x9d4c5353
TCAFOPB, TCAFC TCAF_17thFleetCB 0x9e527c53
ADANPLA, TCAFA TCAF_17thFleetShipA 0xee14c77c
ADANPLB, TCAFB TCAF_17thFleetShipB 0xef1af07c

6 of 6. The control matters as much as the hits: four same-shaped names the series does not contain — TCAF_FleetCC, TCAF_17thFleetCC, TCAF_ShipA_01, TCAF_17thFleetShipC_01 — score 0 of 4, so this is not "any plausible name hits an unnamed slot".

🔴 Refuted: the undeclared tables are not tutorial chatter

The eight tables that neither route 1 nor route 2 names are undeclared because nothing on the disc declares them — no UnitMessageSet entry, and their own names appear as strings nowhere. UnitMessageSet_S<NN>.tbl exists for 22 of the 28 stages and the six missing are exactly S18S23, the tutorials, so "the undeclared eight are tutorial chatter" was the obvious reading. It is wrong: the eight are PresetMessage_Katana_14_S16-2 and PresetMessage_Margras_04_S14-1 — both story stages — plus the six TCAF fleet/ship tables above, which carry no stage tag at all. Not one is a tutorial.

What that leaves is a different and smaller claim: the tutorials ship no UnitMessageSet, and no rule table is tagged _S18_S23, so on this evidence the six tutorial missions have no reactive chatter of their own.

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.

The seven fields, read off the engine's own loader

Settled 2026-08-27 from sub_82213980, the one function in the executable that references all seven field-name strings (they sit contiguously at 0x820A57940x820A5800; the query is the string-xref join, and the seven xrefs land in that single function). It walks the rule table's records through sub_82448AA0 / sub_824482D0 / sub_82448BC8 and builds a 40-byte runtime object per event. The disc-side value census in the artefact agrees with it at every point.

field disc values (all 9 216 records) what the loader does
EffectiveTime 0 8566, 3 453, 5 187, 10 10 × 60[obj+28]
Interval 0 6820, 3 821, 4 1, 5 894, 30 182, 120 498 × 60[obj+20]
IntervalFluctuation 0 8403, 2 813 × 60[obj+24]
Priority 17, 9 (no 8); 1 is 7 738 byte → [obj+17]
Probability 0 6788, then 1,2,3,5,10,20,25,30,40,50,60,100 byte → [obj+16]; 0 makes the loader skip the record entirely
Pattern Sound 8395, Window 821 string → small enum → [obj+18]
MessageCount 026 pair count; clamped to 32

The three time fields are SECONDS, converted to 60 Hz frames. The loader emits mulli rN, rN, 60 for Interval, IntervalFluctuation and EffectiveTime and for nothing else. Every disc value is a small integer (120 s = two minutes is the largest), which is what makes seconds the only reading that survives.

Pattern is a delivery channel, and the enum has two dead arms. The loader strcmps the value against "Log" → 2, "Window" → 3, "Demo" → 4, defaulting to 1. The disc only ever says Sound (→ default 1) or Window (→ 3); Log and Demo are declared in code and never used in data. Do not conflate this with the radio delivery category (None/Emergency/Killed/ Noise) that mission-phase-deployment.md found in ScriptMessage_S02_msg.tbl — different table, different axis. (Killed does sit at 0x820A5784, immediately before this string block, which is exactly the kind of adjacency that invites the mistake.)

The Yes/No element is a 32-bit mask, and that is why MessageCount is capped at 32. The loader runs two passes over the positional payload: sub_82448BC8(rec, 2·i) collects the even slots (the message ids) and sub_82448BC8(rec, 2·i+1) the odd ones, strcmps each against "Yes" (0x820A57A4), and sets 1 << i in a u32 stored at [obj+32]. The clamp and the mask width are the same constant. Confirmation from the data: the largest MessageCount on the disc is 26, no record exceeds the clamp, and no record's Yes count ever exceeds its MessageCount. This also independently re-derives the pair layout from the executable side, having first been inferred from MessageCount · 2 == positional count.

The 40-byte runtime object

+16  u8   Probability     (per cent)
+17  u8   Priority        (1..9)
+18  u8   Pattern         (1 = Sound, 3 = Window; 2 = Log and 4 = Demo unused)
+20  u32  Interval             * 60   (frames)
+24  u32  IntervalFluctuation  * 60
+28  u32  EffectiveTime        * 60
+32  u32  the Yes bitmask
+36  u32  zero at construction

[obj+16]'s byte is also the loader's gate: it is read with sub_824482D0 and sign-extended, and a zero Probability skips the record before the object is ever allocated.

Residual, characterised

Cross-tabbing MessageCount == 0 against Probability == 0 over all 9 216 records: 6 786 are zero on both (the event is simply not used by that speaker), 2 417 are set on both, 11 carry a Probability but no lines, and 2 carry lines with Probability 0 — those two ship dialogue the loader can never reach, because it skips the record first. Thirteen dead records out of 9 216.

🟡 What this does not settle

All seven fields and the Yes/No flag are settled above; the static side of this system is complete. What is not established: the numeric effect of the ducking level at node[+20] (its mechanism is clear, its attenuation is not), what the state values other than 0x10/0x20 mean, and whether sub_822168E8/sub_82217980 do anything beyond stop/resume. None of these change the data model a port needs.

🔴 One handle was tried and refuted: searching the whole image for functions that touch +16/+17/+18/+20/+28/+32/+36 returns dozens of unrelated matches, because those offsets recur across unrelated layouts. Offset shape is not an identifier — the call graph is. The bounded version of the same search, restricted to this compilation unit after the call graph had established which functions matter, is sound and is what pinned +17/+18. Pattern's two live values are named, but that Window means an on-screen text window is a reading of the name, not something measured.

How the three phase tables are merged — and what that costs

Settled 2026-08-27. sub_82215A58 is the UnitMessageSet reader: it references exactly CrewCount, PresetMessage_Phase1, PresetMessage_Phase2 and PresetMessage_Phase3, and it reaches the loader from one call site (0x82215D98). So a crew's three phase tables are loaded into one map, not three. The map is keyed by a hash of the event record's name (sub_82455C78, the rolling-accumulator / character-sum-top-byte shape idxd-tag-hash.md documents; which member of that family this entry point is was not pinned here, and the finding does not depend on it).

Because all 64 event names recur in every phase table, every merge collides. sub_82213980's insert therefore has a reconciliation arm, and sub_82213840 — a function called from nowhere else — is its comparator.

sub_82213840 compares six of the eight fields

It walks the two message-id vectors elementwise, then compares +16 (Probability), +17 (Priority), +18 (Pattern), +20 (Interval), +24 (IntervalFluctuation) and +28 (EffectiveTime), and returns a bool. It does not look at +32 (the Yes mask) or +36.

On a colliding key the loader compares, and then — whatever the answer — frees the newcomer and keeps the incumbent. A mismatch additionally clears the byte the loader returns (lbz r3, 80(r31) at the epilogue); the caller turns that into a flag at 0x82215D9C0x82215DDC and propagates it.

What the disc actually does with that

Measured over the 22 UnitMessageSet_S<NN>.tbl (numbers in the artefact):

UnitMessageSet records naming ≥ 2 phase tables 298
duplicate-key insertions to reconcile 26 432
identical on the compared fields 26 208 (99.15 %)
different — loader clears its flag 224
compared fields equal but Yes mask differs 0

Of the 224, 189 differ only in the message list — per-phase dialogue variants for the same event and the same timings. The other 35 also move Probability/Priority/Pattern/Interval.

The phase tables are not additive. For a given crew and event, phase 1's rule wins and the later phases' are discarded — so 224 authored variants never play. A port that merges them additively, or that lets a later phase override, will not behave like the game.

The comparator's blind spot is never exercised. There is no record pair that agrees on the six compared fields and disagrees on the Yes mask, so the fact that +32 is excluded from the comparison changes nothing on the shipped disc. Worth recording precisely because it is the kind of gap that looks like a latent bug until it is counted.

Who fires a bark, and the first half of the policy

Settled 2026-08-27, reached through the call graph, not through offset shape (that route is refuted — see the note below).

The event names are executable strings, so the firing sites name themselves

36 of the 64 event names ship as strings in 0x820AEEC80x820B2160, and the functions referencing them are the sites that raise each event:

site events it raises reading
sub_823800A8 17 — DAMAGED*, HP_LESS_*, KILLED*, KILL_UNIT, LOST_{BRIDGE,SHIELD,THRUSTER,TURRET}_BY_PLAYER, SHIELD_DOWNED*, ATTACK_SUCCEEDED damage / destruction
sub_82320B48 15 — all twelve ORDER_/ORDOR_ names plus COUNTER, COUNTER_TO_PLAYER, FOUND_PLAYER squadron orders
sub_8237B020 9 — PLAYER_HP_LESS_*, KILL_PLAYER, DAMAGED_TO_PLAYER, AVOIDED_BY_PLAYER, DAMAGED, KILLED, SHIELD_DOWNED player state
sub_823B1438 PLAYER_AMMO_LESS_10/40 ammunition
sub_82381B10 AVOID_ATTACK, AVOIDED_PLAYER_ATTACK evasion
sub_82398CC0 / sub_823969D0 / sub_82362F18 ATTACK_IS_AVOIDED / EXTENDED / FOUND_PLAYER one each

squadron-orders.md already names sub_82320B48 as the selector for the twelve-string enum and warns it is a comms axis. What it does not say is that the function is one of eight bark firing sites that share a single entry point, and that it also raises three non-order events.

sub_8220FA98 — the shared entry, and the Probability roll

All eight sites call it, and nothing else in the file does more. It takes a lock around this+4, bails unless the enable byte at [this+8843] is set, then:

  1. looks the speaker up in the map at [this+8820];
  2. indexes a vector inside that entry by [this+8816] 1 (a byte; the natural reading is the phase, but that is a reading, not measured);
  3. looks the event name up in that entry's map → the 40-byte rule object;
  4. rolls: f0 = rand01 · 100.0, and if f0 > [rule+16] it returns without firing.

Probability is a straight per-occurrence percentage. The scaling constant at 0x820856F8 is exactly 100.0, read from the image — which is what turns the disc census (1,2,3,5,10,20,25,30,40,50,60,100) from "looks like per cent" into per cent. sub_8220D970 builds the uniform: two rand() calls folded into 16 bits, converted to float.

sub_82210670 — pick a line

Called only when the roll passes. In order:

  1. Duplicate suppression. It walks the list at [this+8736] for a node whose +8 and +12 match this (speaker, rule) pair; if one is pending it returns immediately. The same bark cannot be queued twice.
  2. Eligibility. MessageCount is recomputed from the vector (([rule+8] [rule+4]) >> 2), and a message is eligible only if its bit is clear in the mask at [rule+36] — the word the loader zeroes at construction. So +36 is a runtime exclusion mask over the message list; "already spoken" is the obvious reading, and the write site was not found this iteration.
  3. Uniform pick. f0 = eligible_count · rand01, then walk the eligible bits subtracting 1.0 (the constant at 0x8208583C) until the counter runs out or f0 drops to 0.0 (0x8209FD28). That index is the chosen line.
  4. Hand-off. It reads [rule+18] Pattern, [rule+17] Priority and [rule+28] EffectiveTime, and passes them with the chosen message to sub_822109B0.

A measured negative worth having: the pick path reads +17, +18, +28 and +36 — and not +20 (Interval), +24 (IntervalFluctuation) or +32 (the disc Yes mask). Those three are consumed somewhere after the hand-off, not on the way in. Within the whole chatter compilation unit (0x8220C0000x82216000), +17 and +18 are read by exactly two functions — sub_82213840 (the comparator) and sub_82210670 — which is what makes this attribution safe where a binary-wide offset search is not.

The queue, Priority, and what Yes/No actually means

Settled 2026-08-27 from sub_822109B0 (enqueue) and sub_8220FC50 (the per-frame tick, called from sub_821AB650 and sub_821B5FB8).

The pending queue

sub_822109B0 allocates from a fixed pool: 68-byte nodes starting at this+32, with [this+8740] as the live count and [this+8736] as the list head. If the count is already 128 it returns 0 and the bark is dropped.

A slot is free iff its byte at +23 is zero — and +23 holds Pattern, which is never 0 for a loaded rule (the enum is 1…4). The presentation field doubles as the occupancy flag.

node content
+4 the chosen message id
+8 the speaker entry
+12 the rule object
+16 EffectiveTime (frames)
+20, +21 zero at enqueue
+22 Priority
+23 Pattern — also the in-use flag
+24 state byte, zero at enqueue
+25 the chosen message index

Priority orders the list, and its fast path is dead

Insertion compares Priority against 10. At or above 10 the node is pushed straight onto the front with no ordering; below 10 it is spliced ahead of the first node with a strictly smaller Priority, leaving the list descending by Priority. No shipped record reaches 10 — the disc's range is 1…9 — so the front-push arm never runs on the retail data, and every bark takes the ordered path.

sub_82210AF0 (called only from the tick) walks the list and returns the largest [node+20] among pending nodes whose Priority is higher than the one being considered and whose [node+24] is not 0x10 — a preemption / ducking query against higher-priority speech already queued.

Yes/No is a one-shot flag — the +32/+36 pair closes

The tick ends a line with exactly this, at 0x82210160:

r10 = node[+25]            ; the index of the line just spoken
r9  = rule[+32]            ; the Yes mask, straight off the disc
r8  = rule[+36]            ; the used mask, zeroed by the loader
rule[+36] = ((1 << r10) & r9) | r8

The spoken line's bit enters the used mask only if its Yes bit is set. Since sub_82210670 treats a message as eligible only when its bit in +36 is clear, the meaning is exact:

  • Yes → the line is one-shot. Once spoken it is permanently removed from that rule's pool.
  • No → the line repeats. Its bit never enters +36.

That is what the census counts: 8 940 No and 388 Yes — 388 lines across the disc are authored to be heard once. It also closes the earlier open question "what reads +32": nothing until a line finishes, and then only to gate this one AND.

The tick also branches on Pattern < 3 (using this+8776 on the taken side), so Window (3) is on a different presentation path from Sound (1) — consistent with the two values the disc ships.

Interval and IntervalFluctuation — the cooldown, and the system closes

Settled 2026-08-27 from sub_82210C38, the retire-a-line routine (called from the tick sub_8220FC50 and from sub_8220FB90). It is the only place in the chatter code that reads the rule's +20 and +24 as words, which is what distinguishes them from the byte fields at the same offsets in the queue node.

When a line finishes:

rule = node[+12]
if rule:
    f1  = rand01()                       ; sub_8220D970
    f0  = (float)rule[+24]               ; IntervalFluctuation, frames
    f13 = (float)rule[+20]               ; Interval, frames
    node[+16] = (int)(f0 * f1 + f13)     ; fmadds
else:
    node[+16] = 0
node[+24] = 0x20                         ; state: cooling down

Interval is a floor and IntervalFluctuation is uniform additive jitter: the cooldown is Interval + IntervalFluctuation · rand01 frames, i.e. uniform over [Interval, Interval + IntervalFluctuation). On the disc that is Interval ∈ {0,3,4,5,30,120} seconds with a jitter of 0 or up to +2 seconds (813 records carry IntervalFluctuation 2).

The cooldown is per (speaker, event), and the queue node is the timer. node[+16] — the field that held EffectiveTime while the line was playing — is reused for the cooldown. In state 0x20 the tick decrements it each frame and, only when it reaches zero, unlinks the node, clears [node+23] to free the pool slot and decrements the count:

node[+16] -= elapsed
if node[+16] > 0: keep waiting
else: unlink; node[+23] = 0; [this+8740]--

That closes the loop with the duplicate check in sub_82210670, which refuses to fire when a node with the same (speaker, rule) is already in the list. The "already queued" bail and the cooldown are the same mechanism: a retired line keeps its node alive for Interval + jitter frames, and for exactly that long the same speaker cannot repeat the same event. Nothing per-rule or global is involved — the rule object itself holds no timestamp.

⚠️ Port note: the lingering node still occupies one of the 128 pool slots. A long Interval (120 s = 7 200 frames) parks a slot for two minutes.

The same routine also stops the presentation — sub_822168E8 on this+8776 for Pattern 1, or on node+28 when Pattern ≥ 3 — and, when [node+20] == 1, sweeps the pending list calling sub_82217980(…, 0) on every node whose state is not 0x10. Together with sub_82210AF0 (which returns the largest [node+20] among higher-priority pending nodes) that makes +20 a ducking level: raised while a higher-priority line plays, released when it retires. The exact attenuation is not established here.

🟡 Not settled

  • The policy the seven fields drive — see "what this does not settle" above.
  • Which of the 49 speakers is a wingman — the "13 tables" partition above is a count, not a verified roster.