re: the title's build-in measured in the guest's draw stream -- the flashes are real
The settle-time decode was confirmed only against a SETTLED frame, which shows the end state is right and says nothing about whether the five flashes ever happen. This runs the oracle: a draw capture armed before the title exists, so the window contains the frames in which the screen is built. The flashes fire in a six-frame window and are absent from all 155 other sampled frames. `ptlogo_back2eff1` is drawn in exactly two frames at t = 54.0 against a decoded peak of t54-56; `ptlogo1` first appears at t = 42.2 against a decoded t42. Units-per-frame was taken from the GLOW's period alone, a different element, so the timings are not circular. The two holders are continuous from frame 134. The plate glow's quad carries a per-vertex colour whose alpha IS the element's fade alpha, so the ramp is read straight out of the guest: observed range 0..80 against a decoded peak of 80, exact and unfitted; period 51.158 presented frames over 20 cycle starts. Fitting the decoded ramp gives RMS 13.16 alpha levels against 38.18 for the same ramp REVERSED -- if the shape carried no information those would be equal, so the asymmetry is real and correctly directed. Further controls: symmetric triangle 15.73, flat 31.13. `ptlogo_back2eff3` was never drawn, and that is expected rather than a miss: a 2-unit flash peak is 0.85 of a presented frame, so catching one is a matter of phase. A port drawing all five every time shows more sweep than the console. METHOD.md gains the trap this cost: a 2D draw's identity is its vertex geometry, not its bound texture. These sprites sample shared pages, and matching texture dimensions produced a false negative (no flash is ever drawn) and a false positive (the intro movie's 640x360 YUV planes read as `ptbase2`) in the same pass. Also records the top-level restriction on the settle window, which the port raised and which is verified here: top-level [160,236] width 76, including the `ptloop` leaves [269,540] width 271 -- an instant past the end of every top-level element's timeline. Evidence committed as a derived per-frame series, not the 7 MB raw log. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QsEPXWVaEpyfudtR6re1Pd
This commit is contained in:
79
tools/re-capture/buildin_timeline.py
Executable file
79
tools/re-capture/buildin_timeline.py
Executable file
@@ -0,0 +1,79 @@
|
||||
#!/usr/bin/env python3
|
||||
"""When does each title sprite get DRAWN, frame by frame, in the real game?
|
||||
|
||||
Reads a `xenia_re_ui_draws_NN.log` armed early (so the window contains the frames
|
||||
in which the screen is BUILT, not just its steady state) and reports, per texture
|
||||
size, the first and last frame it is bound in.
|
||||
|
||||
The point is a falsifiable prediction. `docs/re/structures/ui-settle-time.md`
|
||||
decodes `ptlogo_back2eff1`..`eff5` as five staggered two-frame flashes that sweep
|
||||
across the logo once and are extinguished by keyframe t110, while
|
||||
`ptlogo_back2eff` and `ptlogo_back2` hold for the rest of the screen. Four of the
|
||||
five have UNIQUE decoded dimensions, so the log can confirm or refute that
|
||||
directly:
|
||||
|
||||
eff1 167x126 eff2 258x203 eff3 408x203 eff4 749x203
|
||||
|
||||
If they appear in a short contiguous run of early frames and never again, the
|
||||
decode is right. If they are bound every frame, or never, it is wrong.
|
||||
|
||||
buildin_timeline.py <log> [--dims WxH,...]
|
||||
"""
|
||||
import re, sys, collections
|
||||
|
||||
# GP_TITLE build 4, from `sylpheed-cli`/`sprite_dims`. Two share 1133x280, which
|
||||
# is why the capture also records per-vertex colour alpha.
|
||||
KNOWN = {
|
||||
(167,126): "ptlogo_back2eff1 FLASH t54-58",
|
||||
(258,203): "ptlogo_back2eff2 FLASH t58-62",
|
||||
(408,203): "ptlogo_back2eff3 FLASH t62-66",
|
||||
(749,203): "ptlogo_back2eff4 FLASH t~64",
|
||||
(1133,280): "ptlogo_back2eff / eff5 (AMBIGUOUS: same size)",
|
||||
(1118,262): "ptlogo_back2 holds t80-243",
|
||||
(919,113): "ptlogo1 holds",
|
||||
(992,104): "ptlogo2 holds",
|
||||
(1280,720): "pteff04 full-screen",
|
||||
(640,360): "ptbase2",
|
||||
(694,20): "ptcopyright",
|
||||
(399,180): "pteff03 / pteff03a (sweeps)",
|
||||
(517,131): "ptlogoall_eff",
|
||||
(235,180): "ptlogoall_eff2",
|
||||
(640,319): "pteff01",
|
||||
(37,17): "ptlogo_tm",
|
||||
}
|
||||
|
||||
def main():
|
||||
path = sys.argv[1]
|
||||
frame = None
|
||||
seen = collections.defaultdict(list) # dims -> [frames]
|
||||
per_frame = collections.Counter()
|
||||
frames = []
|
||||
for line in open(path, errors="replace"):
|
||||
m = re.match(r"--- frame (\d+) ---", line)
|
||||
if m:
|
||||
frame = int(m.group(1)); frames.append(frame); continue
|
||||
if frame is None:
|
||||
continue
|
||||
for w, h in re.findall(r"tex\[base=0x[0-9A-F]+ (\d+)x(\d+) fmt=\d+\]", line):
|
||||
seen[(int(w), int(h))].append(frame)
|
||||
if line.startswith(("0","1","2","3","4","5","6","7","8","9")) or re.match(r"^\s*\d+ prim=", line):
|
||||
per_frame[frame] += 1
|
||||
|
||||
if not frames:
|
||||
print("no frames in the log"); return
|
||||
lo, hi = min(frames), max(frames)
|
||||
print(f"frames {lo}..{hi} ({len(set(frames))} distinct) draws {sum(per_frame.values())}\n")
|
||||
rows = []
|
||||
for dims, fl in seen.items():
|
||||
s = sorted(set(fl))
|
||||
rows.append((s[0], dims, s[-1], len(s), len(fl)))
|
||||
rows.sort()
|
||||
print(f" {'first':>7} {'last':>7} {'frames':>7} {'draws':>7} {'size':>10} what")
|
||||
for first, dims, last, nf, nd in rows:
|
||||
name = KNOWN.get(dims, "")
|
||||
span = last - first
|
||||
tag = " ⟵ TRANSIENT" if nf <= 12 and span <= 20 else (" (every frame)" if nf > 0.8*len(set(frames)) else "")
|
||||
print(f" {first:>7} {last:>7} {nf:>7} {nd:>7} {dims[0]:>4}x{dims[1]:<4} {name}{tag}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user