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/tools/re-capture/archive_naming.py
Claude (auto) a543b8da96 re: correction -- the naming sweep already covered the 24; it just never said so
archive_naming.py already harvests 6027 candidate names under 16 prefixes, and
testing its candidate set directly shows it names all 24 StageParameter_S<NN>
objects, 24/24.  The previous entry presented that naming as new -- it is not.
What was new was the identification (which object is which stage, the shared
_Tutorial table, IsBoss16Enable = S16), not the method.

The real gap, now closed: the sweep reported only per-archive percentages and
never emitted WHICH entry got which name, which is exactly why nobody could say
the settings objects were StageParameter_*.  It now prints the resolved name
families per archive -- 6573 named entries, 1631 families disc-wide.

Determinism caught again by the verify loop: the resolved map was built by
iterating a set, so collided hashes picked a different winner each run.  Now
iterated sorted().  Second time in two iterations -- any map built from a set
needs a sort.

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

86 lines
4.0 KiB
Python

#!/usr/bin/env python3
"""How much of each pak's TOC can be named from the asset strings on the disc.
Harvests every plausible asset-name string from every archive, hashes each under
the known path prefixes, and reports per archive how many of its entries are
explained. Regenerates docs/re/data/archive-naming.txt.
"""
import sys, os, glob, re, collections
HERE = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, HERE)
import unitgroup as U
from unit_substructures import pak_entries
EXT = r'(prt|t32|rat|tbl|xpr|ttc|ttf|TTC|TTF|bin|col|rgn|prm)'
PRE = ['', '2d\\', 'ui\\', 'hud\\', 'Stage\\', 'stage\\', 'message\\', 'language\\',
'prt\\', 'view\\'] + [l + '\\' for l in ('eng', 'jpn', 'fra', 'deu', 'ita', 'esp')]
def main():
paks = sorted(glob.glob('/work/sylph_extract/**/*.pak', recursive=True))
names = set()
toc = {}
for pk in paks:
ents = pak_entries(pk)
toc[os.path.basename(pk)] = [h for h, b in ents]
for h, b in ents:
for m in re.finditer((r'[A-Za-z0-9_\\.\-]{4,60}\.' + EXT).encode(), b):
names.add(m.group(0).decode('latin-1'))
H = set()
for n in names:
for p in PRE:
H.add(U.name_hash(p + n))
print("# How much of each pak's TOC can be named from the disc's own strings")
print("# Regenerate: python3 tools/re-capture/archive_naming.py")
print("# See docs/re/structures/archive-naming.md")
print("\n candidate asset-name strings harvested: %d" % len(names))
print(" path prefixes tried: %d" % len(PRE))
print("\n archive entries named pct")
rows = sorted(((k, v) for k, v in toc.items()), key=lambda r: -len(r[1]))
for nm, ent in rows:
nd = sum(1 for h in ent if h in H)
print(" %-30s %6d %6d %5.1f%%" % (nm, len(ent), nd, 100.0 * nd / len(ent) if ent else 0))
# WHICH entries got named — the per-archive content index. The percentage
# above is a statistic; this is the part that is actually usable.
NAMES = {}
for n in sorted(names): # sorted: a set's order varies per run
for p in PRE:
NAMES.setdefault(U.name_hash(p + n), p + n)
total = sum(1 for ent in toc.values() for h in ent if h in NAMES)
print("\n## What each archive is made of (resolved names, digits collapsed to #)")
print(" total named entries across the disc: %d" % total)
for nm, ent in rows:
res = [NAMES[h] for h in ent if h in NAMES]
if not res:
continue
fam = collections.Counter(
re.sub(r'\d+', '#', r.rsplit('\\', 1)[-1]) for r in res)
print("\n %s %d named, %d families" % (nm, len(res), len(fam)))
for k, c in sorted(fam.items(), key=lambda kv: (-kv[1], kv[0])):
print(" x%-4d %s" % (c, k))
def _ix(t): return U.ixud_hash([ord(c) for c in t])
FN = [('name_hash', U.name_hash), ('tag_hash', U.tag_hash), ('ixud_hash', _ix)]
SUB = ['', '2d\\', 'eng\\', 'Data\\', 'ArmsSt\\', 'Marker\\']
print("\n## CONTROL is the unnameable pair keyed by a DIFFERENT hash?")
print(" (6 prefixes x 3 hash families; the menu paks are the positive control)")
for nm in ['GP_MAIN_GAME_E2D.pak', 'GP_READY_ROOM.pak', 'GP_TITLE.pak', 'GP_PAUSE_MENU.pak']:
S = set(toc[nm])
for fname, f in FN:
best = (0, '')
for p in SUB:
n = sum(1 for v in names if f(p + v) in S)
if n > best[0]: best = (n, p or '<bare>')
print(" %-22s %-10s %4d / %-5d %s" % (nm, fname, best[0], len(S), best[1]))
print("\n## CONTROL the executable holds almost no asset names")
print(" sylpheed.db `strings`: 7140 rows, of which 2 look like asset paths")
print(" Data\\gmicon002_2.t32 and Data\\gmicon006_2.t32")
for n in ['Data\\gmicon002_2.t32', 'Data\\gmicon006_2.t32', 'gmicon002_2.t32']:
print(" %-28s resolves anywhere: %s" % (n, U.name_hash(n) in H))
if __name__ == "__main__":
main()