#!/usr/bin/env python3 """matchup-grid-audit.py — list player_clans assignment per game in a matchup-grid batch. Helps detect contaminated data when a resumed batch was launched with the wrong pinning convention (e.g., bare `AI_PIN_PERSONALITY` instead of per-slot `AI_PIN_PERSONALITY_P0/P1`). The expected assignment for a pair `_vs_` is: /as_/ → player_clans = {"0": "", "1": ""} /as_/ → player_clans = {"0": "", "1": ""} Any game whose meta.json deviates from that should be flagged or pruned before the matchup-grid-report runs. Usage: python3 tools/matchup-grid-audit.py """ from __future__ import annotations import json import sys from pathlib import Path def expected_clans(pair: str, perspective: str) -> dict[str, str]: a, b = pair.split("_vs_") if perspective == a: return {"0": a, "1": b} if perspective == b: return {"0": b, "1": a} return {} def audit(batch_dir: Path) -> int: bad = 0 good = 0 for pair_dir in sorted(batch_dir.iterdir()): if not pair_dir.is_dir() or "_vs_" not in pair_dir.name: continue pair = pair_dir.name for sub in sorted(pair_dir.iterdir()): if not sub.is_dir() or not sub.name.startswith("as_"): continue perspective = sub.name[len("as_"):] expected = expected_clans(pair, perspective) if not expected: continue for game in sorted(sub.iterdir()): if not game.is_dir(): continue meta = game / "meta.json" if not meta.exists(): continue try: data = json.loads(meta.read_text()) except Exception: print(f" PARSE_ERR {pair}/{sub.name}/{game.name}") bad += 1 continue got = data.get("player_clans") or {} if got == expected: good += 1 else: print(f" MISMATCH {pair}/{sub.name}/{game.name}: expected={expected} got={got}") bad += 1 print() print(f"Total: {good} correct, {bad} mismatched/parse-err") return 1 if bad else 0 def main(argv: list[str]) -> int: if len(argv) != 2: print(f"Usage: {argv[0]} ", file=sys.stderr) return 2 return audit(Path(argv[1])) if __name__ == "__main__": sys.exit(main(sys.argv))