The open question was whether "last keyframe" holds beyond the two
elements I could score against a capture. It cannot be scored disc-wide --
only two ambiguous elements sit on a screen with a live capture -- but the
blast radius can be measured, and it argues the same way.
genuinely ambiguous elements 2 305
the two rules AGREE on 409 (17.7 %)
they DIFFER on 1 896 (82.3 %)
dwell (current): invisible pose 1 711 (74.2 %), zero-scale 195 (8.5 %)
last : invisible pose 1 618 (70.2 %), zero-scale 43 (1.9 %)
Two things follow. It is not a marginal choice: the rules disagree on 82%
of the affected elements, so "either is fine" is not available. And the
current rule produces 4.5x more degenerate poses -- a zero-scale pose is
collapsed to nothing, i.e. an element's PRE-ROLL before it has grown in,
which is definitionally not a rest. 195 elements currently rest at a frame
they are only passing through, against 43 under last.
That is an argument from the data's own structure rather than from the two
captures, and it points the same direction.
Kept honest: it is indirect. Fewer degenerate results is not the same as
more correct results, and last still returns an invisible pose 70% of the
time -- right for a transient element, wrong for a persistent one. The
default stays put; the numbers are in HANDOFF for whoever decides.
69 lines
2.8 KiB
Python
Executable File
69 lines
2.8 KiB
Python
Executable File
"""What would each rest-pose rule do to the 2 305 genuinely-ambiguous elements?
|
|
|
|
Only two of them sit on a screen with a live capture, so the rule cannot be
|
|
scored disc-wide. What CAN be quantified is the blast radius: how often the
|
|
rules disagree, and how often each returns a pose that draws nothing.
|
|
"""
|
|
import struct, zlib, glob, 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 (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 []
|
|
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,times=[],[]
|
|
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))
|
|
times.append(struct.unpack_from(">i",d,b+36)[0] if b+40<=end else None)
|
|
if poses: out.append((times,poses))
|
|
pos=end
|
|
return out
|
|
def dwell(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 q:(q[0]>>24)&0xff
|
|
n=agree=0
|
|
inv=collections.Counter(); deg=collections.Counter()
|
|
for pak in sorted(glob.glob("/work/sylph_extract/dat/GP_*.pak")):
|
|
for d in entries(pak[:-4]):
|
|
for t,p in elements(d):
|
|
if len(p)==1: continue
|
|
if any(p[i]==p[i+1] for i in range(len(p)-1)): continue
|
|
n+=1
|
|
rd=p[dwell(t,p)]; rl=p[-1]
|
|
agree += rd==rl
|
|
for tag,r in (("dwell",rd),("last",rl)):
|
|
if alpha(r)==0: inv[tag]+=1
|
|
if r[1]==0 or r[2]==0: deg[tag]+=1
|
|
print(f"genuinely ambiguous elements (plateau-less, 2+ keyframes): {n}")
|
|
print(f" the two rules AGREE on : {agree} ({100*agree/n:.1f} %)")
|
|
print(f" they DIFFER on : {n-agree} ({100*(n-agree)/n:.1f} %)")
|
|
print()
|
|
for tag in ("dwell","last"):
|
|
print(f" rule {tag:6}: returns an INVISIBLE pose {inv[tag]:5d} ({100*inv[tag]/n:5.1f} %),"
|
|
f" zero-scale {deg[tag]:4d} ({100*deg[tag]/n:4.1f} %)")
|