| 1 | """Run the unchanged season matches; keep game outcomes and timing evidence.""" |
| 2 | import json |
| 3 | from pathlib import Path |
| 4 | import sys |
| 5 | |
| 6 | ROUTE = Path(__file__).resolve().parent |
| 7 | ROOT = ROUTE.parents[1] |
| 8 | sys.path.insert(0, str(ROOT / 'referee')) |
| 9 | import play |
| 10 | |
| 11 | |
| 12 | def main(): |
| 13 | rows = [] |
| 14 | for opponent in sorted(p.name for p in (ROOT / 'field').iterdir() if p.is_dir()): |
| 15 | for opening_index, opening in enumerate(play.openings(), 1): |
| 16 | for seat in (0, 1): |
| 17 | brains = [play.brain(str(ROUTE)), play.brain(str(ROOT / 'field' / opponent))] |
| 18 | if seat: |
| 19 | brains.reverse() |
| 20 | result = play.match(brains, opening) |
| 21 | our_times = [m[2] for i, m in enumerate(result['moves']) if i >= len(opening) and i % 2 == seat] |
| 22 | points = 0.5 if result['winner'] is None else float(result['winner'] == seat) |
| 23 | rows.append({'opponent': opponent, 'opening': opening_index, |
| 24 | 'seat': 'black' if seat == 0 else 'white', 'points': points, |
| 25 | 'reason': result['reason'], 'moves': len(result['moves']), |
| 26 | 'max_reply_ms': max(our_times, default=0)}) |
| 27 | out = ROUTE / 'out' |
| 28 | out.mkdir(exist_ok=True) |
| 29 | (out / 'games.json').write_text(json.dumps(rows, indent=2) + '\n') |
| 30 | groups = {opp: [r for r in rows if r['opponent'] == opp] for opp in sorted({r['opponent'] for r in rows})} |
| 31 | lines = ['# Season check evidence', '', 'Generated by `python3 routes/codex-line-threats/audit.py`.', |
| 32 | 'Uses the unchanged project referee, all six openings, and both colours.', '', |
| 33 | '| Opponent | Wins | Draws | Losses | Points share |', '|---|---:|---:|---:|---:|'] |
| 34 | for opp, games in groups.items(): |
| 35 | counts = [sum(r['points'] == value for r in games) for value in (1, 0.5, 0)] |
| 36 | lines.append(f'| {opp} | {counts[0]} | {counts[1]} | {counts[2]} | {sum(r["points"] for r in games)/len(games):.6f} |') |
| 37 | lines += ['', f'Overall points share: {sum(r["points"] for r in rows)/len(rows):.6f}.', |
| 38 | f'Maximum measured reply latency: {max(r["max_reply_ms"] for r in rows)} ms.', |
| 39 | 'Reply latency is measured by the project referee on this local machine; it is not a cross-machine runtime guarantee.', |
| 40 | f'End reasons: {json.dumps({reason: sum(r["reason"] == reason for r in rows) for reason in sorted({r["reason"] for r in rows})})}.', |
| 41 | '', 'Per-game evidence, including losses, is in `games.json`.'] |
| 42 | report = '\n'.join(lines) + '\n' |
| 43 | (out / 'season.md').write_text(report) |
| 44 | print(report) |
| 45 | |
| 46 | |
| 47 | if __name__ == '__main__': |
| 48 | main() |