Skip to content

Commit 7bd24cb

Browse files
committed
Merge branch 'main' into john/scml-ladder
# Conflicts: # codeclash/cli/ladder.py # configs/ablations/ladder/corewar__gemini_3_5_flash.yaml # configs/ablations/ladder/corewar__gpt_5_5.yaml # configs/ablations/ladder/corewar__opus_4_7.yaml # configs/ablations/ladder/corewar__opus_4_8.yaml # configs/ablations/ladder/corewar__sonnet_5.yaml # configs/ablations/ladder/robotrumble__gemini_3_5_flash.yaml # configs/ablations/ladder/robotrumble__gpt_5_5.yaml # configs/ablations/ladder/robotrumble__opus_4_7.yaml # configs/ablations/ladder/robotrumble__opus_4_8.yaml # configs/ablations/ladder/robotrumble__sonnet_5.yaml # configs/ladder/README.md # configs/ladder/battlesnake.yaml # configs/ladder/battlesnake__gemini_3_5_flash.yaml # configs/ladder/battlesnake__gpt_5_5.yaml # configs/ladder/battlesnake__opus_4_7.yaml # configs/ladder/battlesnake__opus_4_8.yaml # configs/ladder/battlesnake__sonnet_5.yaml # configs/ladder/battlesnake_llama_smoke.yaml # configs/ladder/corewar.yaml # configs/ladder/make_scml.yaml # configs/ladder/robotrumble.yaml
2 parents 0abe7dd + 9d36a75 commit 7bd24cb

236 files changed

Lines changed: 747 additions & 873 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
---
2+
name: create-arena-ladder
3+
description: >-
4+
Build a CC:Ladder for any CodeClash arena: import human-written solutions as git
5+
branches, rank them via round-robin PvP + Elo, and assemble the ladder configs.
6+
Use when asked to "create a ladder", "import human solutions", "push human bots as
7+
branches", or "make a CC:<arena> ladder" for arenas like BattleSnake, RobotRumble,
8+
CoreWar, Gomoku, RoboCode, SCML, etc.
9+
---
10+
11+
# Create an Arena Ladder (CC:Ladder)
12+
13+
A **ladder** turns a curated set of human-written bots into a ranked gauntlet, then
14+
measures how far up a model can climb. Two phases: **make the ladder** (rank the humans
15+
via round-robin), then **run the ladder** (a model climbs rung by rung until it loses).
16+
17+
This skill produces, for arena `<A>`: `human/<author>/<name>` branches on `CodeClash-ai/<A>`,
18+
a `make_<a>.yaml` (round-robin), a ranked `rungs/<a>.yaml`, and a run config `<a>.yaml`.
19+
20+
Work in the `repo/` clone. Reference implementations to mirror:
21+
`configs/ablations/ladder/{make_battlesnake,battlesnake,rungs/battlesnake}.yaml` and its
22+
`README.md`. For worked examples of the import step, the `john/*-ladder` branches show two
23+
shapes end-to-end (gomoku = function-port, robocode = copy-in).
24+
25+
---
26+
27+
## Mechanics you must respect (arena-agnostic)
28+
29+
- **Where human code lives:** each arena has its **own** repo under `CodeClash-ai`
30+
(hardcoded `GH_ORG` in `codeclash/constants.py`). The arena Dockerfile `git clone`s it
31+
into `/workspace`. Human bots are **branches of that per-arena repo**, NOT of this
32+
monorepo — so `git branch -a` here shows no `human/*`.
33+
- **How a branch becomes a player:** a player with `branch_init: human/foo/bar` makes
34+
`Player.__init__` (`codeclash/agents/player.py`) run `git fetch && git checkout` in the
35+
clone; that branch's files overlay `/workspace`. `agent: dummy` = static opponent.
36+
`push: True` (the climbing player) needs `GITHUB_TOKEN`.
37+
- **What a branch must contain:** the arena's **submission file(s)** at the path its
38+
`validate_code` expects; everything else (engine, assets) comes from the base clone.
39+
**The single source of truth is `codeclash/arenas/<a>/<a>.py` — read its `submission`
40+
attribute and `validate_code` method.** Those two define the contract. Examples:
41+
BattleSnake → `main.py` HTTP server (`info/start/end/move`); Gomoku → `main.py` with
42+
`get_move(board,color)`; SCML → agent with `decide(observation)`; RoboCode → Java class
43+
under `robots/custom/`; CoreWar → `warrior.red`.
44+
45+
---
46+
47+
## Phase 0 — Prerequisites
48+
49+
- Arena class `codeclash/arenas/<a>/<a>.py` exists; record its `submission` path + exact
50+
`validate_code` requirements.
51+
- Arena repo `github.com/CodeClash-ai/<A>` exists and its Dockerfile clones it.
52+
- Docker running; `GITHUB_TOKEN` set (public repos → `gh auth token` works for push + run).
53+
54+
## Phase 1 — Source many human solutions
55+
56+
The hard part is finding a large set. Best sources: official leaderboards and
57+
`awesome-<arena>` repos (BattleSnake used `awesome-battlesnake`; RobotRumble crawled
58+
`robotrumble.org/boards/2`; RoboCode drew from RoboWiki/GitHub). Capture author + bot name
59+
per candidate → these become the branch slugs. Keep a provenance record (source URL,
60+
author, license) as you go — a table plus a header comment in each imported file.
61+
62+
## Phase 2 — Adapt each solution to the arena's contract
63+
64+
Every bot must end up matching the one submission contract. Do NOT discard a bot merely for
65+
being in another language — pick the cheapest import shape:
66+
67+
- **Copy-in** — source is already in the arena's language/framework: drop the files in and
68+
rename/repackage (e.g. RoboCode: main class → `MyTank`, `package custom;`). Mechanical.
69+
- **Function-contract port** — reimplement the core "given state, choose a move" logic as
70+
the arena's single entry function (Gomoku `get_move`, SCML `decide`, BattleSnake `move`).
71+
Ignore the source's GUI/protocol/CLI wrapper; keep its evaluation + search faithful.
72+
73+
Porting hard rules (adapt per arena; worth writing up as a guide for any porting agents):
74+
- **Runtime-only deps** — match the arena image (often stdlib-only Python 3.10; re-express
75+
array math in pure Python). No trained weights / NN unless you can obtain the binary.
76+
- **One entry point, never raise** — a crash or illegal move = a forfeit; wrap the body and
77+
fall back to a safe legal move.
78+
- **Fast enough** — a round-robin plays many games; cap search depth / rollouts.
79+
- Only skip a bot if it truly can't run. **Log every skip with a reason — never silently
80+
drop bots.**
81+
82+
## Phase 3 — Validate (two-stage), then push
83+
84+
Local validation is necessary but NOT sufficient — a local shim skips Docker, the repo's
85+
`server.py`, and real payloads. Gate every bot in two stages before it earns a branch:
86+
87+
1. **Stage 1 (local):** syntax/import + the arena's `validate_code` legality check. Cheap;
88+
catches most breakage without Docker. Fix or drop failures.
89+
2. **Stage 2 (arena, REQUIRED):** play each stage-1 pass through the real arena image and
90+
confirm it completes a full game without erroring. This is the step that actually gates.
91+
Requires Docker. (Scripting both stages over a folder of candidates is worth it at scale.)
92+
3. **Push** each stage-2-healthy bot as `human/<author>/<name>` (dedupe identical content).
93+
Use consistent kebab/lowercase slugs; branches must be pushed before any arena run, since
94+
`branch_init` fetches from the remote.
95+
96+
## Phase 4 — Make the ladder (round-robin + Elo)
97+
98+
1. Write `configs/ablations/ladder/make_<a>.yaml` (mirror `make_battlesnake.yaml`):
99+
`tournament.rounds: 0`, a `game` block, and `players:` = every `human/*` branch as
100+
`{agent: dummy, branch_init: ...}`.
101+
2. Pre-build the image **once** to avoid a build stampede under many workers:
102+
`docker build -t codeclash/<a> -f codeclash/arenas/<a>/<A>.Dockerfile .`
103+
3. Run all-pairs PvP (resumable — skips pairs already logged; `--workers ≈ cores-2`):
104+
`GITHUB_TOKEN=$(gh auth token) uv run codeclash ladder make configs/ablations/ladder/make_<a>.yaml --workers N`
105+
→ logs land under `logs/ladder/<A>/`. Fast arenas run on a laptop; big ones (e.g. SCML's
106+
~1275 pairs) want an AWS box under `tmux`/`nohup`.
107+
4. Rank: `python -m codeclash.analysis.metrics.elo -d logs/ladder/<A> --output-dir assets/<a>_elo`
108+
→ prints the Bradley-Terry/Elo order (weakest → strongest); that ordering IS the ladder.
109+
Tip: run a cheap low-`sims` pilot first and eyeball that baselines sit near the bottom.
110+
111+
## Phase 5 — Assemble the ranked configs
112+
113+
1. `configs/ablations/ladder/rungs/<a>.yaml` — the ranked opponents, **weakest first,
114+
strongest last** (each `{agent: dummy, branch_init: human/...}`), in Elo order.
115+
2. `configs/ablations/ladder/<a>.yaml` — the climber `player` (starting at the weakest
116+
rung, `push: True`) + `ladder: !include ablations/ladder/rungs/<a>.yaml` + a
117+
`ladder_rules` block. Model on `battlesnake.yaml`.
118+
3. Optional `<a>__<model>.yaml` per-model variants (swap `model: !include mini/models/...`);
119+
they share the same `rungs/<a>.yaml` include.
120+
121+
`ladder_rules` (optional; defaults reproduce historical behavior):
122+
```yaml
123+
ladder_rules:
124+
min_round_win_fraction: 0.5 # must win strictly more than this fraction of rounds
125+
win_last_k: 1 # ...and must win the last K rounds
126+
```
127+
128+
## Phase 6 — Run the ladder
129+
130+
`uv run codeclash ladder run configs/ablations/ladder/<a>.yaml`
131+
→ prints the highest rung reached; logs under `LOCAL_LOG_DIR/<user>/LadderTournament.*`.
132+
133+
---
134+
135+
## Deliverables checklist
136+
- [ ] N validated `human/<author>/<name>` branches pushed to `CodeClash-ai/<A>`.
137+
- [ ] **Stage-2 arena smoke passed** (each bot plays a real game in Docker), skips logged.
138+
- [ ] `make_<a>.yaml` + round-robin run + Elo ranking (`assets/<a>_elo`).
139+
- [ ] `rungs/<a>.yaml` (weakest→strongest) + `<a>.yaml` (climber + `ladder_rules`).
140+
- [ ] `ladder run` executes end-to-end against a sample model.
141+
- [ ] Provenance recorded (source/author/license per bot); note in `ladder/README.md` if
142+
the arena is new.
143+
144+
## Gotchas
145+
- Human branches go to the **per-arena** repo, not the monorepo; `branch_init` fetches from
146+
the remote, so **push before any run** and keep Docker up.
147+
- A bot that fails `validate_code` silently forfeits. A local shim can pass yet fail in the
148+
arena — the **stage-2 Docker smoke** is the real gate, not stage 1.
149+
- Pre-build the arena image once before a `--workers N` run, or workers stampede the build.
150+
- Port aggressively into the single submission contract; skip only un-runnable bots, and
151+
log every skip. A port must reproduce the original's behavior.

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ codeclash run configs/test/battlesnake.yaml
7373
Once this works, you should be set up to run a real tournament!
7474
To run *Claude Sonnet 4.5* against *o3* in a *BattleSnake* tournament with *5 rounds* and *1000 competition simulations* per round, run:
7575
```bash
76-
uv run codeclash run configs/examples/BattleSnake__claude-sonnet-4-5-20250929__o3__r5__s1000.yaml
76+
uv run codeclash run configs/pvp/BattleSnake__claude-sonnet-4-5-20250929__o3__r15__s1000.yaml
7777
```
7878

7979
## ⚔️ How It Works

codeclash/agents/mini_anthropic_model.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
`model_class: codeclash.agents.mini_anthropic_model.AnthropicModel` and provide the API key,
1212
base URL, and model name (see the `*_env` config fields, which keep endpoint-specific values in
1313
the environment rather than in committed configs). Requires the optional `anthropic` dependency
14-
(`uv pip install -e '.[llama]'`). See configs/ablations/ladder/robotrumble_llama.yaml.
14+
(`uv pip install -e '.[llama]'`). See configs/ladder/robotrumble_llama.yaml.
1515
"""
1616

1717
import json

codeclash/agents/utils.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ class GameContext(BaseModel):
2626
round: int
2727
rounds: int
2828
working_dir: str
29+
arena_description: str = ""
2930

3031
def _render_prompt_templates(self) -> dict:
3132
context = self.model_dump()

codeclash/analysis/code_evolve/main.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
from codeclash.arenas import ARENAS
1919
from codeclash.constants import LOCAL_LOG_DIR
2020

21-
MODELS_PATH = Path("configs/models.yaml")
21+
MODELS_PATH = Path("configs/mini/model_roster.yaml")
2222
TARGET_ROUNDS = [1, 15, 5, 10]
2323

2424

codeclash/arenas/battlesnake/battlesnake.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,4 +203,9 @@ def validate_code(self, agent: Player) -> tuple[bool, str | None]:
203203
error_msg.append(f"There should be a `{func}` function implemented in `{self.submission}`")
204204
if len(error_msg) > 0:
205205
return False, "\n".join(error_msg + ["Don't change the function signatures!"])
206+
if "__main__" not in bot_content:
207+
return False, (
208+
f'`{self.submission}` must keep its `if __name__ == "__main__"` block that starts '
209+
"the server, or the bot fails to launch."
210+
)
206211
return True, None

codeclash/cli/ladder.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,7 @@ def _resolve_ladder_rules(ladder_rules: dict, rounds: int) -> tuple[int, int]:
4646
typer.echo(f"ladder_rules.min_round_wins must be an integer, got {min_round_wins!r}.")
4747
raise typer.Exit(1)
4848
if not 1 <= min_round_wins <= rounds:
49-
typer.echo(
50-
f"ladder_rules.min_round_wins must be in [1, {rounds}] (tournament.rounds), got {min_round_wins}."
51-
)
49+
typer.echo(f"ladder_rules.min_round_wins must be in [1, {rounds}] (tournament.rounds), got {min_round_wins}.")
5250
raise typer.Exit(1)
5351

5452
# win_last_k: number of trailing rounds the player must win (1 == just the final round, 0 == disabled).
@@ -87,7 +85,7 @@ def make(
8785
):
8886
"""Build a ladder: run PvP tournaments across all pairs of players (for ranking).
8987
90-
[dim]• codeclash ladder make configs/ablations/ladder/make_battlesnake.yaml[/dim]
88+
[dim]• codeclash ladder make configs/ladder/make_battlesnake.yaml[/dim]
9189
"""
9290
yaml_content = config_path.read_text()
9391
preprocessed_yaml = resolve_includes(yaml_content, base_dir=CONFIG_DIR)

codeclash/tournaments/pvp.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ def get_agent(self, agent_config: dict, prompts: dict) -> Player:
7979
round=1,
8080
rounds=self.rounds,
8181
working_dir=str(DIR_WORK),
82+
arena_description=self.game.description,
8283
)
8384

8485
return get_agent(agent_config, game_context, environment)

codeclash/utils/generate_confs.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,14 @@
33
44
Each configuration file specifies a tournament between two models in a given arena,
55
including the number of rounds and simulations per round. The configurations are saved
6-
as YAML files in the specified output directory (default: configs/main/).
6+
as YAML files in the specified output directory (default: configs/pvp/).
77
88
Also generates a tracking JSON file at configs/tracker.json to keep track of
99
the number of tournaments and rounds played for each pair of models in each arena.
1010
1111
Usage:
1212
13-
python codeclash/utils/generate_confs.py -m configs/models.yaml -r 15 -s 1000
13+
python codeclash/utils/generate_confs.py -m configs/mini/model_roster.yaml -r 15 -s 1000
1414
"""
1515

1616
import argparse
@@ -184,7 +184,7 @@ def main(models, arenas, rounds: int, simulations: int, record_ratio: float, out
184184
"-m",
185185
"--models",
186186
type=str,
187-
default="configs/models.yaml",
187+
default="configs/mini/model_roster.yaml",
188188
help="Path to model configurations.",
189189
)
190190
parser.add_argument(
@@ -218,7 +218,7 @@ def main(models, arenas, rounds: int, simulations: int, record_ratio: float, out
218218
"-o",
219219
"--output",
220220
type=Path,
221-
default=Path("configs/main/"),
221+
default=Path("configs/pvp/"),
222222
help="Output directory for configuration files (default: main/).",
223223
)
224224
args = parser.parse_args()

codeclash/viewer/static/js/picker.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -503,7 +503,7 @@ async function fillTextareaWithAWSSubmitCommands() {
503503
// Format output as AWS submit commands
504504
const commands = successfulResults.map(
505505
(result) =>
506-
`aws/run_job.py -- aws/docker_and_sync.sh codeclash run configs/main/${result.config_name}`,
506+
`aws/run_job.py -- aws/docker_and_sync.sh codeclash run configs/pvp/${result.config_name}`,
507507
);
508508
textarea.value = commands.join("\n");
509509

0 commit comments

Comments
 (0)