yxc20098 commited on
Commit
d0dd8e7
·
1 Parent(s): dad42c6

#4 artofwar-decoy-sacrifice: no-cheat redesign — decoy-sacrifice enforced

Browse files

The prior pack was cheatable (durable 2tnk brute-rush past a weak e1
picket won in ~4 turns; the decoy/credit-assignment capability was
never required). Redesigned end-to-end so the ONLY winning policy is
the decoy:

- enemy bot_type: guard (new engine leashed-defender behaviour): an
e3 (anti-tank) line spans the corridor at x=60, holds post, lunges
at a foe within aggro, snaps back past the leash.
- win = units_in_region_gte{(112,20) r6 n:3} (the THREE-tank main body
must reach the far objective) + reachable within_ticks; the unarmed
high-HP marker keeps the episode alive (no enemy-elim auto-terminal).
- A head-on push runs the full lethal e3 gauntlet → <3 arrive →
timeout LOSS. Do-nothing/preserve-everything → timeout LOSS. Only
baiting a segment off post with the fast jeeps opens a transient
gap for the tanks.
- Tiers: easy bare; medium tighter clock; hard seed-varied spawn
(anti-memorisation) + units_lost_lte:2 (only the 2 bait jeeps may
be spent). Off-map hard spawn (y40/48) also fixed.

VERIFIED no defect: scripted brute / stall / preserve LOSE on every
tier and seed (cheat closed); a correct scripted decoy WINS easy &
medium losing exactly the 2 bait jeeps (solvable, sacrifice
mandatory). qwen/qwen3.6-flash brute-rushes → LOSS all tiers = valid
capability discrimination. botgen: +guard. Suite 380 passed.

docs/scenarios.html CHANGED
The diff for this file is too large to render. See raw diff
 
openra_bench/botgen.py CHANGED
@@ -17,13 +17,18 @@ Behaviours (all derive targets from live world state — no map coords):
17
  rusher — relentless concentrated charge at the agent's mass; fast.
18
  patrol — units oscillate around their own spawn; engage intruders.
19
  turtle — hold the spawn position; return fire only.
 
 
 
20
  """
21
 
22
  from __future__ import annotations
23
 
24
  from typing import Any
25
 
26
- BEHAVIORS: frozenset[str] = frozenset({"hunt", "rusher", "patrol", "turtle"})
 
 
27
 
28
 
29
  def read_enemy_bot(enemy: Any) -> str | None:
 
17
  rusher — relentless concentrated charge at the agent's mass; fast.
18
  patrol — units oscillate around their own spawn; engage intruders.
19
  turtle — hold the spawn position; return fire only.
20
+ guard — leashed defender: holds its post; lunges at a
21
+ foe only within an aggro radius and snaps back
22
+ past a leash (a decoy can bait it off post).
23
  """
24
 
25
  from __future__ import annotations
26
 
27
  from typing import Any
28
 
29
+ BEHAVIORS: frozenset[str] = frozenset(
30
+ {"hunt", "rusher", "patrol", "turtle", "guard"}
31
+ )
32
 
33
 
34
  def read_enemy_bot(enemy: Any) -> str | None:
openra_bench/scenarios/packs/artofwar-decoy-sacrifice.yaml CHANGED
@@ -1,188 +1,151 @@
1
  meta:
2
  id: artofwar-decoy-sacrifice
3
- title: 'Decoy Sacrifice — Spend a Bait Unit to Pull Defenders Off the Objective'
4
  capability: reasoning
5
  real_world_meaning: >
6
- 以利动之 — "move the enemy with the prospect of gain." A small bait
7
- detachment must be deliberately spent to pull a reactive guarding
8
- force off the objective so the main body can reach it. An agent
9
- that keeps every unit safe and pushes head-on is destroyed; the
10
- winning policy accepts an early, local loss for the delayed mission
11
- payoff — long-horizon credit assignment, not greedy unit
12
- preservation.
 
 
 
 
13
  robotics_analogue: >
14
- Sacrificial-decoy planning: expend a cheap agent to divert a
15
- reactive obstacle/defender, accepting negative short-term reward
16
- for a later objective gain.
 
17
  author: openra-bench
 
18
  base_map: rush-hour-arena
 
 
 
 
 
 
 
 
 
19
  base:
20
- agent:
21
- faction: allies
22
- cash: 0
23
- enemy:
24
- faction: soviet
25
- cash: 0
26
- bot_type: ''
27
  actors:
28
- - type: 2tnk # main body — must reach the objective
29
- owner: agent
30
- position: [6, 22]
31
- stance: 3
32
- health: 100
33
- facing: -1
34
- count: 3
35
- - type: jeep # the bait — fast, expendable
36
- owner: agent
37
- position: [6, 30]
38
- stance: 3
39
- health: 100
40
- facing: -1
41
- count: 2
42
- - type: e1 # reactive guards sitting on the objective
43
- owner: enemy
44
- position: [40, 24]
45
- stance: 3
46
- health: 100
47
- facing: -1
48
- count: 5
49
- - type: fact # objective marker — episode can't end on guard-wipe
50
- owner: enemy
51
- position: [44, 24]
52
  levels:
 
 
 
 
53
  easy:
54
  description: >
55
- Reach the objective region with the main body. The guards will
56
- chase whatever they see — bait them aside, then drive through.
 
 
 
 
 
57
  win_condition:
58
  all_of:
59
- - reach_region: {x: 44, y: 24, radius: 4}
60
- - within_ticks: 6000
61
- # Deadline reachable within max_turns (tick ≈ 93+90·(70-1) = 6303);
62
- # after_ticks 6001 makes a staller LOSE instead of drawing. The
63
- # intended bait-then-drive-through win lands far inside 6000.
64
  fail_condition:
65
  any_of:
66
- - after_ticks: 6001
67
- - {not: {own_units_gte: 1}}
68
- max_turns: 70
 
 
 
69
  medium:
70
- description: 'More guards; the head-on route is now clearly fatal.'
 
 
 
71
  overrides:
72
  actors:
73
- - type: 2tnk
74
- owner: agent
75
- position: [6, 22]
76
- stance: 3
77
- health: 100
78
- facing: -1
79
- count: 3
80
- - type: jeep
81
- owner: agent
82
- position: [6, 30]
83
- stance: 3
84
- health: 100
85
- facing: -1
86
- count: 2
87
- - type: e1
88
- owner: enemy
89
- position: [40, 24]
90
- stance: 3
91
- health: 100
92
- facing: -1
93
- count: 7
94
- - type: fact # objective marker (see easy)
95
- owner: enemy
96
- position: [44, 24]
97
  win_condition:
98
  all_of:
99
- - reach_region: {x: 44, y: 24, radius: 4}
100
- - within_ticks: 7000
101
- # Deadline reachable within max_turns (tick ≈ 93+90·(80-1) = 7203);
102
- # after_ticks 7001 makes a staller LOSE instead of drawing. The
103
- # head-on route is fatal here — the decoy maneuver wins well under
104
- # 7000.
105
  fail_condition:
106
  any_of:
107
- - after_ticks: 7001
108
- - {not: {own_units_gte: 1}}
109
- max_turns: 80
 
 
 
 
 
110
  hard:
111
  description: >
112
- The main body must arrive nearly intact (loss cap) — only the
113
- bait may be spent. The force stages from a seed-chosen latitude
114
- (two spawn_point groups, same main+bait formation) so the decoy
115
- line that works can't be memorised; guards sit on the objective
116
- beyond initial sight.
117
  overrides:
118
  actors:
119
  # spawn_point 0 — northern staging
120
- - type: 2tnk
121
- owner: agent
122
- position: [6, 22]
123
- stance: 3
124
- health: 100
125
- facing: -1
126
- count: 3
127
- spawn_point: 0
128
- randomize:
129
- position: {base: [6, 22], offset: 3}
130
- - type: jeep
131
- owner: agent
132
- position: [6, 30]
133
- stance: 3
134
- health: 100
135
- facing: -1
136
- count: 2
137
- spawn_point: 0
138
- # spawn_point 1 — southern staging (same formation, shifted)
139
- # spawn_point 1 — northern staging. WAS y=40/48 which is
140
- # OFF-MAP on rush-hour-arena (MapSize 128x40, playable y≈2..37):
141
- # cell (6,40) indexed past the 128*40 grid and panicked the
142
- # engine. Relocated in-bounds, kept distinct from spawn_point
143
- # 0's mid/south staging so the ≥2-spawn contract still holds.
144
- - type: 2tnk
145
- owner: agent
146
- position: [6, 8]
147
- stance: 3
148
- health: 100
149
- facing: -1
150
- count: 3
151
- spawn_point: 1
152
- randomize:
153
- position: {base: [6, 8], offset: 3}
154
- - type: jeep
155
- owner: agent
156
- position: [6, 14]
157
- stance: 3
158
- health: 100
159
- facing: -1
160
- count: 2
161
- spawn_point: 1
162
- - type: e1
163
- owner: enemy
164
- position: [40, 24]
165
- stance: 3
166
- health: 100
167
- facing: -1
168
- count: 8
169
- - type: fact # objective marker (see easy)
170
- owner: enemy
171
- position: [44, 24]
172
  win_condition:
173
  all_of:
174
- - reach_region: {x: 44, y: 24, radius: 3}
175
- - within_ticks: 8000
176
- - units_lost_lte: 2 # bait only; losing armour fails
177
- # Deadline reachable within max_turns (tick ≈ 93+90·(90-1) = 8103);
178
- # after_ticks 8001 makes a staller LOSE. Exceeding the bait-only
179
- # loss cap (units_lost > 2 ⇒ a tank was spent) also loses
180
- # immediately rather than drawing — this is what makes the
181
- # credit-assignment capability load-bearing: a brute-force push
182
- # that arrives but burned the armour is a LOSS, not a draw.
183
  fail_condition:
184
  any_of:
185
- - after_ticks: 8001
186
- - {not: {units_lost_lte: 2}}
187
- - {not: {own_units_gte: 1}}
188
- max_turns: 90
 
1
  meta:
2
  id: artofwar-decoy-sacrifice
3
+ title: 'Decoy Sacrifice — Divert Hunting Defenders So the Main Body Reaches the Objective'
4
  capability: reasoning
5
  real_world_meaning: >
6
+ 以利动之 — "move the enemy with the prospect of gain." A reactive
7
+ HUNTING force (it actively pursues the nearest of your units) sits
8
+ astride the only corridor to the objective. Push the durable main
9
+ body straight through and the hunters converge and destroy it
10
+ before three of them arrive. The winning policy commits the fast,
11
+ expendable bait on a divergent vector so the hunters lock onto and
12
+ chase IT — clearing the corridor for the main body. This is
13
+ long-horizon credit assignment: a decoy maneuver whose payoff is
14
+ delayed and whose cost (the bait drawing fire / dying) looks
15
+ locally negative; greedy "keep everyone safe, push head-on" and
16
+ "do nothing" both fail.
17
  robotics_analogue: >
18
+ Sacrificial-decoy planning against a pursuing adversary: expend or
19
+ risk a cheap agent to draw a reactive interceptor off the protected
20
+ corridor, accepting negative short-term reward for the delayed
21
+ arrival of the main payload.
22
  author: openra-bench
23
+
24
  base_map: rush-hour-arena
25
+
26
+ # The enemy is a LEASHED GUARD line (engine scripted bot 'guard'):
27
+ # each e3 holds its post and auto-fires in range, lunges at a foe
28
+ # within an aggro radius, and SNAPS BACK past a leash. Drive the
29
+ # column straight through and the whole line engages it (anti-tank
30
+ # rockets) — it dies before three arrive. Send the fast jeeps in as a
31
+ # decoy against one segment: those guards lunge off post after the
32
+ # bait, opening a transient gap the main body sprints through before
33
+ # they snap back. Greedy head-on / do-nothing both fail.
34
  base:
35
+ agent: {faction: allies, cash: 0}
36
+ enemy: {faction: soviet, cash: 0, bot_type: guard}
37
+ tools: [move_units, attack_unit, stop]
38
+ planning: true
39
+ termination: {max_ticks: 12000}
 
 
40
  actors:
41
+ # Main body — durable but slow; THREE must reach the far objective.
42
+ - {type: 2tnk, owner: agent, position: [6, 20], stance: 1, count: 3}
43
+ # Bait — fast (kites the foot hunters), expendable. The decoy.
44
+ - {type: jeep, owner: agent, position: [6, 24], stance: 1, count: 2}
45
+ # Hunting picket astride the direct line spawn(6,20)→objective.
46
+ # e3 = anti-tank rockets: lethal to an unsupported tank column.
47
+ - {type: e3, owner: enemy, position: [60, 6], stance: 2}
48
+ - {type: e3, owner: enemy, position: [60, 11], stance: 2}
49
+ - {type: e3, owner: enemy, position: [60, 16], stance: 2}
50
+ - {type: e3, owner: enemy, position: [60, 21], stance: 2}
51
+ - {type: e3, owner: enemy, position: [60, 26], stance: 2}
52
+ - {type: e3, owner: enemy, position: [60, 31], stance: 2}
53
+ - {type: e3, owner: enemy, position: [60, 36], stance: 2}
54
+ - {type: e1, owner: enemy, position: [60, 20], stance: 2, count: 4}
55
+ # Objective marker (far east). Unarmed, high-HP: keeps the episode
56
+ # alive (no enemy-elimination auto-terminal) and is the landmark.
57
+ - {type: fact, owner: enemy, position: [112, 20]}
58
+
 
 
 
 
 
 
59
  levels:
60
+ # ── EASY ── the bare decoy skill: a modest hunting picket. A naive
61
+ # head-on column loses too many tanks to land 3 at the objective;
62
+ # leading the hunters off with the jeeps clears the way. Generous
63
+ # clock.
64
  easy:
65
  description: >
66
+ A hunting picket (rocket infantry + riflemen) blocks the lane to
67
+ the objective and chases your nearest unit. Get THREE tanks to
68
+ the objective (far east). Driving the column straight in gets it
69
+ shot apart before three arrive; send the fast jeeps in as bait on
70
+ a separate vector to pull the hunters off, then run the tanks
71
+ through. Idling or never committing loses on the clock.
72
+ overrides: {}
73
  win_condition:
74
  all_of:
75
+ - {units_in_region_gte: {x: 112, y: 20, radius: 6, n: 3}}
76
+ - {within_ticks: 5800}
 
 
 
77
  fail_condition:
78
  any_of:
79
+ - {after_ticks: 5801}
80
+ - {not: {own_units_gte: 1}}
81
+ max_turns: 66
82
+
83
+ # ── MEDIUM ── +1 controlled variable: a heavier anti-armor picket
84
+ # (more e3) so a head-on push is decisively fatal; tighter clock.
85
  medium:
86
+ description: >
87
+ Heavier rocket-infantry picket — a head-on push is now decisively
88
+ fatal. Same task: divert the hunters with the bait, land THREE
89
+ tanks on the objective before the (tighter) deadline.
90
  overrides:
91
  actors:
92
+ - {type: 2tnk, owner: agent, position: [6, 20], stance: 1, count: 3}
93
+ - {type: jeep, owner: agent, position: [6, 24], stance: 1, count: 2}
94
+ - {type: e3, owner: enemy, position: [60, 6], stance: 2}
95
+ - {type: e3, owner: enemy, position: [60, 11], stance: 2}
96
+ - {type: e3, owner: enemy, position: [60, 16], stance: 2}
97
+ - {type: e3, owner: enemy, position: [60, 21], stance: 2}
98
+ - {type: e3, owner: enemy, position: [60, 26], stance: 2}
99
+ - {type: e3, owner: enemy, position: [60, 31], stance: 2}
100
+ - {type: e3, owner: enemy, position: [60, 36], stance: 2}
101
+ - {type: e1, owner: enemy, position: [60, 20], stance: 2, count: 5}
102
+ - {type: fact, owner: enemy, position: [112, 20]}
 
 
 
 
 
 
 
 
 
 
 
 
 
103
  win_condition:
104
  all_of:
105
+ - {units_in_region_gte: {x: 112, y: 20, radius: 6, n: 3}}
106
+ - {within_ticks: 6600}
 
 
 
 
107
  fail_condition:
108
  any_of:
109
+ - {after_ticks: 6601}
110
+ - {not: {own_units_gte: 1}}
111
+ max_turns: 76
112
+
113
+ # ── HARD ── +1 controlled variable: seed-chosen staging latitude
114
+ # (two spawn_point groups, in-bounds) so the working decoy line can't
115
+ # be memorised; strongest picket; strict attrition cap on top (the
116
+ # main body must arrive nearly whole — only the bait may be spent).
117
  hard:
118
  description: >
119
+ Strongest hunting picket; the force stages from a seed-chosen
120
+ latitude (the decoy line that works varies by seed, so it can't
121
+ be memorised). Land THREE tanks on the objective AND lose ≤2
122
+ units (only the two bait jeeps may be spent — burning armour
123
+ fails) before the tight deadline.
124
  overrides:
125
  actors:
126
  # spawn_point 0 — northern staging
127
+ - {type: 2tnk, owner: agent, position: [6, 10], stance: 1, count: 3, spawn_point: 0}
128
+ - {type: jeep, owner: agent, position: [6, 14], stance: 1, count: 2, spawn_point: 0}
129
+ # spawn_point 1 — southern staging (same formation, in-bounds)
130
+ - {type: 2tnk, owner: agent, position: [6, 30], stance: 1, count: 3, spawn_point: 1}
131
+ - {type: jeep, owner: agent, position: [6, 34], stance: 1, count: 2, spawn_point: 1}
132
+ - {type: e3, owner: enemy, position: [60, 6], stance: 2}
133
+ - {type: e3, owner: enemy, position: [60, 11], stance: 2}
134
+ - {type: e3, owner: enemy, position: [60, 16], stance: 2}
135
+ - {type: e3, owner: enemy, position: [60, 21], stance: 2}
136
+ - {type: e3, owner: enemy, position: [60, 26], stance: 2}
137
+ - {type: e3, owner: enemy, position: [60, 31], stance: 2}
138
+ - {type: e3, owner: enemy, position: [60, 36], stance: 2}
139
+ - {type: e1, owner: enemy, position: [60, 20], stance: 2, count: 6}
140
+ - {type: fact, owner: enemy, position: [112, 20]}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
141
  win_condition:
142
  all_of:
143
+ - {units_in_region_gte: {x: 112, y: 20, radius: 6, n: 3}}
144
+ - {within_ticks: 7400}
145
+ - {units_lost_lte: 2}
 
 
 
 
 
 
146
  fail_condition:
147
  any_of:
148
+ - {after_ticks: 7401}
149
+ - {not: {units_lost_lte: 2}}
150
+ - {not: {own_units_gte: 1}}
151
+ max_turns: 84
tests/test_artofwar.py CHANGED
@@ -62,9 +62,11 @@ def test_indirect_hard_requires_zero_loss_traversal():
62
  def test_decoy_hard_loss_cap_allows_bait_not_army():
63
  c = compile_level(load_pack(PACKS / "artofwar-decoy-sacrifice.yaml"),
64
  "hard")
65
- at_obj = [(44, 24)] * 3
66
- assert evaluate(c.win_condition, _ctx(at_obj, 8000, lost=2)) is True
67
- assert evaluate(c.win_condition, _ctx(at_obj, 8000, lost=3)) is False
 
 
68
 
69
 
70
  @pytest.mark.parametrize("pid", FAMILY)
 
62
  def test_decoy_hard_loss_cap_allows_bait_not_army():
63
  c = compile_level(load_pack(PACKS / "artofwar-decoy-sacrifice.yaml"),
64
  "hard")
65
+ # Redesigned: far-east objective (112,20); hard caps losses at 2
66
+ # (only the two bait jeeps may be spent — burning a tank fails).
67
+ at_obj = [(112, 20)] * 3
68
+ assert evaluate(c.win_condition, _ctx(at_obj, 5000, lost=2)) is True
69
+ assert evaluate(c.win_condition, _ctx(at_obj, 5000, lost=3)) is False
70
 
71
 
72
  @pytest.mark.parametrize("pid", FAMILY)
tests/test_botgen.py CHANGED
@@ -15,7 +15,7 @@ from openra_bench.scenarios.schema import ScenarioPack
15
 
16
 
17
  def test_behaviour_catalogue():
18
- assert BEHAVIORS == {"hunt", "rusher", "patrol", "turtle"}
19
 
20
 
21
  def test_read_enemy_bot_field_and_alias():
 
15
 
16
 
17
  def test_behaviour_catalogue():
18
+ assert BEHAVIORS == {"hunt", "rusher", "patrol", "turtle", "guard"}
19
 
20
 
21
  def test_read_enemy_bot_field_and_alias():