#!/usr/bin/env python3 """Compare two UBER joeylog.txt files by per-op surface hash. Used by the planar 68k rewrite (project_planar_68k_plan.md): IIgs captures the golden reference, each 68k port re-runs UBER after a primitive conversion, and this tool tells you which ops produced different pixels. Without this, "looks right visually" misses the subtle mismatches that cascade into hard-to-debug corruption. Usage: tools/diff-uber-hashes Exit code: 0 = all hashes match 1 = at least one mismatch 2 = usage error or missing file """ import re import sys # Match e.g.: # UBER: drawCircle r=80: 56 iters / 4 frames = 840 ops/sec | hash=A1B2C3D4 LINE_RE = re.compile( r"UBER:\s+(?P[^:]+):\s+\d+\s+iters\s+/\s+\d+\s+frames\s+=\s+\d+\s+ops/sec\s+\|\s+hash=(?P[0-9A-Fa-f]+)" ) def parse_log(path): """Return ordered dict {op_name: hash} from a UBER log file. Multiple runs may be concatenated in the same log (joeyLog appends) -- in that case the LAST hash for each op wins, matching the most recent run. """ hashes = {} with open(path) as f: for line in f: m = LINE_RE.search(line) if m: hashes[m.group("op").strip()] = m.group("hash").upper() return hashes def main(argv): if len(argv) != 3: sys.stderr.write( "usage: diff-uber-hashes \n" ) return 2 try: ref = parse_log(argv[1]) test = parse_log(argv[2]) except OSError as e: sys.stderr.write(f"error: {e}\n") return 2 if not ref: sys.stderr.write(f"error: no UBER hash lines found in {argv[1]}\n") return 2 if not test: sys.stderr.write(f"error: no UBER hash lines found in {argv[2]}\n") return 2 mismatches = 0 matches = 0 for op, ref_hash in ref.items(): test_hash = test.get(op) if test_hash is None: print(f" MISSING in test: {op} (ref={ref_hash})") mismatches += 1 elif test_hash != ref_hash: print(f" MISMATCH {op}: ref={ref_hash} test={test_hash}") mismatches += 1 else: matches += 1 extras = [op for op in test if op not in ref] for op in extras: print(f" EXTRA in test: {op} (test={test[op]})") total = len(ref) + len(extras) print() if mismatches == 0 and not extras: print(f"OK: {matches}/{total} ops match") return 0 print(f"FAIL: {matches} match, {mismatches} mismatch, {len(extras)} extras") return 1 if __name__ == "__main__": sys.exit(main(sys.argv))