re: the player-facing chatter partition; correcting the wingman claim

The 13 tables that voice every player-facing event are all
PresetMessage_Katana_*, the PLAYER's own sets -- unit-group-table.md already
names the link (DeltaSaber_T_Player carries msg=MessageSet_Katana).  Exact
partition: 13 voice all 14 events, 131 voice none, 0 voice some, and no
non-Katana table voices any of them.  CharacterKATANA is the only speaker
exclusive to the set.

Corrects preset-message-rules.md, which called those the wingman tables.  The
wingman roster is owned by isl-condition-builtins.md (UNITS: Bird1-Sandra ...
Rhino2-Katana, Rhino3-Ellen) and needed no experiment.

Residual: Katana_09_S10-1 and Katana_14_S16-2 voice none of the 14.

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 17:42:28 +00:00
parent b710c9b9f0
commit 36f97c6322
5 changed files with 155 additions and 7 deletions

View File

@@ -4390,3 +4390,45 @@ firing, probability, pick, queue, priority, one-shot flag and cooldown.
numeric attenuation of the ducking level, the meaning of node states other than
`0x10`/`0x20`, and whether `sub_822168E8`/`sub_82217980` do more than
stop/resume. ❔ Still open elsewhere: which of the 49 speakers are wingmen.
---
## ✅❌ 2026-08-27 — the player-facing partition, and a correction I owed
**Docs checked, and two of them already owned the question I set out to ask:**
* [`isl-condition-builtins.md`](structures/isl-condition-builtins.md) **owns the
wingman roster** — `UNITS` in `GP_HANGAR_ARSENAL.pak` is seven fields pairing a
craft slot with a pilot (`Bird1-Sandra`, `Bird2-Billy`, `Bird3-Antonius`,
`Bird4-Carl`, `Rhino1-Raymond`, `Rhino2-Katana`, `Rhino3-Ellen`) and names it
as such. So "which of the 49 speakers are wingmen" **did not need an
experiment**.
* [`unit-group-table.md`](structures/unit-group-table.md) **owns the player
link** — Stage 02's own flight is `UN_f001_TCAF_DeltaSaber_T_Player` with
`msg=MessageSet_Katana`, `id=Character_Player_Test`, beside
`msg=MessageSet_Ellen`.
* `hangar-loadout-system.md` owns the `PlayerSET_*` rows.
❌ **Correction to `preset-message-rules.md` (mine).** It said the 14
player-facing events are "voiced only by the wingman tables". Wrong twice: the 13
tables are **all `PresetMessage_Katana_*`**, i.e. the **player's** sets, and the
wingman roster is a separate already-settled list in which `Rhino2-Katana` is the
player's own slot. Corrected in place with a ❌ note.
✅ **What the experiment did establish — an exact partition.** Of the 144 rule
tables: **13 voice all 14** player-facing events, **131 voice none**, and
**0 voice some**. Not a tendency — zero partials. And **none of the 129
non-Katana tables voices *any* of the 14**, so `LOCKON_*`, `PLAYER_HP_LESS_*`,
`PLAYER_AMMO_LESS_*` and `ORDER_WINGMAN_*` are exclusively the player's own
callouts. `CharacterKATANA` is the **only** one of the 15 speakers in that set
exclusive to it. Regenerator extended; artefact carries the partition.
🟡 **Residual:** there are **15** Katana tables, and the two that voice none are
`Katana_09_S10-1` and `Katana_14_S16-2` — all-or-nothing like the rest. Why those
two stage-phases opt out is not established from the disc.
**Method note:** the grep that would have caught this ran *after* the experiment
was designed, not before. The rule says grep the doc that owns the DATA first;
here the data was "wingman", the owning doc was `isl-condition-builtins.md`, and
reading it first would have reframed the iteration rather than corrected it
afterwards.

View File

@@ -98,7 +98,7 @@ files, which is how the same ground got covered twice.
| [`structures/sound-pak-contents.md`](structures/sound-pak-contents.md) | Census of `sound.pak`, and the limit of the leading-region rule | ✅ CONFIRMED, 5 135/5 135 names hash into the TOC, **9 519/9 519** entries accounted for, and a full 4 114-bank manifest (408.3 min of audio) computed from PsuedoBytesPerSec without decoding; ⚠️ leading-region rule holds for 1 571/4 382 eng and 0/5 100 jpn |
| [`structures/sound-cue-table.md`](structures/sound-cue-table.md) | The cue index in `tables.pak` — message id -> cue -> sound id -> `.slb` bank | ✅ CONFIRMED, 1 326/1 338 script message ids bind to a bank; SOUNDS and FILES agree on the same 12 absentees, 0 orphan files |
| [`structures/cutscene-message-table.md`](structures/cutscene-message-table.md) | Cutscene dialogue — speaker, portrait, on-screen seconds, audio cue per page | ✅ CONFIRMED, field count = 9·PageCount+2 for all 7 PageCounts, 1 252/1 252 caption keys match, 138 ids close both ways |
| [`structures/preset-message-rules.md`](structures/preset-message-rules.md) | The reactive-chatter rule table — 64 events, the (message, Yes/No) pair payload, the `Sperkers` roster | ✅ CONFIRMED, the seven fields read off `sub_82213980`, phase tables merge non-additively (224 variants discarded); 8 firing sites share one entry, Probability is a per-cent roll against rand*100.0; Yes = one-shot line; queue is Priority-ordered and capped at 128; the cooldown is Interval + Fluctuation*rand held in the lingering queue node (three are seconds ×60, the Yes/No element is a u32 mask, hence the 32 clamp); MessageCount·2 == positional count in 9 216/9 216; five naming routes union to 144/144 (six names predicted from the speaker roster before hashing, control 0/4); 2 388/2 405 message ids bind to a cue |
| [`structures/preset-message-rules.md`](structures/preset-message-rules.md) | The reactive-chatter rule table — 64 events, the (message, Yes/No) pair payload, the `Sperkers` roster | ✅ CONFIRMED, the seven fields read off `sub_82213980`, phase tables merge non-additively (224 variants discarded); 8 firing sites share one entry, Probability is a per-cent roll against rand*100.0; Yes = one-shot line; queue is Priority-ordered and capped at 128; the cooldown is Interval + Fluctuation*rand held in the lingering queue node; the 14 player-facing events are voiced by the player set alone (13/15 Katana tables, 0/129 others) (three are seconds ×60, the Yes/No element is a u32 mask, hence the 32 clamp); MessageCount·2 == positional count in 9 216/9 216; five naming routes union to 144/144 (six names predicted from the speaker roster before hashing, control 0/4); 2 388/2 405 message ids bind to a cue |
| [`structures/isl-mission-timer.md`](structures/isl-mission-timer.md) | The mission timer — two sequential countdowns, not a limit and a warning | ✅ CONFIRMED — read from `sub_822639B8`; the threshold reading is |
| [`structures/archive-naming.md`](structures/archive-naming.md) | Which archives the disc can name — 100% for menu paks, **0.0%** for the six 2D paks and `GP_READY_ROOM` | ✅ CONFIRMED — 6027 names × 16 prefixes, bimodal |
| [`structures/hud-config.md`](structures/hud-config.md) | The in-game HUD configuration — 16 records, 419 asset paths, and a per-stage `ResourceTable` | ✅ CONFIRMED — 0 of 419 config paths resolve as pak entries |

View File

@@ -193,6 +193,27 @@ distinct declared PresetMessage tables: 137
CONTROL — PresetMessage_* names that hit an IDXD entry but are NOT rule tables: 137
their record-name families: {('Generic', 'Message_'): 134, ('Generic',): 3}
player-facing events (14 of the 64): who voices them
tables voicing ALL of them : 13
tables voicing NONE : 131
tables voicing SOME : 0 []
all message\PresetMessage_Katana_01_S01-1.tbl
all message\PresetMessage_Katana_02_S01-2.tbl
all message\PresetMessage_Katana_03_S02-1.tbl
all message\PresetMessage_Katana_04_S02-2.tbl
all message\PresetMessage_Katana_05_S03-3.tbl
all message\PresetMessage_Katana_06_S07-1.tbl
all message\PresetMessage_Katana_07_S09-2.tbl
all message\PresetMessage_Katana_08_S09-3.tbl
all message\PresetMessage_Katana_10_S11-1.tbl
all message\PresetMessage_Katana_11_S12-1.tbl
all message\PresetMessage_Katana_12_S13-1.tbl
all message\PresetMessage_Katana_13_S15-1.tbl
all message\PresetMessage_Katana_15_S28.tbl
speakers in the all-set: 15
exclusive to it: ['CharacterKATANA']
also elsewhere : 14
rule tables, by name:
0xde14f001 message\PresetMessage_ADAN_Camael.tbl
0x31092781 message\PresetMessage_ADAN_Commander.tbl

View File

@@ -67,10 +67,12 @@ mismatches. That is what pins the pairing. The second element of each pair is
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}`.
* **Player-facing events are voiced only by the PLAYER's own tables** — see
"the player-facing partition" below. 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}`. (❌ An earlier revision of
this document called those "the wingman tables". They are not.)
* 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.
@@ -532,8 +534,58 @@ 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.
## ✅ The player-facing partition — and a correction
**Settled 2026-08-27.** The 13 tables that voice every player-facing event are
**all `PresetMessage_Katana_NN_S<stage>-<phase>.tbl`** — the player character's
own chatter sets.
| | |
|---|---|
| tables voicing **all 14** player-facing events | **13** |
| tables voicing **none** | **131** |
| tables voicing **some** | **0** |
Zero partials: the split is exact, not a tendency. And it is not merely that few
others voice them — **none of the 129 non-Katana tables voices *any* of the 14**.
There are **15** Katana tables in total; the two that voice none are
`Katana_09_S10-1` and `Katana_14_S16-2`, and they are all-or-nothing like
everything else. Why those two stage-phases opt out is not established.
Of the 15 speakers named across the 13, **`CharacterKATANA` is the only one
exclusive to them**; the other 14 all appear in other tables too.
[`unit-group-table.md`](unit-group-table.md) supplies the link that makes this
make sense — the player's own flight in Stage 02 reads
```
unit=UN_f001_TCAF_DeltaSaber_T_Player msg=MessageSet_Katana id=Character_Player_Test
unit=UN_f001_TCAF_DeltaSaber_T msg=MessageSet_Ellen id=ELLEN
```
so `MessageSet_Katana` is the message set attached to the **player craft**.
`LOCKON_*` is the player's own lock-on callout, `PLAYER_AMMO_LESS_*` the player's
own ammunition, and `ORDER_WINGMAN_*` the player *issuing* an order — all of
which belong on the player's set and nowhere else.
### ❌ Correction, and the doc that already owned the question
The question this iteration set out to answer — "which of the 49 speakers are
wingmen" — was **already answered elsewhere and did not need this experiment**.
[`isl-condition-builtins.md`](isl-condition-builtins.md) owns it: the `UNITS`
record in `GP_HANGAR_ARSENAL.pak` is seven fields pairing a craft slot with a
pilot — `Bird1-Sandra`, `Bird2-Billy`, `Bird3-Antonius`, `Bird4-Carl`,
`Rhino1-Raymond`, `Rhino2-Katana`, `Rhino3-Ellen` — and it names that **the
wingman roster**. [`hangar-loadout-system.md`](hangar-loadout-system.md) owns the
`PlayerSET_*` rows that go with it.
So the earlier phrasing here was wrong twice over: the 13 are the **player's**
tables, not wingman tables, and the wingman roster is a separate, already-settled
list in which `Rhino2-Katana` is the player's own slot.
## 🟡 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.
* Why `Katana_09_S10-1` and `Katana_14_S16-2` opt out of the player-facing
events entirely.

View File

@@ -362,6 +362,39 @@ def main():
oshape[tuple(sorted({r['squadron'].rstrip('0123456789') for r in recs}))] += 1
print(' their record-name families: %s' % dict(oshape))
# the player-facing partition: which tables voice LOCKON_* / PLAYER_* /
# ORDER_WINGMAN_*, and who speaks in them
PLAYER_FACING = ['LOCKON_1', 'LOCKON_12', 'LOCKON_UP', 'LOCKON_DOWN',
'PLAYER_HP_LESS_10', 'PLAYER_HP_LESS_30', 'PLAYER_HP_LESS_50',
'PLAYER_AMMO_LESS_00', 'PLAYER_AMMO_LESS_10', 'PLAYER_AMMO_LESS_40',
'ORDER_WINGMAN_ATTACK', 'ORDER_WINGMAN_COVER',
'ORDER_WINGMAN_EXTENDED', 'ORDER_WINGMAN_FORMATION']
full, none_, partial = [], [], []
spk_full, spk_rest = set(), set()
for h, recs in rule.items():
byname = {r['squadron']: r for r in recs}
got = [k for k in PLAYER_FACING
if any(n is None for _, n, _ in byname[k]['fields'])]
who = [n for _, n, _ in byname['Sperkers']['fields'] if n]
nm = A.get(h, '%#010x' % h)
if len(got) == len(PLAYER_FACING):
full.append(nm); spk_full.update(who)
elif not got:
none_.append(nm); spk_rest.update(who)
else:
partial.append((nm, len(got))); spk_rest.update(who)
print()
print('player-facing events (%d of the 64): who voices them' % len(PLAYER_FACING))
print(' tables voicing ALL of them : %d' % len(full))
print(' tables voicing NONE : %d' % len(none_))
print(' tables voicing SOME : %d %s' % (len(partial), partial))
for nm in sorted(full):
print(' all %s' % nm)
print(' speakers in the all-set: %d' % len(spk_full))
print(' exclusive to it: %s' % sorted(spk_full - spk_rest))
print(' also elsewhere : %d' % len(spk_full & spk_rest))
print()
print('rule tables, by name:')
for h in sorted(rule, key=lambda k: A.get(k, '~%#010x' % k)):