yxc20098 commited on
Commit
c6741a5
ยท
1 Parent(s): aa71fc7

econ-harvester-defense-raid: per-scenario chokepoint map

Browse files

Replaces the generic rush-hour-arena base_map with a tailored
96x40 chokepoint-arena that funnels every raider through ONE
4-cell corridor at y=18..21. The water wall at x=46..54 splits
the map into a WEST lobe (agent base + harv + OFF-AXIS ore patch)
and an EAST lobe (raider spawn + persistent far-east enemy fact).

The single corridor mouth at (~44..46, 18..21) becomes the
canonical defensive position the model must read from the map
geometry. The off-axis patch keeps the harv commute strictly
inside the west lobe so the harv never touches the corridor,
which de-couples 'read the raid vector' from 'follow the harv'
and makes set_stance + position-the-escort-on-the-corridor-mouth
the load-bearing capability.

Hard tier keeps its two seed-driven AGENT spawn_point groups
(NORTH y=8 / SOUTH y=32) so the BASE moves but the corridor
mouth defensive position stays invariant.

Validation (scripted policies via openra_bench.eval_core.run_level):
stall (only observe) โ€” LOSS every tier, seeds 1..2:
easy seed=1 loss / easy seed=2 loss
medium seed=1 loss / medium seed=2 loss
hard seed=1 loss / hard seed=2 loss
In every case the harv is killed (units_lost=1), confirming
the corridor + raider geometry still produces a real harv-death
LOSS rather than a draw.

Map spec (deterministic, content-addressed):
generator: chokepoint-arena, name: econ-harvester-defense-raid-arena
width: 96, height: 40, cordon: 2
pinch_x: 50, pinch_width: 8, corridor_width: 4, corridor_y: 20

data/maps/econ-harvester-defense-raid-arena.oramap ADDED
Binary file (876 Bytes). View file
 
openra_bench/scenarios/packs/econ-harvester-defense-raid.yaml CHANGED
@@ -11,14 +11,39 @@
11
  # The load-bearing verb is `set_stance(escort, 2 or 3)` + escort
12
  # movement onto the harv's lane.
13
  #
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
  # DESIGN
15
  # ------
16
  # Agent has fact + powr + proc + 1ร— harv + ONE NEAR ore patch (mine
17
- # at x=25). The proc seeds the harv's auto-harvest loop (per
18
- # world.rs::auto_route_idle_harvesters โ€” a scenario-placed idle harv
19
- # auto-routes once its owner owns a refinery), so the harv cycles
20
- # proc(12,20) โ†” mine(25,20) WITHOUT an explicit harvest order. Income
21
- # is steady AS LONG AS THE HARV LIVES.
22
  #
23
  # Threat: enemy `bot_type: raider` (openra-sim/src/scripted_bot.rs:
24
  # ScriptedBehavior::Raider) โ€” its tick() picks the agent's harv
@@ -26,55 +51,59 @@
26
  # one every 12 ticks (the raider interval). New units added to the
27
  # raider's roster (via scheduled_events::spawn_actors) are picked up
28
  # on the next own_mobile() scan, so SCHEDULED raid waves all chase
29
- # the harv.
 
 
30
  #
31
  # Wave schedule (scheduled_events: spawn_actors):
32
- # easy: 1 wave โ€” 1ร— 2tnk at tick 600 from (40,20)
33
- # medium: 2 waves โ€” 1ร— 2tnk at tick 600, 1ร— 2tnk at tick 1500 โ€” both at (40,20)
34
- # hard: 3 waves on 2 seed-driven axes โ€” see hard tier comment
35
  #
36
  # Escort: 2ร— 2tnk at base, stance:0 (HoldFire). The escort SITS at
37
  # the proc; even when a raider walks into its weapon range the
38
  # stance:0 units do not fire (verified per CLAUDE.md stance fix:
39
  # stance:0 never auto-engages, even when shot at, even when an enemy
40
  # is sitting on top of them). The agent must EITHER:
41
- # (a) `set_stance(escort, 3)` โ€” Hunt: idle escort actively advances
42
- # on visible enemies (raider walks into LOS as it approaches
43
- # the harv's lane, escort engages); OR
44
- # (b) `set_stance(escort, 2)` AND `attack_move(escort, raider lane)`
45
- # โ€” Defend stance auto-fires on whatever enters weapon range,
46
- # so move the escort east along the harv's commute axis to
47
- # INTERCEPT the raid wave; OR
 
48
  # (c) `attack_unit(escort, raider_id)` โ€” explicit order overrides
49
  # stance:0, escort closes and kills the raider.
50
  # Any of (a), (b), (c) clears the bar. The capability is "flip the
51
- # escort from passive to active" โ€” same verb as combat-tanya-vs-rush.
 
52
  #
53
  # THE NO-CHEAT BAR (every lazy / stall / wrong-shape policy LOSES on
54
  # every level and every hard seed; the intended capability WINS):
55
  #
56
  # โ€ข stall (only observe): escort stays stance:0 at base, never
57
- # engages. Raider wave 1 reaches the harv around tick ~750
58
- # (40โ†’16 โ‰ˆ 24 cells at 2tnk speed) and kills it. unit_type_count_gte
59
- # :{harv,1} fails โ†’ fail_condition fires โ†’ LOSS.
60
  #
61
  # โ€ข defensive-but-passive (place / re-issue HoldFire on escort,
62
  # observe): identical outcome โ€” the escort is structurally
63
  # incapable of engaging. Harv dies โ†’ LOSS.
64
  #
65
- # โ€ข aggressive-but-no-escort (send escort east to the raider's
66
- # spawn point at x=40, leaving harv alone on the lane between
67
- # proc(12,20) and mine(25,20)): the escort marches AROUND or
68
- # PAST the next raid wave, the wave reaches the undefended harv,
69
- # harv dies โ†’ LOSS. (On easy with one wave the escort might
70
- # catch the lone raider, but on medium/hard, the second/third
71
- # wave finds the harv unprotected because the escort overshot.)
72
  #
73
  # โ€ข intended (set_stance(escort, 2 or 3) and/or attack_move escort
74
- # onto the harv's lane ~x=20..25): raider walks into the escort's
75
- # fire envelope on its approach, escort kills the raid wave
76
- # before contact with the harv, harv survives, auto-route loop
77
- # continues, cash bar clears โ†’ WIN.
 
78
  #
79
  # ENGINE FACTS (CLAUDE.md):
80
  # - Tick budget ~90/turn. easy max_turns=35 โ‡’ ceiling 93+90ยท34=3153;
@@ -97,21 +126,24 @@
97
  # intended path; the load-bearing verb is the stance flip OR
98
  # an explicit attack order (which overrides stance per the
99
  # CLAUDE.md stance fix).
100
- # - Persistent far enemy `fact` at (120,20) โ€” anti-DRAW per
101
- # CLAUDE.md (engine auto-`done`s on enemy-elim before win/fail
102
- # evaluates; the inert far marker keeps the episode alive past
103
- # the raider being destroyed).
 
 
 
104
  # - `unit_type_count_gte: {harv, 1}` in fail_condition is the
105
  # hard harv-survival predicate โ€” the moment the harv dies the
106
  # fail clause fires and the LOSS is immediate.
107
- # - hard tier uses TWO seed-driven SCHEDULED_EVENTS spawn AXES
108
- # (raid approaches from the north vs the south of the harv's
109
- # lane). The `spawn_actors` event itself doesn't honour
110
- # spawn_point, so per-seed axis variation is done by giving
111
- # each seed group a distinct AGENT spawn_point โ€” the active
112
- # base latitude (y=14 vs y=26) determines which raid lane is
113
- # "the harv's lane". This satisfies the โ‰ฅ2-distinct-seed-driven-
114
- # spawn-groups hard-tier requirement (CLAUDE.md ยง7).
115
  #
116
  # Validate (no model / no network):
117
  # python3 -m openra_bench.scenarios.validate \
@@ -131,25 +163,35 @@ meta:
131
  decision is to FLIP the escort from passive to active โ€” set its
132
  stance to defend / attack-anything, or order it onto the
133
  harvester's lane to intercept โ€” so the raider dies on the
134
- escort's fire envelope before reaching the harvester. Stalling
135
- or leaving the escort on hold-fire lets every wave through; the
136
- harvester dies, income stops, the cash bar is unmet.
 
 
 
137
  robotics_analogue: >
138
  Mining-fleet protection under recurring asset-targeted attack โ€”
139
  an autonomous extraction site with an extractor robot, a
140
  processing depot, and a static defense detail. If the defense
141
  detail is in safe-mode (hold fire) the recurring attacker
142
  destroys the extractor on the first cycle; the operator must
143
- flip the detail to engage and (optionally) reposition it onto
144
- the extractor's commute lane.
145
  benchmark_anchor:
146
  - "SC2 harvester harass defense (Lambo, Reaper drops on workers)"
147
  - "convoy / extractor protection under recurring harass"
148
  - "RTS stance management โ€” flip escort from passive to active"
149
  - "SOC active-vs-passive monitoring posture"
 
150
  author: openra-bench
151
 
152
- base_map: rush-hour-arena
 
 
 
 
 
 
153
  starting_cash: 0 # ALL income via harv survival; bar measures live throughput.
154
 
155
  base:
@@ -175,73 +217,91 @@ base:
175
 
176
  levels:
177
  # โ”€โ”€ EASY โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
178
- # Bare skill: ONE raid wave (1ร— 2tnk) at tick 600. Harv cycles
179
- # proc(12,20)โ†”mine(25,20) on auto-route. Escort: 2ร— 2tnk at base
180
- # stance:0. Stall / passive: raider reaches harv ~tick 750, harv
181
- # dies, LOSS. Intended: set_stance(escort, 2 or 3) and/or
182
- # attack_move escort east to ~x=20, escort engages the raider on
183
- # approach, harv survives, cash accumulates from auto-harvest.
184
- # Bar: cash >= 800 (~2 refines ร— ~400cr) before tick 3000.
 
 
185
  # max_turns 35 โ‡’ ceiling 93+90ยท34=3153 > 3001 โœ“.
186
  easy:
187
  description: >
188
- You own a Construction Yard, Power Plant, Refinery, ONE Ore
189
- Truck on auto-harvest to the near patch at (25,20), and TWO
190
- medium tanks (2tnk) parked at your refinery on HOLD-FIRE
191
- stance. At tick 600 an enemy raider tank spawns at (40,20)
192
- and drives STRAIGHT at your harvester (the raider bot targets
193
- harvesters specifically). Win when cash >= 800 AND your
194
- harvester is still alive AND your refinery is intact, before
195
- tick 3000. Standing the escort idle (HOLD-FIRE) lets the
196
- raider walk through and kill the harvester. The intended play
197
- is to FLIP the escort's stance to Defend (2) or AttackAnything
198
- (3), OR explicitly attack-order it onto the raider's approach
199
- lane.
 
 
 
200
  starting_cash: 0
201
  overrides:
 
 
 
 
 
 
 
 
 
 
202
  actors:
203
- # Agent base โ€” fact + powr seed the construction & power;
204
- # proc seeds the harv auto-harvest loop.
205
- - {type: fact, owner: agent, position: [10, 20]}
206
- - {type: powr, owner: agent, position: [10, 18]}
207
- - {type: proc, owner: agent, position: [12, 20]}
208
  # Single harv โ€” pre-placed adjacent to proc. With proc on the
209
  # field, world.rs::auto_route_idle_harvesters installs a
210
  # Harvest activity on the next tick; harv cycles proc<->mine.
211
- - {type: harv, owner: agent, position: [14, 20]}
212
  # Escort: 2ร— 2tnk at the proc on stance:0 (HoldFire). The
213
- # capability under test is "flip the escort active" โ€” stance:0
214
- # never auto-engages, so a "place defenders and observe"
215
- # policy LOSES.
216
- - {type: 2tnk, owner: agent, position: [13, 18], stance: 0}
217
- - {type: 2tnk, owner: agent, position: [13, 22], stance: 0}
218
- # Near ore patch โ€” the harv's auto-route target.
219
- - {type: mine, owner: neutral, position: [25, 20]}
220
- # Persistent far enemy fact โ€” anti-DRAW (engine auto-done
221
- # mitigation; without this the run collapses to DRAW the
222
- # instant the raid wave is destroyed, before the cash bar
223
- # evaluates).
224
- - {type: fact, owner: enemy, position: [120, 20]}
 
 
 
 
 
225
  scheduled_events:
226
- # Single raid wave at tick 600 โ€” one 2tnk spawned east of
227
- # the patch. Under bot_type:raider the new tank joins the
228
- # raider's own_mobile() roster on the next tick and beelines
229
- # toward the harv via harv_foes().
230
  - tick: 600
231
  type: spawn_actors
232
  actors:
233
- - {type: 2tnk, owner: enemy, position: [40, 20]}
234
  win_condition:
235
  # Ordered `then:` enforces "harv must SURVIVE past the raid
236
  # window and THEN keep refining to the bar" โ€” clause 1
237
  # (after_ticks: 1700) latches only past wave 1's typical
238
- # kill-window (raider spawns tick 600, reaches harv ~tick 900,
239
- # kills undefended harv by ~tick 1500). A stall run loses its
240
- # harv around tick ~1500 so by the time clause 1 latches the
241
- # `unit_type_count_gte:{harv,1}` clause has already failed โ†’
242
- # fail_condition fires first. The intended escort engagement
243
- # kills the raider, harv keeps cycling, cash crosses 1200
244
- # well after tick 1700, all_of latches โ†’ WIN.
245
  all_of:
246
  - then:
247
  id: econ-harvester-defense-raid-easy
@@ -260,52 +320,64 @@ levels:
260
 
261
  # โ”€โ”€ MEDIUM โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
262
  # +1 controlled variable: TWO raid waves (tick 600, tick 1500) and
263
- # a tighter bar (cash >= 1600 โ‰ˆ 4 refines). A one-time "kill the
264
- # first raider" play has to ALSO survive the second wave โ€” the
265
- # escort must be RETURNED to a covering position (or kept on
266
- # AttackAnything stance:3) so the late wave dies on approach too.
 
267
  # max_turns 45 โ‡’ ceiling 4053 > 3901 โœ“.
268
  medium:
269
  description: >
270
- Same setup as easy โ€” fact + powr + proc + 1ร— harv on auto-harvest to the near patch at (25,20), 2ร— 2tnk escort at the
271
- refinery on HOLD-FIRE โ€” but TWO raider waves arrive: one at
272
- tick 600 and a second at tick 1500. Both spawn at (40,20) and
273
- drive at your harvester. Win when cash >= 1600 AND your
274
- harvester is alive AND your refinery is intact, before tick
275
- 3900. Stalling, leaving the escort on hold-fire, and chasing
276
- the first raider far enough east to miss the second wave all
277
- LOSE. The intended play is to flip the escort active (stance
278
- Defend or AttackAnything) and position it on the harvester's
279
- lane so BOTH waves die on approach.
 
 
280
  starting_cash: 0
281
  overrides:
 
 
 
 
 
 
 
 
 
 
282
  actors:
283
- - {type: fact, owner: agent, position: [10, 20]}
284
- - {type: powr, owner: agent, position: [10, 18]}
285
- - {type: proc, owner: agent, position: [12, 20]}
286
- - {type: harv, owner: agent, position: [14, 20]}
287
- - {type: 2tnk, owner: agent, position: [13, 18], stance: 0}
288
- - {type: 2tnk, owner: agent, position: [13, 22], stance: 0}
289
- - {type: mine, owner: neutral, position: [25, 20]}
290
- - {type: fact, owner: enemy, position: [120, 20]}
291
  scheduled_events:
292
  - tick: 600
293
  type: spawn_actors
294
  actors:
295
- - {type: 2tnk, owner: enemy, position: [40, 20]}
296
  - tick: 1500
297
  type: spawn_actors
298
  actors:
299
- - {type: 2tnk, owner: enemy, position: [40, 20]}
300
  win_condition:
301
- # `then:` forces cash bar to be reached AFTER tick 2700 โ€”
302
- # past the second raid wave's kill-window (wave 2 spawns at
303
- # tick 1500, reaches harv ~tick 1800, kills undefended harv
304
- # by ~tick 2400). Aggressive-no-escort (drive escort east
305
- # past patch) lets wave 1 chip the harv heavily AND wave 2
306
- # finish it; the harv dies before clause 1 latches โ†’ fail
307
- # clause unit_type_count_gte:{harv,1} fires โ†’ LOSS even
308
- # though enough cash may have accumulated by ~tick 2200.
309
  all_of:
310
  - then:
311
  id: econ-harvester-defense-raid-medium
@@ -324,103 +396,122 @@ levels:
324
 
325
  # โ”€โ”€ HARD โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
326
  # +1 axis vs medium: TWO seed-driven AGENT spawn_point groups โ€”
327
- # base latitude NORTH y=14 vs SOUTH y=26, round-robined per seed.
328
  # Each group has its own fact/powr/proc/harv/escort/mine on its
329
- # own y-band. The agent's load-bearing decision is the same
330
- # (flip + position escort) but the LATITUDE varies per seed, so
331
- # a memorised y=20 opening fails on both hard seed groups.
 
 
 
332
  #
333
  # Hard tier โ‰ฅ2-spawn-groups requirement (CLAUDE.md ยง7): satisfied
334
  # via two AGENT spawn_point groups (spawn_point: 0 / 1) โ€” every
335
  # agent actor is duplicated across both groups; the active group
336
  # is round-robined by seed.
337
  #
338
- # Three raid waves (ticks 600, 1500, 2400) spawn ON A CENTRAL
339
- # AXIS at y=20 (between the two possible latitudes โ€” like
340
- # econ-protect-harvester-route's hard tier). The raider bot
341
- # targets the agent's on-latitude harv, so each raider must
342
- # detour to y=14 or y=26 (a 5-6 cell extra leg) on top of its
343
- # west-bound traversal. The detour gives the escort time to
344
- # intercept the raider on the agent's own y-band. The escort
345
- # MUST be moved onto the agent's latitude โ€” a memorised "send
346
- # escort east on y=20" opening drops them onto a lane the raider
347
- # only briefly crosses, while the harv on y=14/26 is unprotected
348
- # for the rest of the raider's approach.
349
  # max_turns 50 โ‡’ ceiling 4503 > 4301 โœ“.
350
  hard:
351
  description: >
352
- Tougher version: your base latitude varies per seed โ€” NORTH
353
- base (y=14) OR SOUTH base (y=26). Each base has its own fact,
354
- powr, proc, harvester on auto-harvest to its OWN near patch
355
- (mine at (25,14) for NORTH; mine at (25,26) for SOUTH), and
356
- 2ร— 2tnk escort at the refinery on HOLD-FIRE. THREE raider
357
- heavy tanks arrive at ticks 600, 1500, and 2400 from the
358
- central east corridor at (40,20); each one detours north or
359
- south to your harvester's latitude. Win when cash >= 2800
360
- AND your harvester is alive AND your refinery is intact,
361
- before tick 4300. Stalling, leaving escort on hold-fire,
362
- chasing past the lane, and memorising the wrong latitude all
363
- LOSE. Flip the escort to Defend / AttackAnything (or attack-order it onto your harvester's lane) so all three waves die
364
- on approach.
 
 
365
  starting_cash: 0
366
  overrides:
 
 
 
 
 
 
 
 
 
 
367
  actors:
368
- # spawn_point 0 โ€” NORTH base (y=14). CLAUDE.md: agent actors
369
  # must be duplicated across both spawn_point groups (the
370
  # filter drops any group-untagged actor on either side).
371
- - {type: fact, owner: agent, position: [10, 14], spawn_point: 0}
372
- - {type: powr, owner: agent, position: [8, 12], spawn_point: 0}
373
- - {type: proc, owner: agent, position: [12, 14], spawn_point: 0}
374
- - {type: harv, owner: agent, position: [14, 14], spawn_point: 0}
375
  # Escort placed on open ground east of the proc so attack_move
376
- # has a clear east lane (no powr or building footprint to
377
- # path around).
378
- - {type: 2tnk, owner: agent, position: [15, 13], stance: 0, spawn_point: 0}
379
- - {type: 2tnk, owner: agent, position: [15, 15], stance: 0, spawn_point: 0}
380
- # spawn_point 1 โ€” SOUTH base (y=26). Symmetric.
381
- - {type: fact, owner: agent, position: [10, 26], spawn_point: 1}
382
- - {type: powr, owner: agent, position: [8, 28], spawn_point: 1}
383
- - {type: proc, owner: agent, position: [12, 26], spawn_point: 1}
384
- - {type: harv, owner: agent, position: [14, 26], spawn_point: 1}
385
- - {type: 2tnk, owner: agent, position: [15, 25], stance: 0, spawn_point: 1}
386
- - {type: 2tnk, owner: agent, position: [15, 27], stance: 0, spawn_point: 1}
387
- # Two ore patches โ€” one per latitude. Mines are neutral, so
388
- # the spawn_point filter (agent-side) does not affect them;
389
- # both place every seed. The active harv only uses ITS OWN
390
- # patch (auto-route picks the nearest ore the owner can
391
- # reach, which on each seed is the on-latitude one).
392
- - {type: mine, owner: neutral, position: [25, 14]}
393
- - {type: mine, owner: neutral, position: [25, 26]}
394
- # Persistent far enemy fact โ€” anti-DRAW.
395
- - {type: fact, owner: enemy, position: [120, 20]}
 
396
  scheduled_events:
397
- # Three raid waves spawn on a CENTRAL corridor at y=20 (per
398
- # econ-protect-harvester-route's hard idiom). Each raider is
399
- # bot-controlled and detours 6 cells north/south to reach the
400
- # on-latitude harv โ€” the detour gives the escort time to
401
- # intercept on the agent's own y-band. The escort MUST land
402
- # on the agent's latitude; memorising the y=20 corridor
403
- # leaves the harv unprotected for the bulk of the raider's
404
- # approach lane.
 
 
 
405
  - tick: 600
406
  type: spawn_actors
407
  actors:
408
- - {type: 2tnk, owner: enemy, position: [40, 20]}
409
  - tick: 1500
410
  type: spawn_actors
411
  actors:
412
- - {type: 2tnk, owner: enemy, position: [40, 20]}
413
  - tick: 2400
414
  type: spawn_actors
415
  actors:
416
- - {type: 2tnk, owner: enemy, position: [40, 20]}
417
  win_condition:
418
  # `then:` forces cash bar AFTER tick 3500 โ€” past wave 3
419
- # (spawns tick 2400, reaches harv ~tick 2700, kills
420
- # undefended harv by ~tick 3300). Stall / passive / off-axis
421
  # all lose their harv before tick 3500 โ†’ unit_type_count_gte
422
- # fail clause fires โ†’ LOSS. Intended (escort on harv's
423
- # latitude) kills each wave on approach; harv cycles
424
  # uninterrupted through all three waves and accumulates
425
  # cash past tick 3500.
426
  all_of:
 
11
  # The load-bearing verb is `set_stance(escort, 2 or 3)` + escort
12
  # movement onto the harv's lane.
13
  #
14
+ # MAP โ€” econ-harvester-defense-raid-arena (96ร—40 chokepoint-arena)
15
+ # ----------------------------------------------------------------
16
+ # Per-scenario map (replaces the generic rush-hour-arena). A single
17
+ # vertical water wall at x=46..54 with one open corridor at y=18..21
18
+ # splits the playable area into a WEST lobe (the agent's base + the
19
+ # harv + the off-axis ore patch) and an EAST lobe (the raider spawn
20
+ # axis + the persistent far-east enemy fact). The corridor at
21
+ # y=18..21 is the ONE lane any raid wave must use to cross from the
22
+ # east into the west lobe โ€” there is no other route around the wall.
23
+ #
24
+ # The ore patch sits OFF-AXIS from the agent base (easy/medium: base
25
+ # at y=10..14, patch at y=28; hard NORTH: base at y=8, patch at
26
+ # y=12; hard SOUTH: base at y=32, patch at y=28). Off-axis means the
27
+ # harv commutes between proc and mine on a y-band that is NOT the
28
+ # raid corridor's y-band โ€” so the agent can read the raid vector
29
+ # (the single corridor mouth at the west wall, xโ‰ˆ44..46, y=18..21)
30
+ # INDEPENDENTLY of where its own harv currently is, and plant the
31
+ # defense on that one lane.
32
+ #
33
+ # This is what the scenario MEASURES: reading the raid vector from
34
+ # the map geometry (one corridor, one mouth) and positioning the
35
+ # escort there โ€” not spamming defense everywhere or following the
36
+ # harv around. set_stance + escort movement onto the corridor mouth
37
+ # is the load-bearing capability the chokepoint exposes.
38
+ #
39
  # DESIGN
40
  # ------
41
  # Agent has fact + powr + proc + 1ร— harv + ONE NEAR ore patch (mine
42
+ # OFF-AXIS from the base). The proc seeds the harv's auto-harvest
43
+ # loop (per world.rs::auto_route_idle_harvesters โ€” a scenario-placed
44
+ # idle harv auto-routes once its owner owns a refinery), so the harv
45
+ # cycles proc โ†” mine WITHOUT an explicit harvest order. Income is
46
+ # steady AS LONG AS THE HARV LIVES.
47
  #
48
  # Threat: enemy `bot_type: raider` (openra-sim/src/scripted_bot.rs:
49
  # ScriptedBehavior::Raider) โ€” its tick() picks the agent's harv
 
51
  # one every 12 ticks (the raider interval). New units added to the
52
  # raider's roster (via scheduled_events::spawn_actors) are picked up
53
  # on the next own_mobile() scan, so SCHEDULED raid waves all chase
54
+ # the harv. Because the wall blocks every other eastโ†’west route,
55
+ # each spawned raider MUST enter the west lobe through the single
56
+ # corridor at (~50, 18..21).
57
  #
58
  # Wave schedule (scheduled_events: spawn_actors):
59
+ # easy: 1 wave โ€” 1ร— 2tnk at tick 600 from (75,20)
60
+ # medium: 2 waves โ€” 1ร— 2tnk at tick 600, 1ร— 2tnk at tick 1500 โ€” both at (75,20)
61
+ # hard: 3 waves on the central east corridor โ€” see hard tier comment
62
  #
63
  # Escort: 2ร— 2tnk at base, stance:0 (HoldFire). The escort SITS at
64
  # the proc; even when a raider walks into its weapon range the
65
  # stance:0 units do not fire (verified per CLAUDE.md stance fix:
66
  # stance:0 never auto-engages, even when shot at, even when an enemy
67
  # is sitting on top of them). The agent must EITHER:
68
+ # (a) `set_stance(escort, 3)` and move them onto the corridor
69
+ # mouth โ€” Hunt: idle escort actively advances on visible
70
+ # enemies (raider walks into LOS as it exits the corridor,
71
+ # escort engages); OR
72
+ # (b) `set_stance(escort, 2)` AND `attack_move(escort, corridor
73
+ # mouth)` โ€” Defend stance auto-fires on whatever enters weapon
74
+ # range, so move the escort EAST onto the west mouth of the
75
+ # corridor (xโ‰ˆ44..46, y=18..21) to INTERCEPT the raid wave; OR
76
  # (c) `attack_unit(escort, raider_id)` โ€” explicit order overrides
77
  # stance:0, escort closes and kills the raider.
78
  # Any of (a), (b), (c) clears the bar. The capability is "flip the
79
+ # escort from passive to active AND plant it on the corridor mouth"
80
+ # โ€” same verb as combat-tanya-vs-rush plus a positioning read.
81
  #
82
  # THE NO-CHEAT BAR (every lazy / stall / wrong-shape policy LOSES on
83
  # every level and every hard seed; the intended capability WINS):
84
  #
85
  # โ€ข stall (only observe): escort stays stance:0 at base, never
86
+ # engages. Raider wave 1 traverses the corridor, detours to the
87
+ # active harv latitude and kills it. unit_type_count_gte:
88
+ # {harv,1} fails โ†’ fail_condition fires โ†’ LOSS.
89
  #
90
  # โ€ข defensive-but-passive (place / re-issue HoldFire on escort,
91
  # observe): identical outcome โ€” the escort is structurally
92
  # incapable of engaging. Harv dies โ†’ LOSS.
93
  #
94
+ # โ€ข spam-defense-everywhere (escort split across the base
95
+ # perimeter, neither piece on the corridor mouth): the raider
96
+ # walks straight through the corridor on the same lane every
97
+ # wave; whichever escort piece is on the wrong band never
98
+ # contributes. On medium/hard the second/third wave finishes
99
+ # the harv โ†’ LOSS.
 
100
  #
101
  # โ€ข intended (set_stance(escort, 2 or 3) and/or attack_move escort
102
+ # onto the corridor mouth ~x=44..46, y=18..21): the wall geometry
103
+ # forces every raider through the escort's fire envelope on its
104
+ # approach, escort kills each raid wave before contact with the
105
+ # harv, harv survives, auto-route loop continues, cash bar
106
+ # clears โ†’ WIN.
107
  #
108
  # ENGINE FACTS (CLAUDE.md):
109
  # - Tick budget ~90/turn. easy max_turns=35 โ‡’ ceiling 93+90ยท34=3153;
 
126
  # intended path; the load-bearing verb is the stance flip OR
127
  # an explicit attack order (which overrides stance per the
128
  # CLAUDE.md stance fix).
129
+ # - Persistent far enemy `fact` at (90,20) in the EAST lobe โ€”
130
+ # anti-DRAW per CLAUDE.md (engine auto-`done`s on enemy-elim
131
+ # before win/fail evaluates; the inert far marker keeps the
132
+ # episode alive past the raider being destroyed). The wall
133
+ # prevents agent units from reaching it without crossing the
134
+ # corridor, so an intended-defense play does not incidentally
135
+ # destroy the marker.
136
  # - `unit_type_count_gte: {harv, 1}` in fail_condition is the
137
  # hard harv-survival predicate โ€” the moment the harv dies the
138
  # fail clause fires and the LOSS is immediate.
139
+ # - hard tier uses TWO seed-driven AGENT spawn_point groups (base
140
+ # latitude NORTH y=8 vs SOUTH y=32). Each group has its own
141
+ # fact/powr/proc/harv/escort + mine on its own y-band. The
142
+ # load-bearing decision is the same (flip + position escort on
143
+ # the corridor mouth) but the LATITUDE of the base varies per
144
+ # seed, so a memorised opening from the wrong latitude leaves
145
+ # the harv unprotected. This satisfies the โ‰ฅ2-distinct-seed-
146
+ # driven-spawn-groups hard-tier requirement (CLAUDE.md ยง7).
147
  #
148
  # Validate (no model / no network):
149
  # python3 -m openra_bench.scenarios.validate \
 
163
  decision is to FLIP the escort from passive to active โ€” set its
164
  stance to defend / attack-anything, or order it onto the
165
  harvester's lane to intercept โ€” so the raider dies on the
166
+ escort's fire envelope before reaching the harvester. The map
167
+ geometry funnels every raider through a single corridor mouth,
168
+ so the right place to plant the escort is unambiguous if the
169
+ agent reads the map. Stalling, leaving the escort on hold-fire,
170
+ or spreading defense everywhere instead of on the corridor mouth
171
+ all let the harvester die.
172
  robotics_analogue: >
173
  Mining-fleet protection under recurring asset-targeted attack โ€”
174
  an autonomous extraction site with an extractor robot, a
175
  processing depot, and a static defense detail. If the defense
176
  detail is in safe-mode (hold fire) the recurring attacker
177
  destroys the extractor on the first cycle; the operator must
178
+ flip the detail to engage and reposition it onto the single
179
+ access route the attacker must use.
180
  benchmark_anchor:
181
  - "SC2 harvester harass defense (Lambo, Reaper drops on workers)"
182
  - "convoy / extractor protection under recurring harass"
183
  - "RTS stance management โ€” flip escort from passive to active"
184
  - "SOC active-vs-passive monitoring posture"
185
+ - "chokepoint defense โ€” plant the line on the one route"
186
  author: openra-bench
187
 
188
+ # Per-scenario map: chokepoint-arena 96ร—40 with a vertical water wall
189
+ # at x=46..54 broken by a single 4-cell corridor at y=18..21. The
190
+ # corridor is the ONE lane any raider must use to cross from the east
191
+ # raid-spawn lobe into the west base/patch lobe. Top-level base_map
192
+ # is the readable slug; each level's overrides: re-declares the full
193
+ # generator spec so materialize() builds the .oramap deterministically.
194
+ base_map: econ-harvester-defense-raid-arena
195
  starting_cash: 0 # ALL income via harv survival; bar measures live throughput.
196
 
197
  base:
 
217
 
218
  levels:
219
  # โ”€โ”€ EASY โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
220
+ # Bare skill: ONE raid wave (1ร— 2tnk) at tick 600 from (75,20) in
221
+ # the EAST lobe. Harv cycles proc(10,12)โ†”mine(30,28) on auto-route
222
+ # entirely inside the WEST lobe. Escort: 2ร— 2tnk at base stance:0.
223
+ # Stall / passive: raider crosses the corridor (only route), detours
224
+ # south to the harv on its commute and kills it โ†’ LOSS. Intended:
225
+ # set_stance(escort, 2 or 3) and/or attack_move escort east to the
226
+ # west corridor mouth (~x=44..46, y=18..21); escort engages every
227
+ # raider on exit from the corridor, harv survives, cash accumulates.
228
+ # Bar: cash >= 1200 before tick 3000.
229
  # max_turns 35 โ‡’ ceiling 93+90ยท34=3153 > 3001 โœ“.
230
  easy:
231
  description: >
232
+ You own a Construction Yard, Power Plant, Refinery (NW corner,
233
+ around y=10..12), ONE Ore Truck on auto-harvest to an
234
+ OFF-AXIS ore patch SOUTH of base at (30,28), and TWO medium
235
+ tanks (2tnk) parked at your refinery on HOLD-FIRE stance.
236
+ A vertical water wall at x=46..54 splits the map; the ONLY
237
+ crossing is a 4-cell corridor at y=18..21. At tick 600 an
238
+ enemy raider tank spawns at (75,20) in the eastern lobe and
239
+ drives at your harvester โ€” it MUST come through the corridor
240
+ to reach you. Win when cash >= 1200 AND your harvester is
241
+ still alive AND your refinery is intact, before tick 3000.
242
+ Standing the escort idle (HOLD-FIRE) at base lets the raider
243
+ walk through and kill the harvester. The intended play is to
244
+ FLIP the escort's stance to Defend (2) or AttackAnything (3)
245
+ AND move it onto the west corridor mouth (around x=44..46,
246
+ y=18..21) so the raider dies on its way out of the corridor.
247
  starting_cash: 0
248
  overrides:
249
+ base_map:
250
+ generator: chokepoint-arena
251
+ name: econ-harvester-defense-raid-arena
252
+ width: 96
253
+ height: 40
254
+ cordon: 2
255
+ pinch_x: 50
256
+ pinch_width: 8
257
+ corridor_width: 4
258
+ corridor_y: 20
259
  actors:
260
+ # Agent base โ€” NW corner; fact + powr seed the construction
261
+ # & power; proc seeds the harv auto-harvest loop.
262
+ - {type: fact, owner: agent, position: [8, 10]}
263
+ - {type: powr, owner: agent, position: [8, 8]}
264
+ - {type: proc, owner: agent, position: [10, 12]}
265
  # Single harv โ€” pre-placed adjacent to proc. With proc on the
266
  # field, world.rs::auto_route_idle_harvesters installs a
267
  # Harvest activity on the next tick; harv cycles proc<->mine.
268
+ - {type: harv, owner: agent, position: [12, 12]}
269
  # Escort: 2ร— 2tnk at the proc on stance:0 (HoldFire). The
270
+ # capability under test is "flip the escort active AND
271
+ # position it on the corridor mouth" โ€” stance:0 never auto-
272
+ # engages, and a "place defenders at base and observe"
273
+ # policy LOSES because the corridor is too far east for
274
+ # base-bound defenders to fire on.
275
+ - {type: 2tnk, owner: agent, position: [14, 12], stance: 0}
276
+ - {type: 2tnk, owner: agent, position: [14, 14], stance: 0}
277
+ # OFF-AXIS ore patch โ€” south of base; the harv's auto-route
278
+ # target. Diagonal commute proc(10,12)โ†”mine(30,28) stays
279
+ # entirely in the WEST lobe (x<46), so the harv never
280
+ # touches the corridor.
281
+ - {type: mine, owner: neutral, position: [30, 28]}
282
+ # Persistent far enemy fact โ€” east lobe, anti-DRAW (engine
283
+ # auto-done mitigation). Behind the wall and reachable only
284
+ # via the corridor, so an intended defense play does not
285
+ # incidentally destroy the marker.
286
+ - {type: fact, owner: enemy, position: [90, 20]}
287
  scheduled_events:
288
+ # Single raid wave at tick 600 โ€” one 2tnk spawned in the
289
+ # east lobe. The raider bot's harv_foes() picks the agent's
290
+ # harv; the wall forces the approach through the corridor.
 
291
  - tick: 600
292
  type: spawn_actors
293
  actors:
294
+ - {type: 2tnk, owner: enemy, position: [75, 20]}
295
  win_condition:
296
  # Ordered `then:` enforces "harv must SURVIVE past the raid
297
  # window and THEN keep refining to the bar" โ€” clause 1
298
  # (after_ticks: 1700) latches only past wave 1's typical
299
+ # kill-window. A stall run loses its harv well before clause 1
300
+ # latches; the `unit_type_count_gte:{harv,1}` clause has
301
+ # already failed โ†’ fail_condition fires first. The intended
302
+ # escort engagement at the corridor mouth kills each raider,
303
+ # harv keeps cycling, cash crosses 1200 well after tick 1700,
304
+ # all_of latches โ†’ WIN.
 
305
  all_of:
306
  - then:
307
  id: econ-harvester-defense-raid-easy
 
320
 
321
  # โ”€โ”€ MEDIUM โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
322
  # +1 controlled variable: TWO raid waves (tick 600, tick 1500) and
323
+ # a tighter bar (cash >= 2200 โ‰ˆ 5..6 refines). A one-time "kill the
324
+ # first raider" play has to ALSO hold the corridor against the
325
+ # second wave โ€” the escort must be RETURNED to (or kept on) the
326
+ # corridor mouth (or kept on AttackAnything stance:3) so the late
327
+ # wave dies on approach too.
328
  # max_turns 45 โ‡’ ceiling 4053 > 3901 โœ“.
329
  medium:
330
  description: >
331
+ Same setup as easy โ€” fact + powr + proc + 1ร— harv on
332
+ auto-harvest to the OFF-AXIS patch at (30,28), 2ร— 2tnk escort
333
+ at the refinery on HOLD-FIRE, same chokepoint map โ€” but TWO
334
+ raider waves arrive: one at tick 600 and a second at tick 1500.
335
+ Both spawn at (75,20) in the eastern lobe and must cross the
336
+ single corridor (y=18..21) to reach your harvester. Win when
337
+ cash >= 2200 AND your harvester is alive AND your refinery is
338
+ intact, before tick 3900. Stalling, leaving the escort on
339
+ hold-fire, and chasing the first raider PAST the corridor far
340
+ enough to miss the second wave all LOSE. The intended play is
341
+ to flip the escort active (stance Defend or AttackAnything)
342
+ and HOLD the corridor mouth so BOTH waves die on approach.
343
  starting_cash: 0
344
  overrides:
345
+ base_map:
346
+ generator: chokepoint-arena
347
+ name: econ-harvester-defense-raid-arena
348
+ width: 96
349
+ height: 40
350
+ cordon: 2
351
+ pinch_x: 50
352
+ pinch_width: 8
353
+ corridor_width: 4
354
+ corridor_y: 20
355
  actors:
356
+ - {type: fact, owner: agent, position: [8, 10]}
357
+ - {type: powr, owner: agent, position: [8, 8]}
358
+ - {type: proc, owner: agent, position: [10, 12]}
359
+ - {type: harv, owner: agent, position: [12, 12]}
360
+ - {type: 2tnk, owner: agent, position: [14, 12], stance: 0}
361
+ - {type: 2tnk, owner: agent, position: [14, 14], stance: 0}
362
+ - {type: mine, owner: neutral, position: [30, 28]}
363
+ - {type: fact, owner: enemy, position: [90, 20]}
364
  scheduled_events:
365
  - tick: 600
366
  type: spawn_actors
367
  actors:
368
+ - {type: 2tnk, owner: enemy, position: [75, 20]}
369
  - tick: 1500
370
  type: spawn_actors
371
  actors:
372
+ - {type: 2tnk, owner: enemy, position: [75, 20]}
373
  win_condition:
374
+ # `then:` forces cash bar to be reached AFTER tick 2700 โ€” past
375
+ # the second raid wave's kill-window (wave 2 spawns at tick
376
+ # 1500, reaches harv ~tick 2000, kills undefended harv by
377
+ # ~tick 2600). Aggressive-no-corridor-hold (drive escort
378
+ # east past the corridor) lets wave 1 chip the harv and wave
379
+ # 2 finish it; the harv dies before clause 1 latches โ†’ fail
380
+ # clause unit_type_count_gte:{harv,1} fires โ†’ LOSS.
 
381
  all_of:
382
  - then:
383
  id: econ-harvester-defense-raid-medium
 
396
 
397
  # โ”€โ”€ HARD โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
398
  # +1 axis vs medium: TWO seed-driven AGENT spawn_point groups โ€”
399
+ # base latitude NORTH y=8 vs SOUTH y=32, round-robined per seed.
400
  # Each group has its own fact/powr/proc/harv/escort/mine on its
401
+ # own y-band, ENTIRELY inside the west lobe (above the corridor
402
+ # for NORTH, below for SOUTH). The agent's load-bearing decision
403
+ # is the same (flip + position escort onto the corridor mouth)
404
+ # but the harv's latitude varies per seed, so a memorised opening
405
+ # that ignores the active base latitude leaves the harv exposed
406
+ # on the wrong y after the raider exits the corridor.
407
  #
408
  # Hard tier โ‰ฅ2-spawn-groups requirement (CLAUDE.md ยง7): satisfied
409
  # via two AGENT spawn_point groups (spawn_point: 0 / 1) โ€” every
410
  # agent actor is duplicated across both groups; the active group
411
  # is round-robined by seed.
412
  #
413
+ # Three raid waves (ticks 600, 1500, 2400) spawn on the central
414
+ # east axis at (75,20). Per the raider bot, each one targets the
415
+ # active harv; the wall forces every approach through the
416
+ # corridor (y=18..21), then the raider detours north or south to
417
+ # the active harv latitude (NORTH y=8 needs a ~10-cell detour
418
+ # north of the corridor; SOUTH y=32 needs a ~11-cell detour
419
+ # south). The corridor mouth (xโ‰ˆ44..46, y=18..21) is the SHARED
420
+ # defensive position for BOTH spawn groups, so the load-bearing
421
+ # capability โ€” read the corridor + plant escort there โ€” is
422
+ # invariant across seeds; only the escort's STARTING position
423
+ # changes (north base vs south base).
424
  # max_turns 50 โ‡’ ceiling 4503 > 4301 โœ“.
425
  hard:
426
  description: >
427
+ Tougher version on the same chokepoint map: your base
428
+ latitude varies per seed โ€” NORTH base (y=8) OR SOUTH base
429
+ (y=32). Each base has its own fact, powr, proc, harvester on
430
+ auto-harvest to its OWN OFF-AXIS near patch (mine at (30,12)
431
+ for NORTH; mine at (30,28) for SOUTH), and 2ร— 2tnk escort at
432
+ the refinery on HOLD-FIRE. THREE raider heavy tanks arrive
433
+ at ticks 600, 1500, and 2400 from the central east corridor
434
+ at (75,20); each one drives through the single corridor at
435
+ y=18..21 then detours to your harvester's latitude. Win when
436
+ cash >= 2800 AND your harvester is alive AND your refinery
437
+ is intact, before tick 4300. Stalling, leaving escort on
438
+ hold-fire, parking escort at base, and missing the corridor
439
+ mouth all LOSE. Flip the escort to Defend / AttackAnything
440
+ AND attack-move it onto the corridor mouth (around x=44..46,
441
+ y=18..21) so all three waves die on approach.
442
  starting_cash: 0
443
  overrides:
444
+ base_map:
445
+ generator: chokepoint-arena
446
+ name: econ-harvester-defense-raid-arena
447
+ width: 96
448
+ height: 40
449
+ cordon: 2
450
+ pinch_x: 50
451
+ pinch_width: 8
452
+ corridor_width: 4
453
+ corridor_y: 20
454
  actors:
455
+ # spawn_point 0 โ€” NORTH base (y=8). CLAUDE.md: agent actors
456
  # must be duplicated across both spawn_point groups (the
457
  # filter drops any group-untagged actor on either side).
458
+ - {type: fact, owner: agent, position: [8, 8], spawn_point: 0}
459
+ - {type: powr, owner: agent, position: [8, 6], spawn_point: 0}
460
+ - {type: proc, owner: agent, position: [10, 8], spawn_point: 0}
461
+ - {type: harv, owner: agent, position: [12, 8], spawn_point: 0}
462
  # Escort placed on open ground east of the proc so attack_move
463
+ # has a clear east lane to the corridor mouth.
464
+ - {type: 2tnk, owner: agent, position: [14, 7], stance: 0, spawn_point: 0}
465
+ - {type: 2tnk, owner: agent, position: [14, 9], stance: 0, spawn_point: 0}
466
+ # spawn_point 1 โ€” SOUTH base (y=32). Symmetric.
467
+ - {type: fact, owner: agent, position: [8, 32], spawn_point: 1}
468
+ - {type: powr, owner: agent, position: [8, 34], spawn_point: 1}
469
+ - {type: proc, owner: agent, position: [10, 32], spawn_point: 1}
470
+ - {type: harv, owner: agent, position: [12, 32], spawn_point: 1}
471
+ - {type: 2tnk, owner: agent, position: [14, 31], stance: 0, spawn_point: 1}
472
+ - {type: 2tnk, owner: agent, position: [14, 33], stance: 0, spawn_point: 1}
473
+ # Two ore patches โ€” one per latitude, BOTH entirely in the
474
+ # west lobe (x<46) and on their respective sides of the
475
+ # corridor. Mines are neutral, so the spawn_point filter
476
+ # (agent-side) does not affect them; both place every seed.
477
+ # The active harv only uses ITS OWN patch (auto-route picks
478
+ # the nearest ore the owner can reach, which on each seed
479
+ # is the on-latitude one).
480
+ - {type: mine, owner: neutral, position: [30, 12]}
481
+ - {type: mine, owner: neutral, position: [30, 28]}
482
+ # Persistent far enemy fact โ€” east lobe, anti-DRAW.
483
+ - {type: fact, owner: enemy, position: [90, 20]}
484
  scheduled_events:
485
+ # Three raid waves spawn on the central east corridor axis
486
+ # at (75,20). Each raider is bot-controlled and must traverse
487
+ # the single corridor (y=18..21) โ€” the wall blocks every
488
+ # other eastโ†’west route โ€” then detour ~10-11 cells north or
489
+ # south to reach the on-latitude harv. The detour gives the
490
+ # escort time to intercept at the corridor mouth, but ONLY
491
+ # if the escort is actually parked there. A memorised
492
+ # "send escort east along base latitude" opening drops them
493
+ # at y=8 (NORTH) or y=32 (SOUTH), well off the corridor;
494
+ # the raider exits the corridor unopposed and reaches the
495
+ # harv unmolested.
496
  - tick: 600
497
  type: spawn_actors
498
  actors:
499
+ - {type: 2tnk, owner: enemy, position: [75, 20]}
500
  - tick: 1500
501
  type: spawn_actors
502
  actors:
503
+ - {type: 2tnk, owner: enemy, position: [75, 20]}
504
  - tick: 2400
505
  type: spawn_actors
506
  actors:
507
+ - {type: 2tnk, owner: enemy, position: [75, 20]}
508
  win_condition:
509
  # `then:` forces cash bar AFTER tick 3500 โ€” past wave 3
510
+ # (spawns tick 2400, reaches harv ~tick 2900, kills
511
+ # undefended harv by ~tick 3400). Stall / passive / off-axis
512
  # all lose their harv before tick 3500 โ†’ unit_type_count_gte
513
+ # fail clause fires โ†’ LOSS. Intended (escort planted on the
514
+ # corridor mouth) kills each wave on approach; harv cycles
515
  # uninterrupted through all three waves and accumulates
516
  # cash past tick 3500.
517
  all_of: