Spaces:
Sleeping
Sleeping
fix(scenario): coord-cover-and-move — recalibrate after engine movement fixes
Browse files
openra_bench/scenarios/packs/coord-cover-and-move.yaml
CHANGED
|
@@ -14,18 +14,20 @@
|
|
| 14 |
# also cross. Both squads converge on the eastern gathering region
|
| 15 |
# having routed AROUND the fire zone rather than through it.
|
| 16 |
#
|
| 17 |
-
# Engine
|
| 18 |
-
#
|
| 19 |
-
#
|
| 20 |
-
#
|
| 21 |
-
#
|
| 22 |
-
#
|
| 23 |
-
#
|
| 24 |
-
#
|
| 25 |
-
#
|
| 26 |
-
#
|
| 27 |
-
#
|
| 28 |
-
#
|
|
|
|
|
|
|
| 29 |
#
|
| 30 |
# Real-world anchor: US Army FM 3-21.8 bounding overwatch — one fire
|
| 31 |
# team in a stationary OVERWATCH posture provides suppressive fire
|
|
@@ -39,8 +41,8 @@
|
|
| 39 |
# • stall (observe-only): no squad crosses → win region empty →
|
| 40 |
# after_ticks timeout LOSS.
|
| 41 |
# • both-charge-together: all 6 tanks rush a single eastward column
|
| 42 |
-
# straight through the centre fire zone; the
|
| 43 |
-
#
|
| 44 |
# • single-squad-only: send Squad A through alone (Squad B idle
|
| 45 |
# west); A absorbs the centre fire AND half the force never
|
| 46 |
# reaches the win region (n=4 region clause unreachable) → LOSS.
|
|
@@ -52,16 +54,17 @@
|
|
| 52 |
# deadline losing ≤ the cap → WIN.
|
| 53 |
#
|
| 54 |
# Why the spec works (engine combat sheet):
|
| 55 |
-
# •
|
| 56 |
-
#
|
| 57 |
-
#
|
| 58 |
-
#
|
| 59 |
-
# •
|
| 60 |
-
#
|
| 61 |
-
#
|
| 62 |
-
#
|
| 63 |
# • 2tnk = medium tank (rng4.75c, sight6c, hp400) — out-DPSes
|
| 64 |
-
# infantry but is
|
|
|
|
| 65 |
# • Periphery geometry: the centre fire zone occupies y≈13..27. A
|
| 66 |
# squad that rises to y≈6 (NORTH) or y≈34 (SOUTH) BEFORE reaching
|
| 67 |
# the fire zone's longitude (x≈50) routes entirely outside the
|
|
@@ -133,30 +136,33 @@ base:
|
|
| 133 |
|
| 134 |
levels:
|
| 135 |
# ── EASY ─────────────────────────────────────────────────────────
|
| 136 |
-
# Bare bound-and-cover skill: a
|
| 137 |
-
# centre anchored by
|
| 138 |
-
#
|
| 139 |
-
#
|
| 140 |
-
#
|
| 141 |
-
#
|
| 142 |
-
#
|
| 143 |
-
#
|
| 144 |
-
#
|
| 145 |
-
#
|
|
|
|
|
|
|
|
|
|
| 146 |
easy:
|
| 147 |
description: >
|
| 148 |
Two squads (Squad A at (10,15), Squad B at (10,25), 3 medium
|
| 149 |
-
tanks each) must cross a centre-of-map fire zone — a
|
| 150 |
-
|
| 151 |
-
roughly (50,20) — then land ≥4 tanks in the
|
| 152 |
-
region (x ≈ 100, y ≈ 20, radius 6) before tick
|
| 153 |
-
tank in total. Charging both squads straight
|
| 154 |
-
drives them into the
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
|
| 158 |
-
|
| 159 |
-
switch.
|
| 160 |
overrides:
|
| 161 |
actors:
|
| 162 |
# Squad A — northern fire team, staged at (10, 15).
|
|
@@ -167,17 +173,17 @@ levels:
|
|
| 167 |
- {type: 2tnk, owner: agent, position: [10, 24], stance: 1}
|
| 168 |
- {type: 2tnk, owner: agent, position: [10, 25], stance: 1}
|
| 169 |
- {type: 2tnk, owner: agent, position: [10, 26], stance: 1}
|
| 170 |
-
# Centre fire zone:
|
| 171 |
-
# all stance:2 Defend (auto-fire in range but
|
| 172 |
-
# a stall policy is a clean timeout LOSS, not
|
| 173 |
-
# one). Cells avoid (50,20) — CLAUDE.md footgun:
|
| 174 |
-
# clusters at (50,20) silently fail to place. Off-axis
|
| 175 |
# neighbours (49/51 × 19/21) are verified-placeable.
|
| 176 |
-
- {type:
|
| 177 |
-
- {type:
|
| 178 |
-
- {type:
|
| 179 |
-
- {type:
|
| 180 |
-
- {type: 4tnk, owner: enemy, position: [
|
| 181 |
# Persistent far enemy `fact` — keeps the episode alive past
|
| 182 |
# the central cluster being cleaned out so the win/fail
|
| 183 |
# evaluator sees the terminal frame (no DRAW collapse).
|
|
@@ -195,14 +201,15 @@ levels:
|
|
| 195 |
max_turns: 50
|
| 196 |
|
| 197 |
# ── MEDIUM ───────────────────────────────────────────────────────
|
| 198 |
-
# +1 axis vs easy:
|
| 199 |
-
#
|
| 200 |
-
# zone is genuinely lethal to a 2tnk column crossing the
|
| 201 |
-
# Both-charge-together LOSES (the centre brawl
|
| 202 |
-
#
|
| 203 |
-
# fire AND half the force never reaches the win
|
| 204 |
-
# bound-and-cover (route the squads through the
|
| 205 |
-
# alternating cover/move roles) lands ≥4 tanks
|
|
|
|
| 206 |
medium:
|
| 207 |
description: >
|
| 208 |
Two squads (Squad A at (10,15), Squad B at (10,25), 3 medium
|
|
|
|
| 14 |
# also cross. Both squads converge on the eastern gathering region
|
| 15 |
# having routed AROUND the fire zone rather than through it.
|
| 16 |
#
|
| 17 |
+
# Engine movement-fix recalibration ((A) attack_unit on out-of-sight
|
| 18 |
+
# targets paths normally — no teleport; (B) a moving unit fires AND
|
| 19 |
+
# takes fire en route — no sprint-invincibility): a 6-tank column that
|
| 20 |
+
# attack_moves straight through the centre now genuinely trades fire
|
| 21 |
+
# with the cluster while crossing. The load-bearing punisher is the
|
| 22 |
+
# combination of e3 ANTI-TANK ROCKET soldiers (the real anti-armour
|
| 23 |
+
# DPS) AND 4tnk HEAVY-TANK anchors: probing shows e3 rockets alone or
|
| 24 |
+
# a single 4tnk only cost a charging column ONE tank, but FOUR e3 plus
|
| 25 |
+
# TWO 4tnk reliably cost it TWO. Every level therefore fields an e3
|
| 26 |
+
# rocket cluster anchored by TWO 4tnk (easy uses a smaller 3× e3
|
| 27 |
+
# cluster; medium/hard a 4× e3 cluster, hard adds e1 rifle screens).
|
| 28 |
+
# All enemy units are stance:2 Defend (auto-fire in range but
|
| 29 |
+
# STATIONARY) so a staller is a clean timeout LOSS rather than being
|
| 30 |
+
# chased down by hunters.
|
| 31 |
#
|
| 32 |
# Real-world anchor: US Army FM 3-21.8 bounding overwatch — one fire
|
| 33 |
# team in a stationary OVERWATCH posture provides suppressive fire
|
|
|
|
| 41 |
# • stall (observe-only): no squad crosses → win region empty →
|
| 42 |
# after_ticks timeout LOSS.
|
| 43 |
# • both-charge-together: all 6 tanks rush a single eastward column
|
| 44 |
+
# straight through the centre fire zone; the rocket fire plus the
|
| 45 |
+
# heavy-tank brawl bust the attrition cap → LOSS.
|
| 46 |
# • single-squad-only: send Squad A through alone (Squad B idle
|
| 47 |
# west); A absorbs the centre fire AND half the force never
|
| 48 |
# reaches the win region (n=4 region clause unreachable) → LOSS.
|
|
|
|
| 54 |
# deadline losing ≤ the cap → WIN.
|
| 55 |
#
|
| 56 |
# Why the spec works (engine combat sheet):
|
| 57 |
+
# • e3 = anti-tank rocket soldier (rng4c, sight5c) — the real
|
| 58 |
+
# anti-armour DPS of the fire zone; a 4× e3 cluster plus the two
|
| 59 |
+
# heavy-tank anchors is what kills ≥2 of a 6-tank column charging
|
| 60 |
+
# through the centre.
|
| 61 |
+
# • 4tnk = heavy/mammoth tank — high HP and a heavy cannon; the
|
| 62 |
+
# SECOND 4tnk anchor is what pushes a charging column's losses
|
| 63 |
+
# from one tank to two. Held at the cluster centre (y=20) it
|
| 64 |
+
# reaches a charging column on the y≈14..16 / y≈24..26 lanes.
|
| 65 |
# • 2tnk = medium tank (rng4.75c, sight6c, hp400) — out-DPSes
|
| 66 |
+
# infantry but is ground down by the e3 rockets + 4tnk anchors
|
| 67 |
+
# when it crosses the centre.
|
| 68 |
# • Periphery geometry: the centre fire zone occupies y≈13..27. A
|
| 69 |
# squad that rises to y≈6 (NORTH) or y≈34 (SOUTH) BEFORE reaching
|
| 70 |
# the fire zone's longitude (x≈50) routes entirely outside the
|
|
|
|
| 136 |
|
| 137 |
levels:
|
| 138 |
# ── EASY ─────────────────────────────────────────────────────────
|
| 139 |
+
# Bare bound-and-cover skill: a 3× e3 anti-tank-rocket cluster at the
|
| 140 |
+
# centre anchored by TWO 4tnk heavy tanks. Engine movement-fix
|
| 141 |
+
# recalibration: a charging column now genuinely trades fire while
|
| 142 |
+
# crossing — probing shows e3 rockets are the real anti-armour DPS,
|
| 143 |
+
# but it takes TWO heavy-tank anchors alongside them to cost a
|
| 144 |
+
# 6-tank charge TWO tanks. A both-charge straight through the centre
|
| 145 |
+
# eats the e3 rockets + the heavy-tank brawl and loses 2 tanks
|
| 146 |
+
# (busts the cap); stall times out; single-squad leaves half the
|
| 147 |
+
# force idle AND loses 2 crossing. The intended bound-and-cover
|
| 148 |
+
# routes both squads through the far periphery (y≈6 / y≈34, outside
|
| 149 |
+
# the centre fire zone) and converges east — taking zero losses.
|
| 150 |
+
# Easy is the FORGIVING tier: a smaller 3× e3 cluster (medium/hard
|
| 151 |
+
# field 4× e3), so the bounding-lane geometry is the bare skill.
|
| 152 |
easy:
|
| 153 |
description: >
|
| 154 |
Two squads (Squad A at (10,15), Squad B at (10,25), 3 medium
|
| 155 |
+
tanks each) must cross a centre-of-map fire zone — a small
|
| 156 |
+
anti-tank rocket-soldier (e3) cluster anchored by two heavy
|
| 157 |
+
tanks (4tnk) at roughly (50,20) — then land ≥4 tanks in the
|
| 158 |
+
EAST gathering region (x ≈ 100, y ≈ 20, radius 6) before tick
|
| 159 |
+
4500, losing ≤1 tank in total. Charging both squads straight
|
| 160 |
+
through the centre drives them into the rocket fire and the
|
| 161 |
+
heavy-tank brawl and busts the cap; a single column alone
|
| 162 |
+
absorbs every shot AND leaves half the force behind. Alternate
|
| 163 |
+
ROLES: one squad anchors at the edge of enemy range to draw
|
| 164 |
+
fire while the other sweeps through the periphery (y outside
|
| 165 |
+
the centre fire zone) and crosses; then switch.
|
| 166 |
overrides:
|
| 167 |
actors:
|
| 168 |
# Squad A — northern fire team, staged at (10, 15).
|
|
|
|
| 173 |
- {type: 2tnk, owner: agent, position: [10, 24], stance: 1}
|
| 174 |
- {type: 2tnk, owner: agent, position: [10, 25], stance: 1}
|
| 175 |
- {type: 2tnk, owner: agent, position: [10, 26], stance: 1}
|
| 176 |
+
# Centre fire zone: 3× anti-tank rocket soldiers + 2 heavy-
|
| 177 |
+
# tank anchors, all stance:2 Defend (auto-fire in range but
|
| 178 |
+
# STATIONARY — so a stall policy is a clean timeout LOSS, not
|
| 179 |
+
# an attrition one). Cells avoid (50,20) — CLAUDE.md footgun:
|
| 180 |
+
# enemy clusters at (50,20) silently fail to place. Off-axis
|
| 181 |
# neighbours (49/51 × 19/21) are verified-placeable.
|
| 182 |
+
- {type: e3, owner: enemy, position: [49, 19], stance: 2}
|
| 183 |
+
- {type: e3, owner: enemy, position: [49, 21], stance: 2}
|
| 184 |
+
- {type: e3, owner: enemy, position: [51, 21], stance: 2}
|
| 185 |
+
- {type: 4tnk, owner: enemy, position: [49, 20], stance: 2}
|
| 186 |
+
- {type: 4tnk, owner: enemy, position: [51, 19], stance: 2}
|
| 187 |
# Persistent far enemy `fact` — keeps the episode alive past
|
| 188 |
# the central cluster being cleaned out so the win/fail
|
| 189 |
# evaluator sees the terminal frame (no DRAW collapse).
|
|
|
|
| 201 |
max_turns: 50
|
| 202 |
|
| 203 |
# ── MEDIUM ───────────────────────────────────────────────────────
|
| 204 |
+
# +1 axis vs easy: the e3 anti-tank rocket cluster grows from 3 to
|
| 205 |
+
# 4 (a wider, denser fire zone) while keeping the TWO 4tnk anchors.
|
| 206 |
+
# The fire zone is genuinely lethal to a 2tnk column crossing the
|
| 207 |
+
# centre. Both-charge-together LOSES (the centre brawl + rockets
|
| 208 |
+
# bust units_lost_lte:1); single-squad alone LOSES (lone column
|
| 209 |
+
# absorbs the centre fire AND half the force never reaches the win
|
| 210 |
+
# region). Only true bound-and-cover (route the squads through the
|
| 211 |
+
# far periphery, alternating cover/move roles) lands ≥4 tanks
|
| 212 |
+
# losing ≤1.
|
| 213 |
medium:
|
| 214 |
description: >
|
| 215 |
Two squads (Squad A at (10,15), Squad B at (10,25), 3 medium
|
tests/test_coord_cover_and_move.py
CHANGED
|
@@ -3,7 +3,7 @@
|
|
| 3 |
The bar (binding):
|
| 4 |
- stall LOSES on every level + every seed (after_ticks timeout);
|
| 5 |
- both-charge-through-the-centre LOSES on every level + every seed
|
| 6 |
-
(the heavy-tank brawl
|
| 7 |
- single-squad-only LOSES on every level + every seed (half the force
|
| 8 |
idle → the n=4 region clause is unreachable);
|
| 9 |
- the intended bound-and-cover (periphery route around the fire zone)
|
|
@@ -12,18 +12,20 @@ The bar (binding):
|
|
| 12 |
clause (no DRAW degeneracy);
|
| 13 |
- hard ships ≥2 `spawn_point` groups (seed-driven start variation).
|
| 14 |
|
| 15 |
-
Recalibrated after the engine
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
|
|
|
|
|
|
| 27 |
|
| 28 |
Validation is scripted (no model / network).
|
| 29 |
"""
|
|
@@ -187,36 +189,39 @@ def test_two_squads_six_tanks_each_level():
|
|
| 187 |
)
|
| 188 |
|
| 189 |
|
| 190 |
-
def
|
| 191 |
-
"""The fire zone's lethality is the load-bearing property —
|
| 192 |
-
|
| 193 |
-
|
|
|
|
|
|
|
| 194 |
pack = load_pack(PACK_PATH)
|
| 195 |
-
for lvl in ("medium", "hard"):
|
| 196 |
c = compile_level(pack, lvl)
|
| 197 |
types = [a.type for a in c.scenario.actors if a.owner == "enemy"]
|
| 198 |
-
assert types.count("e3") >=
|
| 199 |
-
f"{lvl}: need ≥
|
|
|
|
| 200 |
)
|
| 201 |
# Persistent far enemy marker (engine auto-done mitigation).
|
| 202 |
assert "fact" in types, f"{lvl}: needs persistent enemy fact"
|
| 203 |
|
| 204 |
|
| 205 |
-
def
|
| 206 |
-
"""
|
| 207 |
-
|
| 208 |
-
|
| 209 |
-
Every level
|
| 210 |
-
|
| 211 |
pack = load_pack(PACK_PATH)
|
| 212 |
-
for lvl
|
| 213 |
c = compile_level(pack, lvl)
|
| 214 |
n4tnk = sum(
|
| 215 |
1 for a in c.scenario.actors
|
| 216 |
if a.owner == "enemy" and a.type == "4tnk"
|
| 217 |
)
|
| 218 |
-
assert n4tnk >=
|
| 219 |
-
f"{lvl}: fire zone must be anchored by ≥
|
| 220 |
f"(heavy-tank brawl), got {n4tnk}"
|
| 221 |
)
|
| 222 |
|
|
|
|
| 3 |
The bar (binding):
|
| 4 |
- stall LOSES on every level + every seed (after_ticks timeout);
|
| 5 |
- both-charge-through-the-centre LOSES on every level + every seed
|
| 6 |
+
(the rocket fire + heavy-tank brawl bust the attrition cap);
|
| 7 |
- single-squad-only LOSES on every level + every seed (half the force
|
| 8 |
idle → the n=4 region clause is unreachable);
|
| 9 |
- the intended bound-and-cover (periphery route around the fire zone)
|
|
|
|
| 12 |
clause (no DRAW degeneracy);
|
| 13 |
- hard ships ≥2 `spawn_point` groups (seed-driven start variation).
|
| 14 |
|
| 15 |
+
Recalibrated after the engine movement fixes ((A) attack_unit on
|
| 16 |
+
out-of-sight targets paths normally — no teleport; (B) a moving unit
|
| 17 |
+
fires AND takes fire en route — no sprint-invincibility). A 6-tank
|
| 18 |
+
column that attack_moves through the centre now genuinely trades fire
|
| 19 |
+
while crossing. Probing showed the e1 rifle cluster on the old easy
|
| 20 |
+
tier could no longer punish the charge at all (0 losses): the
|
| 21 |
+
load-bearing punisher is e3 ANTI-TANK ROCKET soldiers (the real
|
| 22 |
+
anti-armour DPS) PLUS two 4tnk HEAVY-TANK anchors — e3 rockets alone,
|
| 23 |
+
or a single 4tnk, only cost a charge ONE tank; four e3 plus two 4tnk
|
| 24 |
+
cost it TWO. Every level now fields an e3 rocket cluster anchored by
|
| 25 |
+
TWO 4tnk (easy a smaller 3× e3 cluster, medium/hard a 4× e3 cluster,
|
| 26 |
+
hard adds e1 rifle screens). All enemy units are stance:2 Defend
|
| 27 |
+
(auto-fire in range, STATIONARY) so a staller is a clean timeout
|
| 28 |
+
LOSS, not chased down by hunters.
|
| 29 |
|
| 30 |
Validation is scripted (no model / network).
|
| 31 |
"""
|
|
|
|
| 189 |
)
|
| 190 |
|
| 191 |
|
| 192 |
+
def test_fire_zone_uses_anti_tank_rocket_every_level():
|
| 193 |
+
"""The fire zone's lethality is the load-bearing property — post
|
| 194 |
+
engine movement fixes the e3 (anti-tank rocket soldier) cluster is
|
| 195 |
+
the real anti-armour DPS, so EVERY level uses e3 at the centre.
|
| 196 |
+
Easy fields a smaller cluster (≥3 e3, the forgiving tier);
|
| 197 |
+
medium / hard a denser one (≥4 e3)."""
|
| 198 |
pack = load_pack(PACK_PATH)
|
| 199 |
+
for lvl, want_e3 in (("easy", 3), ("medium", 4), ("hard", 4)):
|
| 200 |
c = compile_level(pack, lvl)
|
| 201 |
types = [a.type for a in c.scenario.actors if a.owner == "enemy"]
|
| 202 |
+
assert types.count("e3") >= want_e3, (
|
| 203 |
+
f"{lvl}: need ≥{want_e3} e3 (anti-tank rocket) for fire "
|
| 204 |
+
f"zone; got {types}"
|
| 205 |
)
|
| 206 |
# Persistent far enemy marker (engine auto-done mitigation).
|
| 207 |
assert "fact" in types, f"{lvl}: needs persistent enemy fact"
|
| 208 |
|
| 209 |
|
| 210 |
+
def test_fire_zone_anchored_by_two_heavy_tanks():
|
| 211 |
+
"""Recalibration invariant: probing showed the e3 rockets alone or
|
| 212 |
+
a single 4tnk only cost a 6-tank charge ONE tank — it takes TWO
|
| 213 |
+
4tnk anchors alongside the rocket cluster to reliably cost the
|
| 214 |
+
charge TWO. Every level therefore anchors the fire zone with two
|
| 215 |
+
enemy 4tnk heavy tanks."""
|
| 216 |
pack = load_pack(PACK_PATH)
|
| 217 |
+
for lvl in ("easy", "medium", "hard"):
|
| 218 |
c = compile_level(pack, lvl)
|
| 219 |
n4tnk = sum(
|
| 220 |
1 for a in c.scenario.actors
|
| 221 |
if a.owner == "enemy" and a.type == "4tnk"
|
| 222 |
)
|
| 223 |
+
assert n4tnk >= 2, (
|
| 224 |
+
f"{lvl}: fire zone must be anchored by ≥2 enemy 4tnk "
|
| 225 |
f"(heavy-tank brawl), got {n4tnk}"
|
| 226 |
)
|
| 227 |
|