#!/usr/bin/env python3 """Per-frame alpha of the fade quad (`pteff00.prm`), from a `log_ui_draws` capture. `screen-transitions.md` measures a screen change's fade-out as a ~0.4 s lump and then SPLITS it by arithmetic -- the declared ramp is 10 units, 0.4 s is ~24, "so the other ~14 must be the black hold". That page flags the split as a fit, not a measurement. This measures it. Identifying the quad, rather than guessing at it: the fade quad is a `.prm` PRIMITIVE, so its draw carries NO `tex[base=...]`, and it is its screen's last-painting element (structures/ui-paint-order-key.md). So: per frame, the LAST full-screen draw with no bound texture. Taking merely the last full-screen quad picks up textured backdrops and gets a different answer. ⚠️ The frame axis has gaps. A 260-frame window produced 232 `--- frame` headers, so ~10 % of submitted frames carry no UI draw at all. A duration in frames is therefore +-1 frame per gap it spans, and this prints the gaps so a reader can see which spans are affected. fade_envelope.py """ import re import sys sys.path.insert(0, __file__.rsplit("/", 1)[0]) W, H = 1280, 720 VERT = re.compile(r"col=([0-9A-F]{8})") def envelope(log): """Yield (frame, alpha|None) -- alpha of the last untextured full-screen quad.""" frame, pending_untex = None, None last = {} seen = [] for line in open(log): if line.startswith("--- frame"): if frame is not None: seen.append(frame) frame = int(line.split()[2]) continue m = re.match(r"\s*(\d+) prim=(\d+) indices=(\d+)", line) if m: # a primitive draw has no bound texture pending_untex = ("tex[base=" not in line) and m.group(2) == "13" continue if "vb=0x" in line and pending_untex: cols = VERT.findall(line) # full-screen NDC quad: every vertex at +-1 if cols and line.count("[-1.00,1.00,") >= 1: last[frame] = int(cols[-1][:2], 16) pending_untex = None if frame is not None: seen.append(frame) return last, seen def main(): last, seen = envelope(sys.argv[1]) gaps = [(a, b) for a, b in zip(seen, seen[1:]) if b != a + 1] print(f"# {len(seen)} frame headers, {seen[0]}..{seen[-1]}; " f"{sum(b-a-1 for a,b in gaps)} submitted frames carry no UI draw") print("# gaps: " + " ".join(f"{a}->{b}" for a, b in gaps)) print("frame alpha") for f in seen: a = last.get(f) print(f"{f:6d} {'-' if a is None else a:>5}") return 0 if __name__ == "__main__": raise SystemExit(main())