File size: 3,220 Bytes
02f4a63
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
"""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.
    """
    # Normalize obs to dict
    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"),
    }

    # Save to disk
    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