Spaces:
Running
Adversarial 1v1 spotlight: ladder family + rating + Elo wiring
Browse files- new 'adversarial' capability bucket (schema Literal + leaderboard
CAPABILITIES) — the head-to-head axis an RTS engine uniquely owns
- 3 ladder packs (adversarial-duel/-skirmish/-siege), each a 3-rung
easy→medium→hard escalation of a reactive engine-driven opponent;
per-level actor overrides give real rungs; win=destroy force within
ticks, fail=force wiped, hard adds a loss cap
- adversarial.py: pure ladder_rating (contiguous rungs cleared from
easy; all-seeds-won required) + adversarial_summary
- run_eval emits out['adversarial']; leaderboard ingest records
adversarial_rating + per-pack ladders; app capability table column;
Elo path stays via existing comparative pairwise on shared rungs
- documented swap-in to model-vs-model once the engine exposes an
enemy command channel (pairwise.py / task #3)
- tests: test_adversarial.py (6, incl live-engine pack runs) —
313 passed, 1 skipped
- SCENARIO_AUDIT.md +354 -0
- app.py +3 -2
- openra_bench/adversarial.py +72 -0
- openra_bench/leaderboard.py +8 -1
- openra_bench/run_eval.py +6 -0
- openra_bench/scenarios/packs/adversarial-duel.yaml +148 -0
- openra_bench/scenarios/packs/adversarial-siege.yaml +112 -0
- openra_bench/scenarios/packs/adversarial-skirmish.yaml +112 -0
- openra_bench/scenarios/schema.py +3 -1
- tests/test_adversarial.py +101 -0
|
@@ -0,0 +1,354 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# OpenRA-Bench — Scenario Audit & Gap Analysis
|
| 2 |
+
|
| 3 |
+
Research + audit. No code changes. Prepared 2026-05-17.
|
| 4 |
+
|
| 5 |
+
---
|
| 6 |
+
|
| 7 |
+
## Executive Summary
|
| 8 |
+
|
| 9 |
+
OpenRA-Bench currently ships **85 scenario files** (17 hand-authored
|
| 10 |
+
scenario families + 68 procedurally generated `cat-*` packs across 12
|
| 11 |
+
categories, ~200 difficulty levels + 1 TEMPLATE). The design is
|
| 12 |
+
unusually well-grounded: every win condition is a verifiable predicate,
|
| 13 |
+
difficulty scales by *decision hardness* (info down, decoys up, clock
|
| 14 |
+
down, attrition cap) rather than raw numbers, and each pack carries a
|
| 15 |
+
`real_world_meaning` + `robotics_analogue`. The anti-memorization
|
| 16 |
+
discipline (procedural variation, held-out seeds, generalization-gap
|
| 17 |
+
metric) is consistent with current best practice (Procgen, SMACv2,
|
| 18 |
+
ARC-AGI). The "RTS reasoning transfers" hypothesis is *directly
|
| 19 |
+
supported by published evidence*: lmgame-Bench (arXiv:2505.15146)
|
| 20 |
+
showed RL on Sokoban/Tetris lifted Blocksworld (+≥10pts planning) and
|
| 21 |
+
WebShop (+6pts multi-turn decisions) but **not** math/coding — i.e.
|
| 22 |
+
transfer is real but axis-specific (spatial / planning / embodied),
|
| 23 |
+
exactly the axes this suite targets.
|
| 24 |
+
|
| 25 |
+
**The core finding of this audit:** the capability *taxonomy* is
|
| 26 |
+
sound and the transfer story is defensible, but the **coverage is
|
| 27 |
+
badly skewed**. Frontier models in 2026 are differentiated almost
|
| 28 |
+
entirely on (a) agentic **tool-use / function-calling fidelity** under
|
| 29 |
+
strict APIs (BFCL V4, τ²-bench — GPT-5.5 reports 98% τ²-Telecom,
|
| 30 |
+
Kimi-K2.6 50% Toolathlon), (b) **long-horizon multi-step agentic
|
| 31 |
+
execution** (Terminal-Bench 2.0 — GPT-5.5 82.7%, SWE-bench Verified
|
| 32 |
+
~89%), and (c) **fluid/abstract + spatial reasoning** (ARC-AGI-2 — the
|
| 33 |
+
single largest 2026 model delta; ERQA for embodied/spatial). OpenRA-Bench
|
| 34 |
+
is *strong* on spatial perception/exploration (FRONT/PERC — parallels
|
| 35 |
+
ERQA, the validated transfer target) and *adequate* on constrained
|
| 36 |
+
planning (PLAN/TECH/ECON — parallels PlanBench/Blocksworld). It is
|
| 37 |
+
**weak on adversarial/game-theoretic reasoning** (no opponent-modeling
|
| 38 |
+
scenario despite an RTS engine — the one capability an RTS bench is
|
| 39 |
+
uniquely positioned to own), **weak on long-horizon credit assignment**
|
| 40 |
+
(most clocks are short single-phase tasks, not 30+ step chains like
|
| 41 |
+
Terminal-Bench/OSWorld), and **structurally weak on the very thing
|
| 42 |
+
2026 leaderboards weight most: tool-use/instruction-following fidelity
|
| 43 |
+
under a strict action API** — this is *measured* (action-validity
|
| 44 |
+
sub-score) but no scenario *isolates* it as the primary objective.
|
| 45 |
+
|
| 46 |
+
Additionally, ~68 of 85 packs are the auto-generated `cat-*` family:
|
| 47 |
+
12 categories × ~3 levels × ~variants. Many `cat-c5/c6/c7/c8`
|
| 48 |
+
"budget-allocation / base-placement" packs are near-duplicates of each
|
| 49 |
+
other and of the hand-authored `economy-*` and `building-and-planning`
|
| 50 |
+
scenarios — high level count, low *distinct-capability* count. Several
|
| 51 |
+
economy packs degenerate to identical tests because harvest income is
|
| 52 |
+
0 on the only loadable map (documented engine-prereq S0/S1) — these
|
| 53 |
+
are honest but currently non-discriminating.
|
| 54 |
+
|
| 55 |
+
**Top recommendations (detail in §4):**
|
| 56 |
+
1. **Add an adversarial/opponent-modeling family** (the unique RTS
|
| 57 |
+
value prop; parallels game-theoretic + StarCraft-II-Arena evals).
|
| 58 |
+
Activate the Phase-1 1v1 path — this is the highest-leverage gap.
|
| 59 |
+
2. **Add a strict-action-API instruction-following family** isolating
|
| 60 |
+
tool-call fidelity (parallels BFCL V4 / τ²-bench — the most
|
| 61 |
+
leaderboard-weighted 2026 capability).
|
| 62 |
+
3. **Add 2–3 genuinely long-horizon scenarios** (multi-phase,
|
| 63 |
+
40k+ ticks, credit assignment across opening→tech→assault) to
|
| 64 |
+
parallel Terminal-Bench/OSWorld long-task tracks.
|
| 65 |
+
4. **Cut/merge the redundant `cat-*` over-generation** to ~3 distinct
|
| 66 |
+
levels per *distinct* decision; the level *count* is inflating
|
| 67 |
+
apparent coverage without adding capability breadth.
|
| 68 |
+
5. **Pre-register the transfer panel against ERQA (spatial/embodied),
|
| 69 |
+
Blocksworld/PlanBench (planning), and BFCL/τ²-bench (tool-use)** —
|
| 70 |
+
ERQA is the empirically-validated correlate for the headline claim;
|
| 71 |
+
BFCL validates the under-tested action-fidelity axis.
|
| 72 |
+
|
| 73 |
+
---
|
| 74 |
+
|
| 75 |
+
## STEP 1 — Scenario Inventory
|
| 76 |
+
|
| 77 |
+
### 1.1 Counts
|
| 78 |
+
|
| 79 |
+
| Group | Files | Notes |
|
| 80 |
+
|---|---|---|
|
| 81 |
+
| Hand-authored scenario families | 17 | perception/reasoning/action/strategy/economy |
|
| 82 |
+
| Generated catalog packs (`cat-*`) | 68 | 12 categories (c1–c12), 5–6 variants × 3 levels each |
|
| 83 |
+
| TEMPLATE | 1 | scaffold, not a scenario |
|
| 84 |
+
| **Total scenario files** | **85** | ~200 difficulty levels |
|
| 85 |
+
|
| 86 |
+
The 12 generated categories (from `SCENARIO_CATALOG.md`, verified
|
| 87 |
+
against the `cat-*` files): C1 Frontier Scouting (FRONT), C2 Threat
|
| 88 |
+
Enumeration (PERC), C3 Tech Critical Path (TECH), C4 Power-Budget
|
| 89 |
+
Online (PLAN), C5 Budget Allocation (ECON), C6 Time-Boxed Capital
|
| 90 |
+
Deploy (ECON), C7 Defensive-Direction Commit (PERC), C8 Base-Placement
|
| 91 |
+
& Staging (PERC), C9 Commit-vs-Retreat (RISK), C10 Force Coordination
|
| 92 |
+
(COORD), C11 Tempo/Timing Window (TEMPO), C12 Error Recovery/Replan
|
| 93 |
+
(RISK).
|
| 94 |
+
|
| 95 |
+
### 1.2 Capability buckets and what each actually tests
|
| 96 |
+
|
| 97 |
+
The Perception→Reasoning→Action chain is the framing
|
| 98 |
+
(`EVAL_STACK_PLAN.md`); every scenario is scored on all three links
|
| 99 |
+
with a `weakest_link` diagnostic (`openra_bench/scoring.py`), plus a
|
| 100 |
+
continuous `reward_vector` goal tracker (`openra_bench/goal_tracker.py`)
|
| 101 |
+
and pass/fail via pure predicates (`openra_bench/scenarios/win_conditions.py`).
|
| 102 |
+
|
| 103 |
+
| Bucket | Sub-skill | Scenarios | What it actually tests (predicate grammar) |
|
| 104 |
+
|---|---|---|---|
|
| 105 |
+
| **Perception** | spatial state-estimation under fog | `perception-frontier-reading`, `perception-target-vs-fog`, C2, C7, C8 | `explored_pct_gte`, `enemies/buildings_discovered_gte` under a clock + attrition cap. Read the minimap, steer sensing. |
|
| 106 |
+
| **Perception/Front** | which-unexplored-region-to-commit | `reasoning-frontier-commit`, C1 | Frontier selection (pathfinding solved; *choosing* the region is the test). Strong ERQA/Active-Neural-SLAM analogue. |
|
| 107 |
+
| **Reasoning** | constrained sequential planning | C3 (tech path), C4 (power budget), `building-and-planning` | Precondition-ordered build to a deadline with `power_surplus_gte≥0`. Blocksworld/PlanBench analogue. |
|
| 108 |
+
| **Reasoning/Econ** | resource allocation / multi-objective | `economy-investment`, `economy-time-box`, `economy-force-buildup`, C5, C6, `economy-harvest-*` | Convert a fixed budget into units AND infrastructure under a clock; commit a wide-vs-deep allocation. Harvest variants currently non-discriminating (income=0, see §1.3). |
|
| 109 |
+
| **Reasoning/Risk** | risk call / replan under partial info | `reasoning-risk-route`, `strategy-dilemma`, C9 (commit-vs-retreat), C12 (error recovery) | Safe-long vs short-lethal route; engage-vs-hold with attrition cap; rebuild after a setback (`after_ticks` gate). |
|
| 110 |
+
| **Action** | multi-unit coordination / execution | `action-multiunit-coordination`, `strategy-twobody`, C10 | Drive dispersed squads to converge in a region with `units_lost_lte:0`. Watch-And-Help / SMAC analogue. |
|
| 111 |
+
| **Action** | sequenced execution to deadline | `action-sequenced-execution`, `rush-hour`, `strategy-gauntlet` | Ordered build→deploy→reposition without dropping a step; multi-squad sweep-and-clear. |
|
| 112 |
+
| **Tempo** | timing-window discipline | C11 | `after_ticks:t0` then `units_killed_gte:N` within `T1` — act in a window, not before. |
|
| 113 |
+
|
| 114 |
+
All run on the single Rust-loadable map `rush-hour-arena`.
|
| 115 |
+
|
| 116 |
+
### 1.3 Honest limitations already documented in-repo
|
| 117 |
+
|
| 118 |
+
- **Harvest economy is non-functional** on the only loadable map: the
|
| 119 |
+
Python schema rejects `mine`/`gmine`, the `.oramap` files seed no
|
| 120 |
+
ore, and `silo` storage is hardcoded inert
|
| 121 |
+
(`SCENARIO_BRAINSTORM.md` §"Verified ground-truth constraints").
|
| 122 |
+
Consequence: `economy-harvest-timebox` / `economy-harvest-investment`
|
| 123 |
+
/ harvest variants degenerate to spend-only tests identical to the
|
| 124 |
+
non-harvest economy packs. They are honestly tagged but **currently
|
| 125 |
+
non-discriminating** — they inflate the economy-bucket count without
|
| 126 |
+
adding a distinct capability. Blocked on engine-prereqs S0 (ore
|
| 127 |
+
source) + S1 (silo storage).
|
| 128 |
+
- **No enemy-destruction predicate** — offensive success is proxied by
|
| 129 |
+
`units_killed_gte` + positional `reach_region`/`building_in_region`.
|
| 130 |
+
- **No adversarial slot today** — the 1v1/Elo path is Phase-1, not
|
| 131 |
+
built (`EVAL_STACK_PLAN.md`). The "Elo leaderboard" is currently
|
| 132 |
+
only meaningful for the fixed-scenario composite, not head-to-head.
|
| 133 |
+
|
| 134 |
+
---
|
| 135 |
+
|
| 136 |
+
## STEP 2 — What Frontier Benchmarks Measure Today (2026)
|
| 137 |
+
|
| 138 |
+
Verified via the four required sources + targeted search. URLs in §5.
|
| 139 |
+
|
| 140 |
+
### 2.1 Required model pages
|
| 141 |
+
|
| 142 |
+
- **Qwen3.6-35B-A3B** (HF page, released ~Apr 2026). Reports a
|
| 143 |
+
coding/agentic-heavy battery: SWE-bench Verified 73.4, SWE-bench
|
| 144 |
+
Pro 49.5, Terminal-Bench 2.0 51.5, **TAU3-Bench 67.2**, Tool
|
| 145 |
+
Decathlon 26.9, MCPMark 37.0; reasoning: MMLU-Pro 85.2, GPQA 86.0,
|
| 146 |
+
AIME26 92.7; **spatial-intelligence block: EmbSpatialBench 84.3,
|
| 147 |
+
RefSpatialBench 64.3, RefCOCO 92.0**. Takeaway: even a mid-size
|
| 148 |
+
model now reports a *dedicated embodied-spatial benchmark block* and
|
| 149 |
+
a *dedicated tool/agent block* — these are the differentiating axes.
|
| 150 |
+
- **Kimi-K2.6** (HF page). Agentic-first: OSWorld-Verified 73.1,
|
| 151 |
+
**Toolathlon 50.0, MCPMark 55.9**, BrowseComp 83.2, Terminal-Bench
|
| 152 |
+
2.0 66.7, SWE-bench Verified 80.2; reasoning AIME 2026 96.4,
|
| 153 |
+
GPQA-Diamond 90.5. Tool-use / computer-use / long-horizon browsing
|
| 154 |
+
dominate the card.
|
| 155 |
+
- **GPT-5.5** (OpenAI announcement; page is 403 to direct fetch,
|
| 156 |
+
corroborated via search). Released ~Apr 23 2026. Headline numbers:
|
| 157 |
+
**SWE-bench Verified 88.7**, SWE-bench Pro 58.6, **Terminal-Bench
|
| 158 |
+
2.0 82.7 (SOTA)**, **τ²-bench Telecom 98.0**, **ARC-AGI-2 85.0
|
| 159 |
+
(+11.7 over prior, the launch's single largest delta)**, GDPval
|
| 160 |
+
84.9. The two most-emphasized capabilities: agentic
|
| 161 |
+
coding/tool-orchestration and ARC-AGI-2 fluid reasoning.
|
| 162 |
+
- **Artificial Analysis** leaderboard. The page no longer exposes
|
| 163 |
+
components inline, but the current **Intelligence Index v4.0** =
|
| 164 |
+
10 evals: **GDPval-AA, τ²-Bench Telecom, Terminal-Bench Hard,
|
| 165 |
+
SciCode, AA-LCR (long-context reasoning), AA-Omniscience, IFBench
|
| 166 |
+
(instruction-following), Humanity's Last Exam, GPQA Diamond,
|
| 167 |
+
CritPt**. Note the heavy weighting toward *agentic tool-use*
|
| 168 |
+
(τ²-bench), *long-horizon terminal agency* (Terminal-Bench Hard),
|
| 169 |
+
and *instruction-following fidelity* (IFBench). Pass@1, ±<1% CI.
|
| 170 |
+
|
| 171 |
+
### 2.2 Broadly-used benchmarks, the capability each isolates, and the
|
| 172 |
+
real-world proxy
|
| 173 |
+
|
| 174 |
+
| Benchmark | Isolates | Real-world proxy | How models differentiate (2026) |
|
| 175 |
+
|---|---|---|---|
|
| 176 |
+
| **SWE-bench Verified / Pro** | repo-level multi-file code editing to a passing test | autonomous coding agents | Top cluster ~80–89% Verified (GPT-5.5 88.7); Pro still <60% — the hard frontier |
|
| 177 |
+
| **Terminal-Bench 2.0** | long-horizon shell agency: plan→iterate→tool-coordinate, deterministic check | CLI/devops automation agents | Wide spread: GPT-5.5 82.7 vs Qwen3.6 51.5 — strong separator |
|
| 178 |
+
| **τ-bench / τ²-bench (Telecom)** | multi-turn **tool use under a strict policy/API**, dialog state | customer-service / API agents | Near-saturating at the top (GPT-5.5 98.0) but mid-tier collapses — fidelity cliff |
|
| 179 |
+
| **BFCL V4** | function-calling: parallel/multiple selection, **relevance (when NOT to call)**, multi-turn, agentic memory | tool/function-calling fidelity | AST-graded; single-turn solved, multi-turn/relevance still discriminating |
|
| 180 |
+
| **GAIA** | compound multi-step tool-use + reasoning across web/files | general assistant agents | Multi-hop chains expose planning/credit-assignment failures |
|
| 181 |
+
| **OSWorld** | full GUI computer control, 15- vs 50-step tasks | computer-use agents | Long-task tracks isolate horizon length specifically |
|
| 182 |
+
| **ARC-AGI-2** | fluid abstract pattern induction, anti-memorization | genuine generalization | Largest 2026 deltas; GPT-5.5 85.0 — the prestige reasoning metric |
|
| 183 |
+
| **GPQA / AIME / MMLU-Pro / HLE** | hard knowledge + math reasoning | expert QA | Largely saturating top-end except HLE — *not* a transfer target for game RL (lmgame-Bench) |
|
| 184 |
+
| **ERQA** | **embodied spatial / trajectory / state-estimation / multi-view / task reasoning** (400 MCQ VQA) | robotics perception+planning | CoT moves it only +4–6.5pts → *reasoning-shaping* finetune is what moves it; the validated OpenRA-Bench transfer target |
|
| 185 |
+
| **PlanBench / Blocksworld** | precondition-ordered sequential planning, cost-optimal, replanning | task-graph scheduling, robot task planning | Mystery-Blocksworld defeats memorization; *the* planning correlate |
|
| 186 |
+
| **lmgame-Bench (Sokoban/Tetris RL → transfer)** | game-RL → spatial/planning transfer | the OpenRA-Bench hypothesis itself | RL→Blocksworld +≥10, →WebShop +6; NOT →GSM8K/coding |
|
| 187 |
+
| **SMAC → SMACv2** | multi-agent micro; the anti-memorization cautionary tale | strategy-game RL | Open-loop policy beat SMAC ⇒ procedural variation mandatory (the suite follows this) |
|
| 188 |
+
|
| 189 |
+
Net 2026 picture: differentiation has moved off knowledge/math QA
|
| 190 |
+
(saturating) onto **agentic tool-use fidelity, long-horizon execution,
|
| 191 |
+
computer/embodied control, and fluid reasoning (ARC-AGI-2)**.
|
| 192 |
+
|
| 193 |
+
---
|
| 194 |
+
|
| 195 |
+
## STEP 3 — Capability ↔ Coverage ↔ Parallel Benchmark Gap Table
|
| 196 |
+
|
| 197 |
+
| Capability / real-world use case | OpenRA-Bench coverage | Parallel benchmark | Verdict |
|
| 198 |
+
|---|---|---|---|
|
| 199 |
+
| Spatial state-estimation / perception under partial obs | `perception-target-vs-fog`, `perception-frontier-reading`, C2, C7, C8 | **ERQA** (spatial/state-est), SpatialVLM, VSR | **Well-covered** — and this is the empirically-validated transfer target |
|
| 200 |
+
| Frontier exploration: which unknown region to commit | `reasoning-frontier-commit`, C1 | Active Neural SLAM, frontier exploration, ERQA-trajectory | **Well-covered** |
|
| 201 |
+
| Constrained sequential planning (precondition graph + deadline) | C3, C4, `building-and-planning` | **PlanBench / Blocksworld**, ALFWorld | **Adequate** (parallels exist; single-map limits variety) |
|
| 202 |
+
| Resource allocation / multi-objective under a budget | `economy-investment`, `economy-time-box`, C5, C6 | PlanBench cost-optimal, SmartPlay | **Adequate** (spend-only) — harvest variants **non-discriminating** (engine S0/S1) |
|
| 203 |
+
| Risk assessment / replanning under partial info | `reasoning-risk-route`, `strategy-dilemma`, C9, C12 | PlanBench replanning, StarCraft-II-Arena | **Adequate** |
|
| 204 |
+
| Long-horizon multi-step credit assignment | thin: most clocks are short single-phase; C12 only gates with `after_ticks` | **Terminal-Bench 2.0**, OSWorld 50-step, GAIA | **Weak** — no genuine opening→tech→assault chain |
|
| 205 |
+
| Multi-unit / multi-agent coordination | `action-multiunit-coordination`, `strategy-twobody`, C10 | Watch-And-Help, SMAC(v2) | **Adequate** |
|
| 206 |
+
| Tool-use / function-calling **fidelity under a strict action API** | only as a *cross-cutting score* (`actions_warned/issued`); no scenario isolates it as the objective | **BFCL V4**, **τ²-bench**, IFBench | **Weak/structural gap** — yet this is the most leaderboard-weighted 2026 axis |
|
| 207 |
+
| Instruction-following under strict constraints | implicit in win predicates; not isolated | **IFBench** (in AA Index v4) | **Weak** |
|
| 208 |
+
| Adversarial / game-theoretic reasoning (opponent modeling, deception, counter-strategy) | **none** — no live adversary; `rush-hour` enemy is scripted; 1v1/Elo is Phase-1 unbuilt | StarCraft-II-Arena, game-theory evals; *the unique RTS value prop* | **Missing** — largest strategic gap |
|
| 209 |
+
| Tempo / timing-window discipline | C11 | TextStarCraft II, SmartPlay | **Adequate** (but only 1 category, 15 levels) |
|
| 210 |
+
| Fluid/abstract anti-memorization generalization | enforced by procedural variation + held-out seeds + generalization-gap metric | **ARC-AGI-2** philosophy | **Methodologically well-covered** (design discipline is correct) |
|
| 211 |
+
|
| 212 |
+
### Scenarios with weak / no transfer story (flag)
|
| 213 |
+
|
| 214 |
+
- **`rush-hour` / `strategy-gauntlet` "sweep-and-clear"** — primarily a
|
| 215 |
+
search-and-destroy execution task. Defensible as multi-robot
|
| 216 |
+
patrol-and-clear, but with a *scripted* enemy it tests execution,
|
| 217 |
+
not adversarial reasoning. Keep, but it should not be load-bearing
|
| 218 |
+
for the "strategy" claim.
|
| 219 |
+
- **`economy-harvest-timebox` / `economy-harvest-investment`** —
|
| 220 |
+
currently economically identical to the non-harvest economy packs
|
| 221 |
+
(income=0). Honest, but they are *placeholders*, not distinct tests;
|
| 222 |
+
do not count them as economy coverage until S0/S1 land.
|
| 223 |
+
- **`cat-c5/c6/c7/c8` over-generation** — many variants differ only in
|
| 224 |
+
region coordinates / target counts. The *decision* is the same; this
|
| 225 |
+
is level inflation, not capability breadth (mild
|
| 226 |
+
SMAC-style memorization risk if seeds aren't truly held out per
|
| 227 |
+
variant). Not "game trivia," but low marginal information.
|
| 228 |
+
- The suite has **no** scenario that is pure game-trivia with zero
|
| 229 |
+
transfer story — the `real_world_meaning`/`robotics_analogue`
|
| 230 |
+
discipline is doing its job. The problem is *skew and redundancy*,
|
| 231 |
+
not arbitrariness.
|
| 232 |
+
|
| 233 |
+
---
|
| 234 |
+
|
| 235 |
+
## STEP 4 — Concrete, Prioritized Edit Advice
|
| 236 |
+
|
| 237 |
+
### P0 — Add an adversarial / opponent-modeling family (highest leverage)
|
| 238 |
+
|
| 239 |
+
An RTS engine's unique, defensible value vs every other benchmark is
|
| 240 |
+
**live adversarial / game-theoretic reasoning** — and the suite
|
| 241 |
+
currently has *zero*. Build the Phase-1 second RL-controlled slot
|
| 242 |
+
(`EVAL_STACK_PLAN.md` Phase 1) and add:
|
| 243 |
+
|
| 244 |
+
- **Counter-Strategy Read**: opponent commits an observable opening
|
| 245 |
+
(rush vs tech vs expand); agent must read it from partial recon and
|
| 246 |
+
pick the dominant counter. Capability: adversarial reasoning +
|
| 247 |
+
perception. Real-world: competitive multi-agent / negotiation /
|
| 248 |
+
red-teaming. Parallel: **StarCraft-II-Arena (ICLR'25)**,
|
| 249 |
+
game-theoretic LLM evals.
|
| 250 |
+
- **Deception / feint handling**: opponent shows a decoy force; win
|
| 251 |
+
predicate rewards committing against the *real* axis
|
| 252 |
+
(`building_in_region`/`units_killed_gte` keyed off the true threat).
|
| 253 |
+
Parallel: adversarial robustness, opponent modeling.
|
| 254 |
+
|
| 255 |
+
This also makes the **Elo leaderboard** genuinely meaningful
|
| 256 |
+
(head-to-head), which it currently is not.
|
| 257 |
+
|
| 258 |
+
### P0 — Add a strict-action-API instruction-following family
|
| 259 |
+
|
| 260 |
+
The most leaderboard-weighted 2026 axis (τ²-bench in AA Index v4,
|
| 261 |
+
BFCL V4, IFBench) is only measured as a *side diagnostic* here. Add a
|
| 262 |
+
family whose **primary objective is action-API fidelity**:
|
| 263 |
+
|
| 264 |
+
- Tasks where the win predicate is only reachable via a *specific
|
| 265 |
+
command sequence/format under explicit policy constraints* (e.g.
|
| 266 |
+
"achieve X but never issue `attack` before tick T", "use only
|
| 267 |
+
`move_units` + `deploy`, no `build`"), scoring relevance (issuing a
|
| 268 |
+
disallowed call = fail, à la BFCL "relevance detection").
|
| 269 |
+
- Capability: instruction-following + tool-call fidelity under a
|
| 270 |
+
strict API. Real-world: agentic API/tool orchestration, policy
|
| 271 |
+
compliance. Parallel: **BFCL V4 (relevance/multi-turn), τ²-bench,
|
| 272 |
+
IFBench**.
|
| 273 |
+
|
| 274 |
+
### P1 — Add 2–3 genuinely long-horizon, multi-phase scenarios
|
| 275 |
+
|
| 276 |
+
Current clocks are mostly short single-phase. Add scenarios spanning
|
| 277 |
+
opening→scout→economy→tech→army→engagement in **one episode (40k+
|
| 278 |
+
ticks)** with a terminal objective, so the score depends on **credit
|
| 279 |
+
assignment across phases** (an early scouting error must surface as a
|
| 280 |
+
late failure). Capability: long-horizon credit assignment. Parallel:
|
| 281 |
+
**Terminal-Bench 2.0, OSWorld 50-step track, GAIA** multi-hop. This is
|
| 282 |
+
where 2026 models separate most and the suite is currently thin.
|
| 283 |
+
|
| 284 |
+
### P1 — Cut / merge the redundant `cat-*` over-generation
|
| 285 |
+
|
| 286 |
+
Reduce each generated category to **≤3 levels per genuinely distinct
|
| 287 |
+
decision** instead of 5–6 near-duplicate coordinate variants. Net
|
| 288 |
+
~200→~90 levels but *higher distinct-capability density* and lower
|
| 289 |
+
memorization risk. Specifically merge: C5↔C6 (both "budget→units+
|
| 290 |
+
buildings" ECON), C7↔C8 (both "place building in inferred region"
|
| 291 |
+
PERC). Keep the procedural seed variation *within* a level (the
|
| 292 |
+
anti-memorization mechanism) — cut the redundant *level* multiplication.
|
| 293 |
+
|
| 294 |
+
### P1 — Quarantine non-discriminating economy packs
|
| 295 |
+
|
| 296 |
+
Tag `economy-harvest-*` (and any harvest variant) as **engine-prereq /
|
| 297 |
+
not-scored** until S0 (ore source) + S1 (silo storage) land
|
| 298 |
+
(`SCENARIO_BRAINSTORM.md`). Do not count them in economy coverage
|
| 299 |
+
claims. Land S0 (a one-line `VALID_ACTOR_TYPES` add or a
|
| 300 |
+
`resource_fields` scenario field) — it is cheap and unblocks a real,
|
| 301 |
+
distinct economy-throughput capability.
|
| 302 |
+
|
| 303 |
+
### Over/under-representation summary
|
| 304 |
+
|
| 305 |
+
- **Over-represented:** ECON spend-allocation (C5/C6 + 5 hand-authored
|
| 306 |
+
economy packs, several non-discriminating), and generated PERC
|
| 307 |
+
region-placement (C7/C8). Level count >> capability count.
|
| 308 |
+
- **Under-represented:** adversarial/game-theoretic (zero),
|
| 309 |
+
tool-use/API-fidelity isolation (zero dedicated), long-horizon
|
| 310 |
+
credit assignment (thin), tempo (1 category).
|
| 311 |
+
- **Right-sized:** spatial perception/frontier (the validated transfer
|
| 312 |
+
target — keep strong), coordination, risk/replan.
|
| 313 |
+
|
| 314 |
+
### Strengthening the generalization-transfer argument
|
| 315 |
+
|
| 316 |
+
The headline claim ("rush-hour finetune lifted ERQA") needs a
|
| 317 |
+
pre-registered external transfer panel, scored as **per-axis deltas,
|
| 318 |
+
not aggregate** (lmgame-Bench protocol):
|
| 319 |
+
|
| 320 |
+
1. **ERQA** — the *primary* correlate. It is reasoning-sensitive
|
| 321 |
+
(CoT-only moves it just +4–6.5pts), embodied/spatial, and is the
|
| 322 |
+
axis the observed transfer hit. Report ERQA spatial / trajectory /
|
| 323 |
+
task subscores separately. Strongest evidence for the claim.
|
| 324 |
+
2. **Blocksworld / PlanBench (incl. Mystery-Blocksworld)** —
|
| 325 |
+
validates the PLAN/TECH/ECON families; this is the exact transfer
|
| 326 |
+
lmgame-Bench demonstrated (RL→Blocksworld +≥10pts) and defeats
|
| 327 |
+
memorization.
|
| 328 |
+
3. **BFCL V4 / τ²-bench** — validates the *new* P0 action-fidelity
|
| 329 |
+
family and tests whether strict-action-API discipline transfers
|
| 330 |
+
(the most commercially relevant axis; currently untested here).
|
| 331 |
+
4. **Negative controls: GSM8K + a coding eval (e.g. LiveCodeBench)** —
|
| 332 |
+
lmgame-Bench showed game-RL does **not** transfer to math/coding;
|
| 333 |
+
include these to demonstrate the transfer is *specific* (spatial/
|
| 334 |
+
planning), not a generic capability bump. This negative result is
|
| 335 |
+
what makes the positive claim credible.
|
| 336 |
+
5. Continue reporting the **generalization gap on held-out seeds**
|
| 337 |
+
(Procgen/SMACv2/ARC-AGI discipline) — already designed in; keep it
|
| 338 |
+
front-and-center as the anti-memorization guarantee.
|
| 339 |
+
|
| 340 |
+
---
|
| 341 |
+
|
| 342 |
+
## STEP 5 — Sources (verified via search/fetch, 2026-05)
|
| 343 |
+
|
| 344 |
+
- Qwen3.6-35B-A3B model card — https://huggingface.co/Qwen/Qwen3.6-35B-A3B
|
| 345 |
+
- Kimi-K2.6 model card — https://huggingface.co/moonshotai/Kimi-K2.6
|
| 346 |
+
- Introducing GPT-5.5 — https://openai.com/index/introducing-gpt-5-5/ (direct fetch 403; corroborated via search: interestingengineering.com/ai-robotics/opanai-gpt-5-5-agentic-coding-gains, kingy.ai GPT-5.5 benchmarks, llm-stats.com/models/gpt-5.5)
|
| 347 |
+
- Artificial Analysis leaderboard — https://artificialanalysis.ai/leaderboards/models ; methodology — https://artificialanalysis.ai/methodology/intelligence-benchmarking ; Intelligence Index — https://artificialanalysis.ai/evaluations/artificial-analysis-intelligence-index
|
| 348 |
+
- BFCL V4 — https://gorilla.cs.berkeley.edu/leaderboard.html ; paper https://openreview.net/forum?id=2GmDdhBdDk
|
| 349 |
+
- τ²-bench (Sierra) via Agentic AI Benchmarks — https://awesomeagents.ai/leaderboards/agentic-ai-benchmarks-leaderboard/
|
| 350 |
+
- ERQA — https://github.com/embodiedreasoning/ERQA ; Gemini Robotics arXiv:2503.20020 https://arxiv.org/html/2503.20020v1
|
| 351 |
+
- lmgame-Bench arXiv:2505.15146 — https://arxiv.org/abs/2505.15146
|
| 352 |
+
- GAIA / OSWorld / Terminal-Bench / ARC-AGI-2 overview — https://www.marktechpost.com/2026/04/26/top-7-benchmarks-that-actually-matter-for-agentic-reasoning-in-large-language-models/ ; https://www.spheron.network/blog/ai-agent-benchmarking-gpu-cloud-swebench-gaia/
|
| 353 |
+
- In-repo: `SCENARIO_CATALOG.md`, `SCENARIO_BRAINSTORM.md`, `EVAL_STACK_PLAN.md`, `openra_bench/scenarios/win_conditions.py`, `openra_bench/scoring.py`, `openra_bench/goal_tracker.py`, `openra_bench/scenarios/packs/*.yaml`
|
| 354 |
+
- Supporting literature cited in-repo and corroborated: SMAC→SMACv2 (arXiv:2212.07489), Procgen (arXiv:1912.01588), ARC-AGI (arXiv:1911.01547), PlanBench (arXiv:2206.10498), Active Neural SLAM (arXiv:2004.05155)
|
|
@@ -172,8 +172,9 @@ def load_capability_leaderboard() -> pd.DataFrame:
|
|
| 172 |
rows = []
|
| 173 |
cols = [
|
| 174 |
"rank", "model", "episodes", "win_rate", "composite",
|
| 175 |
-
"objective", "
|
| 176 |
-
"
|
|
|
|
| 177 |
]
|
| 178 |
if not rows:
|
| 179 |
return pd.DataFrame(columns=cols)
|
|
|
|
| 172 |
rows = []
|
| 173 |
cols = [
|
| 174 |
"rank", "model", "episodes", "win_rate", "composite",
|
| 175 |
+
"objective", "adversarial_rating", "perception", "reasoning",
|
| 176 |
+
"action", "weakest_link", "reward_vector",
|
| 177 |
+
"held_out_composite", "generalization_gap",
|
| 178 |
]
|
| 179 |
if not rows:
|
| 180 |
return pd.DataFrame(columns=cols)
|
|
@@ -0,0 +1,72 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Adversarial ladder rating (the 1v1 spotlight metric).
|
| 2 |
+
|
| 3 |
+
Each `adversarial-*` pack is a 3-rung ladder (easy → medium → hard) of
|
| 4 |
+
increasing reactive-opponent strength. A model's **ladder rating** on a
|
| 5 |
+
pack is the number of rungs cleared *contiguously from the bottom* — a
|
| 6 |
+
monotone difficulty signal that complements the Elo (which ranks models
|
| 7 |
+
head-to-head on shared rungs via `pairwise.pairwise_elo`).
|
| 8 |
+
|
| 9 |
+
Pure + deterministic; the live opponent is the engine's reactive force
|
| 10 |
+
today, with a documented swap-in to model-vs-model once the engine
|
| 11 |
+
exposes an enemy command channel (see pairwise.py / task #3).
|
| 12 |
+
"""
|
| 13 |
+
|
| 14 |
+
from __future__ import annotations
|
| 15 |
+
|
| 16 |
+
RUNGS: tuple[str, ...] = ("easy", "medium", "hard")
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
def ladder_rating(outcomes: dict[str, str]) -> int:
|
| 20 |
+
"""Rungs cleared contiguously from easy. A rung is cleared iff its
|
| 21 |
+
outcome == "win". easy lost → 0; easy+medium won, hard lost → 2."""
|
| 22 |
+
n = 0
|
| 23 |
+
for r in RUNGS:
|
| 24 |
+
if outcomes.get(r) == "win":
|
| 25 |
+
n += 1
|
| 26 |
+
else:
|
| 27 |
+
break
|
| 28 |
+
return n
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
def is_adversarial_episode(ep: dict) -> bool:
|
| 32 |
+
return ep.get("capability") == "adversarial"
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
def ladder_ratings(stats: dict) -> dict[str, int]:
|
| 36 |
+
"""Per adversarial pack → ladder rating, from a run_eval stats dict.
|
| 37 |
+
|
| 38 |
+
`cell` is "<pack>:<level>"; only the public split counts (held-out
|
| 39 |
+
seeds are anti-memorization, not ladder progression). When a rung
|
| 40 |
+
ran multiple seeds it is cleared only if it was won on *every*
|
| 41 |
+
seed (no lucky-seed promotion)."""
|
| 42 |
+
rungs: dict[str, dict[str, list[str]]] = {}
|
| 43 |
+
for e in stats.get("episodes", []):
|
| 44 |
+
if not is_adversarial_episode(e) or e.get("split", "public") != "public":
|
| 45 |
+
continue
|
| 46 |
+
pack, _, level = str(e.get("cell", "")).rpartition(":")
|
| 47 |
+
if not pack or level not in RUNGS:
|
| 48 |
+
continue
|
| 49 |
+
rungs.setdefault(pack, {}).setdefault(level, []).append(
|
| 50 |
+
e.get("outcome", "?")
|
| 51 |
+
)
|
| 52 |
+
out: dict[str, int] = {}
|
| 53 |
+
for pack, by_level in rungs.items():
|
| 54 |
+
collapsed = {
|
| 55 |
+
lv: ("win" if outs and all(o == "win" for o in outs) else "loss")
|
| 56 |
+
for lv, outs in by_level.items()
|
| 57 |
+
}
|
| 58 |
+
out[pack] = ladder_rating(collapsed)
|
| 59 |
+
return out
|
| 60 |
+
|
| 61 |
+
|
| 62 |
+
def adversarial_summary(stats: dict) -> dict:
|
| 63 |
+
"""Spotlight roll-up: per-pack ratings + the headline mean rating
|
| 64 |
+
(0–3) across adversarial packs played."""
|
| 65 |
+
ratings = ladder_ratings(stats)
|
| 66 |
+
mean = round(sum(ratings.values()) / len(ratings), 4) if ratings else 0.0
|
| 67 |
+
return {
|
| 68 |
+
"ladder_ratings": ratings,
|
| 69 |
+
"mean_ladder_rating": mean,
|
| 70 |
+
"packs": sorted(ratings),
|
| 71 |
+
"max_rung": len(RUNGS),
|
| 72 |
+
}
|
|
@@ -18,7 +18,7 @@ from collections import Counter
|
|
| 18 |
from pathlib import Path
|
| 19 |
from typing import Any
|
| 20 |
|
| 21 |
-
CAPABILITIES = ("perception", "reasoning", "action")
|
| 22 |
DEFAULT_STORE = Path(__file__).parent.parent / "data" / "leaderboard.jsonl"
|
| 23 |
# A run must cover at least this many episodes to be rankable (mirrors
|
| 24 |
# the existing app.py min-games gate; keeps one-off noise off the board).
|
|
@@ -69,6 +69,13 @@ def ingest_run(
|
|
| 69 |
# reward-vector signature, comparable across models/runs.
|
| 70 |
"objective": overall.get("objective_mean", 0.0),
|
| 71 |
"reward_vector": stats.get("reward_vector_mean", {}),
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 72 |
"weakest_link_hist": overall.get("weakest_link_hist", {}),
|
| 73 |
# Anti-memorization: held-out composite + generalization gap
|
| 74 |
# (public − held-out). None when the run had no held-out split.
|
|
|
|
| 18 |
from pathlib import Path
|
| 19 |
from typing import Any
|
| 20 |
|
| 21 |
+
CAPABILITIES = ("perception", "reasoning", "action", "adversarial")
|
| 22 |
DEFAULT_STORE = Path(__file__).parent.parent / "data" / "leaderboard.jsonl"
|
| 23 |
# A run must cover at least this many episodes to be rankable (mirrors
|
| 24 |
# the existing app.py min-games gate; keeps one-off noise off the board).
|
|
|
|
| 69 |
# reward-vector signature, comparable across models/runs.
|
| 70 |
"objective": overall.get("objective_mean", 0.0),
|
| 71 |
"reward_vector": stats.get("reward_vector_mean", {}),
|
| 72 |
+
# Adversarial 1v1 spotlight: mean ladder rating (0–3) + per-pack.
|
| 73 |
+
"adversarial_rating": stats.get("adversarial", {}).get(
|
| 74 |
+
"mean_ladder_rating", 0.0
|
| 75 |
+
),
|
| 76 |
+
"adversarial_ladders": stats.get("adversarial", {}).get(
|
| 77 |
+
"ladder_ratings", {}
|
| 78 |
+
),
|
| 79 |
"weakest_link_hist": overall.get("weakest_link_hist", {}),
|
| 80 |
# Anti-memorization: held-out composite + generalization gap
|
| 81 |
# (public − held-out). None when the run had no held-out split.
|
|
@@ -202,6 +202,12 @@ def evaluate(
|
|
| 202 |
"episodes": episodes,
|
| 203 |
"skipped": skipped,
|
| 204 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 205 |
if held_scores:
|
| 206 |
ho = _agg(held_scores)
|
| 207 |
out["overall_held_out"] = ho
|
|
|
|
| 202 |
"episodes": episodes,
|
| 203 |
"skipped": skipped,
|
| 204 |
}
|
| 205 |
+
# Adversarial spotlight: per-pack ladder ratings + headline mean.
|
| 206 |
+
from .adversarial import adversarial_summary
|
| 207 |
+
|
| 208 |
+
adv = adversarial_summary(out)
|
| 209 |
+
if adv["packs"]:
|
| 210 |
+
out["adversarial"] = adv
|
| 211 |
if held_scores:
|
| 212 |
ho = _agg(held_scores)
|
| 213 |
out["overall_held_out"] = ho
|
|
@@ -0,0 +1,148 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
meta:
|
| 2 |
+
id: adversarial-duel
|
| 3 |
+
title: 'Adversarial: Force Duel'
|
| 4 |
+
capability: adversarial
|
| 5 |
+
real_world_meaning: "Defeat a reactive opposing force of escalating strength:\
|
| 6 |
+
\ focus-fire, target priority and kiting against an enemy that fights\
|
| 7 |
+
\ back — head-to-head tactical reasoning, not micro against a dummy."
|
| 8 |
+
robotics_analogue: "Adversarial multi-agent engagement: prevail over a\
|
| 9 |
+
\ reactive opponent team whose strength increases each ladder rung."
|
| 10 |
+
author: openra-bench
|
| 11 |
+
base_map: rush-hour-arena
|
| 12 |
+
# The ladder: easy/medium/hard are rungs of increasing enemy strength.
|
| 13 |
+
# A model's adversarial rating = the highest rung it can clear (see
|
| 14 |
+
# adversarial.ladder_rating); models are Elo-ranked on shared rungs.
|
| 15 |
+
base:
|
| 16 |
+
agent:
|
| 17 |
+
faction: allies
|
| 18 |
+
cash: 0
|
| 19 |
+
enemy:
|
| 20 |
+
faction: soviet
|
| 21 |
+
cash: 0
|
| 22 |
+
bot_type: ''
|
| 23 |
+
actors:
|
| 24 |
+
- type: 2tnk
|
| 25 |
+
owner: agent
|
| 26 |
+
position: [6, 8]
|
| 27 |
+
stance: 3
|
| 28 |
+
health: 100
|
| 29 |
+
facing: -1
|
| 30 |
+
count: 2
|
| 31 |
+
randomize:
|
| 32 |
+
position:
|
| 33 |
+
base: [6, 8]
|
| 34 |
+
offset: 2
|
| 35 |
+
- type: 1tnk
|
| 36 |
+
owner: agent
|
| 37 |
+
position: [6, 11]
|
| 38 |
+
stance: 3
|
| 39 |
+
health: 100
|
| 40 |
+
facing: -1
|
| 41 |
+
count: 2
|
| 42 |
+
- type: e1
|
| 43 |
+
owner: enemy
|
| 44 |
+
position: [34, 22]
|
| 45 |
+
stance: 3
|
| 46 |
+
health: 100
|
| 47 |
+
facing: -1
|
| 48 |
+
count: 3
|
| 49 |
+
levels:
|
| 50 |
+
easy:
|
| 51 |
+
description: 'Rung 1 — even-ish duel: 4 agent vehicles vs 3 enemy rifle.'
|
| 52 |
+
win_condition:
|
| 53 |
+
all_of:
|
| 54 |
+
- units_killed_gte: 3
|
| 55 |
+
- within_ticks: 14000
|
| 56 |
+
fail_condition:
|
| 57 |
+
not:
|
| 58 |
+
own_units_gte: 1
|
| 59 |
+
max_turns: 60
|
| 60 |
+
medium:
|
| 61 |
+
description: 'Rung 2 — outnumbered: same force vs a reactive armour mix.'
|
| 62 |
+
overrides:
|
| 63 |
+
actors:
|
| 64 |
+
- type: 2tnk
|
| 65 |
+
owner: agent
|
| 66 |
+
position: [6, 8]
|
| 67 |
+
stance: 3
|
| 68 |
+
health: 100
|
| 69 |
+
facing: -1
|
| 70 |
+
count: 2
|
| 71 |
+
randomize:
|
| 72 |
+
position:
|
| 73 |
+
base: [6, 8]
|
| 74 |
+
offset: 2
|
| 75 |
+
- type: 1tnk
|
| 76 |
+
owner: agent
|
| 77 |
+
position: [6, 11]
|
| 78 |
+
stance: 3
|
| 79 |
+
health: 100
|
| 80 |
+
facing: -1
|
| 81 |
+
count: 2
|
| 82 |
+
- type: e1
|
| 83 |
+
owner: enemy
|
| 84 |
+
position: [34, 22]
|
| 85 |
+
stance: 3
|
| 86 |
+
health: 100
|
| 87 |
+
facing: -1
|
| 88 |
+
count: 4
|
| 89 |
+
- type: 1tnk
|
| 90 |
+
owner: enemy
|
| 91 |
+
position: [38, 26]
|
| 92 |
+
stance: 3
|
| 93 |
+
health: 100
|
| 94 |
+
facing: -1
|
| 95 |
+
count: 2
|
| 96 |
+
win_condition:
|
| 97 |
+
all_of:
|
| 98 |
+
- units_killed_gte: 6
|
| 99 |
+
- within_ticks: 12000
|
| 100 |
+
fail_condition:
|
| 101 |
+
not:
|
| 102 |
+
own_units_gte: 1
|
| 103 |
+
max_turns: 70
|
| 104 |
+
hard:
|
| 105 |
+
description: 'Rung 3 — badly outnumbered + a loss cap: win by maneuver.'
|
| 106 |
+
overrides:
|
| 107 |
+
actors:
|
| 108 |
+
- type: 2tnk
|
| 109 |
+
owner: agent
|
| 110 |
+
position: [6, 8]
|
| 111 |
+
stance: 3
|
| 112 |
+
health: 100
|
| 113 |
+
facing: -1
|
| 114 |
+
count: 2
|
| 115 |
+
randomize:
|
| 116 |
+
position:
|
| 117 |
+
base: [6, 8]
|
| 118 |
+
offset: 2
|
| 119 |
+
- type: 1tnk
|
| 120 |
+
owner: agent
|
| 121 |
+
position: [6, 11]
|
| 122 |
+
stance: 3
|
| 123 |
+
health: 100
|
| 124 |
+
facing: -1
|
| 125 |
+
count: 2
|
| 126 |
+
- type: e1
|
| 127 |
+
owner: enemy
|
| 128 |
+
position: [34, 22]
|
| 129 |
+
stance: 3
|
| 130 |
+
health: 100
|
| 131 |
+
facing: -1
|
| 132 |
+
count: 5
|
| 133 |
+
- type: 2tnk
|
| 134 |
+
owner: enemy
|
| 135 |
+
position: [40, 28]
|
| 136 |
+
stance: 3
|
| 137 |
+
health: 100
|
| 138 |
+
facing: -1
|
| 139 |
+
count: 3
|
| 140 |
+
win_condition:
|
| 141 |
+
all_of:
|
| 142 |
+
- units_killed_gte: 8
|
| 143 |
+
- within_ticks: 10000
|
| 144 |
+
- units_lost_lte: 3
|
| 145 |
+
fail_condition:
|
| 146 |
+
not:
|
| 147 |
+
own_units_gte: 1
|
| 148 |
+
max_turns: 80
|
|
@@ -0,0 +1,112 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
meta:
|
| 2 |
+
id: adversarial-siege
|
| 3 |
+
title: 'Adversarial: Break the Position'
|
| 4 |
+
capability: adversarial
|
| 5 |
+
real_world_meaning: "Dislodge a reactive force holding a prepared\
|
| 6 |
+
\ position: commit, absorb the defended ground, and break it before\
|
| 7 |
+
\ the clock — assaulting an opponent with the terrain advantage."
|
| 8 |
+
robotics_analogue: "Adversarial objective seizure: overcome a reactive\
|
| 9 |
+
\ defending team entrenched at the goal under a time budget."
|
| 10 |
+
author: openra-bench
|
| 11 |
+
base_map: rush-hour-arena
|
| 12 |
+
base:
|
| 13 |
+
agent:
|
| 14 |
+
faction: allies
|
| 15 |
+
cash: 0
|
| 16 |
+
enemy:
|
| 17 |
+
faction: soviet
|
| 18 |
+
cash: 0
|
| 19 |
+
bot_type: ''
|
| 20 |
+
actors:
|
| 21 |
+
- type: 2tnk
|
| 22 |
+
owner: agent
|
| 23 |
+
position: [6, 10]
|
| 24 |
+
stance: 3
|
| 25 |
+
health: 100
|
| 26 |
+
facing: -1
|
| 27 |
+
count: 4
|
| 28 |
+
- type: e1
|
| 29 |
+
owner: enemy
|
| 30 |
+
position: [40, 26]
|
| 31 |
+
stance: 3
|
| 32 |
+
health: 100
|
| 33 |
+
facing: -1
|
| 34 |
+
count: 4
|
| 35 |
+
levels:
|
| 36 |
+
easy:
|
| 37 |
+
description: 'Rung 1 — 4 tanks break a 4-rifle position.'
|
| 38 |
+
win_condition:
|
| 39 |
+
all_of:
|
| 40 |
+
- units_killed_gte: 4
|
| 41 |
+
- within_ticks: 13000
|
| 42 |
+
fail_condition:
|
| 43 |
+
not:
|
| 44 |
+
own_units_gte: 1
|
| 45 |
+
max_turns: 60
|
| 46 |
+
medium:
|
| 47 |
+
description: 'Rung 2 — defenders reinforced with armour.'
|
| 48 |
+
overrides:
|
| 49 |
+
actors:
|
| 50 |
+
- type: 2tnk
|
| 51 |
+
owner: agent
|
| 52 |
+
position: [6, 10]
|
| 53 |
+
stance: 3
|
| 54 |
+
health: 100
|
| 55 |
+
facing: -1
|
| 56 |
+
count: 4
|
| 57 |
+
- type: e1
|
| 58 |
+
owner: enemy
|
| 59 |
+
position: [40, 26]
|
| 60 |
+
stance: 3
|
| 61 |
+
health: 100
|
| 62 |
+
facing: -1
|
| 63 |
+
count: 5
|
| 64 |
+
- type: 2tnk
|
| 65 |
+
owner: enemy
|
| 66 |
+
position: [43, 29]
|
| 67 |
+
stance: 3
|
| 68 |
+
health: 100
|
| 69 |
+
facing: -1
|
| 70 |
+
count: 2
|
| 71 |
+
win_condition:
|
| 72 |
+
all_of:
|
| 73 |
+
- units_killed_gte: 7
|
| 74 |
+
- within_ticks: 11000
|
| 75 |
+
fail_condition:
|
| 76 |
+
not:
|
| 77 |
+
own_units_gte: 1
|
| 78 |
+
max_turns: 75
|
| 79 |
+
hard:
|
| 80 |
+
description: 'Rung 3 — dug-in mixed force, tight clock + loss cap.'
|
| 81 |
+
overrides:
|
| 82 |
+
actors:
|
| 83 |
+
- type: 2tnk
|
| 84 |
+
owner: agent
|
| 85 |
+
position: [6, 10]
|
| 86 |
+
stance: 3
|
| 87 |
+
health: 100
|
| 88 |
+
facing: -1
|
| 89 |
+
count: 5
|
| 90 |
+
- type: e1
|
| 91 |
+
owner: enemy
|
| 92 |
+
position: [40, 26]
|
| 93 |
+
stance: 3
|
| 94 |
+
health: 100
|
| 95 |
+
facing: -1
|
| 96 |
+
count: 6
|
| 97 |
+
- type: 2tnk
|
| 98 |
+
owner: enemy
|
| 99 |
+
position: [43, 29]
|
| 100 |
+
stance: 3
|
| 101 |
+
health: 100
|
| 102 |
+
facing: -1
|
| 103 |
+
count: 4
|
| 104 |
+
win_condition:
|
| 105 |
+
all_of:
|
| 106 |
+
- units_killed_gte: 10
|
| 107 |
+
- within_ticks: 9000
|
| 108 |
+
- units_lost_lte: 3
|
| 109 |
+
fail_condition:
|
| 110 |
+
not:
|
| 111 |
+
own_units_gte: 1
|
| 112 |
+
max_turns: 85
|
|
@@ -0,0 +1,112 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
meta:
|
| 2 |
+
id: adversarial-skirmish
|
| 3 |
+
title: 'Adversarial: Outnumbered Skirmish'
|
| 4 |
+
capability: adversarial
|
| 5 |
+
real_world_meaning: "Win while outnumbered by trading space for time:\
|
| 6 |
+
\ pick fights, retreat, and re-engage a reactive enemy on favourable\
|
| 7 |
+
\ terms instead of a head-on brawl — asymmetric tactical reasoning."
|
| 8 |
+
robotics_analogue: "Asymmetric adversarial control: a smaller agent team\
|
| 9 |
+
\ must defeat a larger reactive force by engagement selection."
|
| 10 |
+
author: openra-bench
|
| 11 |
+
base_map: rush-hour-arena
|
| 12 |
+
base:
|
| 13 |
+
agent:
|
| 14 |
+
faction: allies
|
| 15 |
+
cash: 0
|
| 16 |
+
enemy:
|
| 17 |
+
faction: soviet
|
| 18 |
+
cash: 0
|
| 19 |
+
bot_type: ''
|
| 20 |
+
actors:
|
| 21 |
+
- type: 2tnk
|
| 22 |
+
owner: agent
|
| 23 |
+
position: [6, 9]
|
| 24 |
+
stance: 3
|
| 25 |
+
health: 100
|
| 26 |
+
facing: -1
|
| 27 |
+
count: 2
|
| 28 |
+
- type: e1
|
| 29 |
+
owner: enemy
|
| 30 |
+
position: [30, 20]
|
| 31 |
+
stance: 3
|
| 32 |
+
health: 100
|
| 33 |
+
facing: -1
|
| 34 |
+
count: 4
|
| 35 |
+
levels:
|
| 36 |
+
easy:
|
| 37 |
+
description: 'Rung 1 — 2 tanks vs 4 rifle: doable if you fight smart.'
|
| 38 |
+
win_condition:
|
| 39 |
+
all_of:
|
| 40 |
+
- units_killed_gte: 4
|
| 41 |
+
- within_ticks: 14000
|
| 42 |
+
fail_condition:
|
| 43 |
+
not:
|
| 44 |
+
own_units_gte: 1
|
| 45 |
+
max_turns: 60
|
| 46 |
+
medium:
|
| 47 |
+
description: 'Rung 2 — 2 tanks vs 6 mixed: must split the enemy.'
|
| 48 |
+
overrides:
|
| 49 |
+
actors:
|
| 50 |
+
- type: 2tnk
|
| 51 |
+
owner: agent
|
| 52 |
+
position: [6, 9]
|
| 53 |
+
stance: 3
|
| 54 |
+
health: 100
|
| 55 |
+
facing: -1
|
| 56 |
+
count: 2
|
| 57 |
+
- type: e1
|
| 58 |
+
owner: enemy
|
| 59 |
+
position: [30, 20]
|
| 60 |
+
stance: 3
|
| 61 |
+
health: 100
|
| 62 |
+
facing: -1
|
| 63 |
+
count: 4
|
| 64 |
+
- type: 1tnk
|
| 65 |
+
owner: enemy
|
| 66 |
+
position: [35, 24]
|
| 67 |
+
stance: 3
|
| 68 |
+
health: 100
|
| 69 |
+
facing: -1
|
| 70 |
+
count: 2
|
| 71 |
+
win_condition:
|
| 72 |
+
all_of:
|
| 73 |
+
- units_killed_gte: 6
|
| 74 |
+
- within_ticks: 12000
|
| 75 |
+
fail_condition:
|
| 76 |
+
not:
|
| 77 |
+
own_units_gte: 1
|
| 78 |
+
max_turns: 70
|
| 79 |
+
hard:
|
| 80 |
+
description: 'Rung 3 — 3 tanks vs 8, loss-capped: maneuver or lose.'
|
| 81 |
+
overrides:
|
| 82 |
+
actors:
|
| 83 |
+
- type: 2tnk
|
| 84 |
+
owner: agent
|
| 85 |
+
position: [6, 9]
|
| 86 |
+
stance: 3
|
| 87 |
+
health: 100
|
| 88 |
+
facing: -1
|
| 89 |
+
count: 3
|
| 90 |
+
- type: e1
|
| 91 |
+
owner: enemy
|
| 92 |
+
position: [30, 20]
|
| 93 |
+
stance: 3
|
| 94 |
+
health: 100
|
| 95 |
+
facing: -1
|
| 96 |
+
count: 5
|
| 97 |
+
- type: 1tnk
|
| 98 |
+
owner: enemy
|
| 99 |
+
position: [35, 24]
|
| 100 |
+
stance: 3
|
| 101 |
+
health: 100
|
| 102 |
+
facing: -1
|
| 103 |
+
count: 3
|
| 104 |
+
win_condition:
|
| 105 |
+
all_of:
|
| 106 |
+
- units_killed_gte: 8
|
| 107 |
+
- within_ticks: 10000
|
| 108 |
+
- units_lost_lte: 2
|
| 109 |
+
fail_condition:
|
| 110 |
+
not:
|
| 111 |
+
own_units_gte: 1
|
| 112 |
+
max_turns: 80
|
|
@@ -26,7 +26,9 @@ from pydantic import BaseModel, Field, field_validator
|
|
| 26 |
from .win_conditions import WinCondition
|
| 27 |
|
| 28 |
LevelName = Literal["easy", "medium", "hard"]
|
| 29 |
-
|
|
|
|
|
|
|
| 30 |
|
| 31 |
|
| 32 |
def deep_merge(base: dict, patch: dict) -> dict:
|
|
|
|
| 26 |
from .win_conditions import WinCondition
|
| 27 |
|
| 28 |
LevelName = Literal["easy", "medium", "hard"]
|
| 29 |
+
# "adversarial" = head-to-head reasoning vs a reactive opponent (the
|
| 30 |
+
# axis an RTS engine uniquely owns); ranked by a difficulty ladder + Elo.
|
| 31 |
+
Capability = Literal["perception", "reasoning", "action", "adversarial"]
|
| 32 |
|
| 33 |
|
| 34 |
def deep_merge(base: dict, patch: dict) -> dict:
|
|
@@ -0,0 +1,101 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Adversarial 1v1 spotlight: ladder rating + family integ.
|
| 2 |
+
|
| 3 |
+
The ladder metric is pure logic (fast, exhaustive); the 3 packs are
|
| 4 |
+
also compiled and smoke-run on the live Rust engine to prove the new
|
| 5 |
+
`adversarial` capability flows end to end (schema → engine → score →
|
| 6 |
+
leaderboard breakdown).
|
| 7 |
+
"""
|
| 8 |
+
|
| 9 |
+
from __future__ import annotations
|
| 10 |
+
|
| 11 |
+
from pathlib import Path
|
| 12 |
+
|
| 13 |
+
import pytest
|
| 14 |
+
|
| 15 |
+
from openra_bench.adversarial import (
|
| 16 |
+
RUNGS,
|
| 17 |
+
adversarial_summary,
|
| 18 |
+
ladder_rating,
|
| 19 |
+
ladder_ratings,
|
| 20 |
+
)
|
| 21 |
+
from openra_bench.leaderboard import _capability_breakdown, ingest_run
|
| 22 |
+
|
| 23 |
+
PACKS = Path(__file__).parent.parent / "openra_bench" / "scenarios" / "packs"
|
| 24 |
+
ADV = ["adversarial-duel", "adversarial-skirmish", "adversarial-siege"]
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
def test_ladder_rating_is_contiguous_from_easy():
|
| 28 |
+
assert ladder_rating({}) == 0
|
| 29 |
+
assert ladder_rating({"easy": "loss"}) == 0
|
| 30 |
+
assert ladder_rating({"easy": "win"}) == 1
|
| 31 |
+
assert ladder_rating({"easy": "win", "medium": "win"}) == 2
|
| 32 |
+
assert ladder_rating(dict.fromkeys(RUNGS, "win")) == 3
|
| 33 |
+
# non-contiguous: hard won but medium lost → still 1
|
| 34 |
+
assert ladder_rating(
|
| 35 |
+
{"easy": "win", "medium": "loss", "hard": "win"}
|
| 36 |
+
) == 1
|
| 37 |
+
# draw does not clear a rung
|
| 38 |
+
assert ladder_rating({"easy": "draw"}) == 0
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
def test_ladder_ratings_need_all_seeds_won():
|
| 42 |
+
stats = {
|
| 43 |
+
"episodes": [
|
| 44 |
+
{"cell": "adversarial-duel:easy", "capability": "adversarial",
|
| 45 |
+
"split": "public", "outcome": "win"},
|
| 46 |
+
{"cell": "adversarial-duel:easy", "capability": "adversarial",
|
| 47 |
+
"split": "public", "outcome": "loss"}, # one seed lost
|
| 48 |
+
{"cell": "adversarial-duel:medium", "capability": "adversarial",
|
| 49 |
+
"split": "public", "outcome": "win"},
|
| 50 |
+
# non-adversarial + held-out ignored
|
| 51 |
+
{"cell": "rush-hour:easy", "capability": "action",
|
| 52 |
+
"split": "public", "outcome": "win"},
|
| 53 |
+
{"cell": "adversarial-duel:hard", "capability": "adversarial",
|
| 54 |
+
"split": "held_out", "outcome": "win"},
|
| 55 |
+
]
|
| 56 |
+
}
|
| 57 |
+
# easy not all-won → rating 0 (medium can't count, non-contiguous)
|
| 58 |
+
assert ladder_ratings(stats) == {"adversarial-duel": 0}
|
| 59 |
+
s = adversarial_summary(stats)
|
| 60 |
+
assert s["packs"] == ["adversarial-duel"]
|
| 61 |
+
assert s["mean_ladder_rating"] == 0.0 and s["max_rung"] == 3
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
def test_summary_and_leaderboard_carry_adversarial(tmp_path):
|
| 65 |
+
stats = {
|
| 66 |
+
"episodes": [
|
| 67 |
+
{"cell": "adversarial-duel:easy", "capability": "adversarial",
|
| 68 |
+
"split": "public", "outcome": "win", "composite": 0.7},
|
| 69 |
+
{"cell": "adversarial-duel:medium", "capability": "adversarial",
|
| 70 |
+
"split": "public", "outcome": "win", "composite": 0.6},
|
| 71 |
+
{"cell": "adversarial-duel:hard", "capability": "adversarial",
|
| 72 |
+
"split": "public", "outcome": "loss", "composite": 0.2},
|
| 73 |
+
],
|
| 74 |
+
"overall": {"n": 3, "win_rate": 0.66, "composite_mean": 0.5},
|
| 75 |
+
"adversarial": None,
|
| 76 |
+
}
|
| 77 |
+
stats["adversarial"] = adversarial_summary(stats)
|
| 78 |
+
assert stats["adversarial"]["ladder_ratings"] == {"adversarial-duel": 2}
|
| 79 |
+
rec = ingest_run(stats, "m1", store=tmp_path / "lb.jsonl")
|
| 80 |
+
assert rec["adversarial_rating"] == 2 / 1 # mean over 1 pack
|
| 81 |
+
assert rec["adversarial_ladders"] == {"adversarial-duel": 2}
|
| 82 |
+
cap = _capability_breakdown(stats["episodes"])
|
| 83 |
+
assert "adversarial" in cap and cap["adversarial"]["n"] == 3
|
| 84 |
+
|
| 85 |
+
|
| 86 |
+
@pytest.mark.parametrize("pid", ADV)
|
| 87 |
+
def test_adversarial_pack_compiles_and_runs(pid):
|
| 88 |
+
pytest.importorskip("openra_train")
|
| 89 |
+
from openra_bench.eval_core import run_level
|
| 90 |
+
from openra_bench.scenarios import load_pack
|
| 91 |
+
from openra_bench.scenarios.loader import compile_level
|
| 92 |
+
|
| 93 |
+
pack = load_pack(PACKS / f"{pid}.yaml")
|
| 94 |
+
for lvl in RUNGS:
|
| 95 |
+
c = compile_level(pack, lvl)
|
| 96 |
+
assert c.meta.capability == "adversarial"
|
| 97 |
+
assert c.map_supported, f"{pid}:{lvl} map must be Rust-loadable"
|
| 98 |
+
c = compile_level(pack, "easy")
|
| 99 |
+
res = run_level(c, lambda rs, C: [C.observe()], seed=1)
|
| 100 |
+
assert res.outcome in {"win", "draw", "loss"}
|
| 101 |
+
assert res.turns >= 1
|