Files
Sylpheed/tools/re-capture/plateauless_endstate.py
sim ac0ad579fd 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

64 lines
2.8 KiB
Python
Executable File

"""Plateau-less elements whose LAST keyframe is visible -- do they contradict
the entry->hold->exit model, or are they 'slides in and stops'?"""
import struct, zlib, glob, os, collections
from disc import disc_root
DECL_AT, DECL_ENTRY, KEYFRAME = 0x20, 60, 40
def entries(base):
stub=open(base+".pak","rb").read()
if stub[:4]!=b"IPFB": return
n=struct.unpack_from(">I",stub,4)[0]
segs=sorted(glob.glob(base+".p[0-9][0-9]"))
if not segs: return
blob=b"".join(open(s,"rb").read() for s in segs)
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: yield (zlib.decompress(st[10:]) if st[:2]==b"Z1" else st)
except Exception: continue
def elements(d):
if d[:4]!=b"RATC": return []
count=struct.unpack_from(">I",d,0x14)[0]
if not (0<count<4096): return []
names=[]
for i in range(count):
o=DECL_AT+i*DECL_ENTRY
if o+DECL_ENTRY>len(d): return []
names.append(d[o:o+28].split(b"\0")[0].decode("ascii","replace"))
out,pos=[],DECL_AT+count*DECL_ENTRY
while True:
if pos+8>len(d): break
idx,frames=struct.unpack_from(">II",d,pos)
if idx>=count or frames==0 or frames>4096: break
first=pos+12; end=first+frames*KEYFRAME-4
poses=[]
for k in range(frames):
b=first+k*KEYFRAME
if b+36>len(d) or b+36>end: break
poses.append(struct.unpack_from(">I",d,b)+struct.unpack_from(">II",d,b+16)+struct.unpack_from(">ii",d,b+28))
if poses: out.append((names[idx],poses))
pos=end
return out
alpha=lambda q:(q[0]>>24)&0xff
mono=stuck=other=0
ex=[]
for pak in sorted(glob.glob(disc_root() + "/dat/GP_*.pak")):
for d in entries(pak[:-4]):
for nm,p in elements(d):
if len(p)==1: continue
if any(p[i]==p[i+1] for i in range(len(p)-1)): continue
if alpha(p[-1])==0: continue # ends invisible: transient
a=[alpha(q) for q in p]
if all(a[i]<=a[i+1] for i in range(len(a)-1)): # alpha never decreases
mono+=1
if len(ex)<8: ex.append((os.path.basename(pak), nm, a, [(q[1],q[2]) for q in p]))
elif a[-1]==max(a): stuck+=1
else: other+=1
tot=mono+stuck+other
print(f"plateau-less, multi-keyframe, ending VISIBLE: {tot}")
print(f" alpha never decreases (a fade/slide IN that stops): {mono} ({100*mono/max(tot,1):.1f} %)")
print(f" ends at its maximum alpha but dips on the way : {stuck} ({100*stuck/max(tot,1):.1f} %)")
print(f" ends visible but NOT at maximum alpha : {other} ({100*other/max(tot,1):.1f} %)")
print("\nexamples of the monotone kind (alpha sequence, then scale pairs):")
for e in ex: print(" ", e[0], e[1], e[2], e[3])