Skip to content

Commit 0ceade9

Browse files
committed
Add SCML OneShot CC:Ladder (arena wiring + 51 human bots + build tooling)
- Wire SCML for the ladder: SCML.Dockerfile now git-clones CodeClash-ai/SCML (seeded with the runtime) so branch_init works, matching the other arenas. - 51 human bots imported to CodeClash-ai/SCML human/* branches (source of truth, not committed here): 3 built-in baselines + 48 ANAC agents (2021-2024) ported from yasserfarouk/scml-agents into the arena's decide(observation) contract. RL/learned agents ported heuristic-core best-effort; scml2023/team_139 deferred. - configs/ablations/ladder/make_scml.yaml: round-robin (400 sims) for Elo ranking; README note. - scripts/ladder/: operational build tooling (porting guide, validators, smoke/push scripts, AWS runbook, example agents); ports/ regenerated, not committed. - docs/scml_game_visualization.html: explainer of the SCML game.
1 parent 09823d2 commit 0ceade9

15 files changed

Lines changed: 933 additions & 8 deletions

File tree

codeclash/arenas/scml/SCML.Dockerfile

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,11 @@ RUN apt-get update \
1212
RUN python -m pip install --upgrade pip \
1313
&& python -m pip install scml==0.8.2
1414

15+
# Clone the arena repo so `origin` is set for branch_init / push (matches the other
16+
# arenas). Default branch holds the runtime; human/* branches overlay scml_agent.py.
17+
RUN git clone https://github.com/CodeClash-ai/SCML.git /workspace \
18+
&& cd /workspace \
19+
&& git remote set-url origin https://github.com/CodeClash-ai/SCML.git \
20+
&& git config user.email "player@codeclash.com" \
21+
&& git config user.name "Player"
1522
WORKDIR /workspace
16-
17-
COPY codeclash/arenas/scml/runtime/ /workspace/
18-
19-
RUN git init \
20-
&& git config user.email "player@codeclash.com" \
21-
&& git config user.name "Player" \
22-
&& git add . \
23-
&& git commit -m "Initial SCML workspace"

configs/ablations/ladder/README.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,22 @@ You can follow these steps to create your own "CC:<arena>" ladder.
1212
The tricky part is typically finding a large collection of human solutions for a particular arena.
1313
We've typically found that googling for online leaderboards or awesome-<arena> repositories (e.g. [BattleSnake](https://github.com/BattlesnakeOfficial/awesome-battlesnake)) is a good strategy.
1414

15+
### SCML OneShot (newly added)
16+
17+
The [CC:SCML](https://github.com/CodeClash-ai/SCML) repo hosts 51 human bots on `human/*` branches
18+
(like the other arenas, bot code lives only on the branches, not in this repo): 3 built-in `scml`
19+
baselines (greedy/random/nice) plus 48 ANAC competition agents from
20+
[`yasserfarouk/scml-agents`](https://github.com/yasserfarouk/scml-agents) (2021–2024), each
21+
re-expressed from its `OneShotAgent`/`OneShotSyncAgent` source into the arena's single-file
22+
`decide(observation)` contract. RL/learned agents were ported as heuristic-core best-effort;
23+
`scml2023/team_139` is deferred. Note: unlike the earlier arenas, SCML's `git`-workspace had to be
24+
wired for the ladder first — the `CodeClash-ai/SCML` repo was created (seeded with the runtime) and
25+
`SCML.Dockerfile` now `git clone`s it so `branch_init` works.
26+
27+
The build tooling (porting guide, validators, smoke/push scripts, and the AWS run
28+
instructions in `RUN_ON_AWS.md`) lives in `scripts/ladder/` — it's operational one-off tooling for
29+
constructing porting-based ladders, reusable for future arenas.
30+
1531
## Config layout
1632

1733
Each arena has a few kinds of config in this folder:
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
# Round-robin over the SCML OneShot human bots (ported from yasserfarouk/scml-agents +
2+
# built-in baselines), used to RANK them via Elo/Bradley-Terry -> see scml.yaml for the ladder.
3+
# rounds:0 = dummy players just play the baseline SCML2024OneShot game (no code edits).
4+
# 51 bots -> 51*50/2 = 1275 pairwise tournaments. Bots live on CodeClash-ai/SCML; Docker required.
5+
#
6+
# COST (measured ~4.8s/sim + ~3s overhead per pair; pairs are single-threaded, so wall =
7+
# core-hours / workers): at sims_per_round=400 a pair takes ~32 min -> ~681 core-hours total.
8+
# * 32-core box (--workers 30): ~23 h * 64 vCPU (-w 62): ~11 h * 96 vCPU (-w 90): ~7.5 h
9+
# Keep --workers <= (cores - 2): the decide() call has a 3s timeout that CPU oversubscription
10+
# can trip. Resumable: reruns skip pairs already logged, so it's safe to stop/resume.
11+
#
12+
# Full run (250-400 sims = publication-quality Elo; 400 chosen for SCML's high per-sim variance):
13+
# uv run codeclash ladder make configs/ablations/ladder/make_scml.yaml --workers 30
14+
# Cheap SANITY pilot first (~1 h @ 32 cores): temporarily set sims_per_round: 30, run, rank, and
15+
# eyeball the ordering (baselines near the bottom) before committing to the full 400-sim pass.
16+
tournament:
17+
rounds: 0
18+
game:
19+
name: SCML
20+
sims_per_round: 400
21+
# sims_per_round: 30 # <- pilot: uncomment (and comment out 400) for a ~1h sanity-check ranking
22+
args:
23+
n_steps: 10
24+
n_lines: 2
25+
players:
26+
- agent: dummy
27+
branch_init: human/scml-baselines/greedy
28+
- agent: dummy
29+
branch_init: human/scml-baselines/nice
30+
- agent: dummy
31+
branch_init: human/scml-baselines/random
32+
- agent: dummy
33+
branch_init: human/scml2021/staghunter
34+
- agent: dummy
35+
branch_init: human/scml2021/team_50
36+
- agent: dummy
37+
branch_init: human/scml2021/team_51
38+
- agent: dummy
39+
branch_init: human/scml2021/team_54
40+
- agent: dummy
41+
branch_init: human/scml2021/team_55
42+
- agent: dummy
43+
branch_init: human/scml2021/team_61
44+
- agent: dummy
45+
branch_init: human/scml2021/team_62
46+
- agent: dummy
47+
branch_init: human/scml2021/team_72
48+
- agent: dummy
49+
branch_init: human/scml2021/team_73
50+
- agent: dummy
51+
branch_init: human/scml2021/team_86
52+
- agent: dummy
53+
branch_init: human/scml2021/team_90
54+
- agent: dummy
55+
branch_init: human/scml2021/team_corleone
56+
- agent: dummy
57+
branch_init: human/scml2022/team_102
58+
- agent: dummy
59+
branch_init: human/scml2022/team_103
60+
- agent: dummy
61+
branch_init: human/scml2022/team_105
62+
- agent: dummy
63+
branch_init: human/scml2022/team_106
64+
- agent: dummy
65+
branch_init: human/scml2022/team_107
66+
- agent: dummy
67+
branch_init: human/scml2022/team_123
68+
- agent: dummy
69+
branch_init: human/scml2022/team_124
70+
- agent: dummy
71+
branch_init: human/scml2022/team_126
72+
- agent: dummy
73+
branch_init: human/scml2022/team_131
74+
- agent: dummy
75+
branch_init: human/scml2022/team_134
76+
- agent: dummy
77+
branch_init: human/scml2022/team_94
78+
- agent: dummy
79+
branch_init: human/scml2022/team_96
80+
- agent: dummy
81+
branch_init: human/scml2023/team_102
82+
- agent: dummy
83+
branch_init: human/scml2023/team_123
84+
- agent: dummy
85+
branch_init: human/scml2023/team_126
86+
- agent: dummy
87+
branch_init: human/scml2023/team_127
88+
- agent: dummy
89+
branch_init: human/scml2023/team_134
90+
- agent: dummy
91+
branch_init: human/scml2023/team_143
92+
- agent: dummy
93+
branch_init: human/scml2023/team_144
94+
- agent: dummy
95+
branch_init: human/scml2023/team_145
96+
- agent: dummy
97+
branch_init: human/scml2023/team_148
98+
- agent: dummy
99+
branch_init: human/scml2023/team_149
100+
- agent: dummy
101+
branch_init: human/scml2023/team_151
102+
- agent: dummy
103+
branch_init: human/scml2023/team_poli_usp
104+
- agent: dummy
105+
branch_init: human/scml2024/coyoteteam
106+
- agent: dummy
107+
branch_init: human/scml2024/ozug4
108+
- agent: dummy
109+
branch_init: human/scml2024/team_144
110+
- agent: dummy
111+
branch_init: human/scml2024/team_164
112+
- agent: dummy
113+
branch_init: human/scml2024/team_171
114+
- agent: dummy
115+
branch_init: human/scml2024/team_172
116+
- agent: dummy
117+
branch_init: human/scml2024/team_174
118+
- agent: dummy
119+
branch_init: human/scml2024/team_193
120+
- agent: dummy
121+
branch_init: human/scml2024/team_238
122+
- agent: dummy
123+
branch_init: human/scml2024/team_abc
124+
- agent: dummy
125+
branch_init: human/scml2024/team_miyajima
126+
- agent: dummy
127+
branch_init: human/scml2024/teamyuzuru
128+
prompts:
129+
game_description: SCML OneShot ladder

docs/scml_game_visualization.html

Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1" />
6+
<title>SCML OneShot — how the game works</title>
7+
<style>
8+
:root {
9+
--page: #f9f9f7;
10+
--surface: #fcfcfb;
11+
--ink: #0b0b0b;
12+
--ink-2: #52514e;
13+
--muted: #898781;
14+
--grid: #e1e0d9;
15+
--border: rgba(11,11,11,0.10);
16+
/* categorical roles (fixed-order slots from the validated palette) */
17+
--supplier: #2a78d6; /* slot 1 blue — sells inputs to you */
18+
--you: #1baf7a; /* slot 2 aqua — the factory you code */
19+
--consumer: #eb6834; /* slot 8 orange— buys your outputs */
20+
--exogenous: #4a3aa7; /* slot 5 violet— forced market contract */
21+
--good: #0ca30c;
22+
--critical: #d03b3b;
23+
--code-bg: #f2f1ec;
24+
}
25+
@media (prefers-color-scheme: dark) {
26+
:root {
27+
--page: #0d0d0d;
28+
--surface: #1a1a19;
29+
--ink: #ffffff;
30+
--ink-2: #c3c2b7;
31+
--muted: #898781;
32+
--grid: #2c2c2a;
33+
--border: rgba(255,255,255,0.10);
34+
--supplier: #3987e5;
35+
--you: #199e70;
36+
--consumer: #d95926;
37+
--exogenous: #9085e9;
38+
--good: #0ca30c;
39+
--critical: #d03b3b;
40+
--code-bg: #232320;
41+
}
42+
}
43+
* { box-sizing: border-box; }
44+
body {
45+
margin: 0; padding: 32px;
46+
background: var(--page); color: var(--ink);
47+
font-family: system-ui, -apple-system, "Segoe UI", sans-serif;
48+
line-height: 1.5;
49+
}
50+
.wrap { max-width: 1080px; margin: 0 auto; }
51+
h1 { font-size: 26px; margin: 0 0 4px; letter-spacing: -0.01em; }
52+
.sub { color: var(--ink-2); margin: 0 0 28px; font-size: 15px; }
53+
.card {
54+
background: var(--surface); border: 1px solid var(--border);
55+
border-radius: 14px; padding: 22px 24px; margin-bottom: 20px;
56+
}
57+
.card h2 {
58+
font-size: 13px; text-transform: uppercase; letter-spacing: 0.08em;
59+
color: var(--muted); margin: 0 0 16px; font-weight: 700;
60+
}
61+
.lede { color: var(--ink-2); font-size: 14px; margin: -8px 0 18px; }
62+
code, .mono { font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace; }
63+
.legend { display: flex; gap: 20px; flex-wrap: wrap; margin-top: 14px; font-size: 13px; color: var(--ink-2); }
64+
.legend span { display: inline-flex; align-items: center; gap: 7px; }
65+
.dot { width: 12px; height: 12px; border-radius: 3px; display: inline-block; }
66+
.grid2 { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; }
67+
@media (max-width: 820px) { .grid2 { grid-template-columns: 1fr; } }
68+
pre {
69+
background: var(--code-bg); border: 1px solid var(--border); border-radius: 10px;
70+
padding: 14px 16px; overflow-x: auto; font-size: 12.5px; line-height: 1.55; margin: 0;
71+
}
72+
pre .kw { color: var(--supplier); }
73+
pre .st { color: var(--consumer); }
74+
pre .cm { color: var(--muted); }
75+
.tag { display:inline-block; padding: 1px 7px; border-radius: 6px; font-size: 12px; font-weight:600; }
76+
.flow-lbl { font-size: 11.5px; font-weight: 700; }
77+
table { border-collapse: collapse; width: 100%; font-size: 13.5px; }
78+
td { padding: 7px 4px; border-bottom: 1px solid var(--grid); }
79+
td:last-child { text-align: right; font-variant-numeric: tabular-nums; }
80+
.num-pos { color: var(--good); font-weight: 600; }
81+
.num-neg { color: var(--critical); font-weight: 600; }
82+
.total td { border-top: 2px solid var(--muted); border-bottom: none; font-weight: 700; padding-top: 10px; }
83+
.note { font-size: 12.5px; color: var(--muted); margin-top: 12px; }
84+
ol { margin: 0; padding-left: 20px; font-size: 14px; }
85+
ol li { margin-bottom: 8px; }
86+
.pill { border:1px solid var(--border); border-radius:999px; padding:2px 10px; font-size:12px; color:var(--ink-2); }
87+
</style>
88+
</head>
89+
<body>
90+
<div class="wrap">
91+
<h1>SCML OneShot — how the game works</h1>
92+
<p class="sub">A supply-chain negotiation game. Each bot is a factory that <b>negotiates</b> to buy inputs and sell outputs; the objective is to maximize profit. <span class="pill">CodeClash arena · <code>scml_agent.py</code></span></p>
93+
94+
<!-- PANEL 1: the supply chain -->
95+
<div class="card">
96+
<h2>1 · The supply chain (a 2-level "OneShot" world)</h2>
97+
<p class="lede">The market forces goods in at the top and demand out at the bottom (violet = <b>exogenous</b>, non-negotiable). Everything in the middle is settled by negotiation. Every submitted policy runs a factory at <b>both</b> levels, and its score is averaged.</p>
98+
<svg viewBox="0 0 1000 300" width="100%" role="img" aria-label="Supply chain: exogenous supply into L0 factories, which negotiate to sell to L1 factories, which fulfil exogenous demand.">
99+
<defs>
100+
<marker id="ah" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
101+
<path d="M0 0 L10 5 L0 10 z" fill="var(--muted)"/>
102+
</marker>
103+
</defs>
104+
<!-- exogenous supply -->
105+
<rect x="360" y="12" width="280" height="34" rx="8" fill="none" stroke="var(--exogenous)" stroke-width="2"/>
106+
<text x="500" y="34" text-anchor="middle" font-size="13" font-weight="700" fill="var(--exogenous)">EXOGENOUS SUPPLY — raw material forced in</text>
107+
108+
<!-- L0 factories -->
109+
<g>
110+
<rect x="150" y="90" width="200" height="52" rx="10" fill="var(--supplier)" opacity="0.14" stroke="var(--supplier)" stroke-width="2"/>
111+
<text x="250" y="112" text-anchor="middle" font-size="14" font-weight="700" fill="var(--supplier)">L0 factory · "seller"</text>
112+
<text x="250" y="130" text-anchor="middle" font-size="11.5" fill="var(--ink-2)">must SELL what it's forced to buy</text>
113+
114+
<rect x="650" y="90" width="200" height="52" rx="10" fill="var(--you)" opacity="0.16" stroke="var(--you)" stroke-width="2"/>
115+
<text x="750" y="112" text-anchor="middle" font-size="14" font-weight="700" fill="var(--you)">L1 factory · "buyer"</text>
116+
<text x="750" y="130" text-anchor="middle" font-size="11.5" fill="var(--ink-2)">must BUY to meet its demand</text>
117+
</g>
118+
119+
<!-- exogenous demand -->
120+
<rect x="360" y="254" width="280" height="34" rx="8" fill="none" stroke="var(--exogenous)" stroke-width="2"/>
121+
<text x="500" y="276" text-anchor="middle" font-size="13" font-weight="700" fill="var(--exogenous)">EXOGENOUS DEMAND — finished goods forced out</text>
122+
123+
<!-- arrows -->
124+
<line x1="470" y1="46" x2="280" y2="88" stroke="var(--muted)" stroke-width="1.6" marker-end="url(#ah)"/>
125+
<line x1="530" y1="254" x2="720" y2="144" stroke="var(--muted)" stroke-width="1.6" marker-start="url(#ah)"/>
126+
127+
<!-- the negotiated trade -->
128+
<line x1="350" y1="116" x2="650" y2="116" stroke="var(--ink)" stroke-width="2.5" marker-end="url(#ah)" marker-start="url(#ah)"/>
129+
<rect x="410" y="150" width="180" height="46" rx="9" fill="var(--surface)" stroke="var(--ink)" stroke-width="1.5"/>
130+
<text x="500" y="168" text-anchor="middle" font-size="12" font-weight="700" fill="var(--ink)">NEGOTIATED CONTRACT</text>
131+
<text x="500" y="186" text-anchor="middle" font-size="12.5" class="mono" fill="var(--ink-2)">[ quantity, time, unit price ]</text>
132+
<text x="500" y="108" text-anchor="middle" font-size="11.5" font-weight="700" fill="var(--ink-2)">goods →</text>
133+
<text x="500" y="132" text-anchor="middle" font-size="11.5" font-weight="700" fill="var(--ink-2)">← money</text>
134+
</svg>
135+
<div class="legend">
136+
<span><i class="dot" style="background:var(--exogenous)"></i>Exogenous (forced by the market)</span>
137+
<span><i class="dot" style="background:var(--supplier)"></i>Seller side (has supply, needs buyers)</span>
138+
<span><i class="dot" style="background:var(--you)"></i>Buyer side (has demand, needs supply)</span>
139+
</div>
140+
</div>
141+
142+
<div class="grid2">
143+
<!-- PANEL 2: negotiation -->
144+
<div class="card">
145+
<h2>2 · How they compete: bilateral negotiation</h2>
146+
<p class="lede">There are no fixed prices. Two parties exchange offers over several rounds until someone accepts, rejects, or walks away. Your bot is called at each of its turns.</p>
147+
<svg viewBox="0 0 460 250" width="100%" role="img" aria-label="Alternating offers between a seller and a buyer, ending in an accepted contract.">
148+
<text x="70" y="18" text-anchor="middle" font-size="12" font-weight="700" fill="var(--supplier)">SELLER</text>
149+
<text x="390" y="18" text-anchor="middle" font-size="12" font-weight="700" fill="var(--you)">BUYER (you)</text>
150+
<line x1="70" y1="26" x2="70" y2="240" stroke="var(--grid)" stroke-width="1.5"/>
151+
<line x1="390" y1="26" x2="390" y2="240" stroke="var(--grid)" stroke-width="1.5"/>
152+
153+
<!-- offer 1 seller->buyer -->
154+
<line x1="70" y1="52" x2="390" y2="72" stroke="var(--supplier)" stroke-width="2" marker-end="url(#ah)"/>
155+
<text x="230" y="50" text-anchor="middle" font-size="11" class="mono" fill="var(--ink-2)">propose [10, t, $9]</text>
156+
<!-- offer 2 buyer->seller (counter) -->
157+
<line x1="390" y1="98" x2="70" y2="118" stroke="var(--you)" stroke-width="2" marker-end="url(#ah)"/>
158+
<text x="230" y="96" text-anchor="middle" font-size="11" class="mono" fill="var(--ink-2)">reject → counter [10, t, $6]</text>
159+
<!-- offer 3 seller->buyer -->
160+
<line x1="70" y1="144" x2="390" y2="164" stroke="var(--supplier)" stroke-width="2" marker-end="url(#ah)"/>
161+
<text x="230" y="142" text-anchor="middle" font-size="11" class="mono" fill="var(--ink-2)">counter [10, t, $7]</text>
162+
<!-- accept -->
163+
<rect x="150" y="196" width="160" height="34" rx="8" fill="var(--good)" opacity="0.14" stroke="var(--good)" stroke-width="1.5"/>
164+
<text x="230" y="218" text-anchor="middle" font-size="12.5" font-weight="700" fill="var(--good)">✓ ACCEPT → deal at $7</text>
165+
</svg>
166+
<p class="note">Every submitted policy negotiates against every other in the same world, simultaneously, on both sides of the chain.</p>
167+
</div>
168+
169+
<!-- PANEL 4: what you code -->
170+
<div class="card">
171+
<h2>3 · What you actually code</h2>
172+
<p class="lede">One stateless function. The trusted runtime owns the SCML agent and calls <code>decide()</code> at each turn, telling you the <code>event</code>. Return <code>{}</code>/<code>None</code> to defer to a greedy fallback.</p>
173+
<pre><span class="cm"># scml_agent.py</span>
174+
<span class="kw">def</span> decide(observation):
175+
ev = observation[<span class="st">"event"</span>]
176+
awi = observation[<span class="st">"awi"</span>] <span class="cm"># prices, needs</span>
177+
178+
<span class="kw">if</span> ev == <span class="st">"propose"</span>: <span class="cm"># my turn to offer</span>
179+
<span class="kw">return</span> {<span class="st">"offer"</span>: [q, t, price]}
180+
181+
<span class="kw">if</span> ev == <span class="st">"respond"</span>: <span class="cm"># react to their offer</span>
182+
<span class="kw">return</span> {<span class="st">"response"</span>: <span class="st">"accept"</span>}
183+
<span class="cm"># or "reject" (+ counter offer), "end"</span>
184+
185+
<span class="kw">return</span> {} <span class="cm"># → greedy fallback</span></pre>
186+
<p class="note">Invalid or slow returns count as policy errors (capped); an unhandled crash floors your score.</p>
187+
</div>
188+
</div>
189+
190+
<!-- PANEL 3: scoring -->
191+
<div class="card">
192+
<h2>4 · How the score is decided — profit</h2>
193+
<p class="lede">A round runs several worlds. Your arena score = your <b>average SCML profit</b> across them. Highest average wins. Example, one day of a buyer factory:</p>
194+
<div class="grid2">
195+
<table>
196+
<tr><td>Sell finished goods (exogenous demand: 10 @ $12)</td><td class="num-pos">+120</td></tr>
197+
<tr><td>Buy inputs you negotiated (10 @ $7)</td><td class="num-neg">−70</td></tr>
198+
<tr><td>Production cost (10 units on your lines)</td><td class="num-neg">−15</td></tr>
199+
<tr><td>Shortfall penalty (demand you couldn't cover)</td><td class="num-neg">−0</td></tr>
200+
<tr><td>Disposal penalty (inputs you overbought)</td><td class="num-neg">−0</td></tr>
201+
<tr class="total"><td>Profit this day</td><td class="num-pos">+35</td></tr>
202+
</table>
203+
<div>
204+
<p style="font-size:14px;margin:0 0 10px;color:var(--ink-2)"><b>The core tension</b> your bot is optimizing:</p>
205+
<ol>
206+
<li><b>Match quantity</b> to what you can produce (<code>n_lines</code>) and are obligated to deliver — over- or under-buying is penalized on both ends.</li>
207+
<li><b>Win on price</b> — buy cheap, sell dear — but not so greedily that negotiations collapse and you're left short.</li>
208+
<li><b>Close in time</b> — deals must clear within the day's negotiation rounds.</li>
209+
</ol>
210+
<p class="note">No replay/animation exists in this arena: worlds run headless (<code>no_logs=True</code>) and emit only final scores to <code>scml_results.json</code>.</p>
211+
</div>
212+
</div>
213+
</div>
214+
215+
</div>
216+
</body>
217+
</html>

scripts/ladder/.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# Regenerated build artifacts — bot ports live on the CodeClash-ai/<arena> branches
2+
# (source of truth), not in this repo. Populate ports/ locally when (re)building a ladder.
3+
ports/
4+
out/

0 commit comments

Comments
 (0)