79 lines
2.5 KiB
Python
79 lines
2.5 KiB
Python
|
|
#!/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 `<a>_vs_<b>` is:
|
||
|
|
|
||
|
|
<pair>/as_<a>/ → player_clans = {"0": "<a>", "1": "<b>"}
|
||
|
|
<pair>/as_<b>/ → player_clans = {"0": "<b>", "1": "<a>"}
|
||
|
|
|
||
|
|
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 <batch-dir>
|
||
|
|
"""
|
||
|
|
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]} <batch-dir>", file=sys.stderr)
|
||
|
|
return 2
|
||
|
|
return audit(Path(argv[1]))
|
||
|
|
|
||
|
|
|
||
|
|
if __name__ == "__main__":
|
||
|
|
sys.exit(main(sys.argv))
|