Spaces:
Running
Running
Yiyu Tian Claude Opus 4.7 commited on
Commit ·
9afe0cd
1
Parent(s): 9b651dc
tests: tech-production-planning pack — full Rust loop
Browse filesMirror test_building_planning's contributor-loop pattern: a scripted
tech-planner agent walks the depth-first chain (build weap -> fix ->
train 3tnk) and proves easy wins through the declarative win_condition.
Medium/hard need richer play than a scripted depth-first chain (medium
wants parallel queues + 2nd powr, hard wants scouting + pivot to
anti-armor) so we assert determinism + budget-bound rather than win,
matching how test_building_planning handles its harder levels.
Also a schema-level check that the hard fail_condition
(unit_type_count_gte e1 n=10) is wired through compile().
All engine tests gated on openra_train via importorskip.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
tests/test_tech_production_planning.py
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""tech-production-planning pack — full loop on Rust.
|
| 2 |
+
|
| 3 |
+
Canonical Family 2 (Sequential Dependency Planning) from the Benchmark
|
| 4 |
+
Design Proposal: easy = 2-hop chain (weap -> fix -> 3tnk), medium =
|
| 5 |
+
parallel branches under power_surplus_gte:0, hard = adaptive transition.
|
| 6 |
+
|
| 7 |
+
A scripted "tech-planner" agent walks the easy chain (build War Factory
|
| 8 |
+
-> Service Depot -> train one Heavy Tank) and proves the declarative
|
| 9 |
+
win_condition fires; medium/hard run deterministically with the same
|
| 10 |
+
agent (they need richer play than a scripted depth-first chain to win,
|
| 11 |
+
so we assert determinism + budget bound, mirroring test_building_planning).
|
| 12 |
+
"""
|
| 13 |
+
|
| 14 |
+
from __future__ import annotations
|
| 15 |
+
|
| 16 |
+
import pytest
|
| 17 |
+
|
| 18 |
+
pytest.importorskip("openra_train", reason="Rust env wheel not installed")
|
| 19 |
+
|
| 20 |
+
from openra_bench.eval_core import run_level
|
| 21 |
+
from openra_bench.scenarios import load_pack
|
| 22 |
+
from openra_bench.scenarios.loader import PACKS_DIR, compile_level
|
| 23 |
+
from openra_bench.scoring import score_episode
|
| 24 |
+
|
| 25 |
+
PACK = PACKS_DIR / "tech-production-planning.yaml"
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
def tech_planner_agent(render_state, Command):
|
| 29 |
+
"""Walk the 3tnk prereq chain depth-first.
|
| 30 |
+
|
| 31 |
+
Pre-placed: fact, powr, proc. Build weap -> fix, then train one 3tnk.
|
| 32 |
+
`build` queues the item; `place_building` no-ops until the building
|
| 33 |
+
is ready to be placed.
|
| 34 |
+
"""
|
| 35 |
+
btypes = [b["type"] for b in render_state.get("own_buildings", [])]
|
| 36 |
+
producing = render_state.get("production", []) or []
|
| 37 |
+
units = [u["type"] for u in render_state.get("units_summary", [])]
|
| 38 |
+
|
| 39 |
+
if "weap" not in btypes:
|
| 40 |
+
cmds = []
|
| 41 |
+
if "weap" not in producing:
|
| 42 |
+
cmds.append(Command.build("weap"))
|
| 43 |
+
cmds.append(Command.place_building("weap", 18, 22))
|
| 44 |
+
return cmds
|
| 45 |
+
if "fix" not in btypes:
|
| 46 |
+
cmds = []
|
| 47 |
+
if "fix" not in producing:
|
| 48 |
+
cmds.append(Command.build("fix"))
|
| 49 |
+
cmds.append(Command.place_building("fix", 22, 22))
|
| 50 |
+
return cmds
|
| 51 |
+
if "3tnk" not in units:
|
| 52 |
+
if "3tnk" not in producing:
|
| 53 |
+
return [Command.build("3tnk")]
|
| 54 |
+
return [Command.observe()]
|
| 55 |
+
return [Command.observe()]
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
def test_pack_compiles_with_three_levels():
|
| 59 |
+
pack = load_pack(PACK)
|
| 60 |
+
assert pack.meta.id == "tech-production-planning"
|
| 61 |
+
assert pack.meta.capability == "reasoning"
|
| 62 |
+
assert set(pack.levels) == {"easy", "medium", "hard"}
|
| 63 |
+
|
| 64 |
+
|
| 65 |
+
def test_easy_tech_chain_wins_and_scores():
|
| 66 |
+
pack = load_pack(PACK)
|
| 67 |
+
c = compile_level(pack, "easy")
|
| 68 |
+
assert c.map_supported
|
| 69 |
+
assert c.starting_cash == 5000
|
| 70 |
+
|
| 71 |
+
res = run_level(c, tech_planner_agent, seed=1)
|
| 72 |
+
|
| 73 |
+
# The chain went through both weap AND fix (the prereq for 3tnk).
|
| 74 |
+
btypes = res.signals.own_building_types
|
| 75 |
+
assert "weap" in btypes and "fix" in btypes, (
|
| 76 |
+
f"chain incomplete; own_building_types={btypes}"
|
| 77 |
+
)
|
| 78 |
+
# The agent actually trained a Heavy Tank.
|
| 79 |
+
unit_types = [u["type"] for u in res.signals.units_summary]
|
| 80 |
+
assert "3tnk" in unit_types, f"no 3tnk produced; units={unit_types}"
|
| 81 |
+
# Production debited the budget.
|
| 82 |
+
assert res.signals.cash < 5000, "construction must debit the budget"
|
| 83 |
+
assert res.outcome == "win", f"easy should be winnable, got {res.outcome}"
|
| 84 |
+
|
| 85 |
+
sc = score_episode(c, res)
|
| 86 |
+
assert sc.outcome == "win" and 0.0 <= sc.composite <= 1.0
|
| 87 |
+
|
| 88 |
+
|
| 89 |
+
def test_easy_run_is_deterministic():
|
| 90 |
+
c = compile_level(load_pack(PACK), "easy")
|
| 91 |
+
a = run_level(c, tech_planner_agent, seed=7)
|
| 92 |
+
b = run_level(c, tech_planner_agent, seed=7)
|
| 93 |
+
assert (a.outcome, a.turns, a.signals.cash) == (
|
| 94 |
+
b.outcome,
|
| 95 |
+
b.turns,
|
| 96 |
+
b.signals.cash,
|
| 97 |
+
), "same seed must be deterministic"
|
| 98 |
+
|
| 99 |
+
|
| 100 |
+
@pytest.mark.parametrize("level", ["medium", "hard"])
|
| 101 |
+
def test_medium_hard_run_and_score_within_constraints(level):
|
| 102 |
+
"""Harder levels need richer play than the scripted depth-first chain
|
| 103 |
+
(medium wants parallel queues + 2nd powr; hard wants scouting +
|
| 104 |
+
pivot to anti-armor). Assert they execute within declared constraints
|
| 105 |
+
and score deterministically — the contributor loop must be robust
|
| 106 |
+
even on a loss."""
|
| 107 |
+
pack = load_pack(PACK)
|
| 108 |
+
c = compile_level(pack, level)
|
| 109 |
+
a = run_level(c, tech_planner_agent, seed=3)
|
| 110 |
+
b = run_level(c, tech_planner_agent, seed=3)
|
| 111 |
+
assert a.outcome in {"win", "draw", "loss"}
|
| 112 |
+
assert (a.outcome, a.turns, a.signals.cash) == (
|
| 113 |
+
b.outcome,
|
| 114 |
+
b.turns,
|
| 115 |
+
b.signals.cash,
|
| 116 |
+
), "same seed must yield identical outcome"
|
| 117 |
+
sc = score_episode(c, a)
|
| 118 |
+
assert 0.0 <= sc.composite <= 1.0
|
| 119 |
+
# Budget constraint enforced by the engine.
|
| 120 |
+
assert a.signals.cash <= c.starting_cash
|
| 121 |
+
|
| 122 |
+
|
| 123 |
+
def test_hard_fail_predicate_set():
|
| 124 |
+
"""Hard declares overproducing infantry past 10 = fail. Confirm the
|
| 125 |
+
fail_condition is wired through compile (the engine actually checks
|
| 126 |
+
it). Schema-level check, no episode run required."""
|
| 127 |
+
c = compile_level(load_pack(PACK), "hard")
|
| 128 |
+
assert c.fail_condition is not None
|
| 129 |
+
# The fail leaf is unit_type_count_gte on e1 at n=10.
|
| 130 |
+
fc = c.fail_condition.model_dump(exclude_none=True)
|
| 131 |
+
assert fc == {"unit_type_count_gte": {"type": "e1", "n": 10}}, fc
|