File size: 16,238 Bytes
988be21
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
27aa296
 
988be21
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17d0c97
 
988be21
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
27aa296
988be21
 
b53e86d
988be21
 
 
 
 
 
 
27aa296
988be21
 
 
5e1643b
988be21
 
5e1643b
988be21
 
 
 
 
 
 
 
 
17d0c97
 
 
 
 
988be21
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b53e86d
988be21
 
 
 
 
 
 
 
b85bdcb
0c7052b
 
 
 
 
 
 
 
 
 
b85bdcb
 
988be21
 
 
 
17d0c97
988be21
 
 
b85bdcb
988be21
 
 
 
 
 
 
 
 
 
 
 
 
 
17d0c97
988be21
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17d0c97
988be21
 
17d0c97
 
988be21
 
 
 
 
 
 
b53e86d
988be21
 
 
0c7052b
988be21
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
#!/usr/bin/env python3
"""The Text-to-Scene gallery: what every shown configuration built on each of the 20 scenes (user, 2026-10-16:
"gallery可以放上去").

The internal leaderboard's gallery, rebuilt for this site: the evaluator's four candidate renders per model and case
(the benchmark's own cameras, 1280x720), with the case score and cost. Only the configurations on the leaderboard
(data/t2s-sandbox.json, written by scripts/t2s_sandbox_round.py, so run that first); nothing internal (paths, judge
notes, clips) is carried over. Pages:
  gallery/index.html          the agents, in leaderboard order, each with a strip of fixed scenes
  gallery/cases.html          scene by scene: every agent's view-0 render, best first; a click shows all four views
  gallery/model/<key>.html    one agent: its 20 scenes, each with the four renders, score, components and cost
Renders are re-encoded as WebP (1280 px at q72, 480 px thumbnails at q70): about 120 kB a view instead of 260 kB,
with no visible difference at full size. Encoding is incremental.
    python3 scripts/t2s_gallery.py --src ../t2s-src
"""
from __future__ import annotations

import argparse
import html
import json
import math
import re
import shutil
from concurrent.futures import ProcessPoolExecutor
from pathlib import Path

from PIL import Image

ROOT = Path(__file__).resolve().parent.parent
OUT = ROOT / "gallery"
V = "20261016t2s"
STRIP = ["blue-city", "cyberpunk", "egyptian-temple", "nordic-harbour", "winter-town"]   # the same five for everyone
CSS_V, PAPER_CSS = "20261017logo", "5427505a"          # buildingbench.css as scripts/simworld_logo.py last versioned it
MARK = "assets/simworld-mark.png?v=20261017logo"        # the SimWorld emblem (scripts/simworld_logo.py)
e = html.escape


def trunc(x: float, n: int = 3) -> str:
    return f"{math.floor(x * 10 ** n + 1e-9) / 10 ** n:.{n}f}"


def money(x):
    return f"${x:.2f}" if x is not None else "–"


# ---- renders -------------------------------------------------------------------------------------------------
def encode(job):
    src, full, thumb = job
    if full.exists() and thumb.exists() and full.stat().st_mtime >= src.stat().st_mtime:
        return 0
    full.parent.mkdir(parents=True, exist_ok=True)
    im = Image.open(src).convert("RGB")
    im.save(full, "WEBP", quality=72, method=6)
    t = im.resize((480, round(480 * im.height / im.width)), Image.LANCZOS)
    t.save(thumb, "WEBP", quality=70, method=6)
    return 1


def renders(src: Path, D: dict, internal: dict) -> None:
    jobs, keep = [], set()
    for m in D["models"]:
        for c in D["cases"]:
            for v in range(4):
                s = src / "img" / internal[m["name"]] / c["id"] / f"v{v}.jpg"
                if not s.exists() and not m["cases"][c["id"]]["score"]:
                    continue                                   # no scene for this case: left out
                assert s.exists(), s
                full = OUT / "img" / m["key"] / c["slug"] / f"v{v}.webp"
                thumb = full.with_name(f"v{v}_t.webp")
                jobs.append((s, full, thumb))
                keep |= {full, thumb}
    with ProcessPoolExecutor() as ex:
        n = sum(ex.map(encode, jobs, chunksize=8))
    for f in (OUT / "img").rglob("*.webp"):                 # a configuration that left the leaderboard
        if f not in keep:
            f.unlink()
    for d in sorted((OUT / "img").rglob("*"), reverse=True):
        if d.is_dir() and not any(d.iterdir()):
            d.rmdir()
    print(f"renders: {n} encoded, {len(jobs) - n} unchanged")


# ---- pages ---------------------------------------------------------------------------------------------------
def page(root: str, title: str, desc: str, body: str) -> str:
    nav = (f'<header class="site-header"><div class="brand"><a aria-label="Code4Scene home" class="brand-home" href="{root}index.html#top">'
           f'<img alt="" class="brand-mark" decoding="async" height="30" src="{root}{MARK}" width="30"/>'
           '<span>Code4Scene</span></a><a class="brand-by" href="https://simworld.org/">by SimWorld</a></div>'
           f'<nav aria-label="Main navigation"><a href="{root}index.html#top">Benchmark</a><a href="{root}index.html#leaderboard">Leaderboard</a>'
           f'<a href="{root}index.html#cases">Visualization</a><a class="nav-active" href="{root}gallery/index.html">Gallery</a>'
           '<a href="https://arxiv.org/abs/2609.36777" rel="noopener" target="_blank">Paper ↗</a>'
           '<a href="https://github.com/SimWorld-AI/Code4Scene" rel="noopener" target="_blank">Github ↗</a></nav></header>')
    foot = ('<footer><p>Code4Scene  ·  UC San Diego &amp; UC Berkeley  ·  <a href="https://arxiv.org/abs/2609.36777" rel="noopener" '
            'target="_blank">arXiv:2609.36777</a>  ·  <a href="https://github.com/SimWorld-AI/Code4Scene" rel="noopener" target="_blank">'
            'GitHub</a>  ·  <a href="https://simworld.org/" rel="noopener" target="_blank">SimWorld Home</a></p></footer>')
    return ('<!DOCTYPE html>\n<html lang="en"><head>\n<meta charset="utf-8"/><meta content="width=device-width,initial-scale=1" name="viewport"/>\n'
            f'<title>{e(title)}</title><meta content="{e(desc)}" name="description"/>\n'
            f'<link href="{root}{MARK}" rel="icon" sizes="192x192" type="image/png"/><link href="{root}{MARK}" rel="apple-touch-icon"/>\n'
            f'<link href="{root}assets/paper.css?v={PAPER_CSS}" rel="stylesheet"/>\n'
            f'<link href="{root}assets/buildingbench.css?v={CSS_V}" rel="stylesheet"/>\n'
            f'<link href="{root}assets/gallery.css?v={V}" rel="stylesheet"/>\n'
            f'<link rel="stylesheet" href="{root}assets/explore/gallery-links.css?v=20261019explore1" data-explore-include>\n'
            f'</head><body class="gallery-page"><a class="skip-link" href="#main">Skip to the gallery</a>\n{nav}\n'
            f'<main class="page-main" id="main">\n{body}\n</main>\n{foot}\n'
            f'<script defer src="{root}assets/explore/gallery-links.js?v=20261019explore1" data-explore-include></script>\n'
            f'<script defer src="{root}assets/gallery.js?v={V}"></script>\n</body></html>\n')


def logo(svg: str, uid: str) -> str:
    for gid in set(re.findall(r'id="([^"]+)"', svg)):
        svg = svg.replace(f'id="{gid}"', f'id="{gid}{uid}"').replace(f"url(#{gid})", f"url(#{gid}{uid})")
    return f'<span class="logo" aria-hidden="true">{svg}</span>'


def has_scene(m: dict, c: dict) -> bool:
    """Whether the agent left a scene for this case (one with no valid scene scores 0 and has no renders)."""
    return (OUT / "img" / m["key"] / c["slug"] / "v0.webp").exists()


def views(m: dict, c: dict, prefix: str, first_only: bool = False, chip: str = "") -> str:
    """The four renders as thumbnails linking to full size; gallery.js opens them in a viewer, grouped per agent and
    scene. first_only: show view 1 and keep the other three as links the viewer steps through; chip: a score on it."""
    group = f"{m['key']}/{c['slug']}"
    out = []
    for v in range(4):
        base = f"{prefix}img/{m['key']}/{c['slug']}/v{v}"
        cap = f"{m['name']} · {c['title']} · view {v + 1} of 4 · score {trunc(m['cases'][c['id']]['score'])}"
        img = (f'<img alt="{e(cap)}" decoding="async" height="270" loading="lazy" src="{base}_t.webp?v={V}" width="480"/>'
               if v == 0 or not first_only else "")
        if v == 0 and chip:
            img += f'<span class="gal-chip">{chip}</span>'
        hidden = ' hidden=""' if first_only and v else ""
        out.append(f'<a class="gimg" data-group="{e(group)}" data-caption="{e(cap)}" href="{base}.webp?v={V}"{hidden}>{img}</a>')
    return f'<div class="{"gal-first" if first_only else "gal-views"}">{"".join(out)}</div>'


def brief(text: str) -> str:
    return f'<p class="gal-brief">{e(text)}</p>'


def switcher(active: str) -> str:
    link = lambda key, href, text: (f'<a aria-current="page" class="on" href="{href}">{text}</a>' if key == active
                                    else f'<a href="{href}">{text}</a>')
    return ('<nav aria-label="Browse the gallery" class="gal-switch">' + link("agent", "index.html", "By agent")
            + link("scene", "cases.html", "By scene") + "</nav>")


INTRO = ("What each configuration built on the 20 Research Track scenes in the October 2026 Text-to-Scene re-run, as the "
         "evaluator saw it: four renders per scene from the benchmark's own cameras, with the case score and the agent's cost. "
         "Scores are truncated to three decimals; a case without a valid scene scores 0.")


def basis_tip(m: dict) -> str:
    why = m.get("cost_basis") or (m.get("cost_none") if m["cost"] is None else None)
    return f' title="{e(m["name"])}: {e(why)}"' if why else ""


def basis_note(m: dict) -> str:
    if m.get("cost_basis"):
        return f'<p class="gal-note gal-basis">Cost: {e(m["cost_basis"])}.</p>'
    if m["cost"] is None and m.get("cost_none"):
        return f'<p class="gal-note gal-basis">No cost: {e(m["cost_none"])}.</p>'
    return ""


def index_page(D: dict, logos: dict) -> str:
    rows = []
    for i, m in enumerate(D["models"]):
        strip = "".join(f'<img alt="" decoding="async" height="270" loading="lazy" src="img/{m["key"]}/{s}/v0_t.webp?v={V}" width="480"/>'
                        for s in STRIP if (OUT / "img" / m["key"] / s / "v0.webp").exists())
        rows.append(f'<li><a class="gal-agent" href="model/{m["key"]}.html"><span class="rk">{i + 1}</span>'
                    f'<span class="who">{logo(logos[m["logo"]], f"G{i}")}<span><b>{e(m["name"])}</b><small>{e(m["org"])}</small></span></span>'
                    f'<span class="num"><small>Score</small><b>{trunc(m["score"])}</b></span>'
                    f'<span class="num"{basis_tip(m)}><small>Median cost</small>{money(m["cost"])}{"*" if m.get("cost_basis") else ""}</span>'
                    f'<span class="strip" aria-hidden="true">{strip}</span></a></li>')
    names = ", ".join(D["titles"][s] for s in STRIP)
    body = ('<section class="gal-top"><div class="container"><span class="section-label">Gallery</span>'
            '<h1 class="section-title">Every scene, every agent</h1>'
            f'<p class="section-desc">{e(INTRO)}</p>{switcher("agent")}</div></section>'
            f'<section class="gal-body"><div class="container"><p class="gal-note">In leaderboard order. The strip shows the same five '
            f'scenes for every agent: {e(names)}.</p><ol class="gal-agents">{"".join(rows)}</ol></div></section>')
    return page("../", "Code4Scene · Gallery", "Every Text-to-Scene output of the October 2026 re-run, by agent.", body)


def cases_page(D: dict, logos: dict) -> str:
    pills = "".join(f'<a href="#{c["slug"]}">{e(c["title"])}</a>' for c in D["cases"])
    secs = []
    for c in D["cases"]:
        ranked = sorted((m for m in D["models"] if has_scene(m, c)), key=lambda m: -m["cases"][c["id"]]["score"])
        tiles = []
        for j, m in enumerate(ranked):
            r = m["cases"][c["id"]]
            dot = f'<i aria-hidden="true" class="dot" style="background:{m["colour"]}"></i>'   # 300 tiles: a dot, not the logo
            tiles.append(f'<figure class="gal-tile">{views(m, c, "", True, trunc(r["score"]))}<figcaption>{dot}'
                         f'<a class="who" href="model/{m["key"]}.html#{c["slug"]}">{e(m["name"])}</a>'
                         f'<small>{money(r["cost"])}</small></figcaption></figure>')
        secs.append(f'<section class="gal-case" id="{c["slug"]}"><div class="gal-case-head"><h2>{e(c["title"])}</h2>'
                    f'<a class="gal-up" href="#top-pills">All scenes ↑</a></div>{brief(c["brief"])}'
                    f'<div class="gal-grid">{"".join(tiles)}</div></section>')
    body = ('<section class="gal-top"><div class="container"><span class="section-label">Gallery</span>'
            '<h1 class="section-title">Every agent, scene by scene</h1>'
            f'<p class="section-desc">{e(INTRO)} Agents are ordered by their score on the scene; click a render for all four views.</p>'
            f'{switcher("scene")}<nav aria-label="Scenes" class="gal-pills" id="top-pills">{pills}</nav></div></section>'
            f'<section class="gal-body"><div class="container">{"".join(secs)}</div></section>')
    return page("../", "Code4Scene · Gallery by scene", "Every agent's Text-to-Scene output on each of the 20 scenes.", body)


def model_page(D: dict, i: int, logos: dict) -> str:
    m = D["models"][i]
    prev_, next_ = D["models"][i - 1] if i else None, D["models"][i + 1] if i + 1 < len(D["models"]) else None
    pn = ('<nav aria-label="Other agents" class="gal-pn">'
          + (f'<a href="{prev_["key"]}.html">← {e(prev_["name"])}</a>' if prev_ else "<span></span>")
          + (f'<a href="{next_["key"]}.html">{e(next_["name"])} →</a>' if next_ else "<span></span>") + "</nav>")
    stats = "".join(f'<div><small>{k}</small><b>{v}</b></div>' for k, v in (
        ("Rank", f'{i + 1} <span>of {len(D["models"])}</span>'), ("Score", trunc(m["score"])), ("Detailed", trunc(m["detailed"])),
        ("Overview", trunc(m["overview"])), ("Physical", trunc(m["physical"])), ("Median cost", money(m["cost"]))))
    pills = "".join(f'<a href="#{c["slug"]}">{e(c["title"])}</a>' for c in D["cases"] if has_scene(m, c))
    secs = []
    for c in D["cases"]:
        if not has_scene(m, c):
            continue
        r = m["cases"][c["id"]]
        comp = f'Detailed {trunc(r["detailed"])} · Overview {trunc(r["overview"])} · Physical {trunc(r["physical"])}'
        secs.append(f'<section class="gal-case" id="{c["slug"]}"><div class="gal-case-head"><h2>{e(c["title"])}</h2>'
                    f'<span class="gal-score">{trunc(r["score"])}</span><span class="gal-comp">{comp}</span>'
                    f'<span class="gal-cost">{money(r["cost"])}</span>'
                    f'<a class="gal-up" href="../cases.html#{c["slug"]}">All agents on this scene →</a></div>'
                    f'{brief(c["brief"])}{views(m, c, "../")}</section>')
    body = ('<section class="gal-top"><div class="container"><nav aria-label="Breadcrumb" class="gal-crumb"><a href="../index.html">Gallery</a> / '
            f'<span>{e(m["name"])}</span></nav><h1 class="section-title gal-name">{logo(logos[m["logo"]], "H")}{e(m["name"])}</h1>'
            f'<p class="section-desc">{e(m["org"])} · Text-to-Scene re-run, October 2026 · the 20 Research Track scenes, four '
            f'renders each from the evaluator\'s cameras.</p><div class="gal-stats">{stats}</div>'
            + basis_note(m) +
            f'<nav aria-label="Scenes" class="gal-pills">{pills}</nav></div></section>'
            f'<section class="gal-body"><div class="container">{"".join(secs)}{pn}</div></section>')
    return page("../../", f"Code4Scene · {m['name']} · Gallery",
                f"What {m['name']} built on each of the 20 Text-to-Scene scenes, with scores and costs.", body)


def main() -> None:
    ap = argparse.ArgumentParser(description=__doc__.split("\n")[0])
    ap.add_argument("--src", type=Path, required=True)
    a = ap.parse_args()
    D = json.loads((ROOT / "data/t2s-sandbox.json").read_text())
    D["titles"] = {c["slug"]: c["title"] for c in D["cases"]}
    internal = {m["name"]: m["key"] for m in json.loads((a.src / "data.json").read_text())["models"]}
    import subprocess
    logos = json.loads(subprocess.run(
        ["node", "-e", "global.window={};eval(require('fs').readFileSync(process.argv[1],'utf8'));"
         "process.stdout.write(JSON.stringify(window.SITE.logos))", str(ROOT / "data/site.js")],
        capture_output=True, text=True, check=True).stdout)
    OUT.mkdir(exist_ok=True)
    renders(a.src, D, internal)
    (OUT / "index.html").write_text(index_page(D, logos))
    (OUT / "cases.html").write_text(cases_page(D, logos))
    md = OUT / "model"
    if md.exists():
        shutil.rmtree(md)
    md.mkdir()
    for i, m in enumerate(D["models"]):
        (md / f"{m['key']}.html").write_text(model_page(D, i, logos))
    print(f"gallery: {len(D['models'])} agents x {len(D['cases'])} scenes, {len(D['models']) + 2} pages")


if __name__ == "__main__":
    main()