From 7d34428bec0d539adb4f08192bf0e53f6fc96e8c Mon Sep 17 00:00:00 2001 From: "Claude (auto)" Date: Thu, 27 Aug 2026 17:42:28 +0000 Subject: [PATCH] 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) Claude-Session: https://claude.ai/code/session_01PMRJjbxLqZtsb5Vb7KunPE --- docs/re/BACKLOG.md | 42 ++++++++++++++ docs/re/INDEX.md | 2 +- docs/re/data/preset-messages.txt | 21 +++++++ docs/re/structures/preset-message-rules.md | 64 ++++++++++++++++++++-- tools/re-capture/preset_messages.py | 33 +++++++++++ 5 files changed, 155 insertions(+), 7 deletions(-) diff --git a/docs/re/BACKLOG.md b/docs/re/BACKLOG.md index ee8eb954..902e6434 100644 --- a/docs/re/BACKLOG.md +++ b/docs/re/BACKLOG.md @@ -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. diff --git a/docs/re/INDEX.md b/docs/re/INDEX.md index eece3254..9b552839 100644 --- a/docs/re/INDEX.md +++ b/docs/re/INDEX.md @@ -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 | diff --git a/docs/re/data/preset-messages.txt b/docs/re/data/preset-messages.txt index b1e181b6..dd47d41e 100644 --- a/docs/re/data/preset-messages.txt +++ b/docs/re/data/preset-messages.txt @@ -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 diff --git a/docs/re/structures/preset-message-rules.md b/docs/re/structures/preset-message-rules.md index 362cebc5..0da5e0e4 100644 --- a/docs/re/structures/preset-message-rules.md +++ b/docs/re/structures/preset-message-rules.md @@ -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-.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. diff --git a/tools/re-capture/preset_messages.py b/tools/re-capture/preset_messages.py index 6c9c8adb..59130371 100644 --- a/tools/re-capture/preset_messages.py +++ b/tools/re-capture/preset_messages.py @@ -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)):