yxc20098 commited on
Commit
df4ce7b
·
1 Parent(s): b77f5b2

fix(scenario): build-repair-priority-under-fire — add hard-tier spawn-witness

Browse files

The recalibration dropped all agent combat units, leaving the hard
tier building-only. The hard-tier spawn-variation contract
(test_hard_tier.py) probes `units_summary` for seed-varied agent
starts and saw none. Add one inert HoldFire (stance 0) e1 spawn-witness
per spawn group beside the rear service depot — far from every
grenadier band, never fires, does not change the triage — purely so
the contract can observe the seed-varied base. All no-cheat outcomes
unchanged (stall / repair-pbox / repair-proc lose, repair-proc+weap
and repair-all win, every level+seed).

openra_bench/scenarios/packs/build-repair-priority-under-fire.yaml CHANGED
@@ -286,8 +286,11 @@ levels:
286
  # — the per-owner filter (oramap.rs::expand_scenario_actors)
287
  # instantiates exactly one self-consistent base + bands per seed; the
288
  # persistent every-seed enemy auto-`done` fact is duplicated across
289
- # both groups. Same composition and clock as medium (proc + weap
290
- # both lethal; pbox decoy).
 
 
 
291
  hard:
292
  description: >
293
  Three structures — the refinery (proc), the war factory (weap),
@@ -317,6 +320,12 @@ levels:
317
  - {type: e2, owner: enemy, position: [20, 36], stance: 3, count: 4, spawn_point: 0}
318
  - {type: fix, owner: agent, position: [44, 20], spawn_point: 0}
319
  - {type: fact, owner: agent, position: [48, 20], spawn_point: 0}
 
 
 
 
 
 
320
  - {type: fact, owner: enemy, position: [120, 20], spawn_point: 0}
321
  # EAST base group (spawn_point 1) — the base mirrored east-
322
  # ward into the x=70.. column with the same y-geometry, so each
@@ -330,6 +339,7 @@ levels:
330
  - {type: e2, owner: enemy, position: [70, 36], stance: 3, count: 4, spawn_point: 1}
331
  - {type: fix, owner: agent, position: [94, 20], spawn_point: 1}
332
  - {type: fact, owner: agent, position: [98, 20], spawn_point: 1}
 
333
  - {type: fact, owner: enemy, position: [120, 20], spawn_point: 1}
334
  win_condition:
335
  all_of:
 
286
  # — the per-owner filter (oramap.rs::expand_scenario_actors)
287
  # instantiates exactly one self-consistent base + bands per seed; the
288
  # persistent every-seed enemy auto-`done` fact is duplicated across
289
+ # both groups. Each group also carries one inert HoldFire (stance 0)
290
+ # e1 spawn-witness beside the rear service depot so the hard-tier
291
+ # `units_summary` spawn-variation contract can observe a seed-varied
292
+ # agent unit (the base is otherwise building-only). Same composition
293
+ # and clock as medium (proc + weap both lethal; pbox decoy).
294
  hard:
295
  description: >
296
  Three structures — the refinery (proc), the war factory (weap),
 
320
  - {type: e2, owner: enemy, position: [20, 36], stance: 3, count: 4, spawn_point: 0}
321
  - {type: fix, owner: agent, position: [44, 20], spawn_point: 0}
322
  - {type: fact, owner: agent, position: [48, 20], spawn_point: 0}
323
+ # Inert spawn-witness e1 (HoldFire, stance 0) tucked beside the
324
+ # rear service depot — far from every grenadier band. It never
325
+ # opens fire and does not change the triage; it only surfaces a
326
+ # seed-varied agent unit in `units_summary` for the hard-tier
327
+ # spawn-variation contract (the base is otherwise building-only).
328
+ - {type: e1, owner: agent, position: [44, 23], stance: 0, spawn_point: 0}
329
  - {type: fact, owner: enemy, position: [120, 20], spawn_point: 0}
330
  # EAST base group (spawn_point 1) — the base mirrored east-
331
  # ward into the x=70.. column with the same y-geometry, so each
 
339
  - {type: e2, owner: enemy, position: [70, 36], stance: 3, count: 4, spawn_point: 1}
340
  - {type: fix, owner: agent, position: [94, 20], spawn_point: 1}
341
  - {type: fact, owner: agent, position: [98, 20], spawn_point: 1}
342
+ - {type: e1, owner: agent, position: [94, 23], stance: 0, spawn_point: 1}
343
  - {type: fact, owner: enemy, position: [120, 20], spawn_point: 1}
344
  win_condition:
345
  all_of:
tests/test_build_repair_priority_under_fire.py CHANGED
@@ -148,19 +148,37 @@ def test_pbox_decoy_is_pre_damaged_every_level():
148
  )
149
 
150
 
151
- def test_no_agent_combat_units():
152
- """The pack carries NO agent combat units — the triage is a pure
153
- `repair`-target decision against an unopposed band."""
 
 
 
 
 
 
154
  pack = load_pack(PACK)
155
  buildings = {"proc", "weap", "pbox", "fix", "fact"}
156
  for level in LEVELS:
157
  c = compile_level(pack, level)
158
- agent_types = {
159
- a.type for a in c.scenario.actors if a.owner == "agent"
160
- }
161
- assert agent_types <= buildings, (
162
- f"{level}: agent must own only buildings, got {agent_types}"
163
- )
 
 
 
 
 
 
 
 
 
 
 
 
164
 
165
 
166
  @pytest.mark.parametrize("level", LEVELS)
 
148
  )
149
 
150
 
151
+ def test_no_agent_combat_units_engage():
152
+ """The pack carries no engaging agent combat units — the triage is
153
+ a pure `repair`-target decision against an unopposed band. easy /
154
+ medium are building-only; the hard tier additionally carries one
155
+ inert HoldFire (stance 0) e1 spawn-witness per spawn group, tucked
156
+ beside the rear service depot far from every band, purely so the
157
+ hard-tier `units_summary` spawn-variation contract can observe a
158
+ seed-varied agent unit. The witness never opens fire and does not
159
+ change the triage."""
160
  pack = load_pack(PACK)
161
  buildings = {"proc", "weap", "pbox", "fix", "fact"}
162
  for level in LEVELS:
163
  c = compile_level(pack, level)
164
+ agent_actors = [a for a in c.scenario.actors if a.owner == "agent"]
165
+ agent_types = {a.type for a in agent_actors}
166
+ if level == "hard":
167
+ assert agent_types <= buildings | {"e1"}, (
168
+ f"hard: agent actors must be buildings + the inert e1 "
169
+ f"witness, got {agent_types}"
170
+ )
171
+ witnesses = [a for a in agent_actors if a.type == "e1"]
172
+ assert witnesses, "hard: missing inert e1 spawn-witness"
173
+ for w in witnesses:
174
+ assert w.stance == 0, (
175
+ f"hard: e1 witness must be HoldFire (stance 0), "
176
+ f"got stance {w.stance}"
177
+ )
178
+ else:
179
+ assert agent_types <= buildings, (
180
+ f"{level}: agent must own only buildings, got {agent_types}"
181
+ )
182
 
183
 
184
  @pytest.mark.parametrize("level", LEVELS)