Read a tournament folder: metadata (if present) plus every discoverable game. The arena comes from ``metadata.json`` when available, otherwise from the folder name (which encodes it, e.g. ``PvpTournament.RobotRumble.r1.s1.p2...``).
(folder: Path)
| 103 | return FiggieReplayer() |
| 104 | return None |
| 105 | |
| 106 | |
| 107 | def _player_model(player: dict) -> str | None: |
| 108 | """Extract a display model name from a player config (mirrors the viewer).""" |
| 109 | config = player.get("config", {}) or {} |
| 110 | model = config.get("model") |
| 111 | if isinstance(model, dict): |
| 112 | return model.get("model_name") |
| 113 | return model |
| 114 | |
| 115 | |
| 116 | def _arena_from_name(folder: Path) -> str: |
| 117 | """Derive the arena from a tournament folder name. |
| 118 | |
| 119 | e.g. ``PvpTournament.RobotRumble.r1.s1.p2.flail.luisa.260702175500`` -> ``RobotRumble``. |
| 120 | """ |
| 121 | parts = folder.name.split(".") |
| 122 | return parts[1] if len(parts) > 1 else "" |
| 123 | |
| 124 | |
| 125 | def _fill_sim_winners(games: list[GameRef], renderer: ReplayRenderer | None, players: list[dict]) -> None: |
| 126 | """Populate each game's per-sim winner via the renderer's cheap ``peek_winner``. |
| 127 | Skipped entirely for arenas that don't implement it (avoids extra sim I/O).""" |
| 128 | if renderer is None or type(renderer).peek_winner is ReplayRenderer.peek_winner: |
| 129 | return |
| 130 | for g in games: |
| 131 | try: |
| 132 | res = renderer.peek_winner(read_sim(g), players) |
| 133 | except Exception: |
| 134 | res = None |
| 135 | if res is not None: |
| 136 | g.sim_winner, g.sim_draw = res |
| 137 | |
| 138 | |
| 139 | def load_tournament(folder: Path) -> TournamentInfo: |
| 140 | """Read a tournament folder: metadata (if present) plus every discoverable game. |
| 141 | |
| 142 | The arena comes from ``metadata.json`` when available, otherwise from the folder name |
| 143 | (which encodes it, e.g. ``PvpTournament.RobotRumble.r1.s1.p2...``). |
| 144 | """ |
| 145 | folder = Path(folder) |
| 146 | meta_path = folder / "metadata.json" |
| 147 | if not meta_path.exists(): |
| 148 | # A ladder / aggregate folder is a parent of PvP sub-tournaments, each with its own |
| 149 | # metadata.json (e.g. LadderTournament.<Arena>.../ from `ladder run`, or logs/ladder/<Arena>/ |
| 150 | # from `ladder make`). Merge every sub-tournament's games, tagged by matchup. |
| 151 | children = [d for d in sorted(folder.iterdir()) if d.is_dir() and (d / "metadata.json").exists()] |
| 152 | if children: |
| 153 | arena, players, games = _arena_from_name(folder), [], [] |
| 154 | for child in children: |
| 155 | ct = load_tournament(child) |
| 156 | arena = arena or ct.arena |
| 157 | players = players or ct.players |
| 158 | matchup = ", ".join(p["name"] for p in ct.players) or child.name |
| 159 | for g in ct.games: |
| 160 | g.group = matchup |
| 161 | games.append(g) |
| 162 | games.sort(key=lambda g: (g.group, g.round, g.sim)) |
no test coverage detected