Files
Sylpheed/tools/re-capture/paint_key_census.py
sim e909c7c133 chore: retire the last dead paths and names from the consolidation
Nothing here changes what a tool computes; it changes where tools look.

- tools/re-capture: 33 censuses globbed /work/sylph_extract, a path that has
  existed nowhere since /work became a clone, so they matched nothing and
  printed empty results. They now resolve the disc through a new disc.py
  from $SYLPHEED_DISC and exit loudly without it (the #44 fix, generalised).
  Nine scripts that imported siblings from the retired Reborn checkout or an
  old session scratchpad now import from their own directory. unitgroup.py
  only needs the variable when --pak is not given.
- sylpheed-xex: the loader only ever uses the XEX2 retail key. The dead
  devkit key and a doc comment claiming a devkit fallback that does not
  exist are gone; Project Sylpheed is a retail XEX2, so no XEX1 key either.
- sylpheed-viewer: real_font_rasterizes looked for /tmp/sylph_extract and so
  always skipped. It reads $SYLPHEED_DISC now, and passes against the disc.
- Comments and docs that named xenia-rs, the Reborn repository or /work/*.pe
  as places to look now name sylpheed.db, Canary's ppc_context.h and the
  flat .pe; docs/re/README.md no longer says the native Canary build does not
  run.

Historical records keep their original paths: findings that were measured
against /work/xenia-rs/sylpheed.db still say so.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-16 22:30:28 +02:00

68 lines
3.2 KiB
Python
Executable File

#!/usr/bin/env python3
"""Census the T8aD paint-order key across every sprite on the disc.
`structures/ui-paint-order-key.md` established that this field sorts a screen's
paint order, from twelve values on two measured screens. This walks all 21 184
sprites so the open question -- what the bits mean -- is asked of the corpus.
./paint_key_census.py <extract root>
The field is a **u16 at +0x0A**: the doc reads a 32-bit word at +0x08 and its
upper half is zero in every sprite.
Two traps this script exists to avoid, both of which produced a wrong published
number before it did:
* **Sprites are mostly RATC children, not pak entries.** A bundle entry's
magic is `RATC`, so filtering pak entries on a `T8aD` magic finds only
4 525 of the 21 184 sprites and 45 of the 216 keys -- and misses GP_TITLE
entirely, which is the screen the page's own evidence comes from.
* **The language variants of GP_MAIN_GAME_*2D.pak are the same screens six
times.** Counting them separately makes every one of their keys look shared
across six paks. They are collapsed into one family here.
"""
import os, sys, glob, struct, re, collections
from disc import disc_root
_SD = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, _SD)
exec(open(os.path.join(_SD, "regn_decode.py")).read().split("# ── the object")[0])
Hh=lambda b,o: struct.unpack_from(">H",b,o)[0]
def fam(n): return re.sub(r'_[DEFIJS]2D\.pak$','_2D.pak',n)
keys=collections.Counter(); kfam=collections.defaultdict(set)
hi_zero=tot=0; langsets=collections.defaultdict(set)
def take(b,o,base,lang):
global hi_zero,tot
tot+=1
if Hh(b,o+8)==0: hi_zero+=1
k=Hh(b,o+10); keys[k]+=1; kfam[k].add(base)
if lang: langsets[lang].add(k)
ROOT = sys.argv[1] if len(sys.argv) > 1 else disc_root()
for f in sorted(glob.glob(ROOT + "/**/*.pak", recursive=True)):
try: E=pak_entries(f)
except Exception: continue
nm=f.split('/')[-1]; base=fam(nm); lang=nm if nm.endswith('2D.pak') else None
for h,b in (E.items() if isinstance(E,dict) else E):
m=bytes(b[:4])
if m==b'T8aD': take(b,0,base,lang)
elif m==b'RATC':
i=b.find(b'T8aD',4)
while i!=-1:
if i+12<=len(b): take(b,i,base,lang)
i=b.find(b'T8aD',i+4)
print(f"ALL T8aD sprites (top-level + RATC children): {tot}")
print(f"upper half of the +0x08 word is zero : {hi_zero}/{tot}")
print(f"distinct u16 keys at +0x0A : {len(keys)}")
fams=sorted({f for s in kfam.values() for f in s})
multi=[k for k in keys if len(kfam[k])>1]
print(f"keys in more than one pak family : {len(multi)}/{len(keys)} = {100*len(multi)/len(keys):.0f}%")
only=[k for k in keys if kfam[k]=={'GP_MAIN_GAME_2D.pak'}]
print(f"keys confined to GP_MAIN_GAME_2D : {len(only)}/{len(keys)}")
print(f"the six language 2D paks have identical key sets: {len({frozenset(v) for v in langsets.values()})==1} ({len(langsets)} paks)")
print(f"\nkeys per family:")
for f in fams:
ks=sorted(k for k in keys if f in kfam[k])
print(f" {f:>28}: {len(ks):3d} keys {ks[0]:04x}..{ks[-1]:04x}")
print(f"\ncommonest 12 keys: {[('%04x'%k,n) for k,n in keys.most_common(12)]}")