yxc20098 commited on
Commit
248d766
·
1 Parent(s): 0eecda5

fix(scenario): combat-target-priority-highvalue — recalibrate after engine movement fixes

Browse files
openra_bench/scenarios/packs/combat-target-priority-highvalue.yaml CHANGED
@@ -19,37 +19,38 @@
19
  # never close; the kill bar (units_killed_gte:K) is never met and
20
  # the after_ticks fail clause fires → LOSS.
21
  # • brute attack_move (attack-move onto the cluster centroid): the
22
- # engine auto-targets the NEAREST hostile the e1 chaff screen is
23
- # posted three columns closer than the rocket soldiers, so the
24
- # squad chews the chaff first while the three e3s fire anti-armour
25
- # rockets through the whole engagement; all four tanks are lost
26
- # → busts `own_units_gte` → LOSS.
27
  # • kill-chaff-first (attack_unit the e1 chaff one at a time): the
28
- # three e3 rockets keep landing through the entire chaff mop-up;
29
- # ~3 tanks die before the rockets are silenced → busts the
30
- # survival floor LOSS.
 
 
 
 
 
31
  # • intended focus-threats-first: attack_unit ALL FOUR tanks onto
32
- # ONE e3 at a time — 4 tank cannons concentrate ~400 HP of armour
33
- # against ~50 HP of infantry, so each e3 dies in 1-2 decision
34
- # turns before it can land more than ~1 rocket; with all three
35
- # rockets silenced inside the first ~3 turns the chaff cannot
36
- # scratch the squad → mop up the e1s → kill bar met, the squad
37
- # stays above the survival floor → WIN.
38
  #
39
- # Why the spec works (verified against the engine combat sheet):
40
- # e3 = anti-tank rocket soldier (Dragon launcher, range ~5, high
41
- # anti-armour DPS). THREE e3 firing unopposed wipe a 4-tank squad
42
- # in ~540 ticks (~6 decision turns) measured. Left firing
43
- # through a chaff mop-up they reliably kill ~3 tanks.
44
- # e1 = basic rifle infantry. Per-shot damage vs 2tnk armour is
45
- # low; the e1 chaff alone cannot drop the squad below the floor
46
- # inside the budget once the three e3s are dead.
47
- # 2tnk concentrated on a single e3 kill it in 1-2 decision
48
- # turns; the same fire SPREAD across the cluster kills the first
49
- # e3 in 4+ turns, granting all three rockets free anti-tank
50
- # shots. That focus-vs-spread delta is the discrimination.
51
- # Measured (deterministic): chaff-first loses 3 tanks, focus-
52
- # threats-first loses 0, brute loses all 4, on every level.
 
53
  #
54
  # Engine guardrails:
55
  # • Map: rush-hour-arena (128 × 40, playable x in [2..126], y in
@@ -201,29 +202,32 @@ levels:
201
  max_turns: 30
202
 
203
  # ── MEDIUM ───────────────────────────────────────────────────────
204
- # +1 axis vs easy: a TIGHTER survival floor the squad must keep
205
- # ALL FOUR tanks alive (own_units_gte:4, zero loss). The cluster is
206
- # the same 9 e1 + 3 e3; the controlled variable is purely the
207
- # zero-loss floor. A brute and a kill-chaff-first play both LOSE:
208
- # the three e3s firing through the chaff mop-up score ~3 tank kills
209
- # and bust the zero-loss floor. The intended focus-threats-first
210
- # WINS concentrating all four tanks on each rocket soldier in
211
- # turn ends it in 1-2 decision ticks before it can land more than
212
- # ~1 rocket, preserving the whole squad.
 
 
 
 
213
  medium:
214
  description: >
215
  Four medium tanks stage at x=48, y=18..21 with your
216
  construction yard behind them. The eastern enemy cluster is a
217
- compact screen of NINE rifle infantry (e1, cheap chaff) at
218
  x=66 backed by THREE rocket soldiers (e3, anti-tank Dragon
219
  launcher — the high-threat units) at x=69. Threat-priority
220
  doctrine: concentrate ALL FOUR tanks' fire on ONE rocket
221
- soldier first (kill in 1-2 decision turns), then the second,
222
- then the third, THEN mop up the rifle chaff. Engaging the
223
- chaff first via attack_move auto-targeting the nearer rifle
224
- screen, or by attacking the cheap infantry first leaves the
225
- rocket soldiers firing through the entire mop-up and they kill
226
- your tanks. Win when 12 enemy units are killed AND all four
227
  tanks survive AND your construction yard still stands, before
228
  tick 2700.
229
  overrides:
@@ -233,9 +237,10 @@ levels:
233
  - {type: 2tnk, owner: agent, position: [48, 20], stance: 1}
234
  - {type: 2tnk, owner: agent, position: [48, 21], stance: 1}
235
  - {type: fact, owner: agent, position: [4, 20]}
236
- # Compact contiguous screen of NINE cheap rifle infantry
237
- # along x=66, y=15..23 (engine auto-target picks the nearest
238
  # hostile under attack_move).
 
239
  - {type: e1, owner: enemy, position: [66, 15], stance: 2}
240
  - {type: e1, owner: enemy, position: [66, 16], stance: 2}
241
  - {type: e1, owner: enemy, position: [66, 17], stance: 2}
@@ -245,53 +250,54 @@ levels:
245
  - {type: e1, owner: enemy, position: [66, 21], stance: 2}
246
  - {type: e1, owner: enemy, position: [66, 22], stance: 2}
247
  - {type: e1, owner: enemy, position: [66, 23], stance: 2}
 
 
248
  # THREE rocket soldiers behind the chaff screen at x=69 —
249
  # inside Dragon range 5 of the contact line, not the nearest
250
  # target (e1s at x=66 are 3 cells closer ⇒ engine auto-
251
- # target picks the chaff under attack_move). With 3 e3s
252
- # firing through the chaff mop-up a brute or chaff-first
253
- # play takes ~3 tank kills and busts the zero-loss floor.
254
  - {type: e3, owner: enemy, position: [69, 19], stance: 2}
255
  - {type: e3, owner: enemy, position: [69, 20], stance: 2}
256
  - {type: e3, owner: enemy, position: [69, 21], stance: 2}
257
  - {type: fact, owner: enemy, position: [120, 20]}
258
  win_condition:
259
  all_of:
260
- - {units_killed_gte: 12}
261
- - {own_units_gte: 4}
262
  - {within_ticks: 2700}
263
  - {building_count_gte: {type: fact, n: 1}}
264
- # Fail floor aligned to the zero-loss win floor (own_units_gte:4)
265
- # any tank lost (own≤3) is a real LOSS, not a dead-zone DRAW.
266
  fail_condition:
267
  any_of:
268
  - {after_ticks: 2701}
269
- - {not: {own_units_gte: 4}}
270
  max_turns: 30
271
 
272
  # ── HARD ─────────────────────────────────────────────────────────
273
  # +1 axis vs medium: the strike force (and its construction yard)
274
  # stage from TWO seed-driven spawn_point groups (NORTH corridor
275
  # y=10..13 OR SOUTH corridor y=27..30) so the engagement geometry
276
- # varies per seed. The chaff screen (10 e1, y=15..24) + rocket trio
277
- # is symmetric across y=20 both spawns face the same priority
278
- # decision (focus one e3 next next mop up chaff). The
279
- # zero-loss survival floor from medium is retained.
 
280
  hard:
281
  description: >
282
  Four medium tanks stage at ONE of two corridors (NORTH
283
  y=10..13 OR SOUTH y=27..30, chosen by seed, anti-
284
  memorisation) with your construction yard behind them. The
285
- eastern enemy cluster is a compact screen of TEN rifle
286
  infantry (e1, cheap chaff) at x=66 backed by THREE rocket
287
  soldiers (e3, anti-tank — the high-threat units) at x=69.
288
  Threat-priority doctrine: concentrate ALL FOUR tanks on ONE
289
  rocket soldier at a time — kill it cleanly, then the next,
290
  then the third, THEN mop up the rifle chaff. Starting on the
291
  rifle chaff leaves the rockets firing through the whole
292
- engagement and they kill your tanks. Win when 13 enemy units
293
- are killed AND all four tanks survive AND your construction
294
- yard still stands, before tick 2700.
295
  overrides:
296
  actors:
297
  # spawn_point 0 — NORTH staging corridor (y=10..13).
@@ -306,11 +312,12 @@ levels:
306
  - {type: 2tnk, owner: agent, position: [48, 29], stance: 1, spawn_point: 1}
307
  - {type: 2tnk, owner: agent, position: [48, 30], stance: 1, spawn_point: 1}
308
  - {type: fact, owner: agent, position: [4, 29], spawn_point: 1}
309
- # Compact contiguous screen of TEN cheap rifle infantry along
310
- # x=66, y=15..24 — symmetric across y=19.5 so either spawn
311
- # faces an equivalent decision. Engine auto-target picks the
312
- # nearest hostile under attack_move ⇒ this chaff line is what
313
- # a brute engages first.
 
314
  - {type: e1, owner: enemy, position: [66, 15], stance: 2}
315
  - {type: e1, owner: enemy, position: [66, 16], stance: 2}
316
  - {type: e1, owner: enemy, position: [66, 17], stance: 2}
@@ -321,25 +328,26 @@ levels:
321
  - {type: e1, owner: enemy, position: [66, 22], stance: 2}
322
  - {type: e1, owner: enemy, position: [66, 23], stance: 2}
323
  - {type: e1, owner: enemy, position: [66, 24], stance: 2}
 
324
  # THREE rocket soldiers behind the chaff screen at x=69 —
325
  # inside Dragon range 5 of the contact line, symmetric across
326
  # y=20 so either spawn faces an equivalent focus-fire
327
  # decision. Starting on the chaff lets all three fire through
328
- # the whole mop-up and bust the zero-loss floor.
329
  - {type: e3, owner: enemy, position: [69, 19], stance: 2}
330
  - {type: e3, owner: enemy, position: [69, 20], stance: 2}
331
  - {type: e3, owner: enemy, position: [69, 21], stance: 2}
332
  - {type: fact, owner: enemy, position: [120, 20]}
333
  win_condition:
334
  all_of:
335
- - {units_killed_gte: 13}
336
- - {own_units_gte: 4}
337
  - {within_ticks: 2700}
338
  - {building_count_gte: {type: fact, n: 1}}
339
- # Fail floor aligned to the zero-loss win floor (own_units_gte:4)
340
- # any tank lost (own≤3) is a real LOSS, not a dead-zone DRAW.
341
  fail_condition:
342
  any_of:
343
  - {after_ticks: 2701}
344
- - {not: {own_units_gte: 4}}
345
  max_turns: 30
 
19
  # never close; the kill bar (units_killed_gte:K) is never met and
20
  # the after_ticks fail clause fires → LOSS.
21
  # • brute attack_move (attack-move onto the cluster centroid): the
22
+ # squad drives INTO the cluster and is enveloped it bleeds 2
23
+ # tanks and fails to clear the kill bar before the deadline
24
+ # LOSS on every level.
 
 
25
  # • kill-chaff-first (attack_unit the e1 chaff one at a time): the
26
+ # three e3 rockets keep landing anti-armour fire through the
27
+ # entire chaff mop-up; on medium/hard the squad burns the budget
28
+ # on the 12-strong rifle screen, the rockets attrit it below the
29
+ # survival floor, and it fails to clear all 15 enemies before the
30
+ # deadline → LOSS. (On EASY the cluster is small enough — 9 e1 +
31
+ # 3 e3 = 12 — that a chaff-first play can still finish; easy is
32
+ # the forgiving bare-skill tier, the load-bearing chaff-first
33
+ # LOSS is medium + hard.)
34
  # • intended focus-threats-first: attack_unit ALL FOUR tanks onto
35
+ # ONE e3 at a time — silences the three rocket soldiers FAST,
36
+ # then mops up the rifle screen; kill bar met, squad stays above
37
+ # the survival floor WIN on every level.
 
 
 
38
  #
39
+ # RECALIBRATION FINDING (engine movement fixes moving units take
40
+ # fire en route, attack_unit on out-of-sight targets paths normally,
41
+ # no sprint-invincibility):
42
+ # With the post-fix combat model the squad takes ~the same number
43
+ # of tank losses regardless of fine target order (concentrating
44
+ # fire on a bunched stack ALSO bunches the stack's return-fire
45
+ # exposure). The survivor-count delta the pack originally relied on
46
+ # for the chaff-vs-threat distinction is much weaker; the survival
47
+ # floor is therefore own_units_gte:3 (allow the ~1 loss a perfect
48
+ # focus engagement still takes closing the distance), NOT the old
49
+ # zero-loss own_units_gte:4 which became unsatisfiable. The
50
+ # load-bearing chaff-vs-threat discrimination is restored on
51
+ # medium + hard via a BIGGER cluster (12 e1 + 3 e3 = 15, kill bar
52
+ # 15): a chaff-first play cannot clear all 15 in budget while the
53
+ # three e3 attrit it — it busts the kill bar AND the floor.
54
  #
55
  # Engine guardrails:
56
  # • Map: rush-hour-arena (128 × 40, playable x in [2..126], y in
 
202
  max_turns: 30
203
 
204
  # ── MEDIUM ───────────────────────────────────────────────────────
205
+ # +1 axis vs easy: a BIGGER clusterTWELVE e1 chaff + 3 e3 (15
206
+ # enemies, kill bar 15) instead of easy's 9+3=12. The bigger
207
+ # cluster makes the threat-priority discrimination LOAD-BEARING:
208
+ # a kill-chaff-first play spends the budget mopping the 12-strong
209
+ # rifle screen while the three e3 rockets attrit the squad it
210
+ # loses 2 tanks (busts own_units_gte:3) AND fails to clear all 15
211
+ # before the deadline (kills ~12 of 15) LOSS. The intended
212
+ # focus-threats-first silences the three e3 fast, then clears the
213
+ # whole 15-unit cluster keeping ≥3 tanks ⇒ WIN. (Engine
214
+ # recalibration note: with the post-movement-fix combat model
215
+ # even a perfect focus engagement loses ~1 tank closing the
216
+ # distance — the survival floor is own_units_gte:3, NOT the old
217
+ # zero-loss own_units_gte:4 which became unsatisfiable.)
218
  medium:
219
  description: >
220
  Four medium tanks stage at x=48, y=18..21 with your
221
  construction yard behind them. The eastern enemy cluster is a
222
+ compact screen of TWELVE rifle infantry (e1, cheap chaff) at
223
  x=66 backed by THREE rocket soldiers (e3, anti-tank Dragon
224
  launcher — the high-threat units) at x=69. Threat-priority
225
  doctrine: concentrate ALL FOUR tanks' fire on ONE rocket
226
+ soldier first, then the second, then the third, THEN mop up
227
+ the rifle chaff. Engaging the chaff first leaves the rocket
228
+ soldiers firing through the entire mop-up they attrit your
229
+ squad AND you run out of time before clearing the 15-strong
230
+ cluster. Win when 15 enemy units are killed AND at least 3
 
231
  tanks survive AND your construction yard still stands, before
232
  tick 2700.
233
  overrides:
 
237
  - {type: 2tnk, owner: agent, position: [48, 20], stance: 1}
238
  - {type: 2tnk, owner: agent, position: [48, 21], stance: 1}
239
  - {type: fact, owner: agent, position: [4, 20]}
240
+ # Compact contiguous screen of TWELVE cheap rifle infantry
241
+ # along x=66, y=14..25 (engine auto-target picks the nearest
242
  # hostile under attack_move).
243
+ - {type: e1, owner: enemy, position: [66, 14], stance: 2}
244
  - {type: e1, owner: enemy, position: [66, 15], stance: 2}
245
  - {type: e1, owner: enemy, position: [66, 16], stance: 2}
246
  - {type: e1, owner: enemy, position: [66, 17], stance: 2}
 
250
  - {type: e1, owner: enemy, position: [66, 21], stance: 2}
251
  - {type: e1, owner: enemy, position: [66, 22], stance: 2}
252
  - {type: e1, owner: enemy, position: [66, 23], stance: 2}
253
+ - {type: e1, owner: enemy, position: [66, 24], stance: 2}
254
+ - {type: e1, owner: enemy, position: [66, 25], stance: 2}
255
  # THREE rocket soldiers behind the chaff screen at x=69 —
256
  # inside Dragon range 5 of the contact line, not the nearest
257
  # target (e1s at x=66 are 3 cells closer ⇒ engine auto-
258
+ # target picks the chaff under attack_move).
 
 
259
  - {type: e3, owner: enemy, position: [69, 19], stance: 2}
260
  - {type: e3, owner: enemy, position: [69, 20], stance: 2}
261
  - {type: e3, owner: enemy, position: [69, 21], stance: 2}
262
  - {type: fact, owner: enemy, position: [120, 20]}
263
  win_condition:
264
  all_of:
265
+ - {units_killed_gte: 15}
266
+ - {own_units_gte: 3}
267
  - {within_ticks: 2700}
268
  - {building_count_gte: {type: fact, n: 1}}
269
+ # Fail floor aligned to the win floor (own_units_gte:3) — a squad
270
+ # at own≤2 is a real LOSS, not a dead-zone DRAW.
271
  fail_condition:
272
  any_of:
273
  - {after_ticks: 2701}
274
+ - {not: {own_units_gte: 3}}
275
  max_turns: 30
276
 
277
  # ── HARD ─────────────────────────────────────────────────────────
278
  # +1 axis vs medium: the strike force (and its construction yard)
279
  # stage from TWO seed-driven spawn_point groups (NORTH corridor
280
  # y=10..13 OR SOUTH corridor y=27..30) so the engagement geometry
281
+ # varies per seed. The cluster is the same 12 e1 + 3 e3 (15
282
+ # enemies, kill bar 15) as medium; the chaff screen (y=14..25) +
283
+ # rocket trio is symmetric across y=19.5 both spawns face the
284
+ # same priority decision (focus one e3 → next → next → mop up
285
+ # chaff). The own_units_gte:3 survival floor from medium is kept.
286
  hard:
287
  description: >
288
  Four medium tanks stage at ONE of two corridors (NORTH
289
  y=10..13 OR SOUTH y=27..30, chosen by seed, anti-
290
  memorisation) with your construction yard behind them. The
291
+ eastern enemy cluster is a compact screen of TWELVE rifle
292
  infantry (e1, cheap chaff) at x=66 backed by THREE rocket
293
  soldiers (e3, anti-tank — the high-threat units) at x=69.
294
  Threat-priority doctrine: concentrate ALL FOUR tanks on ONE
295
  rocket soldier at a time — kill it cleanly, then the next,
296
  then the third, THEN mop up the rifle chaff. Starting on the
297
  rifle chaff leaves the rockets firing through the whole
298
+ engagement and you run out of time and tanks. Win when 15
299
+ enemy units are killed AND at least 3 tanks survive AND your
300
+ construction yard still stands, before tick 2700.
301
  overrides:
302
  actors:
303
  # spawn_point 0 — NORTH staging corridor (y=10..13).
 
312
  - {type: 2tnk, owner: agent, position: [48, 29], stance: 1, spawn_point: 1}
313
  - {type: 2tnk, owner: agent, position: [48, 30], stance: 1, spawn_point: 1}
314
  - {type: fact, owner: agent, position: [4, 29], spawn_point: 1}
315
+ # Compact contiguous screen of TWELVE cheap rifle infantry
316
+ # along x=66, y=14..25 — symmetric across y=19.5 so either
317
+ # spawn faces an equivalent decision. Engine auto-target
318
+ # picks the nearest hostile under attack_move ⇒ this chaff
319
+ # line is what a brute engages first.
320
+ - {type: e1, owner: enemy, position: [66, 14], stance: 2}
321
  - {type: e1, owner: enemy, position: [66, 15], stance: 2}
322
  - {type: e1, owner: enemy, position: [66, 16], stance: 2}
323
  - {type: e1, owner: enemy, position: [66, 17], stance: 2}
 
328
  - {type: e1, owner: enemy, position: [66, 22], stance: 2}
329
  - {type: e1, owner: enemy, position: [66, 23], stance: 2}
330
  - {type: e1, owner: enemy, position: [66, 24], stance: 2}
331
+ - {type: e1, owner: enemy, position: [66, 25], stance: 2}
332
  # THREE rocket soldiers behind the chaff screen at x=69 —
333
  # inside Dragon range 5 of the contact line, symmetric across
334
  # y=20 so either spawn faces an equivalent focus-fire
335
  # decision. Starting on the chaff lets all three fire through
336
+ # the whole mop-up — the squad bleeds and runs out of time.
337
  - {type: e3, owner: enemy, position: [69, 19], stance: 2}
338
  - {type: e3, owner: enemy, position: [69, 20], stance: 2}
339
  - {type: e3, owner: enemy, position: [69, 21], stance: 2}
340
  - {type: fact, owner: enemy, position: [120, 20]}
341
  win_condition:
342
  all_of:
343
+ - {units_killed_gte: 15}
344
+ - {own_units_gte: 3}
345
  - {within_ticks: 2700}
346
  - {building_count_gte: {type: fact, n: 1}}
347
+ # Fail floor aligned to the win floor (own_units_gte:3) — a squad
348
+ # at own≤2 is a real LOSS, not a dead-zone DRAW.
349
  fail_condition:
350
  any_of:
351
  - {after_ticks: 2701}
352
+ - {not: {own_units_gte: 3}}
353
  max_turns: 30
tests/test_combat_target_priority_highvalue.py CHANGED
@@ -4,27 +4,40 @@ Wave-11 ACTION pack: threat-weighted target prioritization (SC2
4
  focus-fire target priority / military target prioritization anchor).
5
  A 4-tank squad faces a mixed enemy cluster — a screen of cheap rifle
6
  chaff (e1) backed by THREE high-threat anti-armour rocket soldiers
7
- (e3). The squad must FOCUS THE ROCKET SOLDIERS FIRST: 4-vs-1
8
- concentrated fire deletes each e3 in 1-2 decision turns before it can
9
- land more than ~1 rocket. Killing the chaff first leaves all three
10
- rockets firing through the entire mop-up and they whittle the squad
11
- below the survival floor.
12
 
13
- Win = units_killed_gte:K AND own_units_gte:F AND within_ticks:2700
14
  AND building_count_gte:fact:1 (a non-combat anchor: the squad must
15
- still own its construction yard while it focus-fires). Fail floors
16
- are aligned to the win floor so every non-timeout outcome is a real
17
- WIN or LOSS — no dead-zone DRAW.
 
 
 
 
 
 
 
 
 
 
 
 
18
 
19
  Bar (per CLAUDE.md), verified deterministic across seeds 1-4 on
20
  every level:
21
  • stall (observe only) → LOSS every level/seed (after_ticks).
22
- • brute attack_move → LOSS (auto-targets the chaff screen;
23
- the three e3s wipe the squad).
24
- • kill-chaff-first → LOSS (rockets fire through the chaff
25
- mop-up and bust the survival floor).
26
- • focus-threats-first WIN every level/seed (all three e3s
27
- silenced inside the first ~3 turns, squad preserved).
 
 
28
  """
29
 
30
  from __future__ import annotations
@@ -76,9 +89,9 @@ def _stall_policy():
76
 
77
 
78
  def _brute_policy():
79
- """attack_move onto the cluster centroid — the engine auto-targets
80
- the NEAREST hostile (the chaff screen), so the three e3s fire
81
- through the whole engagement and wipe the squad."""
82
  def pol(obs, Cmd):
83
  ids = _tank_ids(obs)
84
  if not ids:
@@ -142,12 +155,16 @@ def test_brute_attack_move_loses(level, seed):
142
  )
143
 
144
 
145
- @pytest.mark.parametrize("level", LEVELS)
146
  @pytest.mark.parametrize("seed", SEEDS)
147
  def test_kill_chaff_first_loses(level, seed):
148
  """Explicitly attacking the cheap e1 chaff first leaves the three
149
- rockets firing through the whole mop-up; the squad drops below the
150
- survival floor a real LOSS."""
 
 
 
 
151
  c = compile_level(load_pack(PACK), level)
152
  res = run_level(c, _focus_policy("e1"), seed=seed)
153
  assert res.outcome == "loss", (
 
4
  focus-fire target priority / military target prioritization anchor).
5
  A 4-tank squad faces a mixed enemy cluster — a screen of cheap rifle
6
  chaff (e1) backed by THREE high-threat anti-armour rocket soldiers
7
+ (e3). The squad must FOCUS THE ROCKET SOLDIERS FIRST: silence the
8
+ rocket soldiers fast, then mop up the chaff. Killing the chaff first
9
+ leaves all three rockets firing anti-armour fire through the entire
10
+ mop-up.
 
11
 
12
+ Win = units_killed_gte:K AND own_units_gte:3 AND within_ticks:2700
13
  AND building_count_gte:fact:1 (a non-combat anchor: the squad must
14
+ still own its construction yard). Fail floors are aligned to the win
15
+ floor so every non-timeout outcome is a real WIN or LOSS — no
16
+ dead-zone DRAW.
17
+
18
+ Recalibrated after the engine movement fixes (moving units take fire
19
+ en route; attack_unit on out-of-sight targets paths normally; no
20
+ sprint-invincibility). Finding: with the post-fix combat model the
21
+ squad takes ~the same tank losses regardless of fine target order
22
+ (concentrating fire bunches the stack's return-fire exposure too) —
23
+ the survival floor is own_units_gte:3 (a perfect focus engagement
24
+ still loses ~1 tank closing the distance), NOT the old zero-loss
25
+ own_units_gte:4. The chaff-vs-threat discrimination is restored on
26
+ medium + hard via a BIGGER cluster (12 e1 + 3 e3 = 15, kill bar 15):
27
+ a chaff-first play cannot clear all 15 in budget while the three e3
28
+ attrit it, so it busts the kill bar AND the floor.
29
 
30
  Bar (per CLAUDE.md), verified deterministic across seeds 1-4 on
31
  every level:
32
  • stall (observe only) → LOSS every level/seed (after_ticks).
33
+ • brute attack_move → LOSS every level/seed (drives into the
34
+ cluster, bleeds 2 tanks, fails the kill bar).
35
+ • kill-chaff-first → LOSS on MEDIUM + HARD (the 15-strong
36
+ cluster cannot be cleared chaff-first in budget; the e3 attrit
37
+ the squad). EASY is the forgiving bare-skill tier — its smaller
38
+ 12-unit cluster lets a chaff-first play still finish, so the
39
+ load-bearing chaff-first LOSS is medium + hard.
40
+ • focus-threats-first → WIN every level/seed.
41
  """
42
 
43
  from __future__ import annotations
 
89
 
90
 
91
  def _brute_policy():
92
+ """attack_move onto the cluster centroid — the squad drives INTO
93
+ the cluster and is enveloped; it bleeds two tanks and fails to
94
+ clear the kill bar before the deadline ⇒ LOSS."""
95
  def pol(obs, Cmd):
96
  ids = _tank_ids(obs)
97
  if not ids:
 
155
  )
156
 
157
 
158
+ @pytest.mark.parametrize("level", ["medium", "hard"])
159
  @pytest.mark.parametrize("seed", SEEDS)
160
  def test_kill_chaff_first_loses(level, seed):
161
  """Explicitly attacking the cheap e1 chaff first leaves the three
162
+ rockets firing anti-armour fire through the whole mop-up; on the
163
+ 15-strong medium/hard cluster the squad cannot clear all 15 in
164
+ budget and the e3 attrit it below the survival floor — a real
165
+ LOSS. EASY is excluded: its smaller 12-unit cluster is the
166
+ forgiving bare-skill tier where a chaff-first play can still
167
+ finish (the load-bearing chaff-first LOSS is medium + hard)."""
168
  c = compile_level(load_pack(PACK), level)
169
  res = run_level(c, _focus_policy("e1"), seed=seed)
170
  assert res.outcome == "loss", (