Spaces:
Running
Running
File size: 9,615 Bytes
e3e79cb | 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 | #!/usr/bin/env python3
"""Author per-scenario maps for each scenario pack.
The bench currently shares one 128ร40 `rush-hour-arena.oramap` across
194 of 210 packs. Each scenario should have a map TAILORED to what it
measures โ small focused arenas for short-execution packs (Tanya C4),
chokepoint maps for defensive packs, multi-zone maps for expansion /
multi-base packs, bridges for crossing-defense packs.
This script declares a per-pack map spec (one dict per pack id) and:
--dry-run : materialize the .oramaps + print a summary;
do NOT edit any pack YAMLs.
--apply : in addition, rewrite each pack's `base_map:`
line to point at the new per-scenario .oramap.
--validate : run a stall-policy smoke through each pack to
confirm the new map loads cleanly (no panic) and
the scenario still terminates.
Authoring rule of thumb (from CLAUDE.md):
- small open arena โ short-execution packs (Tanya C4, engineer
capture easy, spy infiltrate easy).
- medium with corridor / chokepoint โ packs that need a forced
pinch (def-bridge-chokepoint, chokepoint family).
- large open arena โ search / perception packs (spec-nuke-strike,
scout-far-frontier).
- bridges-arena โ packs whose briefing references bridges
(def-bridge-chokepoint).
- naval-arena โ packs with water + ships (combat-naval-shore-strike).
The 12-pack debug grid is the first wave; the remaining ~198 packs
follow in subsequent waves once these are validated.
"""
from __future__ import annotations
import argparse
import sys
from pathlib import Path
HERE = Path(__file__).resolve().parent
REPO = HERE.parent
if str(REPO) not in sys.path:
sys.path.insert(0, str(REPO))
from openra_bench.mapgen import materialize # noqa: E402
# โโ per-pack map specs โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
# Each spec is the dict you'd put under `base_map:` in the pack YAML.
# Generators understand: arena, bridges-arena, chokepoint-arena,
# naval-arena. `name` slugs become the .oramap filename.
PER_PACK_MAPS: dict[str, dict] = {
# โโ special-ability family โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
# Short execution; small focused arena keeps the run on the
# action, not the navigation.
"spec-tanya-c4-strike": {
"generator": "arena",
"name": "spec-tanya-c4-strike-arena",
"title": "Tanya Strike โ close-range execution",
"width": 80, "height": 32, "cordon": 2,
},
# Engineer is fragile; one obstacle band creates a north/south
# choice that tests path planning under attrition.
"spec-engineer-capture": {
"generator": "arena",
"name": "spec-engineer-capture-arena",
"title": "Engineer Capture โ escort path",
"width": 96, "height": 36, "cordon": 2,
"obstacles": [{"x": 44, "y": 14, "w": 8, "h": 8}],
},
# Spy infiltrates fogged; medium arena with TWO cover clusters
# offers a stealth-route choice.
"spec-spy-infiltrate": {
"generator": "arena",
"name": "spec-spy-infiltrate-arena",
"title": "Spy Infiltrate โ fogged approach",
"width": 96, "height": 36, "cordon": 2,
"obstacles": [
{"x": 30, "y": 8, "w": 6, "h": 4},
{"x": 30, "y": 24, "w": 6, "h": 4},
{"x": 56, "y": 16, "w": 6, "h": 4},
],
},
# Thief drains cash from proc/silo. Single corridor approach
# tests whether the model finds the only path.
"spec-thief-steal-cash": {
"generator": "chokepoint-arena",
"name": "spec-thief-steal-cash-arena",
"title": "Thief โ single corridor approach",
"width": 80, "height": 32, "cordon": 2,
"pinch_x": 40, "pinch_width": 8, "corridor_width": 4,
"corridor_y": 16,
},
# Nuke targets a CLUSTER; the test is spatial commit (find +
# aim). Large open arena gives plausible alternative cluster
# positions.
"spec-nuke-strike": {
"generator": "arena",
"name": "spec-nuke-strike-arena",
"title": "Nuke Strike โ large arena, find the cluster",
"width": 144, "height": 48, "cordon": 3,
},
# โโ economy family โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
# Pure macro: small open arena, no obstacles, no enemies.
"econ-mine-and-grow": {
"generator": "arena",
"name": "econ-mine-and-grow-arena",
"title": "Mine and Grow โ pure macro",
"width": 72, "height": 32, "cordon": 2,
},
# Contested patch in the centre; obstacle band between the two
# bases forces an approach commitment.
"econ-contested-expansion": {
"generator": "arena",
"name": "econ-contested-expansion-arena",
"title": "Contested Expansion โ single channel",
"width": 112, "height": 36, "cordon": 2,
"obstacles": [
{"x": 50, "y": 6, "w": 8, "h": 8},
{"x": 50, "y": 22, "w": 8, "h": 8},
],
},
# Multi-patch decision; medium arena with room for 3+ patches.
"econ-multi-patch-allocation": {
"generator": "arena",
"name": "econ-multi-patch-allocation-arena",
"title": "Multi-Patch Allocation",
"width": 128, "height": 40, "cordon": 2,
},
# Second-base race; medium arena with clear best vs second-best
# placement zones.
"econ-second-base-race": {
"generator": "arena",
"name": "econ-second-base-race-arena",
"title": "Second Base Race",
"width": 112, "height": 36, "cordon": 2,
},
# Defend harvester from a raider on a single lane.
"econ-harvester-defense-raid": {
"generator": "arena",
"name": "econ-harvester-defense-raid-arena",
"title": "Harvester Defense โ single raid lane",
"width": 96, "height": 36, "cordon": 2,
"obstacles": [
{"x": 48, "y": 6, "w": 6, "h": 10},
{"x": 48, "y": 22, "w": 6, "h": 10},
],
},
# โโ defense / combat family โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
# Bridges! Real water channel with 3 crossings โ the briefing
# finally matches the terrain.
"def-bridge-chokepoint": {
"generator": "bridges-arena",
"name": "def-bridge-chokepoint-arena",
"title": "Bridge Chokepoint โ 3 crossings",
"width": 128, "height": 40, "cordon": 2,
"axis": "vertical",
"channel_x": 60, "channel_width": 3,
"bridges": [
{"pos": 8, "width": 3},
{"pos": 18, "width": 3},
{"pos": 28, "width": 3},
],
},
# Naval shore strike already uses naval-arena; keep but namespace
# to the pack so future tuning is per-scenario.
"combat-naval-shore-strike": {
"generator": "naval-arena",
"name": "combat-naval-shore-strike-arena",
"title": "Naval Shore Strike",
"width": 96, "height": 40, "cordon": 2,
},
}
def main() -> int:
ap = argparse.ArgumentParser(description=__doc__,
formatter_class=argparse.RawDescriptionHelpFormatter)
ap.add_argument("--dry-run", action="store_true",
help="materialize maps and print summary; no YAML edits")
ap.add_argument("--apply", action="store_true",
help="rewrite each pack's base_map to the per-scenario id")
ap.add_argument("--packs", default="all",
help="comma-separated pack ids or 'all'")
args = ap.parse_args()
target = (set(args.packs.split(",")) if args.packs != "all"
else set(PER_PACK_MAPS))
materialized: list[tuple[str, str, Path]] = []
for pid, spec in sorted(PER_PACK_MAPS.items()):
if pid not in target:
continue
mid = materialize(spec)
out = REPO / "data" / "maps" / f"{mid}.oramap"
materialized.append((pid, mid, out))
print(f" {pid:34s} โ {mid:42s} ({out.stat().st_size} bytes)")
print(f"\nmaterialized {len(materialized)}/{len(target)} pack maps")
if args.apply:
from openra_bench.scenarios.loader import PACKS_DIR
edited = 0
for pid, mid, _ in materialized:
yaml_path = PACKS_DIR / f"{pid}.yaml"
if not yaml_path.exists():
print(f" โ pack file missing: {yaml_path}")
continue
raw = yaml_path.read_text()
# Replace the first `base_map: <something>` line with the
# new logical id. We use a regex anchored to start-of-line
# so we don't touch any documentation that mentions
# "base_map:" inside a comment.
import re
new_raw, n = re.subn(
r"^base_map: .*$",
f"base_map: {mid}",
raw,
count=1,
flags=re.MULTILINE,
)
if n == 0:
print(f" โ no base_map: line in {pid}")
continue
if new_raw != raw:
yaml_path.write_text(new_raw)
edited += 1
print(f"\napplied to {edited} pack YAMLs")
return 0
if __name__ == "__main__":
sys.exit(main())
|