blit() and fill_quad() both opened with
let sx_pct = if kf.scale_x == 0 { 100 } else { kf.scale_x };
so an element whose pose is collapsed to nothing rendered at FULL SIZE.
I first described this as "a 1-pixel sliver", reading the .max(1) in the
size arithmetic two lines below -- the guard above it meant .max(1) never
saw a zero. Read the whole function, not the lines you went looking for.
Control run before the change: if 0 meant "unset", something on the disc
would use it throughout. Nothing does.
elements with a keyframe group 15 493
at least one keyframe at scale 0 2 166
EVERY keyframe at scale 0 0
zero is a transient the element grows out of 1 762
ptlogo_eff3.t32 runs 0% -> 200%. An "unset" marker no element ever uses
throughout is not a marker.
Both functions now return without drawing when either scale is 0.
Reach: 24 of 24 renders byte-identical across GP_TITLE (all 16 builds),
GP_PAUSE_MENU and GP_OPTIONS -- additive on every screen the port needs.
195 elements have a guessed rest pose at scale 0 and 126 of those have a
non-zero alpha, so the old code painted them; all 126 are in
GP_READY_ROOM.pak, which S1 already declared a no-go. They are
tactical-map sprites at sx=0/sy=100, mid-horizontal-wipe, that the
coercion drew full-width.
So: a real correctness fix with no visible effect on the menu port.
Suite green, 122 passed / 0 failed across 3 suites at commit time.
79 lines
3.4 KiB
Python
79 lines
3.4 KiB
Python
"""Of the elements whose guessed rest pose has scale 0%, how many actually paint?
|
|
|
|
⚠️ The premise this was first written with was WRONG. blit() did not draw a
|
|
zero-scale element as a 1-pixel sliver: it coerced `scale == 0` to **100 %**, so
|
|
a fully-collapsed element rendered at FULL SIZE. The `.max(1)` further down never
|
|
saw a zero. Read the code before describing what it does -- the guard was two
|
|
lines above the arithmetic I had read.
|
|
|
|
That coercion is now removed (scale 0 draws nothing); this script is what sized
|
|
the change. It counts elements whose guessed rest pose is zero-scale and whose
|
|
alpha is non-zero, i.e. the ones the old code actually painted.
|
|
"""
|
|
import struct, zlib, glob, os, collections
|
|
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 i,h,(zlib.decompress(st[10:]) if st[:2]==b"Z1" else st)
|
|
except Exception: continue
|
|
def elements_t(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
|
|
for _ in range(count):
|
|
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,times=[],[]
|
|
for k in range(frames):
|
|
blk=first+k*KEYFRAME
|
|
if blk+36>len(d) or blk+36>end: break
|
|
poses.append(struct.unpack_from(">I",d,blk)+struct.unpack_from(">II",d,blk+16)+struct.unpack_from(">ii",d,blk+28))
|
|
times.append(struct.unpack_from(">i",d,blk+36)[0] if blk+40<=end else None)
|
|
if poses: out.append((idx,names[idx],times,poses))
|
|
pos=end
|
|
return out
|
|
has_plateau=lambda p: any(p[i]==p[i+1] for i in range(len(p)-1))
|
|
def dwell_pick(t,p):
|
|
best=(0,-1)
|
|
for k in range(len(t)-1):
|
|
if t[k] is None or t[k+1] is None: continue
|
|
d=t[k+1]-t[k]
|
|
if d>=best[1]: best=(k,d)
|
|
return best[0]
|
|
alpha=lambda p:(p[0]>>24)&0xff
|
|
n=paint=0; where=collections.Counter(); ex=[]
|
|
for pak in sorted(glob.glob("/work/sylph_extract/dat/GP_*.pak")):
|
|
for i,h,d in entries(pak[:-4]):
|
|
for idx,name,t,p in elements_t(d):
|
|
if has_plateau(p): continue
|
|
r=p[dwell_pick(t,p)]
|
|
if r[1]==0 or r[2]==0:
|
|
n+=1
|
|
if alpha(r)>0:
|
|
paint+=1; where[os.path.basename(pak)]+=1
|
|
if len(ex)<10: ex.append((os.path.basename(pak),i,idx,name,r[1],r[2],alpha(r)))
|
|
print(f"rest poses with scale 0%%: {n}")
|
|
print(f" ... of which alpha > 0, so blit paints a 1-pixel sliver: {paint}")
|
|
print(f" ... alpha == 0, harmless: {n-paint}")
|
|
if paint:
|
|
print("\nby archive:"); [print(f" {c:4d} {k}") for k,c in where.most_common()]
|
|
print("\nexamples (pak, entry, element idx, name, sx, sy, alpha):")
|
|
[print(" ",e) for e in ex]
|