OpenRA-Bench / EVAL_STACK_PLAN.md
yxc20098's picture
Add Rust-backed eval stack: scenario packs, adapter, spine, integration tests
098c6e0
|
Raw
History Blame Contribute Delete
9.01 kB

A newer version of the Gradio SDK is available: 6.19.0

Upgrade

OpenRA-Bench → Unified Eval Stack Plan

Goal

Turn OpenRA-Bench into an efficient, customizable harness to evaluate model performance on spatial, multi-modal, and complex multi-target multi-step reasoning / planning, by reusing the mature eval stack from OpenRA-RL-Training on top of the Rust environment (OpenRA-Rust).

Two evaluation modes:

  1. Fixed-scenario eval — N episodes of one model on a controlled scenario, scored with verifiable rubrics + composite score + 95% CIs.
  2. Pairwise adversarial 1v1 — two models in one 1v1 map, win-rate / Elo.

Scenarios must be controlled and isolate specific capabilities (e.g. the observed failure: model cannot connect an unexplored region to a target area to explore — a vision/perception + spatial-planning deficiency).

Core framing: the Perception → Reasoning → Action chain

Every scenario evaluates one chain: read the (visual + symbolic) state → form a multi-step plan → emit valid commands that execute it. The eval must attribute failure to a specific link, not just report a score:

  • Perception — did the model correctly read the minimap/state? (e.g. locate the unexplored region and the target). Probe via state-readback / forced-choice checks derived from ground-truth obs.
  • Reasoning — given correct perception, did it form a valid plan? (e.g. connect unexplored→target, sequence multi-target order). Probe via plan quality vs optimal (path length, target ordering, sub-goal coverage).
  • Action — did it emit syntactically/semantically valid commands that realize the plan? Probe via action-validity rate and plan↔execution drift.

Per-scenario rubrics carry one diagnostic per link so a low score points at the broken link. This is the primary product differentiator vs a raw win-rate bench.

Decisions (locked)

  • Repo: refactor OpenRA-Bench in place; it imports Training's eval stack. OpenRA-RL-Training stays source of truth for the engine code.
  • Backend: Rust only (openra_train PyO3). C# (openra-rl) is slow/fragile and is dropped from the eval path. Rust must be made faithful to the C# reference where scenarios require it.
  • Multi-modal: reuse Training's minimap_renderer.render_minimap() PNG, injected as image_url in the agent prompt.

Source components reused from OpenRA-RL-Training

Component Path Role in Bench
Episode engine openra_rl_training/training/agent_rollout.py (play_episodes_async) Drives the real model loop (currently Bench's agent fn is a no-op)
Reward dims training/reward_funcs.py Per-scenario weighted scoring
Rust pool training/rust_env_pool.py The only backend
Minimap training/minimap_renderer.py Multi-modal observation
Scenarios/rubrics scenarios/*.yaml, curricula/*.yaml, verifiable metrics Controlled tasks + pass/fail
CI comparison scripts/build_eval_comparison.py Stat-sound model comparison

Rust faithfulness gap (drives sequencing)

  • Commands: 3/22 (Move, Attack, Observe). Missing: Build/Train/Harvest/Deploy/ Sell/Repair/Stance/Transport/Power/RallyPoint/Guard/Patrol…
  • Observations: ~30% of C# proto. Missing: economy, production, military stats, spatial tensor, kill_events, result/reward fields.
  • Scenarios: 2 hand-built (rush-hour, scout-maginot). No generic .oramap load.
  • Engine: movement (A*), combat, projectiles, fog, static defenses = done. Economy, production/tech, transport, multi-armament = not done.

→ Movement/combat/fog scenarios + combat-only 1v1 work today. Economy / production / tech scenarios require Rust engine work first.

CRITICAL FINDING (verified end-to-end, local)

play_episodes_async (agent_rollout.py:4815) is hardwired to the C# gRPC server via openra_env.mcp_ws_client.OpenRAMCPClient and is entangled with TRL (tokenizer, prompt_ids/completion_ids, worker pool, partial cache). It is not reusable as-is on Rust. rust_env_pool is used only by the lighter rollout.py path.

Also: minimap_renderer.render_minimap() expects state["minimap"] (ASCII), units_summary, enemy_summarynone of which the Rust env emits.

Verified live Rust obs schema (openra_train rush-hour, local wheel, Python 3.12, anaconda):

keys = enemy_buildings_summary, enemy_hp, enemy_positions, explored_cells,
       explored_percent, game_tick, unit_hp, unit_positions, units_killed
unit_positions = {actor_id: {cell_x, cell_y[, target, activity, ...]}}
step() -> (obs, reward=0.0 hardcoded, done:bool, info={game_tick, warnings})

No minimap ASCII, no economy/military/result/reward, no terrain.

Consequence: Phase 0 builds a Bench-side episode loop that reuses components (reward_funcs, minimap_renderer, scenario loader, action parser) behind a Rust→schema adapter (openra_bench/rust_adapter.py). The adapter is the crux and overlaps directly with the "make Rust faithful" workstream:

  • unit_positionsunits_summary (renderer/prompt schema)
  • enemy_positions + enemy_buildings_summaryenemy_summary
  • synthesize ASCII minimap from explored_cells + scenario map dims
  • load terrain_png from the scenario's base .oramap (as Training does)
  • derive scoring signals (kills, discovery, exploration, outcome) from obs deltas since Rust reward is hardcoded 0.0 — feeds reward_funcs + the P/R/A diagnostics directly.

Target Bench layout

OpenRA-Bench/
  openra_bench/
    eval_core.py      # thin wrapper over play_episodes_async, Rust backend forced
    agent.py          # REAL model agent (OpenAI-compatible), minimap multimodal
    scenarios/        # controlled eval scenarios (symlink/copy + Bench-authored)
    rubrics.py        # verifiable + composite scoring (reuse Training)
    pairwise.py       # 1v1 adversarial orchestration + Elo
  evaluate.py         # fixed-scenario CLI (rewritten to use eval_core)
  compare.py          # CI comparison front-end (wraps build_eval_comparison)
  app.py              # leaderboard (fed by both modes)

Phases

Phase 0 — Integration spine (no Rust changes)

  • Bench depends on openra_rl_training + openra_train.
  • eval_core.py: wrap play_episodes_async, force Rust pool.
  • agent.py: real OpenAI-compatible model agent w/ minimap PNG.
  • Rewrite evaluate.py → fixed-scenario eval producing eval_stats.json.
  • compare.py → 95% CI tables. Wire results into app.py leaderboard.
  • Validate on rush-hour + scout-maginot (these are the perception tasks).

Phase 1 — Adversarial 1v1 (combat-only, current mechanics)

  • Rust: add a second RL-controlled player slot in a 1v1 map (both sides accept Commands; remove scripted-enemy assumption).
  • Bench pairwise.py: two-model orchestration, win-rate + Elo, leaderboard.

Phase 2 — Controlled scenario library (current mechanics)

  • Author perception/spatial scenarios that isolate the unexplored→target connection failure; maze/chokepoint pathfinding; multi-target prioritization.
  • Verifiable rubrics per scenario (intelligence_pct, path-optimality, etc.).

Phase 3 — Rust mechanics expansion (unlocks scenario families)

  • 3a Economy: ore/cash/harvester obs + HARVEST cmd + economy reward.
  • 3b Production/tech: production queue, BUILD/TRAIN, available_production, power.
  • Each sub-phase ships its scenario family + rubrics.

Test coverage (live engine, no mocks)

tests/test_rust_integration.py — 17 tests, ~1.9s, boots real openra_train with rule-based bots (idle / charge / hunter):

  • tool correctness: move_units reaches target; idle units hold; attack path; reset schema; same-seed determinism (bit-for-bit).
  • corner cases: empty command list, invalid unit id (warns, no raise), invalid attack target, out-of-bounds move — all safe.
  • invariants: explored% and units_killed monotonic non-decreasing; discovery set cumulative.
  • stack: adapter signal tracking; win-condition predicates + composites
    • unknown-key rejection; deterministic win/fail plumbing (trivially true win/fail conditions ⇒ exact outcome, not bot-skill dependent); all authored packs run end-to-end.

Sequencing (locked)

Phase 0 → Phase 2 (+ P/R/A diagnostics) → Phase 1 → Phase 3

Scenario breadth + per-link diagnostics first: fastest path to exposing real model strengths/weaknesses on current Rust mechanics. Adversarial 1v1 and the economy/production engine work follow.

Model provider abstraction (Phase 0)

openra_bench/agent.py exposes a provider-agnostic agent. Adapters:

  • openai-compatible (default): covers local vLLM (matches Training's rollout path) and OpenRouter (test target). Same Chat Completions + multimodal image_url for the minimap PNG. Base URL + key from config/env.
  • bedrock: separate adapter (AWS SDK / Converse API), added when needed.

Selected via Bench config (provider, base_url, model, api_key_env). Phase 0 validates with OpenRouter.