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