@@ -40,6 +40,7 @@ def __init__(
4040 score_type : SCORING_TYPES = "per_round_tertiary" ,
4141 max_round : int = 15 ,
4242 only_specific_round : bool = False ,
43+ include_round_0 : bool = False ,
4344 ):
4445 """This class builds a win matrix from a log directory, it doesn't fit anything yet.
4546 It also adds a "ALL" game to the win matrix, which is the sum of all games.
@@ -62,6 +63,9 @@ def __init__(
6263
6364 The `max_round` parameter controls the maximum number of rounds to include in the score calculation (default: 15).
6465 The `only_specific_round` parameter controls whether to only include the specific round (True) or all rounds up to max_round (False).
66+ The `include_round_0` parameter controls whether round 0 is counted. In normal PvP/climbing
67+ tournaments round 0 is the identical-codebases baseline and is excluded. For ladder
68+ construction (`ladder make`, `tournament.rounds: 0`) round 0 IS the match, so set this True.
6569 """
6670 self .win_matrix : dict [str , dict [tuple [str , str ], list [float ]]] = defaultdict (
6771 lambda : defaultdict (lambda : [0.0 , 0.0 ])
@@ -71,6 +75,7 @@ def __init__(
7175 self .score_type = score_type
7276 self .max_round = max_round
7377 self .only_specific_round = only_specific_round
78+ self .include_round_0 = include_round_0
7479 self ._samples : dict [str , dict [tuple [str , str ], list [tuple [float , float ]]]] = defaultdict (
7580 lambda : defaultdict (list )
7681 )
@@ -154,13 +159,19 @@ def _process_tournament(self, metadata_path: Path) -> None:
154159 return
155160
156161 player_names = [p ["name" ] for p in players ]
157- models = [p ["config" ]["model" ]["model_name" ].strip ("@" ) for p in players ]
162+ models = []
163+ for p in players :
164+ try :
165+ models .append (p ["config" ]["model" ]["model_name" ].strip ("@" ))
166+ except KeyError :
167+ # Ladder bots have no model config; identify by branch (flatten "/" to keep years distinct).
168+ models .append (p ["name" ].removeprefix ("human/" ).replace ("/" , "__" ))
158169
159170 # Aggregate scores for each round
160171 p1_round_scores = []
161172 p2_round_scores = []
162173 for idx , stats in metadata ["round_stats" ].items ():
163- if idx == "0" :
174+ if idx == "0" and not self . include_round_0 :
164175 continue
165176
166177 round_num = int (idx )
@@ -1547,6 +1558,12 @@ def write_latex_table_plain(results: dict[str, dict], output_dir: Path) -> None:
15471558 parser = argparse .ArgumentParser (description = "Build win matrix and fit Bradley-Terry model" )
15481559 parser .add_argument ("-d" , "--log_dir" , type = Path , default = LOCAL_LOG_DIR )
15491560 parser .add_argument ("--print-matrix" , action = "store_true" , help = "Print win matrix" )
1561+ parser .add_argument (
1562+ "--include-round-0" ,
1563+ action = "store_true" ,
1564+ help = "Count round 0 (normally the excluded identical-codebases baseline). REQUIRED for "
1565+ "ladder construction (`ladder make` uses tournament.rounds: 0, so round 0 IS the match)." ,
1566+ )
15501567 parser .add_argument (
15511568 "-s" ,
15521569 "--score-type" ,
@@ -1578,7 +1595,9 @@ def write_latex_table_plain(results: dict[str, dict], output_dir: Path) -> None:
15781595 args = parser .parse_args ()
15791596
15801597 builder = ScoreMatrixBuilder (
1581- all_games_normalization_scheme = args .all_normalization_scheme , score_type = args .score_type
1598+ all_games_normalization_scheme = args .all_normalization_scheme ,
1599+ score_type = args .score_type ,
1600+ include_round_0 = args .include_round_0 ,
15821601 )
15831602 builder .build (args .log_dir )
15841603
@@ -1632,22 +1651,26 @@ def write_latex_table_plain(results: dict[str, dict], output_dir: Path) -> None:
16321651 ).run ()
16331652 write_bootstrap_metrics_table (bootstrap_results , args .output_dir , game = "ALL" )
16341653
1635- logger .info ("Running EloVsMaxRounds analysis" )
1636- EloVsMaxRounds (
1637- log_dir = args .log_dir ,
1638- max_rounds = 15 ,
1639- all_games_normalization_scheme = args .all_normalization_scheme ,
1640- score_type = args .score_type ,
1641- regularization = args .regularization ,
1642- output_dir = args .output_dir ,
1643- ).run ()
1644-
1645- logger .info ("Running EloOnlyAtRound analysis" )
1646- EloOnlyAtRound (
1647- log_dir = args .log_dir ,
1648- max_rounds = 15 ,
1649- all_games_normalization_scheme = args .all_normalization_scheme ,
1650- score_type = args .score_type ,
1651- regularization = args .regularization ,
1652- output_dir = args .output_dir ,
1653- ).run ()
1654+ # Max-round analyses are multi-round-only; skip them for single-round ladder round-robins.
1655+ if not args .include_round_0 :
1656+ logger .info ("Running EloVsMaxRounds analysis" )
1657+ EloVsMaxRounds (
1658+ log_dir = args .log_dir ,
1659+ max_rounds = 15 ,
1660+ all_games_normalization_scheme = args .all_normalization_scheme ,
1661+ score_type = args .score_type ,
1662+ regularization = args .regularization ,
1663+ output_dir = args .output_dir ,
1664+ ).run ()
1665+
1666+ logger .info ("Running EloOnlyAtRound analysis" )
1667+ EloOnlyAtRound (
1668+ log_dir = args .log_dir ,
1669+ max_rounds = 15 ,
1670+ all_games_normalization_scheme = args .all_normalization_scheme ,
1671+ score_type = args .score_type ,
1672+ regularization = args .regularization ,
1673+ output_dir = args .output_dir ,
1674+ ).run ()
1675+ else :
1676+ logger .info ("Skipping EloVsMaxRounds / EloOnlyAtRound (ladder mode: single round-0 round-robin)" )
0 commit comments