"""Verify the RIO button field from a BT_RIOBANK_LOG=1 run. The banks now reach UNDER the glass and deliberately overlap (the map's foot row sits inside both side columns' reach), so "the address is in the list" is not enough -- the hit test takes the FIRST match, so an address whose rect is entirely shadowed by earlier buttons is dead however big it looks. Reads btl4.log, groups by bank tag, and for each button asks: is there any point in my rect that no EARLIER button in this bank covers? Reports the address census per bank and any shadowed address. python checkbank.py [--expect 0x00-0x47] """ import re import sys from collections import OrderedDict BANK_RE = re.compile(r"\[riobank\] (.+?) bounds=\((-?\d+),(-?\d+),(-?\d+),(-?\d+)\) (\d+) buttons") BTN_RE = re.compile(r"\[riobank\] addr=0x([0-9a-f]+) rect=\((-?\d+),(-?\d+),(-?\d+),(-?\d+)\) class=(\d+)") def parse(path): banks = OrderedDict() current = None with open(path, "r", errors="replace") as handle: for line in handle: match = BANK_RE.search(line) if match: current = match.group(1) # a re-run appends; keep the FIRST pass of each tag banks.setdefault(current, []) if banks[current]: current = None # already captured this tag continue match = BTN_RE.search(line) if match and current is not None: addr = int(match.group(1), 16) x, y, w, h = (int(match.group(i)) for i in range(2, 6)) banks[current].append((addr, x, y, w, h, int(match.group(6)))) return banks def reachable(rect, earlier): """Any point of `rect` not covered by an earlier rect? Sampled on the coordinate grid induced by the earlier rects' edges -- if a gap exists at all, one of these sample points lands in it. """ _, x, y, w, h = rect[:5] xs = {x, x + w - 1} ys = {y, y + h - 1} for _, ex, ey, ew, eh, _ in earlier: for candidate in (ex - 1, ex, ex + ew - 1, ex + ew): if x <= candidate < x + w: xs.add(candidate) for candidate in (ey - 1, ey, ey + eh - 1, ey + eh): if y <= candidate < y + h: ys.add(candidate) for px in sorted(xs): for py in sorted(ys): hit = None for button in earlier: _, ex, ey, ew, eh, _ = button if ex <= px < ex + ew and ey <= py < ey + eh: hit = button break if hit is None: return True return False def main(): path = sys.argv[1] if len(sys.argv) > 1 else "btl4.log" banks = parse(path) if not banks: print("no [riobank] lines -- was BT_RIOBANK_LOG=1 set?") return 1 seen = set() failures = [] for tag, buttons in banks.items(): addresses = [b[0] for b in buttons] duplicates = {a for a in addresses if addresses.count(a) > 1} shadowed = [] for index, button in enumerate(buttons): if not reachable(button, buttons[:index]): shadowed.append(button[0]) seen.update(addresses) status = "ok" if duplicates or shadowed: status = "FAIL" failures.append(tag) print("%-22s %2d buttons 0x%02x-0x%02x %s" % (tag, len(buttons), min(addresses), max(addresses), status)) if duplicates: print(" duplicate addresses: %s" % ", ".join("0x%02x" % a for a in sorted(duplicates))) if shadowed: print(" SHADOWED (no first-hit point): %s" % ", ".join("0x%02x" % a for a in sorted(shadowed))) print("\n%d distinct addresses across %d banks" % (len(seen), len(banks))) missing = [a for a in range(0x00, 0x48) if a not in seen] if missing: print("not placed by any bank: %s" % ", ".join("0x%02x" % a for a in missing)) return 1 if failures else 0 if __name__ == "__main__": sys.exit(main())