OpenRA-Bench / SCENARIO_QUALITY.md
yxc20098's picture
docs: whole-suite no-cheat pass summary + engine footguns; regenerate catalog
ca00016
|
Raw
History Blame Contribute Delete
34 kB
# OpenRA-Bench — Scenario Design-Quality Audit & Scoring
Analysis only. No scenario files changed. Prepared 2026-05-17.
Scope: the 48 `status: active` packs (every pack without
`status: quarantine`); TEMPLATE and all `cat-c*-02..05` quarantine
packs excluded. Grounded against `SCENARIO_AUDIT.md`,
`win_conditions.py`, `scoring.py`, `goal_tracker.py`,
`eval_core.run_level`, the schema, and `openra_env.game_data`
(verified unit/building costs, prerequisites, tech tree).
---
## 1. Executive Summary
The capability taxonomy and predicate grammar are sound, and the
hand-authored families (perception-*, reasoning-*, action-*,
artofwar-*, strict-*) are mostly genuine, well-grounded decision
problems. But the active set has four structural quality problems that
materially weaken it as a discriminating benchmark:
1. **Loss is impossible in ~70% of active packs.** Only a
`fail_condition` can produce `loss`; `run_level` leaves the outcome
at `draw` when the win predicate is unmet (eval_core.py L174, L243).
Every perception-*, reasoning-*, action-*, economy-*, custom-map,
strict-sequence, building-and-planning, and most `cat-*` pack has
**no real fail_condition** (the `cat-*` ones carry
`units_lost_lte: -1`, which is *never* satisfiable because
`units_lost = max(0, …) ≥ 0` — a dead no-op). Consequence: a
do-nothing agent and a near-miss agent both score `draw` (0.5
outcome); the binary outcome only separates win vs. not-win.
Partial credit via `objective_progress` rescues *some*
discrimination, but the headline outcome axis is degenerate on the
majority of the suite.
2. **The `cat-c*` `-00`/`-01` kept representatives are near-exact
duplicates, not distinct decisions.** `diff cat-c1-00 cat-c1-01`
differs only in id/title and ±1 `explored_pct` / ±200 ticks.
c5↔c6, c7↔c8, c9↔c11, c4↔c3 are the same decision with relabelled
meta. The two kept variants per category add **zero** distinct
capability — keep one, cut the second.
3. **`always-movement-wins` degeneracy in the navigation/perception
`cat-*` and several hand-authored packs.** Where the only win
clauses are `explored_pct_gte` + `within_ticks` with no fail and a
generous clock, the *baseline scripted frontier agent*
(`scripted_explore_agent`) already wins — these do not separate
strong from weak models, only "moves" from "doesn't move."
4. **A genuine solvability defect in `strict-production-bom` hard**:
the budget cannot cover the required tech path (see §2). Several
"spend/build" packs are solvable but only test "spend the obvious
thing," not an allocation trade-off, because the engine has no
harvest income (documented S0/S1) so there is never scarcity
pressure beyond a single fixed `starting_cash`.
**Bottom line:** ~9 packs should be CUT now (8 redundant `cat-*`
duplicates + 1 unsolvable level needs a budget fix or cut), ~16 need a
FIX (almost all: "add a real fail_condition so loss≠draw"), and the
hand-authored perception/reasoning/artofwar/strict-bom core is KEEP.
The single highest-leverage repo-wide fix is adding a real
`fail_condition` (timeout-loss or attrition-loss) to every active pack
so the outcome axis stops collapsing loss into draw.
---
## 2. Solvability spot-checks (verified against game_data)
Verified building/unit costs & prereqs: `powr` 300 (no prereq, +100
pwr), `tent`/`barr` 500 (prereq `powr`, −20 pwr), `proc` 1400 (prereq
`powr`), `pbox` 600 (prereq `tent`), `gun` 800 (prereq `weap`),
`tsla` 1200 (prereq `weap`, −75 pwr), `weap` War Factory (~2000),
`fact` 2000, e1 100 / e3 300 (prereq `barr|tent`).
- **strict-production-bom HARD — UNSOLVABLE on budget.** Spec:
`tsla` + 3 e1 + 2 e3, `starting_cash: 4200`, agent faction soviet.
`tsla` requires `weap` (≈2000) which is **not pre-placed and not in
the actor list**. Minimum spend: barr 500 + weap 2000 + tsla 1200 +
3·e1 300 + 2·e3 600 = **4600 > 4200**. Also tsla is −75 power with
only one `powr` (+100) minus barr/weap drain → likely power-starved.
**FIX (raise hard `starting_cash` to ~5200 and pre-place or
budget `weap`) or CUT the hard level.** Easy/medium are solvable
(barr 500 + 3 e1; barr + 3 e1 + 2 e3 within 2200/3000) and a good
BFCL-style fidelity test.
- **building-and-planning** easy (build powr+tent, cash 6000),
medium (2 pbox in region, tent pre-placed, cash 5000: 2·600+powr),
hard (MCV→deploy fact, 2 buildings near (60,20), cash 4000):
all solvable; hard is tight but feasible. KEEP.
- **economy-investment / economy-time-box / economy-force-buildup**:
all levels solvable on the given `starting_cash` (2nd proc 1400 +
2nd powr 300 ≤ budgets; e1 at 100 makes `own_units_gte` trivially
affordable). Solvable but **decision-thin** — no harvest income
means "wide vs deep" is not a real trade (both fit the budget); the
test reduces to "issue the build commands." FIX (see §4).
- **adversarial-* / artofwar-* / reasoning-risk-route /
reasoning-frontier-commit / perception-***: policies exist
(kite/focus-fire; bait then run; take the safe detour; commit to
the correct fog region). Solvable; these are the strongest packs.
- **cat-c3/c4** (`building_total_gte` + `power_surplus_gte: 0`):
solvable (powr is +100, free of prereq; spam powr). But
`power_surplus_gte: 0` + `building_total_gte` is trivially gamed by
building only power plants — see Rigor notes.
- **rush-hour / strategy-gauntlet / strategy-twobody / strategy-
dilemma**: 14 scattered enemy infantry vs ~32 agent combat units,
`units_killed_gte` 3–9; solvable, but with stance-3 auto-fighting
units the agent barely has to act — see Discrimination.
---
## 3. Scored Table (1–5; 5 = best)
solv = solvability · disc = discrimination · grnd = grounding ·
rigor = non-gameable + principled scaling · ovr = overall
| pack id | capability | solv | disc | grnd | rigor | ovr | verdict | one-line note |
|---|---|---|---|---|---|---|---|---|
| action-multiunit-coordination | action | 5 | 4 | 5 | 4 | **4** | KEEP | Real parallel-control test; add fail_condition so non-win≠draw. |
| action-sequenced-execution | action | 5 | 4 | 5 | 4 | **4** | KEEP | after_ticks gates encode ordering well; add timeout fail_condition. |
| adversarial-duel | adversarial | 4 | 4 | 5 | 4 | **4** | KEEP | Has real fail (own_units_gte:1); reactive enemy = unique RTS value. |
| adversarial-siege | adversarial | 4 | 4 | 5 | 4 | **4** | KEEP | Same model as duel; entrenched defender variant — distinct enough. |
| adversarial-skirmish | adversarial | 4 | 4 | 5 | 4 | **4** | KEEP | Outnumbered/maneuver variant; genuine asymmetric tactic. |
| artofwar-decoy-sacrifice | reasoning | 4 | 5 | 5 | 5 | **5** | KEEP | Loss cap forces "spend bait, save army" — true credit assignment. |
| artofwar-indirect-approach | reasoning | 4 | 5 | 5 | 5 | **5** | KEEP | units_lost_lte:0 + hazard short path = clean long-horizon test. |
| artofwar-lure-the-tiger | reasoning | 3 | 4 | 5 | 4 | **4** | FIX | Two-phase intent good, but win has no clause proving the lure happened — reaching region by any survivable route also wins; add an ordering/after_ticks gate. |
| artofwar-sequenced-citadel | reasoning | 5 | 4 | 5 | 4 | **4** | KEEP | after_ticks+within_ticks band genuinely grades order; solid. |
| building-and-planning | reasoning | 4 | 4 | 5 | 4 | **4** | KEEP | 3 genuinely different decisions (tech dep / placement / relocate); add fail_condition. |
| custom-map-no-enemy | perception | 5 | 2 | 4 | 2 | **2** | FIX | Pure nav, no enemy, no fail → baseline move-agent wins easy/medium; only hard (all_units_in_region, tight clock) discriminates. Keep hard, cut/merge easy+medium or add fail. |
| economy-force-buildup | reasoning | 5 | 2 | 3 | 2 | **2** | FIX | e1@100 makes own_units_gte trivial; no scarcity (no harvest). Tests "click build," not allocation. Make win require a building mix + fail on idle. |
| economy-investment | reasoning | 5 | 3 | 4 | 3 | **3** | FIX | Best of the economy packs but "wide vs deep" isn't a real trade w/o income; both paths fit budget. Tighten cash so paths are mutually exclusive; add fail. |
| economy-time-box | reasoning | 5 | 2 | 3 | 2 | **2** | FIX | Near-duplicate of force-buildup + a building_total bar; same triviality. Merge into economy-investment or harden. |
| perception-frontier-reading | perception | 5 | 3 | 5 | 3 | **3** | FIX | Strong concept; but easy/medium = explored_pct only, no fail → baseline frontier agent already wins. Hard (decoys + units_lost_lte:1) is the real test. Add fail to easy/medium. |
| perception-target-vs-fog | perception | 5 | 4 | 5 | 4 | **4** | KEEP | buildings_discovered (not coverage) defeats the blind-sweep agent; medium/hard force inference. Add timeout fail. |
| reasoning-frontier-commit | reasoning | 5 | 4 | 5 | 4 | **4** | KEEP | Single scout = un-hedgeable commitment; decoy buildings vs units is a clean discriminator. |
| reasoning-risk-route | reasoning | 5 | 5 | 5 | 5 | **5** | KEEP | units_lost_lte:0 + lethal short path + clock = textbook non-gameable risk/route. Exemplary. |
| rush-hour | action | 5 | 2 | 3 | 2 | **2** | FIX | ~32 stance-3 auto-fighting agent units vs 14 scattered enemy; kill 3/6/9 in 40 turns is near-automatic. Low discrimination; not load-bearing. Cut to a real sweep (fewer units, coverage+kill, fail on timeout). |
| strategy-dilemma | reasoning | 4 | 2 | 3 | 2 | **2** | FIX | Win = buildings_discovered_gte:1 only → any scout that bumps the enemy base wins; "risk dilemma" framing not enforced by the predicate. Replace win with reach_region + units_lost_lte. |
| strategy-gauntlet | reasoning | 4 | 2 | 3 | 2 | **2** | FIX | Same defect: buildings_discovered_gte:1 with a huge friendly force; corridor never has to be solved. Re-spec to reach_region + attrition. |
| strategy-twobody | action | 4 | 2 | 3 | 2 | **2** | FIX | Two pre-split friendly blobs already near the enemy base; discover-1-building wins with no real coordination. Re-spec to all_units_in_region (true rendezvous). |
| strict-production-bom | action | 3 | 5 | 5 | 5 | **4** | FIX | Excellent BFCL-style exact-spec test (unit_type_count_eq + overproduction fail). HARD level unsolvable on 4200 cash (needs weap+tsla≈4600) — raise cash or cut hard. |
| strict-sequence | action | 5 | 4 | 5 | 4 | **4** | KEEP | Tool allowlist + after_ticks band genuinely tests API-fidelity ordering; add timeout fail so a stalled agent loses, not draws. |
| cat-c1-00 (Frontier Scouting) | perception | 5 | 3 | 4 | 3 | **3** | KEEP | Representative of C1; explored_pct+clock, hard adds units_lost_lte:0. OK as the single C1 kept level. |
| cat-c1-01 | perception | 5 | 2 | 4 | 2 | **2** | CUT | ±1 explored_pct / ±200 ticks vs cat-c1-00. Pure duplicate. |
| cat-c2-00 (Threat Enumeration) | perception | 5 | 3 | 4 | 3 | **3** | KEEP | enemies_discovered + units_lost_lte is a real perception-under-decoy test; keep one. |
| cat-c2-01 | perception | 5 | 2 | 4 | 2 | **2** | CUT | Duplicate of cat-c2-00 (only tick deltas). |
| cat-c3-00 (Tech Critical Path) | reasoning | 4 | 2 | 4 | 2 | **2** | FIX | building_total_gte + power_surplus_gte:0 is gamed by spamming powr (each +100, no prereq) — doesn't test the tech *path*. Require has_building tent/proc to force the dependency. |
| cat-c3-01 | reasoning | 4 | 2 | 4 | 2 | **2** | CUT | Duplicate of cat-c3-00. |
| cat-c4-00 (Power-Budget Online) | reasoning | 4 | 2 | 4 | 2 | **2** | FIX | Same gameability as c3 (powr-spam satisfies both clauses); near-identical to c3. Merge c3+c4 into one fixed pack. |
| cat-c4-01 | reasoning | 4 | 2 | 4 | 2 | **2** | CUT | Duplicate of cat-c4-00 (and of c3). |
| cat-c5-00 (Budget Allocation) | reasoning | 5 | 2 | 3 | 2 | **2** | FIX | powr n:2 + building_total bar, no income → "build 5 cheap buildings"; not an indivisible-budget trade. Same as c6. |
| cat-c5-01 | reasoning | 5 | 2 | 3 | 2 | **2** | CUT | Duplicate of cat-c5-00. |
| cat-c6-00 (Time-Boxed Deploy) | reasoning | 5 | 2 | 3 | 2 | **2** | FIX | own_units_gte+building_total; e1@100 trivial; duplicate decision of c5/economy-time-box. Merge. |
| cat-c6-01 | reasoning | 5 | 2 | 3 | 2 | **2** | CUT | Duplicate of cat-c6-00. |
| cat-c7-00 (Defensive-Direction) | perception | 4 | 3 | 4 | 3 | **3** | KEEP | building_in_region for pbox forces a directional commit; pbox needs tent (pre-placed) — solvable. Keep one. |
| cat-c7-01 | perception | 4 | 2 | 4 | 2 | **2** | CUT | Duplicate of cat-c7-00. |
| cat-c8-00 (Base-Placement) | perception | 4 | 3 | 4 | 3 | **3** | FIX | Same family as c7 (place building in inferred region). Distinct enough to keep one but merge c7/c8 conceptually; ensure region is reachable/buildable. |
| cat-c8-01 | perception | 4 | 2 | 4 | 2 | **2** | CUT | Duplicate of cat-c8-00. |
| cat-c9-00 (Commit-vs-Retreat) | reasoning | 4 | 3 | 4 | 3 | **3** | KEEP | units_killed + units_lost_lte is a real risk call; no real fail (dead −1) — FIX-lite: make fail = units_lost_gte cap. |
| cat-c9-01 | reasoning | 4 | 2 | 4 | 2 | **2** | CUT | Duplicate of cat-c9-00. |
| cat-c10-00 (Force Coordination) | action | 5 | 3 | 4 | 3 | **3** | KEEP | all_units_in_region + units_lost_lte = genuine rendezvous; better-spec'd than strategy-twobody. Keep one. |
| cat-c10-01 | action | 5 | 2 | 4 | 2 | **2** | CUT | Duplicate of cat-c10-00. |
| cat-c11-00 (Tempo Window) | reasoning | 4 | 3 | 4 | 3 | **3** | KEEP | after_ticks + units_killed + within_ticks genuinely encodes a window; only tempo coverage — keep one. |
| cat-c11-01 | reasoning | 4 | 2 | 4 | 2 | **2** | CUT | Duplicate of cat-c11-00 (and decision-twin of c9). |
| cat-c12-00 (Error Recovery) | reasoning | 4 | 2 | 3 | 2 | **2** | FIX | "Replan after setback" but nothing is destroyed at start — it's just a timed build with after_ticks:1200. Not a recovery test; either script a mid-episode loss or CUT. |
| cat-c12-01 | reasoning | 4 | 2 | 3 | 2 | **2** | CUT | Duplicate of cat-c12-00. |
Aggregate: KEEP 19 · FIX 18 · CUT 11. (TEMPLATE excluded.)
---
## 4. CUT list (concrete — safe to delete now)
These add no distinct capability and/or are non-discriminating:
1. **`cat-c1-01`** — byte-near duplicate of cat-c1-00 (±1 pct/±200 tk).
2. **`cat-c2-01`** — duplicate of cat-c2-00.
3. **`cat-c3-01`** — duplicate of cat-c3-00.
4. **`cat-c4-01`** — duplicate of cat-c4-00 (and decision-twin of c3).
5. **`cat-c5-01`** — duplicate of cat-c5-00.
6. **`cat-c6-01`** — duplicate of cat-c6-00 (decision-twin of c5).
7. **`cat-c7-01`** — duplicate of cat-c7-00.
8. **`cat-c8-01`** — duplicate of cat-c8-00.
9. **`cat-c9-01`** — duplicate of cat-c9-00.
10. **`cat-c10-01`** — duplicate of cat-c10-00.
11. **`cat-c11-01`** — duplicate of cat-c11-00.
12. **`cat-c12-01`** — duplicate of cat-c12-00.
Net: keep exactly one representative per `cat-c*` category (the `-00`),
deleting all 12 `-01` actives — removes 12 files with zero capability
loss. (Category-merge candidates, can be done after: fold c4→c3, c6→c5,
c8→c7, c11→c9 since each pair is the same predicate decision.)
Additionally **CUT or hard-FIX**: `cat-c12-00` (not actually an
error-recovery test — nothing fails mid-episode; it is a delayed timed
build), and `strict-production-bom` HARD level (unsolvable on 4200
cash; either raise to ~5200 + pre-place `weap`, or delete the hard
level and keep easy/medium).
---
## 5. The repo-wide FIX (do this first, highest leverage)
**Add a real `fail_condition` to every active pack that lacks one.**
Today `run_level` only emits `loss` if a `fail_condition` evaluates
true; otherwise an unmet win stays `draw` (eval_core L174/L243). With
no fail, a do-nothing agent draws and a 95%-there agent draws — the
outcome axis cannot rank effort. The `cat-*` `units_lost_lte: -1`
"fail" is a dead no-op (units_lost ≥ 0 always).
Mechanical, non-gameable fixes using the *existing* vocabulary:
- Timed tasks (perception-*, reasoning-*, action-*, economy-*,
strict-*, building-and-planning, cat-c1/c3/c4/c5/c6/c7/c8/c12):
`fail_condition: {not: {within_ticks: <max_ticks>}}` — i.e. running
out the clock without meeting the win is a *loss*, not a draw.
- Attrition tasks (anything with a `units_lost_lte: N` win clause):
add `fail_condition: {not: {units_lost_lte: N}}` so exceeding the
cap loses immediately (also prunes wasted ticks).
- `cat-*`: replace the dead `units_lost_lte: -1` with the timeout-loss
above (one-line generator change; regenerate the kept `-00` files).
This single change restores a 3-way outcome (win/draw/loss) across the
suite and makes `objective_progress` partial credit meaningful instead
of every non-win collapsing to 0.5.
Secondary FIXes (per pack, already noted in the table):
- **strategy-dilemma / strategy-gauntlet / strategy-twobody**: win is
`buildings_discovered_gte: 1` — satisfied by *seeing* the pre-placed
enemy base, which a huge friendly stance-3 force does incidentally.
Re-spec to `reach_region` (the objective) + `units_lost_lte`
(enforce the risk/coordination the title claims).
- **cat-c3/c4**: add `has_building: tent` (or `proc`) to the win so
`building_total_gte` can't be met by powr-spam; this restores the
tech-*dependency* the pack claims to test.
- **economy-investment / -time-box / -force-buildup**: tighten
`starting_cash` so the wide and deep paths are mutually exclusive
(currently both fit), and add the timeout fail. Until engine S0/S1
(ore income) lands these can only ever be "spend the obvious thing."
- **artofwar-lure-the-tiger**: add an `after_ticks` gate or a
staging `reach_region` on the lure so the win actually requires the
two-phase plan, not just any survivable route to the objective.
- **rush-hour**: drop it from the "strategy" claim; reduce the
friendly force and require coverage (`explored_pct_gte`) + kills +
timeout-fail so it tests coordinated sweep, not auto-battle.
---
## 6. ADD list (top 10 new scenarios to author)
Gaps the active set still has, with win/fail sketches in the existing
predicate grammar and the external benchmark each parallels.
1. **adversarial-counterstrategy-read** (capability: adversarial).
Opponent commits one of two observable openings (rush near-lane vs
tech far-corner); agent must scout then commit the dominant
counter. Win: `all_of[{buildings_discovered_gte:1},{units_killed_gte:N},{within_ticks:T}]`;
fail: `{not:{own_units_gte:1}}` or timeout. Parallel:
StarCraft-II-Arena, game-theoretic LLM evals. (Fills the
opponent-modeling gap — the unique RTS value prop.)
2. **longhorizon-opening-to-assault** (reasoning). One episode,
40k+ ticks: scout (explored_pct) → tech (`has_building: proc`) →
army (`own_units_gte`) → strike (`reach_region` enemy base) — all
four as `all_of`, terminal-only credit, timeout fail. Parallel:
Terminal-Bench 2.0 / OSWorld 50-step. (Fills long-horizon credit
assignment — currently thin.)
3. **strict-toolban-fidelity** (action). Achieve `reach_region` but
the allowlist excludes `attack*`; win additionally requires
`units_lost_lte: 0` AND a `not{after_ticks:T0}`-then-`reach`
ordering. Any disallowed-tool call → fail. Parallel: BFCL V4
relevance / τ²-bench. (Isolates API fidelity as the objective.)
4. **economy-throughput-real** (reasoning) — *gated on engine S0/S1*.
Once ore income exists: `economy_value_gte: V within_ticks:T`,
wide (2 proc) vs deep (1 proc + harvesters) genuinely mutually
exclusive. Parallel: PlanBench cost-optimal. (Replaces the 5
non-discriminating economy/cat-c5/c6 packs with one real test.)
5. **perception-count-the-threat** (perception). Win:
`enemies_discovered_gte: K` where K = exact hidden count and
over/under-scouting wastes the clock; fail timeout +
`units_lost_lte`. Parallel: ERQA state-estimation. (Strengthens
the validated transfer target with a counting variant.)
6. **reasoning-replan-after-loss** (reasoning) — a *true* error-
recovery pack (what cat-c12 only pretends to be). Pre-place the
agent base in the path of a scripted stance-3 enemy push that
destroys 1–2 buildings early; win =
`all_of[{building_total_gte:N},{has_building:tent},{after_ticks:t_loss},{within_ticks:T}]`.
Parallel: PlanBench replanning. (Requires interrupt/scripted-loss
support — see §7.)
7. **adversarial-feint-handling** (adversarial). Opponent shows a
decoy force on one axis; win keyed to committing against the
*real* axis (`building_in_region`/`units_killed_gte` at the true
threat), losing if you commit to the decoy (timeout). Parallel:
opponent modeling / adversarial robustness.
8. **coordination-staggered-window** (action). Two squads must each
be in *different* regions within *different* tick bands
(`all_of` of two `reach_region` + paired `after_ticks`/`within_ticks`),
forcing true parallel scheduling, not a single column. Parallel:
Watch-And-Help / multi-robot dispatch. (Upgrades the thin
strategy-twobody/cat-c10 coordination bucket.)
9. **tempo-double-window** (reasoning). Two separate strike windows
in one episode (`after_ticks:t1`+`units_killed_gte:a` then a
forced lull then `after_ticks:t2`+`units_killed_gte:b`), so acting
continuously fails. Parallel: TextStarCraft II tempo. (Tempo is
currently a single cat-c11 category.)
10. **navigation-confined-hard-only** (perception). Replace the
trivial custom-map easy/medium with one hard map-read:
`all_units_in_region` in a far bounded corner, tight clock,
`fail_condition` on timeout — a real ARC-/ERQA-style spatial
commit with no degenerate easy tier.
---
## 7. Predicate-vocabulary additions needed
The current leaf set is adequate for §6 #1,2,5,7,8,9,10. To fully
author the others, add:
- **`tool_called`/`tool_not_called: <name>`** (or a scenario-level
`forbidden_tools` enforced as an automatic `fail`) — needed for
#3 strict-toolban-fidelity; today the only tool-fidelity signal is
the cross-cutting `actions_warned/issued` score, not a win/fail
predicate. This is the most leaderboard-relevant missing primitive
(BFCL/τ²-bench relevance detection).
- **`building_destroyed_gte` / `own_building_lost_gte`** — needed for
#6 (detect the scripted setback) and to give offensive packs a real
"took the base" predicate instead of proxying with
`buildings_discovered`/`reach_region`.
- **An ordering composite (e.g. `then: [A, B]` meaning A must have
held true at some tick strictly before B)**`after_ticks` only
approximates sequencing by wall-clock; a true happened-before would
let artofwar-lure-the-tiger / sequenced-citadel grade *order*
rather than *timing*, and make #2/#6 non-gameable.
- A scripted-adversary / mid-episode-event hook (engine side, already
flagged in SCENARIO_AUDIT as Phase-1) is the prerequisite for #1,
#6, #7 to be more than scripted-stance encounters.
---
## 8. Prioritized action summary
**Cut now (12 files, zero capability loss):** all `cat-c*-01` actives
(c1–c12 `-01`). Optionally also retire `cat-c12-00` (mislabelled) and
the `strict-production-bom` hard level (unsolvable) unless budgeted.
**Fix now (one mechanical pass):** add a real `fail_condition`
(timeout-loss; attrition-loss where a `units_lost_lte` win clause
exists) to all ~36 active packs lacking one, and replace the dead
`units_lost_lte:-1` in the kept `cat-*` files. This is the single
change that most improves benchmark discrimination.
**Fix next (per-pack, ~6 packs):** strategy-dilemma/gauntlet/twobody
(win predicate doesn't enforce the claimed decision), cat-c3/c4
(powr-spam gameable — add `has_building`), economy-* (tighten cash so
allocation is a real trade), artofwar-lure-the-tiger (add ordering
gate), rush-hour (re-spec or stop counting it as strategy),
strict-production-bom hard (raise budget or cut).
**Top 10 to add (ranked):** #1 adversarial-counterstrategy-read,
#2 longhorizon-opening-to-assault, #3 strict-toolban-fidelity,
#6 reasoning-replan-after-loss, #7 adversarial-feint-handling,
#8 coordination-staggered-window, #5 perception-count-the-threat,
#9 tempo-double-window, #4 economy-throughput-real (after S0/S1),
#10 navigation-confined-hard-only.
**Predicate additions (in priority order):** (1) tool-call
fidelity predicate / `forbidden_tools` auto-fail; (2)
`building_destroyed_gte`; (3) a happened-before ordering composite;
(4) scripted mid-episode adversary hook (engine, Phase-1).
---
## De-duplication (overlap analysis, 24→21 active)
Win-predicate Jaccard overstates overlap (small vocab); judged at the
decision level. Consolidated the genuinely-aimless dups (same map +
same win signature + same skill):
- **adversarial-siege, adversarial-skirmish → quarantined**:
same as adversarial-duel (rush-hour-arena, kill-reactive-enemy-within
-ticks-with-loss-cap); duel's easy→hard ladder + easy/medium configs
already cover even→outnumbered→entrenched. adversarial-duel is the
canonical adversarial scenario.
- **economy-time-box → quarantined**: = economy-force-buildup + a
"keep a building standing" clause (same budget-under-clock decision).
Kept economy-force-buildup + economy-investment (the latter adds a
distinct economy-vs-military allocation choice).
Kept (NOT aimless dup — each serves a purpose):
- strategy-dilemma vs strategy-gauntlet: same objective but **different
maps** (singles-dilemma vs singles-gauntlet) → different route puzzle.
- artofwar-decoy-sacrifice vs -lure-the-tiger: same map but genuinely
distinct credit-assignment (sacrifice-and-lose vs displace-and-exploit).
Active set: 21 distinct decisions. Quarantined packs remain on disk,
runnable via explicit --packs, excluded from the default sweep.
## Per-scenario closer look — #1 action-multiunit-coordination
Difficulty axis (one new controlled variable per tier, so a tier
failure attributes to a single capability):
- **easy** — 2 regions, exact `(x,y)`. Tests *parallel dispatch*:
serialized one-group-at-a-time touring blows the binding tick
deadline (~30+ turns); only genuine simultaneous control (~17–20
turns) makes it. Split enforced by `units_in_region_gte n=2` (not
`reach_region`, which one touring unit satisfies).
- **medium** — 3 *dispersed* regions (NE / SW bottom-left / SE),
exact `(x,y)`, attrition cap, infantry pickets on the two eastern
lanes. Adds *multi-vector* dispatch: a single eastward column
cannot reach the SW region.
- **hard** — byte-identical setup to medium (same regions, pickets,
deadline, attrition); the ONLY changed variable is
`objective_coords: relative` — the briefing gives compass labels
("the NORTH-EAST corner") instead of coordinates. Tests *spatial
grounding*: the model must localize each target on the minimap
(each region holds one enemy building as the on-map landmark).
Deliberately NOT spawn-varied and NO turrets on the path —
introducing either would add a second uncontrolled variable and
break the clean medium→hard attribution (recorded as a reasoned
exception to the task-#23 spawn-variation contract in
`tests/test_hard_tier.py::NOT_APPLICABLE`).
`objective_coords` (`exact|relative`, schema `Level`/`ScenarioConfig`,
default `exact`) is a reusable knob: any pack can opt into
coordinate-blind objectives. The win predicate always evaluates
against the real engine coordinates regardless of disclosure.
## Per-scenario closer look — #2 action-sequenced-execution
Original defect: the pack claimed to test ordered, non-stalling route
execution but the win condition referenced only the *final* region +
a tick band — a beeline-to-final that skipped every waypoint and
idled to the gate won (verified: model arrived tick 1353, idled ~900
ticks, won). Not model compensation: the scenario didn't enforce what
it advertised.
Fix — new reusable **stateful** predicate `waypoint_sequence`: latches
ordered region-visit progress (monotonic) on a per-episode
`signals.seq_progress` scratch. Wk+1 only counts after Wk; skip /
out-of-order / idle ⇒ never satisfied. Coords-aware phrase
(`exact|relative`). Difficulty axis (one new controlled variable per
tier):
- **easy** — ONE ordered 3-waypoint route, clear lanes, generous
budget. Run: WIN, W1→W2→W3 in order, tick 1893 < 2400.
- **medium** — TWO ordered routes that *cross*, BOTH required ⇒ must
split into two parallel columns (serial overruns); attrition cap.
Run: LOSS (valid) — model split correctly, finished route S in
order, lost column discipline on N and timed out (the exact
re-deliberation failure the pack targets).
- **hard** — TWO 4-waypoint serpentine routes on the larger
`scout-arena` (176×80, `scripts/build_scout_arena_map.py`), given
ONLY by relative *search bands* (no coords); fogged markers revealed
by the `enemy_building_spotted` interrupt; seed-varied 2 spawn
points. Run: LOSS (valid) — machinery all verified working
(interrupts fired on the big map); model drove to literal corners
instead of scanning the bands, ignored the revealed positions,
attrition-bust. Solvable in principle; not compensated.
All tick budgets aligned to ~90 ticks/turn so timeout / attrition /
wipe are reachable **losses** within `max_turns` (no draw degeneracy).
Footgun recorded: a `base_map` placed at the Level level is silently
ignored — it must go inside `overrides:` (guard test added).
## Win-speed bonus (cross-cutting scoring)
Every episode records `win_tick` / `win_turns` / `win_budget` /
`speed` / `composite_base` (score.json + manifest). On a **win** the
composite gets `+SPEED_BONUS·speed` (`SPEED_BONUS=0.05`,
`speed = 1 − win_tick/budget`, budget = tightest `within_ticks` in the
win tree else `max_ticks`). Bounded so a fast win ranks above a slow
win but never lifts a loss above a win or overrides correctness.
Leaderboard carries `win_speed`/`win_turns` and breaks composite/
win-rate ties by faster wins.
## YAML-referenceable generators (mapgen / botgen)
A pack no longer needs a static `.oramap` + static enemy actors:
- **Map** — `base_map: {generator: arena, name: scout-arena,
width: 176, height: 80, cordon: 4}` materializes a deterministic,
content-addressed `.oramap` at compile time (`openra_bench/mapgen.py`;
static string ids still work). #2 hard uses this.
- **Bot**`enemy: {faction: soviet, bot_type: hunt}` (alias `bot:`)
attaches an engine-side scripted opponent. Behaviours (map-agnostic,
ground-truth, per-tick): `hunt` (each unit attacks nearest agent
unit), `rusher` (concentrated relentless charge), `patrol` (oscillate
around own spawn, engage intruders), `turtle` (hold spawn, return
fire). Runs in `openra-sim/scripted_bot` because the Python boundary
can't see fogged enemy ids or command the enemy player; the bench
surface (`openra_bench/botgen.py`) validates the name at compile.
## Whole-suite no-cheat pass (#1–#21, all 21 active packs)
Every active pack has been individually closer-looked + redesigned to
the **"no defect, no cheat"** bar: a scripted brute / stall / greedy /
shortest-path / sweep / wrong-route policy must LOSE on every level +
every hard seed; the intended capability policy must WIN; non-win is a
real reachable timeout LOSS, not a draw. Engine smoke clean across
seeds 1–4 each level (no panics, in-bounds, `map_supported`); hard
tiers in `tests/test_hard_tier.py::UPGRADED` keep ≥2 distinct seed-
driven spawns; reasoned `NOT_APPLICABLE` exceptions documented in the
test. Suite **414 passed, 1 skipped, 0 failed** on the consolidated
`main` after all 21 redesigns landed. Author-side artifacts: the
SCENARIO_REVIEW_CHECKLIST.md is the methodology; per-scenario commit
messages on `main` document each capability, the cheat path that was
closed, and the scripted before/after evidence.
Recurring defect classes the pass eliminated:
- **Inert deadlines** (`within_ticks` ≫ tick reachable at `max_turns`,
i.e. > `93+90·(max_turns-1)`) ⇒ draw degeneracy. Every level now
satisfies `within_ticks` AND `after_ticks` ≤ that bound.
- **Missing `fail_condition`** ⇒ non-win = draw. Every level now has
an `any_of` fail covering at least the timeout (plus force-loss /
attrition where applicable).
- **Capability not enforced by the win predicate** (the laziest play
satisfies it without doing the advertised skill). Fixed with the
right predicate per pack: `units_in_region_gte` (split / main body),
`waypoint_sequence` (ordered / latched), `units_lost_lte` (bait
budget), `buildings_discovered_gte` (fog-dependent discovery),
`enemy_key_buildings_destroyed` (faithful "destroy fact+proc"),
exact-cost `starting_cash` (binding allocation), etc.
- **Off-map actors** (engine panic). Every level's actors verified in
playable bounds across seeds 1–4.
- **Engine auto-`done` on enemy-elimination** terminating before the
intended win/fail evaluated → drew. Mitigation: an unarmed high-HP
enemy `fact` marker at the objective ensures the episode runs to a
real win or timeout LOSS.
Engine footguns surfaced during the pass (flagged for follow-up):
- **`power_surplus_gte` is currently inert**: the obs reports
`power_provided/power_drained = 0` even with placed `powr`, so the
predicate is effectively `0 ≥ N` and never fires meaningfully.
Affected packs (economy-investment, tech-production-planning, etc.)
intentionally do NOT rely on it as the sole discriminator.
- **`deploy` is not executed** by the installed wheel: an MCV reaches
the target cell but never becomes a Construction Yard. `building-
and-planning` hard was redesigned around build-radius creep instead.
- Both are tracked as engine-side follow-ups (separate from the
pre-existing pending task #11 sabotage/specials).
Note: `capture_actor` is deliberately absent until the engine gains a
Capture order (pending S8 / task #11) — the bench never advertises a
tool the engine can't execute (1:1 parity).