re: all 144 chatter rule tables named; the undeclared 8 are NOT tutorial

Three more naming routes: strip _msg from a message table (137/144, superset
of route 1, zero non-rule hits), predict the name from the Sperkers roster
(6/6, control 0/4), and sweep the naming grammar (1/144).  Union 144/144,
and every rule table has its _msg companion.

Refutes the reading left by 3f20787: the undeclared tables are two story-stage
tables and six TCAF fleet/ship tables, no tutorial content at all.

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:22:19 +00:00
parent 3f20787cdc
commit ecf54a4512
5 changed files with 181 additions and 37 deletions

View File

@@ -8,7 +8,7 @@ in the layout cutscene-message-table.md already documents.
Run: python3 preset_messages.py > ../../docs/re/data/preset-messages.txt
"""
import sys, os, glob, collections
import sys, os, re, glob, collections
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from unit_substructures import pak_entries
import unitgroup as U
@@ -170,9 +170,15 @@ def main():
harvest.add(n)
harvest.add(v)
byharvest = {}
byderived = {}
for c in harvest:
if c.startswith('PresetMessage_'):
byharvest.setdefault(U.name_hash('message\\' + c), 'message\\' + c)
if not c.startswith('PresetMessage_'):
continue
byharvest.setdefault(U.name_hash('message\\' + c), 'message\\' + c)
# route 3: a message table's name gives its rule table's name
if c.endswith('_msg.tbl'):
b = 'message\\' + c[:-len('_msg.tbl')] + '.tbl'
byderived.setdefault(U.name_hash(b), b)
declared = collections.Counter()
ums_found = []
@@ -190,12 +196,60 @@ def main():
for v in declared:
bydecl.setdefault(U.name_hash('message\\' + v), 'message\\' + v)
# route 4: names predicted from the Sperkers roster before they were hashed.
# TCAF_Fleet<X><Y> speaks (TCAFOP<Y>, TCAF<X>); TCAF_..Ship<X> speaks
# (ADANPL<X>, TCAF<X>). Six gaps in two series, all six confirmed.
PREDICTED = ['TCAF_FleetBB', 'TCAF_17thFleetBB', 'TCAF_17thFleetCA',
'TCAF_17thFleetCB', 'TCAF_17thFleetShipA', 'TCAF_17thFleetShipB']
# same-shaped names that the series does NOT contain, as the control
CONTROL = ['TCAF_FleetCC', 'TCAF_17thFleetCC', 'TCAF_ShipA_01',
'TCAF_17thFleetShipC_01']
bypred = {}
for c in PREDICTED:
n = 'message\\PresetMessage_%s.tbl' % c
bypred.setdefault(U.name_hash(n), n)
A = {h: n for h, n in byharvest.items() if h in rule}
B = {h: n for h, n in bydecl.items() if h in rule}
C = {h: n for h, n in byderived.items() if h in rule}
D = {h: n for h, n in bypred.items() if h in rule}
print()
print('naming route 1 (any harvested PresetMessage_ string): %d/%d' % (len(A), len(rule)))
print('naming route 2 (UnitMessageSet_S* declarations) : %d/%d' % (len(B), len(rule)))
print('the two routes agree on the same set : %s' % (set(A) == set(B)))
print('naming route 3 (strip _msg from a message table) : %d/%d' % (len(C), len(rule)))
print('naming route 4 (predicted from the Sperkers roster) : %d/%d' % (len(D), len(rule)))
print('routes 1 and 2 agree on the same set : %s' % (set(A) == set(B)))
print('route 3 is a superset of route 1 : %s' % set(A).issubset(C))
print('route 3 names nothing that is not a rule table : %s' % all(
h in rule for h in byderived if h in entries))
print('CONTROL — same-shaped names outside the series, hits : %d/%d' % (
sum(1 for c in CONTROL if U.name_hash('message\\PresetMessage_%s.tbl' % c) in rule),
len(CONTROL)))
A = dict(A); A.update(C); A.update(D)
# route 5: sweep the naming grammar the other 143 names describe,
# PresetMessage_<who>[_NN][_S<nn>-<p>].tbl, over every <who> already seen.
whos = set()
for n in A.values():
base = n.rsplit('\\', 1)[-1]
m = re.match(r'PresetMessage_(.+?)(?:_\d\d)?(?:_S\d\d-\d)?\.tbl$', base)
if m:
whos.add(m.group(1))
stages = ['_S%02d-%d' % (st, ph) for st in range(1, 30) for ph in range(1, 5)]
E = {}
for w in sorted(whos):
for nn in [''] + ['_%02d' % i for i in range(1, 21)]:
for st in [''] + stages:
n = 'message\\PresetMessage_%s%s%s.tbl' % (w, nn, st)
h = U.name_hash(n)
if h in rule and h not in A:
E[h] = n
print('naming route 5 (sweep of the grammar, %d <who> tokens): %d/%d' % (
len(whos), len(E), len(rule)))
A.update(E)
print('UNION of all five routes : %d/%d' % (len(A), len(rule)))
missing_msg = [n for h, n in A.items()
if U.name_hash(n[:-4] + '_msg.tbl') not in entries]
print('rule tables whose _msg companion is absent : %d' % len(missing_msg))
print('UnitMessageSet_S* tables present: %d of %d (%s)' % (
len(ums_found), len(STAGES), ' '.join(ums_found)))
print('missing: %s' % ' '.join(s for s in STAGES if s not in ums_found))