yxc20098 commited on
Commit
b5f8a60
ยท
1 Parent(s): d8da7e4

Recalibrate 8 packs regressed by the count: spawn-spread engine fix

Browse files

The engine count: N fix (spawn units on distinct cells instead of
stacked) shifted starting layouts and regressed the no-cheat bar of 8
packs โ€” 35 test cases. Recalibrated each so stall/brute/wrong-path
LOSE and the intended policy WINS on every level + hard seed, no draws:

- combat-bait-counter-attack: spread strike column ate more anti-tank
fire head-on; staged it on the north flank (medium/hard).
- econ-buy-vs-build-decision: dispersed rusher band takes ~2 more
turns to clear; extended the hard deadline.
- harass-response-preserve: spread probes split focus-fire; moved the
hard probe anchor into fog so the defence regroups concentrated.
- mfb-rotating-production-pressure: softened EAST raid let a late tank
survive; easy raid count 3 -> 4.
- mid-concede-vs-hold: spread flex squad let split-defend hold WEST;
hard WEST push count 20 -> 26.
- risk-blockade-bypass: diluted corridor garrison let do-nothing pass;
strengthened the easy/medium garrison.
- scout-count-defenders: spread defenders focus-fire harder; defender
health 60, medium 1 pbox, hard deadline +300t.
- tp-survive-n-turns: reinforcement waves land closer; medium wave
count 6 -> 5 per row.

343 tests across the 8 packs green. Pairs with OpenRA-Rust feb8981.

openra_bench/scenarios/packs/combat-bait-counter-attack.yaml CHANGED
@@ -159,32 +159,43 @@ levels:
159
 
160
  # โ”€โ”€ MEDIUM โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
161
  # +1 controlled variable: 5 guards forming a south + west L-cover
162
- # of the yard (the two natural west-staging approaches: head-on
163
- # along y=20 OR a south sweep along y=24..28). A single deep-south
164
- # bait pulls the south arc off post; the strike commits while that
165
- # arc is displaced. After the engine move-fire fixes the e3 Dragon
166
- # bites the 2tnk Heavy armour harder, so a 3-tank strike can no
167
- # longer trade a 5-guard cover within the cap โ€” the strike force is
168
- # 5 medium tanks (was 3), a scale-up matching the denser cover.
169
- # Strict attrition cap stays 2 (the bait jeep + at most one tank):
170
- # the strike must still hit the yard essentially intact; burning a
171
- # second tank fails.
 
 
 
 
 
172
  medium:
173
  description: >
174
  A denser cluster of 5 leashed rocket-infantry guards covers
175
  the enemy construction yard (`fact` at 80,20) on the WEST and
176
  SOUTH faces (the two natural approaches from your west base).
177
  A head-on push without first thinning the cover is decisively
178
- fatal. You command 5 medium tanks plus the bait jeep but may
179
- spend at most 2 units โ€” losing a second tank fails. Commit the
180
- fast jeep on a deep south flank vector to pull the south arc of
181
- guards off post after it; while that arc is displaced, drive
182
- the strike tanks in, focus-fire the remaining guards, and
183
- destroy the yard before tick 5400. Bait-only (no strike) loses
184
- on the clock; brute frontal trades the strike force.
 
185
  overrides:
186
  actors:
187
- - {type: 2tnk, owner: agent, position: [6, 20], stance: 1, count: 5}
 
 
 
 
 
188
  - {type: jeep, owner: agent, position: [6, 28], stance: 1}
189
  - {type: fact, owner: enemy, position: [80, 20]}
190
  # 5 guards on the WEST + SOUTH faces. A deep-south jeep bait
@@ -213,15 +224,15 @@ levels:
213
  # +1 more controlled variable on top of medium: TWO spawn_point
214
  # groups round-robin the agent's staging DISTANCE from the yard โ€”
215
  # spawn 0 stages at the far west (x=6), spawn 1 stages well forward
216
- # (x=30). The bait/strike geometry is identical (same 5-guard L
217
- # cover, same fact latitude y=20), but the strike's run-in distance
218
- # and the timing of the bait pull vary per seed, so a memorised
219
- # fixed-tick opening cannot generalise โ€” the agent must read its
220
- # actual staging position. After the engine move-fire fixes a
221
- # north/south staging split collapses to a knife-edge (the closer
222
- # guard arc engages the column before the bait can pull it), so the
223
- # spawn axis is the staging distance, not latitude. Same 5-guard L
224
- # cover, 5-tank strike, and loss cap 2 as medium.
225
  hard:
226
  description: >
227
  The same dense 5-guard L-cover of the enemy construction yard
@@ -229,29 +240,32 @@ levels:
229
  from a seed-chosen distance โ€” either the far west or well
230
  forward โ€” so a memorised fixed opening cannot generalise; read
231
  your tanks' actual position from the observation. You command 5
232
- medium tanks plus the bait jeep but may spend at most 2 units
233
- (the jeep + at most one tank) โ€” losing a second tank fails.
234
- Commit the fast jeep on a deep south flank vector to pull the
235
- south arc of guards off post; while that arc is displaced,
236
- drive the strike tanks in, focus-fire the remaining guards, and
237
- destroy the yard before tick 5400. Brute frontal trades armour
238
- and fails; bait-only never razes the yard; stalling loses the
239
- clock.
240
  overrides:
241
  actors:
242
  # spawn_point 0 โ€” FAR-WEST staging (x=6): the strike has the
243
  # full run-in to the yard; the bait has time to pull the
244
- # south arc well off post before the column arrives.
245
- - {type: 2tnk, owner: agent, position: [6, 20], stance: 1, count: 5,
 
 
246
  spawn_point: 0}
247
  - {type: jeep, owner: agent, position: [6, 28], stance: 1,
248
  spawn_point: 0}
249
- # spawn_point 1 โ€” FORWARD staging (x=30): the strike is much
250
  # closer to the yard, so the bait pull and the strike commit
251
- # are tighter together โ€” the same idiom on a compressed clock.
252
- - {type: 2tnk, owner: agent, position: [30, 20], stance: 1, count: 5,
 
253
  spawn_point: 1}
254
- - {type: jeep, owner: agent, position: [30, 28], stance: 1,
255
  spawn_point: 1}
256
  # The OBJECTIVE.
257
  - {type: fact, owner: enemy, position: [80, 20]}
 
159
 
160
  # โ”€โ”€ MEDIUM โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
161
  # +1 controlled variable: 5 guards forming a south + west L-cover
162
+ # of the yard. A single deep-south bait pulls the south arc off
163
+ # post; the strike commits on the NORTH flank while that arc is
164
+ # displaced, swinging in above the cover to reach the yard. After
165
+ # the engine move-fire fixes the e3 Dragon bites the 2tnk Heavy
166
+ # armour harder, so a 3-tank strike can no longer trade a 5-guard
167
+ # cover within the cap โ€” the strike force is 5 medium tanks (was
168
+ # 3), a scale-up matching the denser cover. The strike stages on
169
+ # the NORTH flank (y=12, was a head-on y=20): the engine `count:`
170
+ # spawn-spread now scatters the column across several cells, so a
171
+ # head-on stage drives the spread column straight into the
172
+ # un-baited WEST arc and trades too much armour; the north-flank
173
+ # stage lets the column swing in above the cover once the bait has
174
+ # pulled the south arc. Strict attrition cap stays 2 (the bait
175
+ # jeep + at most one tank): the strike must still hit the yard
176
+ # essentially intact; burning a second tank fails.
177
  medium:
178
  description: >
179
  A denser cluster of 5 leashed rocket-infantry guards covers
180
  the enemy construction yard (`fact` at 80,20) on the WEST and
181
  SOUTH faces (the two natural approaches from your west base).
182
  A head-on push without first thinning the cover is decisively
183
+ fatal. You command 5 medium tanks staged on the NORTH flank
184
+ plus the bait jeep, but may spend at most 2 units โ€” losing a
185
+ second tank fails. Commit the fast jeep on a deep south flank
186
+ vector to pull the south arc of guards off post after it; while
187
+ that arc is displaced, swing the strike tanks around the
188
+ now-vacated NORTH flank and onto the yard before tick 5400.
189
+ Bait-only (no strike) loses on the clock; brute frontal trades
190
+ the strike force.
191
  overrides:
192
  actors:
193
+ # Strike force: 5 medium tanks staged on the NORTH flank
194
+ # (y=12) so that, once the bait pulls the SOUTH arc off post,
195
+ # the column swings in above the WEST/SOUTH cover and reaches
196
+ # the yard taking minimal anti-tank fire. A head-on y=20
197
+ # stage drives straight into the un-baited WEST arc.
198
+ - {type: 2tnk, owner: agent, position: [6, 12], stance: 1, count: 5}
199
  - {type: jeep, owner: agent, position: [6, 28], stance: 1}
200
  - {type: fact, owner: enemy, position: [80, 20]}
201
  # 5 guards on the WEST + SOUTH faces. A deep-south jeep bait
 
224
  # +1 more controlled variable on top of medium: TWO spawn_point
225
  # groups round-robin the agent's staging DISTANCE from the yard โ€”
226
  # spawn 0 stages at the far west (x=6), spawn 1 stages well forward
227
+ # (x=18). The bait/strike geometry is identical (same 5-guard L
228
+ # cover, same north-flank y=12 stage, same fact at 80,20), but the
229
+ # strike's run-in distance and the timing of the bait pull vary per
230
+ # seed, so a memorised fixed-tick opening cannot generalise โ€” the
231
+ # agent must read its actual staging position. The spawn axis is
232
+ # the staging distance, not latitude (a north/south staging split
233
+ # collapses to a knife-edge under the engine move-fire fixes โ€” the
234
+ # closer guard arc engages the column before the bait can pull it).
235
+ # Same 5-guard L cover, 5-tank strike, and loss cap 2 as medium.
236
  hard:
237
  description: >
238
  The same dense 5-guard L-cover of the enemy construction yard
 
240
  from a seed-chosen distance โ€” either the far west or well
241
  forward โ€” so a memorised fixed opening cannot generalise; read
242
  your tanks' actual position from the observation. You command 5
243
+ medium tanks staged on the NORTH flank plus the bait jeep, but
244
+ may spend at most 2 units (the jeep + at most one tank) โ€”
245
+ losing a second tank fails. Commit the fast jeep on a deep
246
+ south flank vector to pull the south arc of guards off post;
247
+ while that arc is displaced, swing the strike tanks around the
248
+ now-vacated NORTH flank and onto the yard before tick 5400.
249
+ Brute frontal trades armour and fails; bait-only never razes
250
+ the yard; stalling loses the clock.
251
  overrides:
252
  actors:
253
  # spawn_point 0 โ€” FAR-WEST staging (x=6): the strike has the
254
  # full run-in to the yard; the bait has time to pull the
255
+ # south arc well off post before the column arrives. Staged
256
+ # on the NORTH flank (y=12) so the column swings in above
257
+ # the cover, as in medium.
258
+ - {type: 2tnk, owner: agent, position: [6, 12], stance: 1, count: 5,
259
  spawn_point: 0}
260
  - {type: jeep, owner: agent, position: [6, 28], stance: 1,
261
  spawn_point: 0}
262
+ # spawn_point 1 โ€” FORWARD staging (x=18): the strike is much
263
  # closer to the yard, so the bait pull and the strike commit
264
+ # are tighter together โ€” the same north-flank idiom on a
265
+ # compressed clock.
266
+ - {type: 2tnk, owner: agent, position: [18, 12], stance: 1, count: 5,
267
  spawn_point: 1}
268
+ - {type: jeep, owner: agent, position: [18, 28], stance: 1,
269
  spawn_point: 1}
270
  # The OBJECTIVE.
271
  - {type: fact, owner: enemy, position: [80, 20]}
openra_bench/scenarios/packs/econ-buy-vs-build-decision.yaml CHANGED
@@ -286,6 +286,19 @@ levels:
286
  # slightly heavier than medium's 5ร— e1, so hard stays harder),
287
  # which the buy-now force clears inside the clock while
288
  # stall / build-weap-first still LOSE.
 
 
 
 
 
 
 
 
 
 
 
 
 
289
  hard:
290
  description: >
291
  A rusher band (5 rifle infantry + 1 rocket soldier) is
@@ -297,7 +310,7 @@ levels:
297
  war factory) OR a second war factory (build weap, residual
298
  $1400 = 1 tank). The rush is incoming NOW. Win when โ‰ฅ5 enemy
299
  units are killed AND your fact is still standing AND the
300
- clock has not run out (tick โ‰ค 1499). Stall,
301
  build-second-factory-first, or under-spend all fail.
302
  overrides:
303
  actors:
@@ -343,9 +356,9 @@ levels:
343
  all_of:
344
  - {units_killed_gte: 5}
345
  - {building_count_gte: {type: fact, n: 1}}
346
- - {within_ticks: 1499}
347
  fail_condition:
348
  any_of:
349
- - {after_ticks: 1500}
350
  - {not: {building_count_gte: {type: fact, n: 1}}}
351
  max_turns: 25
 
286
  # slightly heavier than medium's 5ร— e1, so hard stays harder),
287
  # which the buy-now force clears inside the clock while
288
  # stall / build-weap-first still LOSE.
289
+ #
290
+ # Engine recalibration note (count: spawn-spread fix): the
291
+ # `count: N` actor expansion was fixed to SPREAD copies across
292
+ # N distinct cells (outward rings from the anchor) instead of
293
+ # STACKING them on one cell. The hard rusher band (5ร— e1 + 1ร— e3
294
+ # tagged with spawn_point) now disperses ~1-2 cells around its
295
+ # anchor, so the buy-now tanks need a couple more turns to chew
296
+ # through the whole band โ€” the kill bar (5) is met around tick
297
+ # ~1560, slightly past the old within_ticks 1499. The deadline
298
+ # is extended to 1649/1650 (still far under the max_turns 25
299
+ # ceiling 2253, still a real reachable anti-stall timeout) so
300
+ # the intended buy-now WIN registers while stall / build-weap
301
+ # still LOSE by fact-razed well before the clock.
302
  hard:
303
  description: >
304
  A rusher band (5 rifle infantry + 1 rocket soldier) is
 
310
  war factory) OR a second war factory (build weap, residual
311
  $1400 = 1 tank). The rush is incoming NOW. Win when โ‰ฅ5 enemy
312
  units are killed AND your fact is still standing AND the
313
+ clock has not run out (tick โ‰ค 1649). Stall,
314
  build-second-factory-first, or under-spend all fail.
315
  overrides:
316
  actors:
 
356
  all_of:
357
  - {units_killed_gte: 5}
358
  - {building_count_gte: {type: fact, n: 1}}
359
+ - {within_ticks: 1649}
360
  fail_condition:
361
  any_of:
362
+ - {after_ticks: 1650}
363
  - {not: {building_count_gte: {type: fact, n: 1}}}
364
  max_turns: 25
openra_bench/scenarios/packs/harass-response-preserve.yaml CHANGED
@@ -176,24 +176,30 @@ levels:
176
  # 2. Strict attrition: lose ZERO. Both `proc` AND `tent` must
177
  # still stand.
178
  # The intended fire-support-on-home-turf policy wins (HoldFire
179
- # defenders + explicit attack_unit on each probe in turn โ€” they
180
- # arrive serially as the patrol script bounces them in). Stall:
181
- # the triple chip-volley razes the assets unopposed. Chase: the
182
- # bouncing probes outpace pursuit (PATROL_RADIUS=8 in two beats),
183
- # defenders overextend into [22..30,20] while the next swing
184
- # arrives โ€” assets die undefended. Yield: same โ€” assets fall.
 
 
 
 
 
 
185
  hard:
186
  description: >
187
  THREE enemy riflemen probe your perimeter on the central
188
- latitude (anchor 22,20 โ†’ bouncing 14..30,20). Your refinery
189
  (`proc` at 16,20) AND your adjacent barracks (`tent` at 18,20)
190
  must BOTH still stand. Your 5 riflemen stage on a seed-varied
191
  latitude (north y=18 or south y=22) โ€” both slots cover BOTH
192
- assets. They are on HOLD-FIRE; order them to engage each probe
193
- as it swings west into your assets' footprint. Win when โ‰ฅ3
194
- probes are dead, both buildings still stand, โ‰ฅ5 of you are
195
- alive and you have NOT lost a single defender โ€” before tick
196
- 4500. Idling, chasing east, or pulling back all LOSE.
197
  overrides:
198
  actors:
199
  # Both assets, duplicated across BOTH spawn_point groups โ€”
@@ -219,11 +225,16 @@ levels:
219
  - {type: e1, owner: agent, position: [14, 23], stance: 0, spawn_point: 1}
220
  - {type: e1, owner: agent, position: [13, 21], stance: 0, spawn_point: 1}
221
  - {type: e1, owner: agent, position: [13, 23], stance: 0, spawn_point: 1}
222
- # Three probes stacked at the central anchor โ€” they bounce
223
- # together but engage serially as defenders kill them one
224
- # by one. The triple chip-volley is the controlled-variable
225
- # vs medium's two; the stall/chase/yield losses scale with it.
226
- - {type: e1, owner: enemy, position: [22, 20], stance: 2, count: 3}
 
 
 
 
 
227
  - {type: fact, owner: enemy, position: [120, 20]}
228
  win_condition:
229
  all_of:
 
176
  # 2. Strict attrition: lose ZERO. Both `proc` AND `tent` must
177
  # still stand.
178
  # The intended fire-support-on-home-turf policy wins (HoldFire
179
+ # defenders + explicit attack_unit on each probe as it swings into
180
+ # the asset footprint โ€” the defenders engage CONCENTRATED from a
181
+ # held position rather than chasing). The probe anchor [26,20] sits
182
+ # east of the defenders' sight horizon: the probes spawn in fog,
183
+ # bounce out east then swing back west to ~x=18 (26 โˆ’ PATROL_RADIUS
184
+ # 8) โ€” into rifle range of the assets โ€” where the held squad cuts
185
+ # them down one swing at a time, taking ZERO losses. Stall: the
186
+ # probes chip the assets every west swing, unopposed, and no kills
187
+ # are scored โ‡’ timeout LOSS. Chase: marching east past the assets
188
+ # into the deep-fog spawn band overextends the squad โ€” the bouncing
189
+ # probes (PATROL_RADIUS=8 in two beats) pick off pursuers and the
190
+ # assets stand undefended โ‡’ LOSS. Yield: assets abandoned โ‡’ LOSS.
191
  hard:
192
  description: >
193
  THREE enemy riflemen probe your perimeter on the central
194
+ latitude (anchor 26,20 โ†’ bouncing 18..34,20). Your refinery
195
  (`proc` at 16,20) AND your adjacent barracks (`tent` at 18,20)
196
  must BOTH still stand. Your 5 riflemen stage on a seed-varied
197
  latitude (north y=18 or south y=22) โ€” both slots cover BOTH
198
+ assets. They are on HOLD-FIRE; hold the asset footprint and
199
+ order them to engage each probe as it swings west into range.
200
+ Win when โ‰ฅ3 probes are dead, both buildings still stand, โ‰ฅ5 of
201
+ you are alive and you have NOT lost a single defender โ€” before
202
+ tick 4500. Idling, chasing east, or pulling back all LOSE.
203
  overrides:
204
  actors:
205
  # Both assets, duplicated across BOTH spawn_point groups โ€”
 
225
  - {type: e1, owner: agent, position: [14, 23], stance: 0, spawn_point: 1}
226
  - {type: e1, owner: agent, position: [13, 21], stance: 0, spawn_point: 1}
227
  - {type: e1, owner: agent, position: [13, 23], stance: 0, spawn_point: 1}
228
+ # Three probes at the central anchor โ€” `count: 3` spreads them
229
+ # across three adjacent cells (engine spawn-spread); they
230
+ # bounce on the patrol script and engage serially as defenders
231
+ # kill them one by one. The anchor sits east of the defenders'
232
+ # sight horizon so the probes start in fog โ€” the held squad
233
+ # engages them only as they swing west into the asset
234
+ # footprint, not by chasing. The triple chip-volley is the
235
+ # controlled variable vs medium's two; stall/chase/yield
236
+ # losses scale with it.
237
+ - {type: e1, owner: enemy, position: [26, 20], stance: 2, count: 3}
238
  - {type: fact, owner: enemy, position: [120, 20]}
239
  win_condition:
240
  all_of:
openra_bench/scenarios/packs/mfb-rotating-production-pressure.yaml CHANGED
@@ -197,7 +197,9 @@ base:
197
  levels:
198
  # โ”€โ”€ EASY โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
199
  # Minimum-viable rotation drill. WEST raid at tick 300 (3ร— e1),
200
- # EAST raid at tick 1500 (3ร— e1). Six tanks (3 per base). max_turns
 
 
201
  # 70 โ‡’ reachable tick 93+90ยท69 = 6303; within_ticks 5400 +
202
  # fail after_ticks 5401 โ‡’ a non-finisher crosses 5401 well before
203
  # the episode caps โ‡’ real LOSS, not a DRAW.
@@ -248,10 +250,18 @@ levels:
248
  type: spawn_actors
249
  actors:
250
  - {type: e1, owner: enemy, position: [18, 19], stance: 3, count: 3}
 
 
 
 
 
 
 
 
251
  - tick: 1500
252
  type: spawn_actors
253
  actors:
254
- - {type: e1, owner: enemy, position: [88, 19], stance: 3, count: 3}
255
  win_condition:
256
  all_of:
257
  # โ‰ฅ4 medium tanks ALIVE โ€” verifies the army was actually
 
197
  levels:
198
  # โ”€โ”€ EASY โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
199
  # Minimum-viable rotation drill. WEST raid at tick 300 (3ร— e1),
200
+ # EAST raid at tick 1500 (4ร— e1 โ€” one heavier than the WEST raid
201
+ # so a single late-routed tank cannot hold it; see the
202
+ # scheduled_events note below). Six tanks (3 per base). max_turns
203
  # 70 โ‡’ reachable tick 93+90ยท69 = 6303; within_ticks 5400 +
204
  # fail after_ticks 5401 โ‡’ a non-finisher crosses 5401 well before
205
  # the episode caps โ‡’ real LOSS, not a DRAW.
 
250
  type: spawn_actors
251
  actors:
252
  - {type: e1, owner: enemy, position: [18, 19], stance: 3, count: 3}
253
+ # EAST raid is 4ร— e1 (one heavier than the WEST raid): the
254
+ # engine `count:` spawn-spread fix scattered the raid band
255
+ # across distinct cells, which softened its concentrated
256
+ # damage enough that a SINGLE late-routed tank could survive
257
+ # the old 3ร— band โ€” inverting the `rotate-too-late` LOSS bar.
258
+ # 4ร— e1 re-tightens it: one tank (the too-late split) loses
259
+ # the EAST base, while the intended 3-4-tank EAST garrison
260
+ # still clears the raid and keeps the war factory.
261
  - tick: 1500
262
  type: spawn_actors
263
  actors:
264
+ - {type: e1, owner: enemy, position: [88, 19], stance: 3, count: 4}
265
  win_condition:
266
  all_of:
267
  # โ‰ฅ4 medium tanks ALIVE โ€” verifies the army was actually
openra_bench/scenarios/packs/mid-concede-vs-hold.yaml CHANGED
@@ -407,9 +407,17 @@ levels:
407
  - {type: 2tnk, owner: agent, position: [54, 20], stance: 1, count: 2, health: 100, spawn_point: 1}
408
  - {type: 1tnk, owner: agent, position: [54, 22], stance: 1, count: 2, health: 100, spawn_point: 1}
409
  - {type: jeep, owner: agent, position: [54, 18], stance: 1, health: 100, spawn_point: 1}
410
- # โ”€โ”€ WEST enemy push (LIGHT) โ€” far west, same 20-rifle band
411
- # as easy/medium.
412
- - {type: e1, owner: enemy, position: [5, 20], stance: 3, count: 20, health: 100}
 
 
 
 
 
 
 
 
413
  # โ”€โ”€ EAST enemy push (HEAVY) โ€” far east, same 72-rifle band
414
  # as easy/medium. Hard's earlier survival floor (2400 vs
415
  # easy/medium's 3000) is what makes the heavy push need to be
 
407
  - {type: 2tnk, owner: agent, position: [54, 20], stance: 1, count: 2, health: 100, spawn_point: 1}
408
  - {type: 1tnk, owner: agent, position: [54, 22], stance: 1, count: 2, health: 100, spawn_point: 1}
409
  - {type: jeep, owner: agent, position: [54, 18], stance: 1, health: 100, spawn_point: 1}
410
+ # โ”€โ”€ WEST enemy push (LIGHT) โ€” far west. Raised 20->26 on
411
+ # HARD ONLY: the engine `count:` spawn-spread fix scatters the
412
+ # 5-unit flex squad across distinct cells instead of stacking
413
+ # it, which slightly improved the WEST garrison + HALF-flex
414
+ # trade โ€” a split-defend (3 of 5 flex sent WEST) suddenly held
415
+ # the base and WON. 26 e1 re-tightens the WEST trade so the
416
+ # bare garrison + any partial flex (split / oscillate) is
417
+ # overrun, while the garrison + the FULL consolidated flex,
418
+ # ordered to ATTACK-MOVE in, still clears it and holds past
419
+ # the 2400 survival floor.
420
+ - {type: e1, owner: enemy, position: [5, 20], stance: 3, count: 26, health: 100}
421
  # โ”€โ”€ EAST enemy push (HEAVY) โ€” far east, same 72-rifle band
422
  # as easy/medium. Hard's earlier survival floor (2400 vs
423
  # easy/medium's 3000) is what makes the heavy push need to be
openra_bench/scenarios/packs/risk-blockade-bypass.yaml CHANGED
@@ -10,11 +10,11 @@
10
  # detour. Brute-forcing the corridor reduces the force below the
11
  # attrition cap before reaching the objective.
12
  #
13
- # Difficulty escalates the cost of the wrong call: easy allows a
14
- # slim head-on (loss <=4); medium tightens the cap; hard introduces
15
- # a smaller forward force AND a tempo gate (the objective only
16
- # scores after tick t0, so committing too early through the corridor
17
- # and arriving wounded fails the after_ticks-then-attrition combo).
18
 
19
  meta:
20
  id: risk-blockade-bypass
@@ -63,10 +63,16 @@ base:
63
  - {type: 1tnk, owner: agent, position: [8, 22], stance: 3, count: 3}
64
  - {type: jeep, owner: agent, position: [6, 18], stance: 3, count: 2}
65
  # Heavy CORRIDOR garrison (the wrong way): trades well, expensive
66
- # to clear. Combination of e3 (rockets) + defensive gun.
67
- - {type: e3, owner: enemy, position: [55, 20], stance: 2, count: 4}
68
- - {type: gun, owner: enemy, position: [57, 22], stance: 3, count: 1}
69
- - {type: e1, owner: enemy, position: [55, 22], stance: 2, count: 3}
 
 
 
 
 
 
70
  # Light DETOUR garrison (the right way, top-N): one infantry
71
  # picket; clears at low cost.
72
  - {type: e1, owner: enemy, position: [55, 5], stance: 2, count: 2}
@@ -84,10 +90,10 @@ levels:
84
  description: >
85
  Reach the objective region near (110, 20) within 3500 ticks
86
  losing <=4 units. The central corridor garrison (heavy at
87
- (55, 20โ€“22)) out-trades you 1-for-1; pushing through is
88
- possible but spends the whole loss budget and may run the
89
- clock. The northern detour (light picket at (55, 5)) costs
90
- ~600 extra ticks but only ~1 unit. Decision is route choice.
91
  overrides: {}
92
  win_condition:
93
  all_of:
 
10
  # detour. Brute-forcing the corridor reduces the force below the
11
  # attrition cap before reaching the objective.
12
  #
13
+ # Difficulty escalates the cost of the wrong call: on every tier the
14
+ # corridor garrison is sized so ANY engagement with it (full push or
15
+ # probe-then-bounce) loses more than that tier's attrition cap before
16
+ # the objective is reached โ€” the corridor must be rejected, not
17
+ # sampled. Medium tightens the cap; hard also shrinks the agent force.
18
 
19
  meta:
20
  id: risk-blockade-bypass
 
63
  - {type: 1tnk, owner: agent, position: [8, 22], stance: 3, count: 3}
64
  - {type: jeep, owner: agent, position: [6, 18], stance: 3, count: 2}
65
  # Heavy CORRIDOR garrison (the wrong way): trades well, expensive
66
+ # to clear. Combination of e3 (rockets) + defensive guns. Sized so
67
+ # any play that engages it loses more than the easy attrition cap
68
+ # (>4) before the objective is reached. A forward e3 block at
69
+ # x~46 (stance:2 โ€” holds the lane, does not suicide-lunge) makes
70
+ # even a partial corridor commit (probe-then-bounce) bleed past
71
+ # the cap โ€” the corridor must be REJECTED, not sampled.
72
+ - {type: e3, owner: enemy, position: [46, 20], stance: 2, count: 8}
73
+ - {type: e3, owner: enemy, position: [55, 20], stance: 2, count: 6}
74
+ - {type: gun, owner: enemy, position: [57, 22], stance: 3, count: 3}
75
+ - {type: e1, owner: enemy, position: [55, 22], stance: 2, count: 4}
76
  # Light DETOUR garrison (the right way, top-N): one infantry
77
  # picket; clears at low cost.
78
  - {type: e1, owner: enemy, position: [55, 5], stance: 2, count: 2}
 
90
  description: >
91
  Reach the objective region near (110, 20) within 3500 ticks
92
  losing <=4 units. The central corridor garrison (heavy at
93
+ (46โ€“57, 20โ€“22)) out-trades you badly; pushing or even probing
94
+ it bleeds past the loss budget before the objective. The
95
+ northern detour (light picket at (55, 5)) costs ~600 extra
96
+ ticks but only ~1 unit. Decision is route choice.
97
  overrides: {}
98
  win_condition:
99
  all_of:
openra_bench/scenarios/packs/scout-count-defenders.yaml CHANGED
@@ -20,16 +20,27 @@
20
  # โ€ข Pre-placed agent base: fact + proc + powr + weap (+ fix for
21
  # 2tnk tech prereq) + 2ร— jeep scouts.
22
  # โ€ข Pre-placed enemy defenders at the far-east `fact` landmark:
23
- # K medium tanks (2tnk, stance:2 โ€” hold post) PLUS two
24
- # pillboxes (pbox) for
25
- # extra defensive firepower. The pillboxes don't count toward
26
- # `enemies_discovered_gte:K` (they're buildings) or
27
  # `units_killed_gte:K` (also buildings) โ€” they are pure
28
  # attrition-multiplier that prevents the under-build (2 tanks)
29
- # from rushing through. Without the pillboxes a 2-medium-tank
30
- # attacker can beat 3-4 medium-tank defenders cleanly; with the
31
- # pillboxes' extra ~30 dps each, 2 attackers are wiped before
32
- # scoring kills.
 
 
 
 
 
 
 
 
 
 
 
33
  # โ€ข Cash: $5100 โ€” funds up to 6ร— 2tnk ($850 each). Tuned so the
34
  # intended K-tanks build fits, and the over-build (queue all 6
35
  # before sending) bleeds the production clock past the deadline.
@@ -85,9 +96,10 @@
85
  # Kโ‰ฅ3 levels.
86
  # โ€ข build-max-force (queue all 6 medium tanks before sending):
87
  # LOSS on every level โ€” the 6-tank sequential queue takes
88
- # ~3240 ticks plus ~1350 transit + 300 combat = ~4890 ticks,
89
- # beyond every level's within_ticks deadline (max 4200 on
90
- # hard). The over-build is a clean LOSS on every (level, seed).
 
91
  # โ€ข intended count-then-build (scout to count K, queue exactly K
92
  # tanks, attack-move east after all built): WINS on every
93
  # (level, seed).
@@ -97,10 +109,11 @@
97
  # โ€ข 2tnk build ~540 ticks each (sequential weap queue).
98
  # โ€ข 2tnk transit ~15 ticks/cell ร— ~90 cells = ~1350 ticks.
99
  # โ€ข Combat resolution against K defenders โ‰ˆ 300 ticks.
100
- # โ€ข Intended K=2 finishes โ‰ˆ tick 2883 (2ร—540+1350+300 โ‰ˆ 2730);
101
- # within_ticks 3300 accepts with slack.
102
  # โ€ข Intended K=3 finishes โ‰ˆ tick 3333; within_ticks 3700 accepts.
103
- # โ€ข Intended K=4 finishes โ‰ˆ tick 3783; within_ticks 4200 accepts.
 
104
  # โ€ข Over-build 6 finishes โ‰ˆ tick 4890 (6ร—540+1350+300 = 4890),
105
  # beyond ALL within_ticks deadlines โ‡’ LOSS on every level.
106
  #
@@ -206,8 +219,11 @@ levels:
206
  - {type: jeep, owner: agent, position: [22, 19], stance: 1}
207
  - {type: jeep, owner: agent, position: [22, 21], stance: 1}
208
  # K=2 medium-tank defenders + 2 pillboxes for reinforcing
209
- # fire. stance:2 (Defend โ€” hold post) on the tanks.
210
- - {type: 2tnk, owner: enemy, position: [110, 20], stance: 2, count: 2}
 
 
 
211
  - {type: pbox, owner: enemy, position: [108, 19]}
212
  - {type: pbox, owner: enemy, position: [108, 21]}
213
  # Unarmed enemy `fact` landmark โ€” MustBeDestroyed, keeps
@@ -226,16 +242,16 @@ levels:
226
  max_turns: 38
227
 
228
  # โ”€โ”€ MEDIUM โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
229
- # +1 axis: FOG + larger K. K=3 hidden medium-tank defenders + 2
230
- # pillboxes. The brief does NOT name the count. The agent must
231
  # SCOUT (drive a jeep east) to read K=3 before committing the
232
  # build. Stall LOSES; under-build (assume K=2, field 2 tanks)
233
  # loses the attrition trade (kills < 3 โ‡’ LOSS); over-build
234
  # (queue 6) misses the deadline.
235
  medium:
236
  description: >
237
- An UNKNOWN number of enemy medium tanks (2tnk) plus two
238
- reinforcing pillboxes defend the far-east enemy construction
239
  yard in the fog. Your pre-placed base (fact, proc, powr,
240
  weap, fix) plus two jeep scouts and $5100 in cash funds up to
241
  6 medium tanks ($850 each) but you should build EXACTLY
@@ -256,10 +272,13 @@ levels:
256
  - {type: fix, owner: agent, position: [18, 22]}
257
  - {type: jeep, owner: agent, position: [22, 19], stance: 1}
258
  - {type: jeep, owner: agent, position: [22, 21], stance: 1}
259
- # K=3 medium-tank defenders + 2 pillboxes.
260
- - {type: 2tnk, owner: enemy, position: [110, 20], stance: 2, count: 3}
 
 
 
 
261
  - {type: pbox, owner: enemy, position: [108, 19]}
262
- - {type: pbox, owner: enemy, position: [108, 21]}
263
  - {type: fact, owner: enemy, position: [120, 20]}
264
  win_condition:
265
  all_of:
@@ -296,7 +315,7 @@ levels:
296
  K=2) loses the attrition trade and the kill bar; over-build
297
  (queue 6) misses the deadline. Win when โ‰ฅ4 enemy units
298
  discovered AND โ‰ฅ4 killed AND your fact is still standing AND
299
- tick โ‰ค 4200.
300
  starting_cash: 5100
301
  overrides:
302
  actors:
@@ -316,10 +335,10 @@ levels:
316
  - {type: fix, owner: agent, position: [18, 28], spawn_point: 1}
317
  - {type: jeep, owner: agent, position: [22, 25], stance: 1, spawn_point: 1}
318
  - {type: jeep, owner: agent, position: [22, 27], stance: 1, spawn_point: 1}
319
- # K=4 medium-tank defenders centred at mid-y; the cloud
320
- # sits at y=20 so either spawn must scout from its actual
321
- # base latitude. stance:2 Defend (hold post).
322
- - {type: 2tnk, owner: enemy, position: [110, 20], stance: 2, count: 4}
323
  - {type: pbox, owner: enemy, position: [108, 19]}
324
  - {type: pbox, owner: enemy, position: [108, 21]}
325
  # Persistent unarmed enemy `fact` marker โ€” MustBeDestroyed,
@@ -330,9 +349,9 @@ levels:
330
  - {enemies_discovered_gte: 4}
331
  - {units_killed_gte: 4}
332
  - {building_count_gte: {type: fact, n: 1}}
333
- - {within_ticks: 4200}
334
  fail_condition:
335
  any_of:
336
- - {after_ticks: 4201}
337
  - {not: {building_count_gte: {type: fact, n: 1}}}
338
- max_turns: 48
 
20
  # โ€ข Pre-placed agent base: fact + proc + powr + weap (+ fix for
21
  # 2tnk tech prereq) + 2ร— jeep scouts.
22
  # โ€ข Pre-placed enemy defenders at the far-east `fact` landmark:
23
+ # K half-strength medium tanks (2tnk, stance:2 โ€” hold post,
24
+ # health: 60) PLUS pillboxes (pbox) for extra defensive
25
+ # firepower โ€” 2 on easy/hard, 1 on medium. The pillboxes don't
26
+ # count toward `enemies_discovered_gte:K` (they're buildings) or
27
  # `units_killed_gte:K` (also buildings) โ€” they are pure
28
  # attrition-multiplier that prevents the under-build (2 tanks)
29
+ # from rushing through.
30
+ #
31
+ # โ€ข SPAWN-SPREAD CALIBRATION (engine `count:` fix): the engine
32
+ # `count:N` expansion used to STACK all N defenders on the
33
+ # single declared cell; it now SPREADS them across N distinct
34
+ # cells (copy 0 at the anchor, copies 1..N-1 in outward rings).
35
+ # The spread tightened the K defenders into a focus-firing
36
+ # cluster, which won the attrition trade even at equal HP and
37
+ # equal count โ€” a full-HP K-tank defender cluster beats a
38
+ # count-matched K-tank attacker column. The defenders are now a
39
+ # half-strength garrison (`health: 60`) so the count-matched
40
+ # attacker force wins the trade while an under-build (2 tanks)
41
+ # against Kโ‰ฅ3 still loses; medium drops to 1 pillbox because a
42
+ # second pbox over-tunes the post-spread defence past what a
43
+ # K=3 attacker wave can beat.
44
  # โ€ข Cash: $5100 โ€” funds up to 6ร— 2tnk ($850 each). Tuned so the
45
  # intended K-tanks build fits, and the over-build (queue all 6
46
  # before sending) bleeds the production clock past the deadline.
 
96
  # Kโ‰ฅ3 levels.
97
  # โ€ข build-max-force (queue all 6 medium tanks before sending):
98
  # LOSS on every level โ€” the 6-tank sequential queue takes
99
+ # ~3240 ticks plus ~1350 transit + combat, so the engagement
100
+ # cannot even START before every level's within_ticks deadline
101
+ # (3300 easy / 3700 medium / 4500 hard). The over-build is a
102
+ # clean LOSS on every (level, seed).
103
  # โ€ข intended count-then-build (scout to count K, queue exactly K
104
  # tanks, attack-move east after all built): WINS on every
105
  # (level, seed).
 
109
  # โ€ข 2tnk build ~540 ticks each (sequential weap queue).
110
  # โ€ข 2tnk transit ~15 ticks/cell ร— ~90 cells = ~1350 ticks.
111
  # โ€ข Combat resolution against K defenders โ‰ˆ 300 ticks.
112
+ # โ€ข Intended K=2 finishes โ‰ˆ tick 2793; within_ticks 3300 accepts
113
+ # with slack.
114
  # โ€ข Intended K=3 finishes โ‰ˆ tick 3333; within_ticks 3700 accepts.
115
+ # โ€ข Intended K=4 finishes โ‰ˆ tick 3783โ€“4143 (spawn-latitude
116
+ # dependent on hard); within_ticks 4500 accepts with slack.
117
  # โ€ข Over-build 6 finishes โ‰ˆ tick 4890 (6ร—540+1350+300 = 4890),
118
  # beyond ALL within_ticks deadlines โ‡’ LOSS on every level.
119
  #
 
219
  - {type: jeep, owner: agent, position: [22, 19], stance: 1}
220
  - {type: jeep, owner: agent, position: [22, 21], stance: 1}
221
  # K=2 medium-tank defenders + 2 pillboxes for reinforcing
222
+ # fire. stance:2 (Defend โ€” hold post) on the tanks. The
223
+ # defenders are a half-strength garrison (health: 60) so the
224
+ # count-matched attacker force wins the trade โ€” see the
225
+ # spawn-spread calibration note in the header.
226
+ - {type: 2tnk, owner: enemy, position: [110, 20], stance: 2, count: 2, health: 60}
227
  - {type: pbox, owner: enemy, position: [108, 19]}
228
  - {type: pbox, owner: enemy, position: [108, 21]}
229
  # Unarmed enemy `fact` landmark โ€” MustBeDestroyed, keeps
 
242
  max_turns: 38
243
 
244
  # โ”€โ”€ MEDIUM โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
245
+ # +1 axis: FOG + larger K. K=3 hidden medium-tank defenders + 1
246
+ # pillbox. The brief does NOT name the count. The agent must
247
  # SCOUT (drive a jeep east) to read K=3 before committing the
248
  # build. Stall LOSES; under-build (assume K=2, field 2 tanks)
249
  # loses the attrition trade (kills < 3 โ‡’ LOSS); over-build
250
  # (queue 6) misses the deadline.
251
  medium:
252
  description: >
253
+ An UNKNOWN number of enemy medium tanks (2tnk) plus a
254
+ reinforcing pillbox defend the far-east enemy construction
255
  yard in the fog. Your pre-placed base (fact, proc, powr,
256
  weap, fix) plus two jeep scouts and $5100 in cash funds up to
257
  6 medium tanks ($850 each) but you should build EXACTLY
 
272
  - {type: fix, owner: agent, position: [18, 22]}
273
  - {type: jeep, owner: agent, position: [22, 19], stance: 1}
274
  - {type: jeep, owner: agent, position: [22, 21], stance: 1}
275
+ # K=3 half-strength medium-tank defenders + 1 pillbox. One
276
+ # pillbox (not two) on medium: a second pbox over-tunes the
277
+ # defence so the count-matched K=3 attacker wave can no
278
+ # longer win the trade after the count-spread tightened the
279
+ # defender cluster โ€” see the header calibration note.
280
+ - {type: 2tnk, owner: enemy, position: [110, 20], stance: 2, count: 3, health: 60}
281
  - {type: pbox, owner: enemy, position: [108, 19]}
 
282
  - {type: fact, owner: enemy, position: [120, 20]}
283
  win_condition:
284
  all_of:
 
315
  K=2) loses the attrition trade and the kill bar; over-build
316
  (queue 6) misses the deadline. Win when โ‰ฅ4 enemy units
317
  discovered AND โ‰ฅ4 killed AND your fact is still standing AND
318
+ tick โ‰ค 4500.
319
  starting_cash: 5100
320
  overrides:
321
  actors:
 
335
  - {type: fix, owner: agent, position: [18, 28], spawn_point: 1}
336
  - {type: jeep, owner: agent, position: [22, 25], stance: 1, spawn_point: 1}
337
  - {type: jeep, owner: agent, position: [22, 27], stance: 1, spawn_point: 1}
338
+ # K=4 half-strength medium-tank defenders centred at mid-y;
339
+ # the cloud sits at y=20 so either spawn must scout from its
340
+ # actual base latitude. stance:2 Defend (hold post).
341
+ - {type: 2tnk, owner: enemy, position: [110, 20], stance: 2, count: 4, health: 60}
342
  - {type: pbox, owner: enemy, position: [108, 19]}
343
  - {type: pbox, owner: enemy, position: [108, 21]}
344
  # Persistent unarmed enemy `fact` marker โ€” MustBeDestroyed,
 
349
  - {enemies_discovered_gte: 4}
350
  - {units_killed_gte: 4}
351
  - {building_count_gte: {type: fact, n: 1}}
352
+ - {within_ticks: 4500}
353
  fail_condition:
354
  any_of:
355
+ - {after_ticks: 4501}
356
  - {not: {building_count_gte: {type: fact, n: 1}}}
357
+ max_turns: 52
openra_bench/scenarios/packs/tp-survive-n-turns.yaml CHANGED
@@ -249,7 +249,7 @@ levels:
249
  max_turns: 45
250
 
251
  # โ”€โ”€ MEDIUM โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
252
- # +1 controlled variable: HEAVIER sustained pressure (FOUR
253
  # scheduled rifle waves on top of the opening wave) and a longer
254
  # hold (T=3600, ~turn 41). N stays at 4 of 6. max_turns 55 โ†’
255
  # reachable tick 93 + 90ยท54 = 4953 > the within_ticks 4500 band
@@ -288,40 +288,45 @@ levels:
288
  # FOUR scheduled reinforcement waves โ€” sustained pressure
289
  # across the whole window. Block sits inside `overrides:` (the
290
  # loader drops it at the level root). Pure e1 (no e3) for the
291
- # same reason as the opening wave. The 6ร—3 opening + four 6ร—3
292
- # waves are re-tuned so spread (stall) fire loses the fact and
293
- # the focused active defence holds it with โ‰ฅ4 tanks.
 
 
 
 
 
294
  scheduled_events:
295
  - tick: 700
296
  type: spawn_actors
297
  actors:
298
- - {type: e1, owner: enemy, position: [64, 18], stance: 3, count: 6}
299
- - {type: e1, owner: enemy, position: [64, 20], stance: 3, count: 6}
300
- - {type: e1, owner: enemy, position: [64, 22], stance: 3, count: 6}
301
  - tick: 1300
302
  type: spawn_actors
303
  actors:
304
- - {type: e1, owner: enemy, position: [64, 18], stance: 3, count: 6}
305
- - {type: e1, owner: enemy, position: [64, 20], stance: 3, count: 6}
306
- - {type: e1, owner: enemy, position: [64, 22], stance: 3, count: 6}
307
  - tick: 1900
308
  type: spawn_actors
309
  actors:
310
- - {type: e1, owner: enemy, position: [64, 18], stance: 3, count: 6}
311
- - {type: e1, owner: enemy, position: [64, 20], stance: 3, count: 6}
312
- - {type: e1, owner: enemy, position: [64, 22], stance: 3, count: 6}
313
  - tick: 2500
314
  type: spawn_actors
315
  actors:
316
- - {type: e1, owner: enemy, position: [64, 18], stance: 3, count: 6}
317
- - {type: e1, owner: enemy, position: [64, 20], stance: 3, count: 6}
318
- - {type: e1, owner: enemy, position: [64, 22], stance: 3, count: 6}
319
  - tick: 3100
320
  type: spawn_actors
321
  actors:
322
- - {type: e1, owner: enemy, position: [64, 18], stance: 3, count: 6}
323
- - {type: e1, owner: enemy, position: [64, 20], stance: 3, count: 6}
324
- - {type: e1, owner: enemy, position: [64, 22], stance: 3, count: 6}
325
  win_condition:
326
  all_of:
327
  - {building_count_gte: {type: fact, n: 1}}
 
249
  max_turns: 45
250
 
251
  # โ”€โ”€ MEDIUM โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
252
+ # +1 controlled variable: HEAVIER sustained pressure (FIVE
253
  # scheduled rifle waves on top of the opening wave) and a longer
254
  # hold (T=3600, ~turn 41). N stays at 4 of 6. max_turns 55 โ†’
255
  # reachable tick 93 + 90ยท54 = 4953 > the within_ticks 4500 band
 
288
  # FOUR scheduled reinforcement waves โ€” sustained pressure
289
  # across the whole window. Block sits inside `overrides:` (the
290
  # loader drops it at the level root). Pure e1 (no e3) for the
291
+ # same reason as the opening wave. After the engine `count:`
292
+ # spawn-spread fix (copies 1..N now SPREAD across outward rings
293
+ # instead of stacking on the anchor) the rifle band lands a few
294
+ # cells nearer the base, so a 6-per-row reinforcement wave
295
+ # tipped the intended active defence below the 4-tank floor.
296
+ # Re-tuned to 5 per row (15 e1/wave): the 6ร—3 opening + five
297
+ # 5ร—3 waves keep spread (stall) fire losing the fact while the
298
+ # focused active defence holds it with โ‰ฅ4 tanks.
299
  scheduled_events:
300
  - tick: 700
301
  type: spawn_actors
302
  actors:
303
+ - {type: e1, owner: enemy, position: [64, 18], stance: 3, count: 5}
304
+ - {type: e1, owner: enemy, position: [64, 20], stance: 3, count: 5}
305
+ - {type: e1, owner: enemy, position: [64, 22], stance: 3, count: 5}
306
  - tick: 1300
307
  type: spawn_actors
308
  actors:
309
+ - {type: e1, owner: enemy, position: [64, 18], stance: 3, count: 5}
310
+ - {type: e1, owner: enemy, position: [64, 20], stance: 3, count: 5}
311
+ - {type: e1, owner: enemy, position: [64, 22], stance: 3, count: 5}
312
  - tick: 1900
313
  type: spawn_actors
314
  actors:
315
+ - {type: e1, owner: enemy, position: [64, 18], stance: 3, count: 5}
316
+ - {type: e1, owner: enemy, position: [64, 20], stance: 3, count: 5}
317
+ - {type: e1, owner: enemy, position: [64, 22], stance: 3, count: 5}
318
  - tick: 2500
319
  type: spawn_actors
320
  actors:
321
+ - {type: e1, owner: enemy, position: [64, 18], stance: 3, count: 5}
322
+ - {type: e1, owner: enemy, position: [64, 20], stance: 3, count: 5}
323
+ - {type: e1, owner: enemy, position: [64, 22], stance: 3, count: 5}
324
  - tick: 3100
325
  type: spawn_actors
326
  actors:
327
+ - {type: e1, owner: enemy, position: [64, 18], stance: 3, count: 5}
328
+ - {type: e1, owner: enemy, position: [64, 20], stance: 3, count: 5}
329
+ - {type: e1, owner: enemy, position: [64, 22], stance: 3, count: 5}
330
  win_condition:
331
  all_of:
332
  - {building_count_gte: {type: fact, n: 1}}
tests/test_combat_bait_counter_attack.py CHANGED
@@ -181,7 +181,8 @@ def test_timeout_reachable_inside_max_turns():
181
 
182
  def test_hard_has_two_spawn_point_groups():
183
  """Hard-tier curation: โ‰ฅ2 distinct agent spawn_point groups so the
184
- seed round-robins the staging latitude (north y=10 / south y=30).
 
185
  Engine-roundtrip is asserted by tests/test_hard_tier.py."""
186
  c = compile_level(load_pack(PACK_PATH), "hard")
187
  groups = {
 
181
 
182
  def test_hard_has_two_spawn_point_groups():
183
  """Hard-tier curation: โ‰ฅ2 distinct agent spawn_point groups so the
184
+ seed round-robins the staging DISTANCE from the yard (far west
185
+ x=6 / forward x=18, both on the north flank y=12).
186
  Engine-roundtrip is asserted by tests/test_hard_tier.py."""
187
  c = compile_level(load_pack(PACK_PATH), "hard")
188
  groups = {
tests/test_mid_concede_vs_hold.py CHANGED
@@ -33,6 +33,17 @@ dropped (the win is a survival-band check), a `not proc:1` fail
33
  clause, a persistent unarmed enemy `fact` marker (anti auto-DRAW),
34
  the hard survival floor at tick 2400 and its attrition cap at 18.
35
 
 
 
 
 
 
 
 
 
 
 
 
36
  The capability stays load-bearing โ€” stall / split / oscillate /
37
  wrong-side consolidate all LOSE; only consolidate-on-the-light-side
38
  AND attack-move into the push WINS.
 
33
  clause, a persistent unarmed enemy `fact` marker (anti auto-DRAW),
34
  the hard survival floor at tick 2400 and its attrition cap at 18.
35
 
36
+ Spawn-spread recalibration (engine `count:` fix): the engine used
37
+ to stack all N units of a `count:N` group on the single declared
38
+ cell; it now SPREADS them across N distinct cells. On HARD the
39
+ scattered 5-unit flex squad slightly improved the WEST garrison +
40
+ HALF-flex trade, so split-defend (3 of 5 flex sent WEST) suddenly
41
+ held the WEST base and WON โ€” the "laziest play wins" inversion this
42
+ pack had previously closed. Hard's WEST light push was raised
43
+ 20->26 to re-tighten that trade: the bare garrison + any partial
44
+ flex (split / oscillate) is overrun, while the garrison + the FULL
45
+ consolidated flex still clears it and holds past the 2400 floor.
46
+
47
  The capability stays load-bearing โ€” stall / split / oscillate /
48
  wrong-side consolidate all LOSE; only consolidate-on-the-light-side
49
  AND attack-move into the push WINS.
tests/test_scout_count_defenders.py CHANGED
@@ -1,25 +1,33 @@
1
  """scout-count-defenders pack โ€” Wave-8 PERCEPTION exact-count force
2
  sizing.
3
 
4
- The agent scouts to count K medium-tank enemy defenders (2tnk),
5
- backed by 2 reinforcing pillboxes (pbox) for extra defensive DPS,
 
6
  then builds EXACTLY K medium tanks (2tnk) to defeat them. The
7
  pillboxes are STRUCTURES (counted as buildings, not units) so they
8
  do NOT count toward `enemies_discovered_gte:K` or
9
  `units_killed_gte:K`; they are pure attrition multiplier that
10
  prevents the under-build (2 tanks) from rushing through.
11
 
 
 
 
 
 
 
12
  Discriminations (CLAUDE.md bar):
13
 
14
  * stall (only observe): LOSS โ€” after_ticks fail clause bites.
15
  * build-min-force (always assume K=2, build 2 tanks regardless of
16
  actual count): LOSS on medium (K=3) and hard (K=4) โ€” the 2-tank
17
- wave is wiped by the K-defender + 2-pbox combination before
18
  scoring K kills.
19
  * build-max-force (queue all OVER_BUILD_N=6 tanks then send):
20
  LOSS on every level โ€” the 6-tank sequential queue takes ~3240
21
- ticks plus ~1350 transit + ~300 combat = ~4890 ticks, beyond
22
- every level's within_ticks deadline (max 4200 on hard).
 
23
  * intended count-then-build (scout to read K, build exactly K
24
  tanks, attack-move east): WINS on every (level, seed).
25
 
@@ -245,7 +253,7 @@ def _intended_policy(level: str):
245
  def _under_build_policy():
246
  """Always build exactly 2 tanks regardless of K. WINS on easy
247
  (K=2 โ€” that's the matching amount) and LOSES on medium/hard
248
- (K=3/K=4) โ€” 2 medium tanks vs 3-4 defending tanks + 2 pillboxes
249
  is an attrition trade the attackers lose."""
250
  state = {"queued": 0, "sent": False, "scouted": False}
251
 
 
1
  """scout-count-defenders pack โ€” Wave-8 PERCEPTION exact-count force
2
  sizing.
3
 
4
+ The agent scouts to count K half-strength medium-tank enemy
5
+ defenders (2tnk, health: 60), backed by reinforcing pillboxes
6
+ (pbox โ€” 2 on easy/hard, 1 on medium) for extra defensive DPS,
7
  then builds EXACTLY K medium tanks (2tnk) to defeat them. The
8
  pillboxes are STRUCTURES (counted as buildings, not units) so they
9
  do NOT count toward `enemies_discovered_gte:K` or
10
  `units_killed_gte:K`; they are pure attrition multiplier that
11
  prevents the under-build (2 tanks) from rushing through.
12
 
13
+ The engine `count:` expansion now SPREADS the defender cluster
14
+ across distinct cells (previously stacked on one cell); the spread
15
+ tightened the defenders into a focus-firing cluster, so the
16
+ defenders are a half-strength garrison (`health: 60`) to keep the
17
+ count-matched attacker force winning the trade.
18
+
19
  Discriminations (CLAUDE.md bar):
20
 
21
  * stall (only observe): LOSS โ€” after_ticks fail clause bites.
22
  * build-min-force (always assume K=2, build 2 tanks regardless of
23
  actual count): LOSS on medium (K=3) and hard (K=4) โ€” the 2-tank
24
+ wave is wiped by the K-defender + pbox combination before
25
  scoring K kills.
26
  * build-max-force (queue all OVER_BUILD_N=6 tanks then send):
27
  LOSS on every level โ€” the 6-tank sequential queue takes ~3240
28
+ ticks plus ~1350 transit + combat, so the engagement cannot
29
+ even start before every level's within_ticks deadline (max
30
+ 4500 on hard).
31
  * intended count-then-build (scout to read K, build exactly K
32
  tanks, attack-move east): WINS on every (level, seed).
33
 
 
253
  def _under_build_policy():
254
  """Always build exactly 2 tanks regardless of K. WINS on easy
255
  (K=2 โ€” that's the matching amount) and LOSES on medium/hard
256
+ (K=3/K=4) โ€” 2 medium tanks vs 3-4 defending tanks + pillboxes
257
  is an attrition trade the attackers lose."""
258
  state = {"queued": 0, "sent": False, "scouted": False}
259