Spaces:
Running
Running
File size: 12,372 Bytes
76ed940 5cfed54 76ed940 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 | """Full contributor-loop validation for econ-multi-patch-allocation.
The pack tests Weber-multi-source / SC2-mineral-patch allocation: 3 (or
4 at hard) ore patches at varied distances from a single refinery, the
agent owns 3 harvesters, and yield-per-harvester scales inversely with
round-trip travel. The capability under test is "distance dominates
throughput; prioritise the NEAR patch" โ NOT naive "one harv per
source" diversification.
Bar (per CLAUDE.md "no defect, no cheat"):
- stall LOSES every tier.
- All-to-FAR LOSES every tier (~1000 cr/harv/4500t).
- All-to-MID LOSES medium/hard (~2000 cr/harv).
- Uniform 1-per-patch LOSES medium (~11000 cr < 14000 bar).
- Wrong-NEAR (memorised cell that matches one spawn but not the other)
LOSES on hard's mismatched seeds.
- Intended capability โ 2+ harvs on the spawn-matched NEAR patch โ WINS
every tier and every seed.
"""
from __future__ import annotations
import pytest
pytest.importorskip("openra_train", reason="Rust env wheel not installed")
pytest.importorskip("openra_rl_training", reason="Rust env wheel not installed")
from openra_bench.eval_core import run_level
from openra_bench.scenarios import load_pack
from openra_bench.scenarios.loader import PACKS_DIR, compile_level
PACK = PACKS_DIR / "econ-multi-patch-allocation.yaml"
# Base/medium patch positions
NEAR = (16, 18)
MID = (40, 18)
FAR = (80, 18)
# Hard tier: 4 patches; NEAREST flips per seed.
P_NORTH = (16, 14) # NEAREST for spawn_point 0 (NORTH base)
P_SOUTH = (16, 28) # NEAREST for spawn_point 1 (SOUTH base)
H_MID = (40, 18)
H_FAR = (80, 18)
# ---------------------------------------------------------------- policies
def stall_policy(rs, Command):
return [Command.observe()]
def _make_alloc(targets):
"""Send harv[i] (in id order) to targets[i] every turn. The
`harvest` order persists so re-issuing is idempotent."""
def f(rs, Command):
harvs = sorted(
(u for u in rs.get("units_summary", []) if u.get("type") == "harv"),
key=lambda u: u["id"],
)
cmds = [Command.harvest([str(h["id"])], *t) for h, t in zip(harvs, targets)]
return cmds or [Command.observe()]
return f
def _make_smart_hard():
"""Hard-tier intended policy: identify the matched NEAR patch from
the harvs' Y row (NORTH base โ harvs at y=13..15 โ near is (16,14);
SOUTH base โ y=27..29 โ near is (16,28)), then allocate all 3."""
def f(rs, Command):
harvs = sorted(
(u for u in rs.get("units_summary", []) if u.get("type") == "harv"),
key=lambda u: u["id"],
)
if not harvs:
return [Command.observe()]
y = harvs[0]["cell_y"]
target = P_NORTH if y < 20 else P_SOUTH
return [Command.harvest([str(h["id"])], *target) for h in harvs]
return f
# ---------------------------------------------------------------- helpers
def _run(level, policy_factory, seed=1):
c = compile_level(load_pack(PACK), level)
assert c.map_supported, "rush-hour-arena terrain must be present"
policy = policy_factory() if callable(policy_factory) else policy_factory
return c, run_level(c, policy, seed=seed)
def _ev(res):
return res.signals.cash + res.signals.resources
# ---------------------------------------------------------------- structural
def test_pack_loads_and_meta_active():
pack = load_pack(PACK)
assert pack.meta.status == "active"
assert pack.meta.id == "econ-multi-patch-allocation"
assert pack.meta.capability == "reasoning"
anchors = pack.meta.benchmark_anchor
assert any("SC2LE" in a for a in anchors)
assert any("Weber" in a for a in anchors)
assert any("supply-chain" in a for a in anchors)
assert any("queueing" in a for a in anchors)
def test_all_tiers_have_reachable_deadlines():
"""tick-alignment idiom: within_ticks โค ceiling AND
after_ticks โค ceiling AND within_ticks == after_ticks (so a
non-finisher LOSES, not draws)."""
pack = load_pack(PACK)
for lvl in ("easy", "medium", "hard"):
L = pack.levels[lvl]
ceiling = 93 + 90 * (L.max_turns - 1)
wt = next(
int(c["within_ticks"])
for c in L.win_condition.model_dump()["all_of"]
if "within_ticks" in c
)
ft = next(
int(c["after_ticks"])
for c in L.fail_condition.model_dump()["any_of"]
if "after_ticks" in c
)
assert wt <= ceiling, f"{lvl}: within_ticks {wt} > ceiling {ceiling}"
assert ft <= ceiling, f"{lvl}: after_ticks {ft} > ceiling {ceiling}"
assert wt == ft, (
f"{lvl}: within_ticks {wt} != after_ticks {ft} "
"(non-finisher must LOSE, not draw)"
)
def test_hard_has_two_seed_driven_spawn_groups():
"""Hard tier must define โฅ2 spawn_point groups so different seeds
place the agent at different starts (the capability test: identify
the NEAREST patch from YOUR base, don't memorise a fixed cell)."""
c = compile_level(load_pack(PACK), "hard")
sp = {
(a.spawn_point if a.spawn_point is not None else 0)
for a in c.scenario.actors
if a.owner == "agent"
}
assert len(sp) >= 2, (
f"hard must define โฅ2 agent spawn_point groups; got {sorted(sp)}"
)
# ---------------------------------------------------------------- EASY
def test_easy_stall_loses():
_, res = _run("easy", lambda: stall_policy)
assert res.outcome == "loss", f"stall must LOSE easy; got {res.outcome} ev={_ev(res)}"
def test_easy_all_to_far_loses():
_, res = _run("easy", lambda: _make_alloc([FAR, FAR]))
assert res.outcome == "loss", (
f"all-to-FAR must LOSE easy (~2000 ev < 8000 bar); "
f"got {res.outcome} ev={_ev(res)}"
)
def test_easy_both_to_near_wins():
_, res = _run("easy", lambda: _make_alloc([NEAR, NEAR]))
assert res.outcome == "win", (
f"both-to-NEAR must WIN easy; got {res.outcome} ev={_ev(res)}"
)
def test_easy_split_near_far_wins():
"""Even the inefficient split (1 NEAR + 1 FAR) clears 8000 because
the NEAR harv alone supplies ~8000 cr; this is the loose-bar easy
tier โ any allocation that USES the NEAR patch passes."""
_, res = _run("easy", lambda: _make_alloc([NEAR, FAR]))
assert res.outcome == "win", (
f"split-NEAR+FAR must WIN easy; got {res.outcome} ev={_ev(res)}"
)
# ---------------------------------------------------------------- MEDIUM
def test_medium_stall_loses():
_, res = _run("medium", lambda: stall_policy)
assert res.outcome == "loss", (
f"stall must LOSE medium; got {res.outcome} ev={_ev(res)}"
)
def test_medium_all_to_far_loses():
_, res = _run("medium", lambda: _make_alloc([FAR, FAR, FAR]))
assert res.outcome == "loss", (
f"all-to-FAR must LOSE medium (~3000 ev < 14000 bar); "
f"got {res.outcome} ev={_ev(res)}"
)
def test_medium_all_to_mid_loses():
_, res = _run("medium", lambda: _make_alloc([MID, MID, MID]))
assert res.outcome == "loss", (
f"all-to-MID must LOSE medium (~6000 ev < 14000 bar); "
f"got {res.outcome} ev={_ev(res)}"
)
def test_medium_uniform_split_loses():
"""The NAIVE one-harv-per-patch heuristic LOSES medium โ the
capability test is "transport cost dominates, not parallelism"."""
_, res = _run("medium", lambda: _make_alloc([NEAR, MID, FAR]))
assert res.outcome == "loss", (
f"uniform 1/1/1 split must LOSE medium (~11000 ev < 14000 bar); "
f"got {res.outcome} ev={_ev(res)}"
)
def test_medium_one_near_two_mid_loses():
"""A "diversify slightly towards MID" allocation still under-uses
the NEAR patch; medium's bar bites at this margin."""
_, res = _run("medium", lambda: _make_alloc([NEAR, MID, MID]))
assert res.outcome == "loss", (
f"1-NEAR+2-MID must LOSE medium (~12000 ev < 14000 bar); "
f"got {res.outcome} ev={_ev(res)}"
)
def test_medium_balanced_2near_1mid_wins():
"""The intended balanced allocation (2 harvs on NEAR + 1 on MID)
wins cleanly โ the textbook Weber-multi answer with this geometry."""
_, res = _run("medium", lambda: _make_alloc([NEAR, NEAR, MID]))
assert res.outcome == "win", (
f"2-NEAR+1-MID (intended) must WIN medium; got {res.outcome} "
f"ev={_ev(res)}"
)
def test_medium_2near_1far_wins():
"""Variant balanced allocation also clears the bar (the NEAR
saturation is soft enough that an extra FAR harv adds ~1000 ev)."""
_, res = _run("medium", lambda: _make_alloc([NEAR, NEAR, FAR]))
assert res.outcome == "win", (
f"2-NEAR+1-FAR must WIN medium; got {res.outcome} ev={_ev(res)}"
)
def test_medium_all_to_near_wins():
"""Concentrating ALL harvs on the NEAR patch is also a valid
optimum at this fleet size (3 harvs don't saturate the patch hard);
the bar discriminates "ignored NEAR" from "used NEAR", not from
"balanced vs concentrated"."""
_, res = _run("medium", lambda: _make_alloc([NEAR, NEAR, NEAR]))
assert res.outcome == "win", (
f"all-to-NEAR must WIN medium; got {res.outcome} ev={_ev(res)}"
)
# ---------------------------------------------------------------- HARD
@pytest.mark.parametrize("seed", [1, 2, 3, 4])
def test_hard_stall_loses_every_seed(seed):
_, res = _run("hard", lambda: stall_policy, seed=seed)
assert res.outcome == "loss", (
f"stall must LOSE hard/seed{seed}; got {res.outcome} ev={_ev(res)}"
)
@pytest.mark.parametrize("seed", [1, 2, 3, 4])
def test_hard_all_to_far_loses_every_seed(seed):
_, res = _run("hard", lambda: _make_alloc([H_FAR, H_FAR, H_FAR]), seed=seed)
assert res.outcome == "loss", (
f"all-to-FAR must LOSE hard/seed{seed} (~4500 ev < 22000 bar); "
f"got {res.outcome} ev={_ev(res)}"
)
@pytest.mark.parametrize("seed", [1, 2, 3, 4])
def test_hard_uniform_1pn_1mid_1far_loses_every_seed(seed):
"""The uniform "one per source" heuristic that drops one of the
two near patches loses every seed โ too much load on transport-
expensive patches."""
_, res = _run("hard", lambda: _make_alloc([P_NORTH, H_MID, H_FAR]), seed=seed)
assert res.outcome == "loss", (
f"uniform 1-PN+1-MID+1-FAR must LOSE hard/seed{seed}; "
f"got {res.outcome} ev={_ev(res)}"
)
def test_hard_memorised_pn_loses_on_south_spawn_seeds():
"""A model that memorises "always send to (16,14)" loses on
SOUTH-base seeds (1 and 3 per round-robin) โ the matched NEAR
patch is (16,28), and (16,14) is now ~14 cells of vertical
travel from the proc, dropping yield to ~16500 ev < 22000."""
for seed in (1, 3):
_, res = _run("hard", lambda: _make_alloc([P_NORTH, P_NORTH, P_NORTH]), seed=seed)
assert res.outcome == "loss", (
f"memorised-PN must LOSE hard/seed{seed} (SOUTH spawn); "
f"got {res.outcome} ev={_ev(res)}"
)
def test_hard_memorised_ps_loses_on_north_spawn_seeds():
"""Symmetric: memorising (16,28) loses on NORTH-base seeds 2 and 4."""
for seed in (2, 4):
_, res = _run("hard", lambda: _make_alloc([P_SOUTH, P_SOUTH, P_SOUTH]), seed=seed)
assert res.outcome == "loss", (
f"memorised-PS must LOSE hard/seed{seed} (NORTH spawn); "
f"got {res.outcome} ev={_ev(res)}"
)
@pytest.mark.parametrize("seed", [1, 2, 3, 4])
def test_hard_smart_spawn_matched_wins_every_seed(seed):
"""The intended capability โ identify the spawn-matched NEAR patch
from the agent's own base position, then concentrate harvs there โ
WINS every seed cleanly."""
_, res = _run("hard", _make_smart_hard, seed=seed)
assert res.outcome == "win", (
f"SMART spawn-matched policy must WIN hard/seed{seed}; "
f"got {res.outcome} ev={_ev(res)}"
)
# ---------------------------------------------------------------- determinism
def test_outcomes_are_deterministic_per_seed():
"""Same seed, same policy โ identical outcome and ev."""
c = compile_level(load_pack(PACK), "medium")
a = run_level(c, _make_alloc([NEAR, NEAR, MID]), seed=2)
b = run_level(c, _make_alloc([NEAR, NEAR, MID]), seed=2)
assert (a.outcome, a.turns, _ev(a)) == (b.outcome, b.turns, _ev(b))
|