#!/usr/bin/env python3 """Read a Xenia UI draw log -- EVERY quad, not the first vertex of each draw. ⚠️ THIS EXISTS BECAUSE THE OBVIOUS READER IS WRONG. A draw line carries `indices=N` vertices on ONE `v:` line, and N is routinely 8 -- two quads batched into a single draw. A reader that takes the first `v: [...]` match per line sees one of them and silently drops the rest. That produced a clean, complete-looking negative twice in this corpus (`f6-unit5-pteff03a-never-drawn.md`, `f6-unit6-...`), both refuted by `f6-unit11-pteff03a-IS-drawn.md`. `REFUTED.md` L170 had already recorded a draw carrying two rotated parallelograms. Use this reader; do not re-roll the regex. from read_draws import read frames = read(path) # {frame: [Quad, ...]} Quad = (page, verts, alpha, blend, cx) """ import re, collections _F = re.compile(r'^--- frame (\d+) ') _TEX = re.compile(r'tex\[base=(0x[0-9A-F]+) (\d+)x(\d+) fmt=(\d+)(?: h=([0-9A-F]+))?\]') _IDX = re.compile(r'indices=(\d+)') _V = re.compile(r'\[(-?\d+\.\d+),(-?\d+\.\d+),z=[-\d.]+,col=([0-9A-F]{8})\]') class Quad(tuple): __slots__ = () def __new__(cls, page, verts, alpha, blend, cx, draw=-1, nquads=1): return tuple.__new__(cls, (page, verts, alpha, blend, cx, draw, nquads)) page = property(lambda s: s[0]) verts = property(lambda s: s[1]) alpha = property(lambda s: s[2]) blend = property(lambda s: s[3]) cx = property(lambda s: s[4]) draw = property(lambda s: s[5]) # index of the draw line within the frame nquads = property(lambda s: s[6]) # how many quads that draw carried def read(path): frames = collections.defaultdict(list) frame = page = blend = None idx = 0 draw_no = -1 for line in open(path, errors='replace'): m = _F.match(line) if m: frame = int(m.group(1)); draw_no = -1; continue if frame is None: continue mt = _TEX.search(line) if mt: page = mt.group(5) or mt.group(1) mi = _IDX.search(line); idx = int(mi.group(1)) if mi else 0 mb = re.search(r'blend=(0x[0-9A-F]+)', line); blend = mb.group(1) if mb else None continue if page is not None and ' v: ' in line: vs = _V.findall(line) draw_no += 1 nq = len(vs) // 4 # every group of 4 vertices is one quad; a partial tail is dropped for q in range(nq): quad = vs[q*4:(q+1)*4] cx = sum(float(v[0]) for v in quad) / 4 frames[frame].append(Quad(page, [(float(a), float(b)) for a, b, _ in quad], int(quad[0][2][:2], 16), blend, round(cx, 3), draw_no, nq)) page = None return dict(frames) if __name__ == '__main__': import sys fr = read(sys.argv[1]) ks = sorted(fr) tot = sum(len(v) for v in fr.values()) print(f"{len(ks)} frames, {tot} quads, frames {ks[0]}..{ks[-1]}") print(f"mean quads/frame {tot/len(ks):.2f}")