| """Build bench export JSON from a final game observation. |
| |
| Custom agents that use OpenRAEnv directly (CNN, RL, multi-agent, etc.) |
| can call build_bench_export() after their game loop to produce a bench |
| submission JSON — the same format the built-in LLM agent auto-exports. |
| |
| Usage: |
| from openra_env.bench_export import build_bench_export |
| |
| obs = await env.step(action) # final observation (obs.done == True) |
| export = build_bench_export( |
| obs, |
| agent_name="DeathBot-9000", |
| agent_type="RL", |
| opponent="Normal", |
| ) |
| print(f"Saved to {export['path']}") |
| |
| # Then submit: |
| # openra-rl bench submit <path> |
| """ |
|
|
| import json |
| from datetime import datetime, timezone |
| from pathlib import Path |
| from typing import Any, Dict, Optional |
|
|
|
|
| def build_bench_export( |
| obs: Any, |
| agent_name: str, |
| agent_type: str = "RL", |
| opponent: str = "Normal", |
| agent_url: str = "", |
| replay_path: str = "", |
| export_dir: Optional[Path] = None, |
| ) -> Dict[str, Any]: |
| """Build and save a bench export JSON from a final observation. |
| |
| Args: |
| obs: Final observation — either a dict or a Pydantic model with |
| .military, .economy, .tick, .result, .explored_percent attributes. |
| agent_name: Display name for the leaderboard. |
| agent_type: One of "Scripted", "LLM", "RL". |
| opponent: Difficulty tier (Beginner/Easy/Medium/Normal/Hard). |
| agent_url: Optional GitHub/project URL. |
| replay_path: Optional path to .orarep replay file. |
| export_dir: Where to save the JSON (default: ~/.openra-rl/bench-exports/). |
| |
| Returns: |
| Dict with all submission fields plus "path" pointing to the saved file. |
| """ |
| |
| if hasattr(obs, "model_dump"): |
| obs_dict = obs.model_dump() |
| elif hasattr(obs, "__dict__") and not isinstance(obs, dict): |
| obs_dict = vars(obs) |
| else: |
| obs_dict = dict(obs) |
|
|
| mil = obs_dict.get("military") or {} |
| kills = mil.get("kills_cost", 0) |
| deaths = mil.get("deaths_cost", 0) |
|
|
| sub = { |
| "agent_name": agent_name, |
| "agent_type": agent_type, |
| "agent_url": agent_url, |
| "opponent": opponent, |
| "games": 1, |
| "result": obs_dict.get("result", ""), |
| "win": obs_dict.get("result") == "win", |
| "ticks": obs_dict.get("tick", 0), |
| "kills_cost": kills, |
| "deaths_cost": deaths, |
| "kd_ratio": round(kills / max(deaths, 1), 2), |
| "assets_value": mil.get("assets_value", 0), |
| "explored_percent": obs_dict.get("explored_percent", 0), |
| "reward_vector": obs_dict.get("reward_vector", {}), |
| "replay_path": replay_path, |
| "timestamp": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"), |
| } |
|
|
| |
| if export_dir is None: |
| export_dir = Path.home() / ".openra-rl" / "bench-exports" |
| export_dir.mkdir(parents=True, exist_ok=True) |
|
|
| ts = datetime.now(timezone.utc).strftime("%Y%m%dT%H%M%SZ") |
| slug = agent_name.replace("/", "_").replace(" ", "_")[:40] |
| export_path = export_dir / f"bench-{slug}-{ts}.json" |
| export_path.write_text(json.dumps(sub, indent=2)) |
|
|
| sub["path"] = str(export_path) |
| return sub |
|
|