Files
Sylpheed/tools/re-capture/eff_bit_alpha_test.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

60 lines
2.8 KiB
Python
Executable File

"""Does bit 0x02 separate sprites by their RGB-vs-alpha content?
Premultiplied alpha predicts RGB <= A everywhere for the flagged group. Tested
below and refuted. What remains is a description: how often RGB exceeds A, which
is the signature of glow art (bright colour carried at low alpha).
Bit comes from the T8aD header; the name from the string immediately preceding
it (validated 17/18 on build 4 against the RATC child order).
"""
import struct, zlib, glob, re, os
from disc import disc_root
import numpy as np
from PIL import Image
NAME = re.compile(rb'[A-Za-z0-9_.]{2,31}\x00')
base = disc_root() + "/dat/GP_TITLE"
stub = open(base + ".pak", "rb").read()
n = struct.unpack_from(">I", stub, 4)[0]
blob = b"".join(open(s, "rb").read() for s in sorted(glob.glob(base + ".p[0-9][0-9]")))
flags = {} # (entry_hash, name, w, h) -> flags
for i in range(n):
h_, off, sz = struct.unpack_from(">III", stub, 0x10 + 12 * i)
st = blob[off:off + sz]
if len(st) < 10: continue
try: d = zlib.decompress(st[10:]) if st[:2] == b"Z1" else st
except Exception: continue
for m in re.finditer(b"T8aD", d):
o = m.start()
try:
fl = struct.unpack_from(">I", d, o + 4)[0]
w = struct.unpack_from(">I", d, o + 0x14)[0]
hh = struct.unpack_from(">I", d, o + 0x18)[0]
except Exception: continue
if not (0 < w <= 4096 and 0 < hh <= 4096): continue
ms = list(NAME.finditer(d[max(0, o - 64):o]))
if not ms: continue
flags[(f"{h_:08x}", ms[-1].group()[:-1].decode("latin1"), w, hh)] = fl
rows = []
for f in sorted(glob.glob("/tmp/tex/*.png")):
b = os.path.basename(f)
m = re.match(r"([0-9a-f]{8})_(.+)_(\d+)x(\d+)\.png$", b)
if not m: continue
key = (m.group(1), m.group(2), int(m.group(3)), int(m.group(4)))
fl = flags.get(key)
if fl is None: continue
a = np.asarray(Image.open(f).convert("RGBA")).astype(int)
rgb = a[:, :, :3].max(axis=2); al = a[:, :, 3]
rows.append((bool(fl & 2), 100 * float((rgb > al).mean()), m.group(2)))
s = [r[1] for r in rows if r[0]]; c = [r[1] for r in rows if not r[0]]
print(f"matched {len(rows)} decoded textures to a T8aD flag word")
print(f" bit SET n={len(s):3d} mean %(RGB>A) {np.mean(s):6.2f} median {np.median(s):6.2f}")
print(f" bit clear n={len(c):3d} mean %(RGB>A) {np.mean(c):6.2f} median {np.median(c):6.2f}")
print(f"\n premultiplied would require ~0% for the flagged group -> REFUTED")
# separability: what threshold best splits them, and how well?
best = (0, None)
for t in np.arange(0, 100, 0.5):
acc = (sum(x > t for x in s) + sum(x <= t for x in c)) / len(rows)
if acc > best[0]: best = (acc, t)
print(f" best single-threshold accuracy: {100*best[0]:.1f}% at %(RGB>A) > {best[1]}")
print(f" (base rate, always-guess-majority: {100*max(len(s),len(c))/len(rows):.1f}%)")