Spaces:
Running
Running
| """No-cheat + solvency proof for `build-repair-priority-under-fire` | |
| (Wave-10 REASONING: criticality-weighted repair triage). | |
| Three of the agent's structures are under simultaneous attrition from | |
| separate grenadier bands. The damage picture is deliberately misleading | |
| and the agent's only job is the TRIAGE DECISION โ which building(s) to | |
| put the `repair` organ on: | |
| * proc (refinery) โ HIGH value, on a LETHAL trajectory: dies inside | |
| ~3 turns unrepaired; required by the win predicate. | |
| * pbox (pillbox) โ LOW value DECOY: pre-placed at health 30 so it | |
| LOOKS the most damaged, but it is Heavy-armoured (a light grenadier | |
| band barely scratches it), is not in the win predicate, and | |
| survives on its own. Repairing it is wasted effort. | |
| * weap (war factory) โ MEDIUM: on easy NO band sits on the war | |
| factory directly, so a bridged proc keeps its band pinned and the | |
| war factory rides out untouched; on medium / hard a light band ALSO | |
| chews the war factory so it too needs repair. | |
| The pack carries NO agent combat units and NO kill bar โ the engine | |
| movement fixes made pre-placed defenders resolve a clustered fire-fight | |
| in one ~90-tick window with the buildings never scratched, collapsing | |
| the old damage race. The triage is now a pure `repair`-target decision | |
| against an unopposed band. The win requires the buildings to SURVIVE a | |
| danger window (`after_ticks` floor + `within_ticks` ceiling), not | |
| merely be alive on turn 1. | |
| No-cheat bar (proven below for every level x seed 1-4): | |
| * STALL (only `observe`) -> proc never bridged -> refinery dies ~turn | |
| 4 -> `not building_count_gte:{proc}` fail fires -> LOSS. | |
| * REPAIR-PBOX-FIRST (toggle repair on the most-damaged-LOOKING | |
| building, the pbox) -> pbox saved (it survives on its own anyway); | |
| the proc gets no repair and dies -> LOSS. | |
| * REPAIR-PROC-ONLY -> WIN on easy (no band sits on the war factory, | |
| so a bridged proc keeps its band pinned) but LOSS on medium / hard | |
| (the light war-factory band kills the unrepaired weap -> | |
| `not building_count_gte:{weap}` fail). | |
| * INTENDED triage (repair proc โ easy; proc + weap โ medium / hard) | |
| -> autorepair out-paces the grenade chip, the buildings ride the | |
| bands out, the survival window opens -> WIN. | |
| Repair-everything also wins (it repairs the proc + weap) โ that is | |
| within the bar: the failure mode this pack discriminates is "repair the | |
| worst-LOOKING building", not "be thorough". | |
| """ | |
| from __future__ import annotations | |
| import pytest | |
| pytest.importorskip("openra_train", reason="Rust env wheel not installed") | |
| pytest.importorskip("openra_rl_training", reason="Rust env wheel not installed") | |
| from openra_bench.eval_core import run_level | |
| from openra_bench.scenarios import load_pack | |
| from openra_bench.scenarios.loader import PACKS_DIR, compile_level | |
| PACK = PACKS_DIR / "build-repair-priority-under-fire.yaml" | |
| LEVELS = ("easy", "medium", "hard") | |
| SEEDS = (1, 2, 3, 4) | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโ scripted policies โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| def _repair_types(rs, Command, types): | |
| """Toggle `repair` on every own building whose type is in `types` | |
| and is below full HP.""" | |
| cmds = [] | |
| for b in (rs.get("_raw", {}) or {}).get("own_buildings", []) or []: | |
| if b.get("type") in types and float(b.get("hp_pct", 1.0)) < 0.999: | |
| cmds.append(Command.repair([str(b["id"])])) | |
| return cmds or [Command.observe()] | |
| def _stall(rs, Command): | |
| return [Command.observe()] | |
| def _repair_pbox(rs, Command): | |
| """Wrong triage: repair the most-damaged-LOOKING building (pbox).""" | |
| return _repair_types(rs, Command, {"pbox"}) | |
| def _repair_proc(rs, Command): | |
| return _repair_types(rs, Command, {"proc"}) | |
| def _repair_proc_weap(rs, Command): | |
| """Intended triage on medium / hard: the two critical buildings.""" | |
| return _repair_types(rs, Command, {"proc", "weap"}) | |
| def _repair_all(rs, Command): | |
| return _repair_types(rs, Command, {"proc", "weap", "pbox", "fix", "fact"}) | |
| def _run(level, policy, seed=1): | |
| c = compile_level(load_pack(PACK), level) | |
| assert c.map_supported, "rush-hour-arena must compile" | |
| return c, run_level(c, policy, seed=seed) | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโ structural โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| def test_pack_loads_with_three_levels_and_repair_tool(): | |
| pack = load_pack(PACK) | |
| assert pack.meta.id == "build-repair-priority-under-fire" | |
| assert pack.meta.capability == "reasoning" | |
| assert pack.meta.status == "active" | |
| assert set(pack.levels) == set(LEVELS) | |
| c = compile_level(pack, "easy") | |
| tools = set(c.scenario.tools or []) | |
| # The triage is exclusively a `repair`-target decision. | |
| for t in ("observe", "repair"): | |
| assert t in tools, f"missing tool {t} in {tools}" | |
| for forbidden in ("build", "place_building", "move_units", "attack_unit"): | |
| assert forbidden not in tools, ( | |
| f"tool {forbidden!r} must NOT be in the repair-triage palette" | |
| ) | |
| def test_benchmark_anchor_lists_triage(): | |
| pack = load_pack(PACK) | |
| anchors = pack.meta.benchmark_anchor | |
| assert anchors, "benchmark_anchor must be non-empty" | |
| blob = " | ".join(anchors).lower() | |
| assert "triage" in blob or "maintenance" in blob, anchors | |
| def test_pbox_decoy_is_pre_damaged_every_level(): | |
| """The pbox decoy is pre-placed already damaged (health: 30) so it | |
| LOOKS the most-damaged building โ the bait for the wrong triage.""" | |
| pack = load_pack(PACK) | |
| for level in LEVELS: | |
| c = compile_level(pack, level) | |
| pboxes = [ | |
| a for a in c.scenario.actors | |
| if a.owner == "agent" and a.type == "pbox" | |
| ] | |
| assert pboxes, f"{level}: no pbox decoy" | |
| for a in pboxes: | |
| assert a.health == 30, ( | |
| f"{level}: pbox decoy must be pre-damaged (health 30), " | |
| f"got {a.health}" | |
| ) | |
| def test_no_agent_combat_units_engage(): | |
| """The pack carries no engaging agent combat units โ the triage is | |
| a pure `repair`-target decision against an unopposed band. easy / | |
| medium are building-only; the hard tier additionally carries one | |
| inert HoldFire (stance 0) e1 spawn-witness per spawn group, tucked | |
| beside the rear service depot far from every band, purely so the | |
| hard-tier `units_summary` spawn-variation contract can observe a | |
| seed-varied agent unit. The witness never opens fire and does not | |
| change the triage.""" | |
| pack = load_pack(PACK) | |
| buildings = {"proc", "weap", "pbox", "fix", "fact"} | |
| for level in LEVELS: | |
| c = compile_level(pack, level) | |
| agent_actors = [a for a in c.scenario.actors if a.owner == "agent"] | |
| agent_types = {a.type for a in agent_actors} | |
| if level == "hard": | |
| assert agent_types <= buildings | {"e1"}, ( | |
| f"hard: agent actors must be buildings + the inert e1 " | |
| f"witness, got {agent_types}" | |
| ) | |
| witnesses = [a for a in agent_actors if a.type == "e1"] | |
| assert witnesses, "hard: missing inert e1 spawn-witness" | |
| for w in witnesses: | |
| assert w.stance == 0, ( | |
| f"hard: e1 witness must be HoldFire (stance 0), " | |
| f"got stance {w.stance}" | |
| ) | |
| else: | |
| assert agent_types <= buildings, ( | |
| f"{level}: agent must own only buildings, got {agent_types}" | |
| ) | |
| def test_every_level_has_a_reachable_timeout_fail(level): | |
| """Non-win must be a real LOSS, never a DRAW: the `after_ticks` | |
| fail must be reachable inside `max_turns` and equal within_ticks+1.""" | |
| c = compile_level(load_pack(PACK), level) | |
| assert c.fail_condition is not None | |
| fail = c.fail_condition.model_dump()["any_of"] | |
| after_ticks = int(fail[0]["after_ticks"]) | |
| reachable = 93 + 90 * (c.max_turns - 1) | |
| assert after_ticks <= reachable, ( | |
| f"{level}: fail after_ticks {after_ticks} unreachable within " | |
| f"{c.max_turns} turns (max tick {reachable}) โ draw degeneracy" | |
| ) | |
| win_all_of = c.win_condition.model_dump().get("all_of", []) | |
| wt = next(int(x["within_ticks"]) for x in win_all_of if "within_ticks" in x) | |
| assert after_ticks == wt + 1, ( | |
| f"{level}: after_ticks {after_ticks} must equal within_ticks+1 ({wt + 1})" | |
| ) | |
| def test_win_has_survival_window(level): | |
| """The win must require the buildings to SURVIVE a danger window โ | |
| an `after_ticks` floor below the `within_ticks` ceiling โ so the win | |
| cannot latch on turn 1 before any damage lands.""" | |
| c = compile_level(load_pack(PACK), level) | |
| all_of = c.win_condition.model_dump().get("all_of", []) | |
| floor = next(int(x["after_ticks"]) for x in all_of if "after_ticks" in x) | |
| ceiling = next( | |
| int(x["within_ticks"]) for x in all_of if "within_ticks" in x | |
| ) | |
| assert 0 < floor < ceiling, ( | |
| f"{level}: win needs a survival window 0 < after_ticks " | |
| f"({floor}) < within_ticks ({ceiling})" | |
| ) | |
| reachable = 93 + 90 * (c.max_turns - 1) | |
| assert floor < reachable, ( | |
| f"{level}: win after_ticks {floor} unreachable within " | |
| f"{c.max_turns} turns (max tick {reachable})" | |
| ) | |
| def test_fail_trips_on_either_required_building(level): | |
| """Losing the proc OR the weap must be an immediate clean LOSS โ no | |
| draw can leak through a building-death + auto-`done` race.""" | |
| c = compile_level(load_pack(PACK), level) | |
| fail = c.fail_condition.model_dump()["any_of"] | |
| not_clauses = [x.get("not", {}) for x in fail if "not" in x] | |
| types = { | |
| c2.get("building_count_gte", {}).get("type") | |
| for c2 in not_clauses | |
| } | |
| assert "proc" in types, f"{level}: fail must trip on proc loss" | |
| assert "weap" in types, f"{level}: fail must trip on weap loss" | |
| def test_win_requires_proc_and_weap(level): | |
| c = compile_level(load_pack(PACK), level) | |
| all_of = c.win_condition.model_dump().get("all_of", []) | |
| bc = { | |
| x["building_count_gte"]["type"] | |
| for x in all_of | |
| if "building_count_gte" in x | |
| } | |
| assert {"proc", "weap"} <= bc, f"{level}: win must require proc and weap" | |
| def test_hard_has_two_spawn_groups(): | |
| c = compile_level(load_pack(PACK), "hard") | |
| sps = { | |
| (a.spawn_point if a.spawn_point is not None else 0) | |
| for a in c.scenario.actors | |
| if a.owner == "agent" | |
| } | |
| assert len(sps) >= 2, f"hard needs โฅ2 spawn groups, got {sps}" | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโ intended WIN โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| def test_intended_triage_wins(level, seed): | |
| """Intended triage โ repair proc (easy) / proc + weap (medium, | |
| hard) โ WINS every level ร every seed.""" | |
| c, r = _run(level, _repair_proc_weap, seed=seed) | |
| assert r.outcome == "win", ( | |
| f"{level} seed{seed}: intended triage should WIN, got {r.outcome} " | |
| f"(tick={r.signals.game_tick})" | |
| ) | |
| def test_repair_everything_wins(level, seed): | |
| """Repairing every damaged building also wins โ it DOES repair the | |
| proc + weap. Thoroughness is not the failure mode under test.""" | |
| c, r = _run(level, _repair_all, seed=seed) | |
| assert r.outcome == "win", ( | |
| f"{level} seed{seed}: repair-all should WIN, got {r.outcome}" | |
| ) | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโ no-cheat: lazy / wrong plays LOSE โโโโโโโโโโโโโโโ | |
| def test_stall_loses(level, seed): | |
| """Stall must LOSE โ the proc band is never bridged; the refinery | |
| dies ~turn 4 and the `not building_count_gte:{proc}` fail fires.""" | |
| c, r = _run(level, _stall, seed=seed) | |
| assert r.outcome == "loss", ( | |
| f"{level} seed{seed} stall must LOSE; got {r.outcome} " | |
| f"(tick={r.signals.game_tick})" | |
| ) | |
| def test_repair_pbox_first_loses(level, seed): | |
| """Repairing the most-damaged-LOOKING building (the low-value pbox | |
| decoy) must LOSE โ the proc gets no repair and dies.""" | |
| c, r = _run(level, _repair_pbox, seed=seed) | |
| assert r.outcome == "loss", ( | |
| f"{level} seed{seed} repair-pbox-first must LOSE; got {r.outcome} " | |
| f"(tick={r.signals.game_tick})" | |
| ) | |
| def test_repair_proc_only_wins_easy(seed): | |
| """On easy no band sits on the war factory, so a bridged proc keeps | |
| its band pinned โ repairing only the proc is sufficient โ WIN.""" | |
| c, r = _run("easy", _repair_proc, seed=seed) | |
| assert r.outcome == "win", ( | |
| f"easy seed{seed} repair-proc-only should WIN, got {r.outcome}" | |
| ) | |
| def test_repair_proc_only_loses_on_medium_and_hard(level, seed): | |
| """On medium / hard a light band ALSO chews the war factory โ | |
| repairing only the proc is no longer enough โ LOSS (the deeper | |
| 2-of-3 triage axis).""" | |
| c, r = _run(level, _repair_proc, seed=seed) | |
| assert r.outcome == "loss", ( | |
| f"{level} seed{seed} repair-proc-only must LOSE; got {r.outcome} " | |
| f"(tick={r.signals.game_tick})" | |
| ) | |