OpenRA-Bench / SCENARIO_BRAINSTORM.md
Yiyu Tian
tech-production-planning pack: canonical Family 2 (Sequential Dependency Planning)
9b651dc
|
Raw
History Blame Contribute Delete
20.8 kB

A newer version of the Gradio SDK is available: 6.20.0

Upgrade

OpenRA-Bench — Scenario Brainstorm & Economy Family Spec

Design document. No engine or existing-pack changes. Two runnable economy packs were drafted and validate OK (see end).

All win conditions below use ONLY leaves/composites that exist in win_conditions.py: explored_pct_gte, enemies_discovered_gte, buildings_discovered_gte, units_killed_gte, units_lost_lte, within_ticks, after_ticks, reach_region, all_units_in_region, own_units_gte, cash_gte, harvesters_gte, power_surplus_gte, has_building, buildings_owned_gte, building_total_gte, building_count_gte{type,n}, building_in_region{type?,x,y,radius,count}; composites all_of / any_of / not.


Verified ground-truth constraints (this drove the design)

  1. No schema-valid ore source. The Rust engine seeds an ore patch in a ~4-cell radius around any actor of type mine (openra-sim/src/world.rs ~L4014, map_actor.actor_type == "mine" at ~L3908). But the Python ScenarioDefinition actor whitelist (openra_rl_training.scenario.VALID_ACTOR_TYPES) has no mine / gmine, so a pack that spawns one fails validation. The 5 .oramap files (map.yaml inspected) embed no resource tiles either — only mpspawn actors. ⇒ Harvest income is 0 in any runnable pack today. harv + proc spawn fine but find no ore. cash_gte is only satisfiable from starting_cash, and harvesters_gte only counts the auto-spawned harvester (it never earns).
  2. silo is schema-valid but inert. silo is in VALID_ACTOR_TYPES, but PlayerResources.resource_capacity is hardcoded 0 (openra-sim/src/traits/mod.rs:76,172, world.rs:3609) and the deposit path converts ore straight to cash ignoring capacity (world.rs:1467-1473). Building silos has no economic effect.
  3. starting_cash works (per-level / pack-wide, schema confirmed), build/train with tech-tree prerequisites works, proc auto-spawns one harv on creation (world.rs:1154).
  4. Only rush-hour-arena is Rust-loadable today (CONTRIBUTING §5); the four singles-* maps are schema-valid but Phase-3-gated.

Design consequence: runnable economy scenarios test the spend / allocation decision over a fixed budget (the part the lead identified as the real test). Harvest-throughput and silo-storage variants are fully specced but tagged engine-prereq.


1. Requested family — Economy-Time-Box (RUNNABLE TODAY)

Pack: packs/economy-time-box.yaml — validates OK. Capability: reasoning. Decision: within a fixed tick budget, convert a fixed cash budget into BOTH deployed units AND a standing economy; idle cash and all-in-on-units both fail.

real_world_meaning — Time-bounded capital deployment: a finite, non-replenishing budget must become mission capability and persistent infrastructure before a hard deadline. robotics_analogue — A field robot on a finite energy budget scheduling expenditure between mission work and standing up persistent infrastructure before a deadline.

Level starting_cash win_condition (real grammar) scaling axis
easy 4000 all_of[own_units_gte:4, building_total_gte:4, within_ticks:22000] loose clock & budget
medium 2400 all_of[own_units_gte:5, has_building:tent, building_total_gte:4, within_ticks:16000] adds tech-dep building, tighter
hard 1500 all_of[own_units_gte:6, building_total_gte:5, units_lost_lte:0, within_ticks:12000] lean budget, short clock, attrition cap

Difficulty comes from the decision getting harder (less budget slack, a tech-dependency constraint, an attrition cap + short clock), not bigger target numbers. Predicates: own_units_gte, building_total_gte, has_building, units_lost_lte, within_ticks. Commands: build, place_building, move_units, deploy, stop. Map: rush-hour-arena.

Engine status: runnable now.

Follow-up (leaderboard scoring, not a predicate today)

A final-economy-value score = cash + Σ asset_value(owned units & buildings) at the deadline would reward "spent it well" beyond the binary cash_gte. No such predicate exists; propose it as a scoring / leaderboard metric computed from the render state at episode end (sum buildable costs of own_buildings + units_summary, add signals.cash). Do not add it as a win leaf — pass/fail stays in the grammar above.

Harvest-income variant — needs S0 + S1 (see §Engine-prereqs)

Same shape but income comes from harvesting and cash_gte rises over time: all_of[cash_gte:N, own_units_gte:K, harvesters_gte:1, within_ticks:T]. Requires S0 (schema-valid ore source so harvesting actually earns) — and S1 only if silo storage is part of the test.


2. Requested family — Economy-Investment (allocation decision)

Pack: packs/economy-investment.yaml — validates OK (runnable variant).

real_world_meaning — Capital allocation under one indivisible budget: the same cash buys either a wide economy (more depot/throughput capacity) or a deep one (more collectors/force); the budget covers exactly one coherent path, so splitting builds neither. robotics_analogue — Fleet capital allocation: a fixed budget buying either added depot/processing capacity or added collector agents — the throughput-vs-collection trade, where indecision yields neither.

(a) Runnable-today variant — RUNNABLE NOW

Income is the fixed starting_cash; the allocation is verified by build predicates (no silo, no harvest dependency).

Level starting_cash win_condition (real grammar) path tested
easy 4500 all_of[building_count_gte{proc,2}, building_count_gte{powr,2}, within_ticks:22000] wide, loose
medium 3200 all_of[building_count_gte{proc,2}, building_count_gte{powr,2}, own_units_gte:3, within_ticks:16000] wide + utilisation, tighter
hard 2600 all_of[own_units_gte:6, building_count_gte{proc,1}, units_lost_lte:0, within_ticks:14000] deep (force) under lean budget + attrition

Difficulty escalates by removing budget slack so the allocation must be committed (a split budget clears no bar), tightening the clock, and adding an attrition cap. Predicates: building_count_gte{type,n}, own_units_gte, units_lost_lte, within_ticks. Commands as above. Map rush-hour-arena.

(b) Full version — ENGINE-PREREQ (needs S0 + S1)

The faithful test the lead asked for: enough cash for two refineries OR one refinery + extra harvesters, scored by harvested cash at the deadline, plus silos that raise the harvest cap.

  • Levels: easy = budget covers either path comfortably (commit to one); medium = budget covers exactly one (the bottleneck differs: 2×proc = unload throughput, 1×proc+harv = gather rate); hard = lean budget + short clock + silo decision (must build silo to avoid capping the resource pool before a return trip).
  • Win grammar (valid leaves, meaningful only after S0/S1): all_of[cash_gte:N, harvesters_gte:H, within_ticks:T] for the allocation; the silo sub-goal is observable as building_count_gte{type:silo,n:K} but only matters after S1.
  • Engine status: needs S0 (income) + S1 (storage). Without them the two allocations are economically identical (income 0) so the scenario is not discriminating — hence variant (a) ships instead.

Engine-prerequisite specs

S0 — schema-valid ore source (smaller, unblocks all harvest income)

Today the only ore mechanism is actor_type == "mine", which the Python schema rejects. Two non-engine-code options the project could take (documented here, not implemented):

  • Add mine/gmine to VALID_ACTOR_TYPES in openra_rl_training/scenario.py (one list entry; the Rust seed loop at world.rs:4014 already handles it), or
  • Add a resource_fields: [{x,y,radius,density}] field to ScenarioDefinition plumbed to terrain.set_resource(...) (openra-sim/src/terrain.rs:193) at world init. Either makes harvest income real and unblocks the §1 harvest variant and §2(b) allocation scoring (without silos).

S1 — silo / resource-storage modeling (the part to flag)

Goal: harvested resource enters a capped pool; cash is realised from the pool gradually (or on cap pressure), and silo buildings raise the cap. Precise change (Rust):

  1. openra-sim/src/traits/mod.rs — TraitState::PlayerResources { cash, resources, resource_capacity } already has the fields; stop hardcoding resource_capacity: 0 at the two constructor sites (mod.rs:172, world.rs:3609). Give players a small base capacity (e.g. from fact/proc).
  2. openra-sim/src/world.rs deposit path (~L1467-1473, Unloading arm of tick_harvesters) — instead of player.set_cash(current + value), add to PlayerResources.resources, clamped to resource_capacity; overflow is lost (matches OpenRA). Add a per-tick drain that converts resources → cash at a fixed rate (the realisation of stored ore).
  3. Silo capacity contribution — when an actor of type silo (and proc, in real OpenRA) becomes owned/active, add its Storage amount to the owning player's resource_capacity; subtract on loss. Implement as a recompute over owned buildings in the building add/remove path (mirror how PowerManager provided/drained is recomputed), reading a per-building storage constant (silo ≈ 3000, proc ≈ 2000 in RA defaults).
  4. (optional) win/score — no resources_gte predicate exists; either add one to _PREDICATES in win_conditions.py (one entry, pure: c.signals.resources >= int(v)) and expose resources on EpisodeSignals, or keep pass/fail on cash_gte and surface stored-value only in the leaderboard score.

With S1, building silos genuinely changes optimal play (don't cap; don't over-invest in storage you can't fill), making §2(b) discriminating.


3. Broader brainstorm — 13 scenario ideas across capabilities

Each: capability · real_world_meaning / robotics_analogue · win (real grammar) · predicates+commands · map · engine status. None duplicate the existing 13 packs.

Perception

P1 — Resource-Patch Discovery Under Fog

  • perception. RWM: locating where to forage before committing collectors is the real foraging problem, not the path to a known patch. RA: SLAM-time frontier selection for a resource map.
  • win: all_of[explored_pct_gte:55, within_ticks:9000]
  • uses explored_pct_gte,within_ticks; move_units,observe,stop. Map rush-hour-arena.
  • engine status: runnable now.

P2 — Count-the-Threats Before Acting

  • perception. RWM: estimating force size from partial sightings before engaging is a recon-quality problem distinct from combat. RA: multi-target detection/enumeration under occlusion.
  • win: all_of[enemies_discovered_gte:N, units_lost_lte:0, within_ticks:T] (levels raise N, add decoy idle units, tighten T).
  • uses enemies_discovered_gte,units_lost_lte,within_ticks; move_units,observe. Map rush-hour-arena.
  • engine status: runnable now.

P3 — Identify the Production Building (signature ID)

  • perception. RWM: classifying an installation by observable signature before acting on it. RA: semantic recognition of a target structure from sensor returns.
  • win: all_of[buildings_discovered_gte:1, within_ticks:T]; hard adds decoy buildings + tighter clock.
  • uses buildings_discovered_gte,within_ticks; move_units,observe. Map rush-hour-arena.
  • engine status: runnable now.

Reasoning

R1 — Tech-Tree Critical Path

  • reasoning. RWM: ordering prerequisite-coupled tasks (B needs power needs ...) to hit a deadline is scheduling, not building. RA: task-graph scheduling with hard precedence + a makespan deadline.
  • win: all_of[has_building:tent, building_total_gte:4, power_surplus_gte:0, within_ticks:T]; levels shorten T, raise depth.
  • uses has_building,building_total_gte,power_surplus_gte, within_ticks; build,place_building,deploy. rush-hour-arena.
  • engine status: runnable now.

R2 — Power-Budget Brownout Avoidance

  • reasoning. RWM: keeping a power budget non-negative while expanding — an online resource-constraint problem. RA: a robot keeping net power ≥ 0 while bringing subsystems online.
  • win: all_of[power_surplus_gte:0, building_total_gte:N, within_ticks:T]; hard raises N with scarce power slack.
  • uses power_surplus_gte,building_total_gte,within_ticks; build,place_building,power_down. rush-hour-arena.
  • engine status: runnable now.

R3 — Defensive-Direction Commitment

  • reasoning. RWM: committing limited defenses to the right approach under directional uncertainty. RA: sensor/effector placement toward the most likely intrusion bearing.
  • win: all_of[building_in_region{type:pbox,x:40,y:20,radius:9, count:2}, within_ticks:T]; levels move/duplicate the threat axis.
  • uses building_in_region,within_ticks; build,place_building. rush-hour-arena.
  • engine status: runnable now.

R4 — Economy-Time-Box (§1) — reasoning — engine status: runnable now.

R5 — Economy-Investment (a) (§2a) — reasoning — runnable now; full (b) needs S0 + S1.

Action

A1 — Synchronised Multi-Squad Arrival

  • action. RWM: executing a plan so dispersed agents converge simultaneously — coordination/execution, not planning. RA: multi-robot rendezvous with a timing constraint.
  • win: all_of[all_units_in_region{x:90,y:20,radius:6}, units_lost_lte:0, within_ticks:T]; levels split start positions wider, tighten T.
  • uses all_units_in_region,units_lost_lte,within_ticks; move_units,attack_move,stop. rush-hour-arena.
  • engine status: runnable now.

A2 — Sequenced Build-then-Relocate

  • action. RWM: executing an ordered build→deploy→reposition sequence without dropping a step under a clock. RA: a fixed manipulation sequence (assemble → carry → place) executed to deadline.
  • win: all_of[building_in_region{x:60,y:20,radius:10,count:2}, units_lost_lte:1, within_ticks:T].
  • uses building_in_region,units_lost_lte,within_ticks; move_units,deploy,build,place_building. rush-hour-arena.
  • engine status: runnable now.

A3 — Harvester Re-tasking Under Interruption

  • action. RWM: keeping a foraging loop alive by promptly re-tasking collectors when the route is disrupted. RA: closed-loop recovery of a collection cycle after an interruption.
  • win: all_of[harvesters_gte:1, cash_gte:N, within_ticks:T] — only meaningful once harvesting earns.
  • uses harvesters_gte,cash_gte,within_ticks; harvest,move_units,stop. rush-hour-arena.
  • engine status: needs S0 (no schema-valid ore source today).

Economy / Tech / Multi-objective

E1 — Economy-Time-Box harvest variant — see §1 — needs S0 (+ S1 if silos are in the test).

E2 — Economy-Investment full — see §2(b) — needs S0 + S1.

M1 — Earn-and-Survive (multi-objective)

  • reasoning (multi-objective). RWM: holding an income loop and a defensive constraint simultaneously — competing objectives under one budget. RA: a robot foraging while maintaining a safety constraint.
  • win: all_of[cash_gte:N, units_lost_lte:0, building_total_gte:K, within_ticks:T] (the cash_gte arm is real income only post-S0; pre-S0 it degenerates to a spend test like §1).
  • uses cash_gte,units_lost_lte,building_total_gte,within_ticks; harvest,build,move_units. rush-hour-arena.
  • engine status: spend-only form runnable now; income form needs S0.

Summary

  • Runnable now: P1, P2, P3, R1, R2, R3, R4 (economy-time-box), R5/§2a (economy-investment runnable variant), A1, A2, and the spend-only form of M1 — all expressible in the existing grammar on rush-hour-arena.
  • Engine-prereq S0 (schema-valid ore source — a one-line whitelist add or a resource_fields scenario field; the Rust seed loop already exists): unblocks any harvest income scenario — A3, E1, the income form of M1, and the §2(b) allocation scoring.
  • Engine-prereq S1 (silo storage: stop hardcoding resource_capacity, route deposits through a capped resources pool in tick_harvesters, recompute capacity from owned silo/proc buildings, optional resources_gte predicate): required only for the silo mechanic in §2(b).
  • Final-economy-value scoring (cash + asset value at deadline) is a desirable leaderboard metric, NOT a win predicate — there is no such leaf and one should not be assumed.

Files written

  • openra_bench/scenarios/packs/economy-time-box.yaml — validator: OK
  • openra_bench/scenarios/packs/economy-investment.yaml — validator: OK
  • SCENARIO_BRAINSTORM.md (this file)

Tech-Tree & Production Planning pack (design-doc Family 2)

Pack: openra_bench/scenarios/packs/tech-production-planning.yaml. Maps the Benchmark Design Proposal's Task Family 2 — Sequential Dependency Planning (Scenarios 2A/2B/2C) directly into one bench pack.

Why a new pack rather than extending existing ones. Two existing packs touch this territory but ladder on different axes:

Pack Easy axis Medium axis Hard axis Ladder shape
building-and-planning 1-hop prereq (powr→tent) + building count spatial direction (place pbox east) MCV relocate / new base building → spatial → mobility (changes axis per level)
strict-production-bom exact 3×e1 (no overproduction) + 2×e3 + tsla + tight budget spec-fidelity (BFCL/τ²-bench style; orthogonal to dependency planning)
tech-production-planning (new) 2-hop chain weap→fix→3tnk parallel branches (barr ‖ weap+fix) + power_surplus_gte:0 forces 2nd powr mid-game infantry tech committed → scout fog → pivot to anti-armor dependency depth → parallel breadth → adaptive replanning (same axis at increasing complexity)

building-and-planning is mixed-axis (Family 2-ish at easy, drifts into Family 3/spatial later). strict-production-bom tests a different capability entirely (instruction-following fidelity, not dependency planning). The new pack is the first time Family 2 is ladded canonically on one axis end-to-end.

Real tech tree (engine ground truth from openra_env/game_data.py):

Building Prereqs Power
powr — +100
proc (refinery) powr −30
weap (war factory) proc −30
fix (service depot) weap −30
barr (soviet barracks) powr −20
3tnk (Heavy Tank) weap + fix unit
e1 / e3 (infantry) `barr tent`

→ Heavy Tank's real chain is powr → proc → weap → fix → 3tnk (4 prereqs deep). Building all of proc + weap + fix + barr drains to −10 power against a 100-power base — a SECOND powr is mandatory for medium. This is the real power_surplus_gte:0 teeth at medium.

Difficulty calibration (one notch under doc spec). Design doc 2A ("Construction Yard + 5000 credits → produce one Heavy Tank from scratch") is harder than every existing easy in the repo (~3× the work). Easy here pre-places fact + powr + proc so the chain becomes weap → fix → 3tnk (2-hop), aligning with strict-production-bom easy's "1 build + N train" workload. Medium and hard are calibrated similarly downward. Hard (adaptive transition under fog) is left at the design-doc difficulty because no existing pack tests online plan revision — this is the pack's core new contribution.

Follow-ups (Design Proposal Families 4 / 5 / 7) — separate PRs

Each likely needs new engine infrastructure that isn't yet wired into the bench:

  • Family 4 — Adversarial Reasoning (Beginner/Normal/Hard AI match). Existing adversarial-*.yaml packs are duels/sieges (combat encounters), not full RTS matches; bot_type: '' is empty across them, so a scripted opponent AI tier doesn't appear to be exposed yet. Needs bot_type integration + a "win full match" win predicate.

  • Family 5 — Reactive Replanning (Early threat / Multi-raid / Catastrophic). The interrupts field exists (adversarial-duel uses enemy_unit_spotted + own_unit_destroyed), but raid-generation hooks (mid-game enemy attack scripts) and an "infrastructure destroyed" trigger aren't represented in any pack.

  • Family 7 — Holistic Strategic Intelligence (full RTS match at 3 difficulties). Needs Family 4's infra (full-match predicate + AI opponent) plus long-horizon scoring beyond the existing rubric.