|
| 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. |
0 commit comments