File size: 5,531 Bytes
701ffc9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
51d66ad
701ffc9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
"""Generate docs/scenarios.html — the human-readable scenario catalog.

For every ACTIVE pack: title, capability, why-it-exists, the runnable
configs (or 3 levels), and per cell the plain-language objective the
model actually sees (objective_brief: description + WIN/LOSE + turn
budget). Run:  python scripts/gen_scenario_docs.py [--open]
"""

from __future__ import annotations

import glob
import html
import os
import sys
from pathlib import Path

ROOT = Path(__file__).resolve().parent.parent
PACKS = ROOT / "openra_bench" / "scenarios" / "packs"
OUT = ROOT / "docs" / "scenarios.html"

sys.path.insert(0, str(ROOT))  # runnable as a standalone script

from openra_bench.game_knowledge import objective_brief  # noqa: E402
from openra_bench.scenarios import load_pack  # noqa: E402

_CAP_COLOR = {
    "perception": "#7497db", "reasoning": "#9b8cce",
    "action": "#5fae7a", "adversarial": "#d2683c",
}


def _esc(s) -> str:
    return html.escape(str(s)).replace("\n", "<br>")


def _cells(pack):
    """[(label, CompiledLevel)] — configs if declared, else 3 levels."""
    out = []
    if pack.configs:
        for c in pack.configs:
            out.append((c.name, pack.compile_config(c.name)))
    else:
        for lv in ("easy", "medium", "hard"):
            out.append((lv, pack.compile(lv)))
    return out


def build() -> str:
    packs = []
    for f in sorted(glob.glob(str(PACKS / "*.yaml"))):
        b = os.path.basename(f)
        if b.startswith(("_", "TEMPLATE")):
            continue
        p = load_pack(f)
        if p.meta.status == "active":
            packs.append(p)

    by_cap: dict[str, list] = {}
    for p in packs:
        by_cap.setdefault(p.meta.capability, []).append(p)

    parts = [
        "<!doctype html><meta charset=utf-8>",
        "<title>OpenRA-Bench — Scenario Catalog</title>",
        """<style>
        body{font:15px/1.5 -apple-system,Segoe UI,Roboto,sans-serif;
             margin:0;background:#0f1115;color:#e6e6e6}
        header{padding:24px 32px;background:#161922;
               border-bottom:1px solid #2a2f3a}
        h1{margin:0;font-size:22px} .sub{color:#9aa3b2;margin-top:6px}
        main{padding:24px 32px;max-width:1100px}
        h2{margin:34px 0 8px;font-size:18px;border-bottom:1px solid #2a2f3a;
           padding-bottom:6px}
        .pack{background:#161922;border:1px solid #2a2f3a;border-radius:10px;
              padding:16px 18px;margin:14px 0}
        .ptitle{font-size:17px;font-weight:600}
        .pid{color:#7e8796;font-size:12px;font-family:ui-monospace,monospace}
        .cap{display:inline-block;padding:2px 9px;border-radius:10px;
             color:#fff;font-size:12px;margin-left:8px;vertical-align:middle}
        .why{color:#c3cad6;margin:8px 0 12px;font-size:14px}
        .cell{border-left:3px solid #2a2f3a;padding:6px 0 6px 14px;
               margin:10px 0}
        .clab{font-weight:600;color:#cdd5e3}
        pre{white-space:pre-wrap;background:#0f1115;border:1px solid #242a35;
            border-radius:6px;padding:10px 12px;margin:6px 0 0;
            font:13px/1.45 ui-monospace,monospace;color:#d7dce6}
        .toc a{color:#7497db;text-decoration:none;margin-right:14px}
        </style>""",
        "<header><h1>OpenRA-Bench — Scenario Catalog</h1>",
        f"<div class=sub>{len(packs)} active scenarios · the title, why "
        "it exists, and the exact objective the model is given per "
        "runnable config.</div>",
        "<div class=sub toc>" + " ".join(
            f"<a href='#{c}'>{c} ({len(v)})</a>"
            for c, v in sorted(by_cap.items())
        ) + "</div></header><main>",
    ]

    for cap in sorted(by_cap):
        parts.append(f"<h2 id='{cap}'>{cap}</h2>")
        for p in sorted(by_cap[cap], key=lambda x: x.meta.id):
            col = _CAP_COLOR.get(cap, "#666")
            parts.append("<div class=pack>")
            parts.append(
                f"<div><span class=ptitle>{_esc(p.meta.title)}</span>"
                f"<span class=cap style='background:{col}'>{cap}</span>"
                f"<div class=pid>{p.meta.id} · {p.base_map}</div></div>"
            )
            parts.append(
                f"<div class=why><b>Why:</b> {_esc(p.meta.real_world_meaning)}"
                f"<br><b>Robotics analogue:</b> "
                f"{_esc(p.meta.robotics_analogue)}</div>"
            )
            try:
                cells = _cells(p)
            except Exception as e:  # noqa: BLE001
                parts.append(f"<div class=why>(compile error: {_esc(e)})</div>")
                cells = []
            for label, cl in cells:
                fog = getattr(cl, "fog_mode", "vision")
                ob = objective_brief(
                    cl.scenario.description, cl.win_condition,
                    cl.fail_condition, cl.max_turns,
                    getattr(cl, "objective_coords", "exact"),
                )
                parts.append(
                    f"<div class=cell><span class=clab>{label}</span> "
                    f"<span class=pid>(level {cl.level} · fog "
                    f"{fog})</span><pre>{_esc(ob)}</pre></div>"
                )
            parts.append("</div>")
    parts.append("</main>")
    return "".join(parts)


def main(argv):
    OUT.parent.mkdir(parents=True, exist_ok=True)
    OUT.write_text(build(), encoding="utf-8")
    print(f"wrote {OUT}")
    if "--open" in argv:
        import subprocess

        subprocess.run(["open", str(OUT)], check=False)


if __name__ == "__main__":
    main(sys.argv[1:])