re(ui): the structural case for last -- 2 293 of 2 305, checked disc-wide
The weakness in the rest-rule finding was that `last` had been SCORED on only two elements. It cannot be scored on more -- only two ambiguous elements sit on a screen with a live capture -- but the entry -> hold -> exit model makes a prediction that can be checked on all 2 305: what does each element's FINAL keyframe look like? final keyframe invisible (a = 0) 1 618 transient: gone at rest final keyframe visible, at max alpha 675 faded in and stopped final keyframe visible, BELOW max alpha 12 genuinely unclear Of the 687 that end visible, 472 have monotonically non-decreasing alpha -- a plain fade-in that stops, [0, 255] over two keyframes in the commonest case (pjex_eff.rat, pghud_speed_cut.t32) -- and another 203 end at their maximum after dipping. So `last` is structurally defensible for 2 293 of 2 305 (99.5 %), against a dwell rule that returns a mid-movement frame by construction. Observed correct for 2, structural for 675, model-consistent for 1 618, unclear for 12. The assumption carrying the 1 618 is stated rather than buried: that a plateau-less element's animation has finished by the time the screen is settled. The draw log establishes exactly this for the two splash glows (drawn frames 94-115, logos 116-211) and establishes nothing for the rest. Default still unchanged. The case is now observational, structural and model-based rather than two data points, but it would move 1 896 elements and the decision belongs with whoever owns the renderer.
This commit is contained in:
@@ -429,6 +429,13 @@ authored version can be deleted.
|
||||
**Three independent observables — animation timing, static composites and the
|
||||
per-frame draw log — all support: plateau where there is one, last keyframe
|
||||
where there is not.**
|
||||
📊 **Structurally, `last` is defensible for 2 293 of the 2 305 (99.5 %)**: 1 618
|
||||
end invisible (a transient, gone at rest), 675 end at maximum alpha (faded in
|
||||
and stopped — the final pose *is* settled), and only **12** end visible below
|
||||
full alpha, which are genuinely unclear. The dwell rule returns a mid-movement
|
||||
frame by construction. ⚠️ The 1 618 rest on an assumption worth seeing: that
|
||||
such an element's animation has finished by the time the screen settles — shown
|
||||
by the draw log for the two splash glows, unshown for the rest.
|
||||
📊 The disc-wide blast radius, for whoever decides: the rules **differ on 82.3 %**
|
||||
of those 2 305, so "either is fine" is not available — and the current rule
|
||||
returns a **zero-scale** (collapsed, pre-roll) pose for **195** of them against
|
||||
|
||||
16
docs/re/data/plateauless-endstate.txt
Normal file
16
docs/re/data/plateauless-endstate.txt
Normal file
@@ -0,0 +1,16 @@
|
||||
# tools/re-capture/plateauless_endstate.py over dat/GP_*.pak -- 2026-08-29
|
||||
|
||||
plateau-less, multi-keyframe, ending VISIBLE: 687
|
||||
alpha never decreases (a fade/slide IN that stops): 472 (68.7 %)
|
||||
ends at its maximum alpha but dips on the way : 203 (29.5 %)
|
||||
ends visible but NOT at maximum alpha : 12 (1.7 %)
|
||||
|
||||
examples of the monotone kind (alpha sequence, then scale pairs):
|
||||
GP_BUNK.pak pjex_eff.rat [0, 255] [(100, 100), (100, 100)]
|
||||
GP_BUNK.pak pjex_eff.rat [0, 255] [(100, 100), (100, 100)]
|
||||
GP_DEBRIEFING_PILOTLOG.pak pjex_eff.rat [0, 255] [(100, 100), (100, 100)]
|
||||
GP_DEBRIEFING_PILOTLOG.pak pjex_eff.rat [0, 255] [(100, 100), (100, 100)]
|
||||
GP_DEBRIEFING_PILOTLOG.pak pjex_eff.rat [0, 255] [(100, 100), (100, 100)]
|
||||
GP_DEBRIEFING_PILOTLOG.pak pjex_eff.rat [0, 255] [(100, 100), (100, 100)]
|
||||
GP_MAIN_GAME_D2D.pak pghud_speed_cut.t32 [0, 255] [(100, 100), (100, 100)]
|
||||
GP_MAIN_GAME_D2D.pak pgmsg_restart_sub.rat [255, 255] [(106, 106), (100, 100)]
|
||||
@@ -574,3 +574,32 @@ produces, and why it scored +0.9982 and +0.9758.
|
||||
✅ So the picture is coherent across three independent observables — animation
|
||||
timing, static composites, and the per-frame draw log — and the rule it supports
|
||||
is: **plateau where there is one, last keyframe where there is not.**
|
||||
|
||||
### ✅ The structural case for `last`, disc-wide — 2 293 of 2 305
|
||||
|
||||
Only two ambiguous elements could be *scored* against a capture. But the
|
||||
entry → hold → exit model makes a prediction that can be checked on all 2 305:
|
||||
what does each element's **final** keyframe look like?
|
||||
([`tools/re-capture/plateauless_endstate.py`](../../../tools/re-capture/plateauless_endstate.py),
|
||||
[data](../data/plateauless-endstate.txt))
|
||||
|
||||
| final keyframe | count | is `last` the right answer? |
|
||||
|---|---|---|
|
||||
| **invisible** (`a = 0`) | **1 618** | the element is a transient; at any settled moment it is *gone* — ✅ model-consistent, and confirmed by the draw log for the two observed |
|
||||
| **visible, at maximum alpha** | **675** | it faded in and stopped — the final pose *is* the settled one ✅ |
|
||||
| visible, **below** maximum alpha | **12** | ❔ genuinely unclear |
|
||||
|
||||
Of the 687 that end visible, **472 have monotonically non-decreasing alpha** — a
|
||||
plain fade-in that stops, `[0, 255]` over two keyframes in the commonest case
|
||||
(`pjex_eff.rat`, `pghud_speed_cut.t32`) — and another 203 end at their maximum
|
||||
after dipping. Only **12 elements in 2 305** end visible at less than full alpha.
|
||||
|
||||
So `last` is **structurally defensible for 2 293 of 2 305 (99.5 %)**, against a
|
||||
dwell rule that returns a mid-movement frame by construction. It is *observed*
|
||||
correct for 2, *structural* for 675, *model-consistent* for 1 618, and unclear
|
||||
for 12.
|
||||
|
||||
⚠️ The 1 618 carry the weight of an assumption, and it should be visible: that a
|
||||
plateau-less element's animation has finished by the time the screen is settled.
|
||||
The draw log establishes this for the two splash glows (drawn on frames 94–115,
|
||||
with the logos on 116–211) and nothing establishes it for the rest.
|
||||
|
||||
62
tools/re-capture/plateauless_endstate.py
Executable file
62
tools/re-capture/plateauless_endstate.py
Executable file
@@ -0,0 +1,62 @@
|
||||
"""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
|
||||
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("/work/sylph_extract/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])
|
||||
Reference in New Issue
Block a user