feat(sole-city-gate): ✨ Implement Sole City Gate integration tool for system interactions
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
parent
32a694eab7
commit
35f55c9ff5
1 changed files with 109 additions and 0 deletions
109
tools/sole-city-gate.py
Normal file
109
tools/sole-city-gate.py
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
#!/usr/bin/env python3
|
||||
"""sole-city-gate.py — score the p1-29c / p1-29d sole-city tier gate for a batch.
|
||||
|
||||
Reads every game_*/turn_stats.jsonl under a batch dir, extracts per-player
|
||||
final tier_peak / cities / cities_lost, and scores the alive-aware gate:
|
||||
|
||||
PASS seed := P0.tier_peak >= TIER AND P1.tier_peak >= TIER
|
||||
Gate PASS := pass_seeds >= QUORUM (default 7/10) AND
|
||||
median game length <= MAX_TURNS (default 384)
|
||||
|
||||
Usage:
|
||||
python3 tools/sole-city-gate.py <batch-dir> [--tier 2] [--quorum 7]
|
||||
[--max-turns 384] [--p0 0] [--p1 1]
|
||||
|
||||
Exit code 0 = gate PASS, 1 = gate FAIL, 2 = usage / no data.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import statistics
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def final_line(ts: Path) -> dict | None:
|
||||
last = None
|
||||
for line in ts.read_text().splitlines():
|
||||
line = line.strip()
|
||||
if line:
|
||||
last = line
|
||||
if last is None:
|
||||
return None
|
||||
return json.loads(last)
|
||||
|
||||
|
||||
def main(argv: list[str]) -> int:
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument("batch_dir")
|
||||
ap.add_argument("--tier", type=int, default=2)
|
||||
ap.add_argument("--quorum", type=int, default=7)
|
||||
ap.add_argument("--max-turns", type=int, default=384)
|
||||
ap.add_argument("--p0", default="0")
|
||||
ap.add_argument("--p1", default="1")
|
||||
args = ap.parse_args(argv[1:])
|
||||
|
||||
root = Path(args.batch_dir)
|
||||
if not root.is_dir():
|
||||
print(f"Not a directory: {root}", file=sys.stderr)
|
||||
return 2
|
||||
|
||||
games = sorted(g for g in root.glob("game_*") if g.is_dir())
|
||||
if not games:
|
||||
print(f"No game_* dirs under {root}", file=sys.stderr)
|
||||
return 2
|
||||
|
||||
rows = []
|
||||
turns = []
|
||||
pass_seeds = 0
|
||||
for game in games:
|
||||
ts = game / "turn_stats.jsonl"
|
||||
if not ts.exists():
|
||||
rows.append((game.name, "NO_STATS"))
|
||||
continue
|
||||
try:
|
||||
final = final_line(ts)
|
||||
except Exception as e: # noqa: BLE001
|
||||
rows.append((game.name, f"PARSE_ERR {e}"))
|
||||
continue
|
||||
if not final:
|
||||
rows.append((game.name, "EMPTY"))
|
||||
continue
|
||||
ps = final.get("player_stats") or {}
|
||||
p0 = ps.get(args.p0, {})
|
||||
p1 = ps.get(args.p1, {})
|
||||
turn = final.get("turn", 0)
|
||||
turns.append(turn)
|
||||
p0_tp = p0.get("tier_peak", 0)
|
||||
p1_tp = p1.get("tier_peak", 0)
|
||||
ok = p0_tp >= args.tier and p1_tp >= args.tier
|
||||
pass_seeds += 1 if ok else 0
|
||||
rows.append((
|
||||
game.name,
|
||||
f"T{turn} outcome={final.get('outcome')} "
|
||||
f"P0_tp={p0_tp} P1_tp={p1_tp} "
|
||||
f"P1_cities={p1.get('cities', '?')} P1_lost={p1.get('cities_lost', '?')} "
|
||||
f"{'PASS' if ok else 'fail'}"
|
||||
))
|
||||
|
||||
for name, detail in rows:
|
||||
print(f"{name}: {detail}")
|
||||
|
||||
scored = len(turns)
|
||||
median_turn = statistics.median(turns) if turns else 0
|
||||
quorum_ok = pass_seeds >= args.quorum
|
||||
length_ok = median_turn <= args.max_turns
|
||||
gate = quorum_ok and length_ok
|
||||
|
||||
print("─" * 60)
|
||||
print(f"scored games: {scored}")
|
||||
print(f"pass seeds (P0_tp>={args.tier} AND P1_tp>={args.tier}): "
|
||||
f"{pass_seeds}/{scored} (quorum {args.quorum} → {'OK' if quorum_ok else 'MISS'})")
|
||||
print(f"median game length: {median_turn} (<= {args.max_turns} → {'OK' if length_ok else 'MISS'})")
|
||||
print(f"GATE: {'PASS' if gate else 'FAIL'}")
|
||||
return 0 if gate else 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main(sys.argv))
|
||||
Loading…
Add table
Reference in a new issue