Yiyu Tian Claude Opus 4.7 commited on
Commit
9afe0cd
·
1 Parent(s): 9b651dc

tests: tech-production-planning pack — full Rust loop

Browse files

Mirror 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