#!/usr/bin/env python3 """Text-to-Scene: the October 2026 sandboxed re-run replaces the paper's Research Track numbers (user, 2026-10-16). The user: "这里是内部的分数,我们重新跑的,我们把分数更新到public site … 这里我们替换原来的T2S leaderboard,然后case也可以 换成最新的,然后gallery可以放上去", then "没跑完的就不用放上去了" (leave out the runs that have not finished). Inputs (--src): a snapshot of the internal Text-to-Scene leaderboard's served folder, plus one extraction. data.json per model: its 20 public "-long" case scores (a case without a valid submission counts 0), state ("final" = all 20 scored), per-case cost and the median cost per case components.json per model and case, the Detailed / Overview / Physical scores of the result that case score comes from (scripts/t2s_components.py) gallery.json the cases' scene briefs grids/-compare.{mp4,jpg} four agents building the same scene (build time-lapse, then an orbit) What it writes: - Leaderboard, Text-to-Scene tab: the bars (assets/leaderboard-bars.js), Pareto (data/pareto.json), tooltips (data/leaderboard-details.json) and the ranking table and its notes (index.html). Only "final" models. Scores follow the internal leaderboard's rule: never rounded up (bar labels x100 rounded down, table values truncated to three decimals). Cost is the median USD per case, rounded to the cent, as there; a model whose agent records no usage has no cost and no Pareto point. - Cases, Text-to-Scene tab: the comparison videos with a scene picker (assets/compare-player.js), in place of the paper round's four live scenes; the Industrial Harbor showcase stays. The case explorer button is Image-to-Scene only; Text-to-Scene links to the gallery (scripts/t2s_gallery.py). - data/t2s-sandbox.json: the public data behind all of it (no internal paths). The Image-to-Scene tab, the Task Format example and the Takeaways keep the paper's numbers. Re-runnable: every output is regenerated from --src, so a later snapshot just needs another run. python3 scripts/t2s_sandbox_round.py --src ../t2s-src [--videos blue-city,ruins,...] """ from __future__ import annotations import argparse import html import json import math import re import shutil import statistics import subprocess from pathlib import Path from bs4 import BeautifulSoup ROOT = Path(__file__).resolve().parent.parent V = "20261016t2s" CASES = 20 # The static Space host does not serve a folder's index.html ("gallery/" redirects to huggingface.co), so every link # names the page, as the rest of the site does with index.html#... GALLERY = "gallery/index.html" WEIGHTS = (0.2, 0.6, 0.2) # Detailed, Overview, Physical # provider -> logo key (data/site.js SITE.logos, pareto.js colours) and bar colours (the figure kit's) ORG = { "Anthropic": ("claude", "#D97757", "#ffffff"), "OpenAI": ("openai", "#10A37F", "#ffffff"), "Google": ("gemini", "#7b1fa2", "#ffffff"), "Meta": ("meta", "#42a5f5", "#111111"), "xAI": ("grok", "#111111", "#ffffff"), "Z.ai": ("zai", "#96650b", "#ffffff"), "DeepSeek": ("deepseek", "#3f51e0", "#ffffff"), "Thinking Machines": ("thinkingmachines", "#686868", "#ffffff"), "Alibaba": ("qwen", "#fe7016", "#111111"), # the internal leaderboard's amber for the bars (not in the kit); the mark is Xiaomi's own, from Simple Icons # (Lobehub has only a MiMo wordmark icon, illegible at 22 px). User, 2026-10-16: "小米 mi 标" "Xiaomi": ("xiaomi", "#f2b418", "#111111"), } # weights availability, by model family (every shown model must be listed: a new one is a decision, not a default). # Muse Spark Open and MiMo V2.6 Pro: OpenRouter's catalogue lists their Hugging Face weights # (meta-models/Muse-Spark-Open-816B-A42B, XiaomiMiMo/MiMo-V2.6-Pro-RL); user, 2026-10-16: tag both open weights. # Inkling is in neither list on purpose: OpenRouter lists its weights (thinkingmachines/Inkling) while the paper's # Image-to-Scene data tags it closed, so its Text-to-Scene run needs a decision once it is final. OPEN = ("GLM-", "DeepSeek ", "Gemma ", "Qwen ", "Muse Spark Open", "MiMo ") CLOSED = ("Claude ", "GPT-", "Gemini ", "Grok ", "Muse Spark 1", "Muse Wonder ") # scene titles (the internal pages print the case id; these are the public names) TITLES = { "abandoned-psychiatric-hospital": "Abandoned Psychiatric Hospital", "blue-city": "Blue City", "castleriver": "Castle River", "cyberpunk": "Cyberpunk", "egyptian-temple": "Egyptian Temple", "european-street-3d-scene": "European Street", "factory-environment": "Factory Environment", "hong-kong": "Hong Kong", "khs-house-of-baeryeom": "House of Baeryeom", "medieval-big-farm-town": "Medieval Big Farm Town", "medieval-buildings-volume-1": "Medieval Buildings", "nordic-harbour": "Nordic Harbour", "paris": "Paris", "post-apocalyptic-stylized-market": "Post-Apocalyptic Stylized Market", "ruins": "Ruins", "scifi-environment-offworld": "Sci-Fi Environment Offworld", "street-new-york": "New York Street", "stylized-elven-valley": "Stylized Elven Valley", "urban-decay": "Urban Decay", "winter-town": "Winter Town", } # Costs the internal table cannot read from the agent stream, recomputed from the agent's own logs (user, 2026-10-16: # "这个成本没有更新?去看看trace重新算一下"): internal key -> (file in --src from scripts/t2s_muse_cost.py, basis shown) TRACE_COSTS = { "musewf10-max-t2s20": ("musewf_cost.json", "sold by subscription; cost = the token counts its CLI logs, at Muse Wonder's per-token list price"), # self-hosted, so nothing was billed (user, 2026-10-16: "Gemma的价格可以估算一下?"); scripts/t2s_token_cost.py "gemma4-think-public95": ("gemma4think_cost.json", "self-hosted; cost = the token counts its agent logs, at Gemma 4 31B's OpenRouter list price " "($0.20 input, $0.40 output per million)"), } # the comparison videos' line-up (internal video/stack_video.py), in the order they are drawn VIDEO_MODELS = ["Claude Opus 5.5 (max)", "GPT-6 Astra (max)", "GPT-6.1 Sol (max)", "Gemini 4 Argon (high)"] VIDEO_ORDER = ["blue-city", "nordic-harbour", "cyberpunk", "medieval-big-farm-town", "factory-environment", "post-apocalyptic-stylized-market", "ruins", "scifi-environment-offworld", "egyptian-temple", "winter-town"] def one(found, what): assert found, f"not found: {what}" return found def trunc(x: float, n: int = 3) -> str: """Truncate, never round up (the internal leaderboard's rule): 0.718775 -> '0.718'.""" return f"{math.floor(x * 10 ** n + 1e-9) / 10 ** n:.{n}f}" def label(x: float) -> str: return str(math.floor(x * 100 + 1e-9)) def slug(name: str) -> str: return re.sub(r"[^a-z0-9.]+", "-", name.lower()).strip("-") def case_slug(case_id: str) -> str: return case_id[:-5] if case_id.endswith("-long") else case_id def weights_open(name: str) -> bool: if name.startswith(OPEN): return True assert name.startswith(CLOSED), f"{name}: add it to OPEN or CLOSED" return False # ---- the data ------------------------------------------------------------------------------------------------ def load(src: Path) -> dict: data = json.loads((src / "data.json").read_text()) comps = {m["key"]: {c["case"]: c for c in m["cases"]} for m in json.loads((src / "components.json").read_text())["models"]} briefs = {c["id"]: c["brief"] for c in json.loads((src / "gallery.json").read_text())["cases"]} cases = data["cases"] assert len(cases) == CASES and all(c in briefs for c in cases), "case list" models, left_out = [], [] for m in data["models"]: per = {c["case"]: c for c in m["cases"]} final = m["state"] == "final" and m["scored"] == m["of"] == CASES and all( per.get(c, {}).get("status") == "scored" and isinstance(per[c].get("score"), (int, float)) for c in cases) if not final: left_out.append(f"{m['name']}: {m['scored']}/{m['of']} scored ({m['state']})") continue assert m["org"] in ORG, f"{m['org']}: add the provider to ORG" rows, cc = {}, comps.get(m["key"]) or {} traced, basis = None, None if m.get("cost_median_usd") is None and m["key"] in TRACE_COSTS and (src / TRACE_COSTS[m["key"]][0]).exists(): traced = json.loads((src / TRACE_COSTS[m["key"]][0]).read_text())["cases"] assert all(traced.get(c, {}).get("cost_usd") is not None for c in cases), f"{m['name']}: traced costs" basis = TRACE_COSTS[m["key"]][1] for c in cases: s = float(per[c]["score"]) got = cc.get(c) or {} if s == 0: # no valid scene, or a failed content check: every component 0 d = o = p = 0.0 else: comp = one(got.get("components"), f"{m['name']} / {c}: components") assert abs(float(got.get("score")) - s) < 1e-9, f"{m['name']} / {c}: score {got.get('score')} != {s}" d, o, p = (float(comp[k] or 0) for k in ("detailed", "overview", "physical")) w = sum(a * b for a, b in zip(WEIGHTS, (d, o, p))) assert abs(w - s) < 2.5e-4, f"{m['name']} / {c}: 0.2D+0.6O+0.2P = {w:.5f}, score {s}" cost = ((m.get("cost_cases") or {}).get(c) or {}).get("cost_usd") if cost is None and traced: cost = traced[c]["cost_usd"] rows[c] = {"score": s, "detailed": d, "overview": o, "physical": p, "cost": round(cost, 6) if isinstance(cost, (int, float)) else None} mean = sum(r["score"] for r in rows.values()) / CASES assert abs(mean - m["mean"]) < 1e-9, f"{m['name']}: mean {mean} != {m['mean']}" med = m.get("cost_median_usd") costs = [r["cost"] for r in rows.values() if r["cost"] is not None] if med is None and traced: med = statistics.median(costs) if med is not None: assert len(costs) == CASES and abs(statistics.median(costs) - med) < 1e-3, f"{m['name']}: median cost" # why a model has no cost, as the pages say it: a self-hosted run reports $0 (the internal table drops it) nocost = None if med is not None else ( "it ran on self-hosted weights, so nothing was billed" if any("unbilled" in s for s in m.get("cost_source") or []) else "its agent records no token usage") logo, colour, ink = ORG[m["org"]] models.append({ "key": slug(m["name"]), "name": m["name"], "org": m["org"], "logo": logo, "colour": colour, "ink": ink, "open": weights_open(m["name"]), "score": round(mean, 6), "detailed": round(sum(r["detailed"] for r in rows.values()) / CASES, 6), "overview": round(sum(r["overview"] for r in rows.values()) / CASES, 6), "physical": round(sum(r["physical"] for r in rows.values()) / CASES, 6), "cost": round(med, 6) if med is not None else None, "cost_basis": basis, "cost_none": nocost, "cases": rows, }) models.sort(key=lambda r: -r["score"]) priced = [r for r in models if r["cost"] is not None] for a in models: a["front"] = a["cost"] is not None and not any( b["cost"] <= a["cost"] and b["score"] >= a["score"] and (b["cost"] < a["cost"] or b["score"] > a["score"]) for b in priced) return {"when": data["when"], "cases": cases, "briefs": briefs, "models": models, "left_out": left_out} def write_data(D: dict) -> None: out = { "track": "Text-to-Scene · Research Track (20 public scenes) · sandboxed re-run", "when": D["when"], "score": "case score = 0.2 x Detailed + 0.6 x Overview + 0.2 x Physical; a case without a valid scene scores 0; " "a model's score is the mean over the 20 cases", "cost": "median USD per case over the model's 20 cases; null when the agent records no usage or the run was " "self-hosted (nothing billed), cost_none says which", "cases": [{"id": c, "slug": case_slug(c), "title": TITLES[case_slug(c)], "brief": D["briefs"][c]} for c in D["cases"]], "models": [{k: m[k] for k in ("key", "name", "org", "logo", "colour", "open", "score", "detailed", "overview", "physical", "cost", "cost_basis", "cost_none", "front", "cases")} for m in D["models"]], } (ROOT / "data/t2s-sandbox.json").write_text(json.dumps(out, indent=1, ensure_ascii=False) + "\n") def write_bars(D: dict) -> None: p = ROOT / "assets/leaderboard-bars.js" s = p.read_text() m = one(re.search(r"const ALL = (\{.*?\});\n", s, re.S), "ALL") ALL = json.loads(m.group(1)) marks = json.loads(one(re.search(r"const LOGOS = (\{.*?\});\n", s, re.S), "LOGOS").group(1)) for r in D["models"]: # the bar draws the provider's mark under it, from LOGOS assert r["org"] in marks, f"{r['org']}: add its mark to LOGOS in assets/leaderboard-bars.js" ALL["t2s"] = [{"name": r["name"], "org": r["org"], "score": r["score"], "colour": r["colour"], "ink": r["ink"], "label": label(r["score"]), "new": False} for r in D["models"]] s = s[:m.start(1)] + json.dumps(ALL, ensure_ascii=False) + s[m.end(1):] s = re.sub(r"chart-interactions\.js\?v=\w+", f"chart-interactions.js?v={V}", s) p.write_text(s) def write_pareto(D: dict) -> None: p = ROOT / "data/pareto.json" d = json.loads(p.read_text()) colours = one(re.search(r"const colors=\{(.*?)\};", (ROOT / "assets/pareto.js").read_text()), "pareto colours").group(1) for r in D["models"]: # a key missing there is drawn in the fallback grey assert r["cost"] is None or re.search(rf"\b{r['logo']}:'#", colours), f"{r['logo']}: add its colour to assets/pareto.js" d["t2s"] = [{"key": r["key"], "name": r["name"], "logo": r["logo"], "open": r["open"], "score": r["score"], "cost": r["cost"]} for r in D["models"] if r["cost"] is not None] p.write_text(json.dumps(d, indent=2, ensure_ascii=False) + "\n") def write_details(D: dict) -> None: p = ROOT / "data/leaderboard-details.json" d = json.loads(p.read_text()) d["t2s"] = [{"key": r["key"], "name": r["name"], "provider": r["org"], "metrics": [ {"label": "Text-to-Scene", "value": r["score"], "currency": False}, {"label": "Detailed Alignment", "value": r["detailed"], "currency": False}, {"label": "Overview Alignment", "value": r["overview"], "currency": False}, {"label": "Physical Safety", "value": r["physical"], "currency": False}, {"label": "Median cost / case", "value": f"{r['cost']:.2f}" if r["cost"] is not None else "not billed (self-hosted)" if "self-hosted" in r["cost_none"] else "not recorded", "currency": r["cost"] is not None}]} for r in D["models"]] p.write_text(json.dumps(d, indent=2, ensure_ascii=False) + "\n") # ---- index.html ------------------------------------------------------------------------------------------------ def site_logos() -> dict: out = 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 return json.loads(out) def table_rows(D: dict) -> str: logos, top, rows = site_logos(), D["models"][0]["score"], [] for i, r in enumerate(D["models"]): svg = logos[r["logo"]] for gid in set(re.findall(r'id="([^"]+)"', svg)): # one gradient per row svg = svg.replace(f'id="{gid}"', f'id="{gid}T{i}"').replace(f"url(#{gid})", f"url(#{gid}T{i})") tags = ('open weights' if r["open"] else "") + \ ('Pareto' if r["front"] else "") tip = f' title="{html.escape(r["name"])}: {html.escape(r["cost_basis"])}"' if r["cost_basis"] else "" cost = (f'${r["cost"]:.2f}{"*" if r["cost_basis"] else ""}' if r["cost"] is not None else f'–') rows.append( f'' f'{i + 1}{html.escape(r["name"])}{tags}' f'{html.escape(r["org"])}' f'
' f'{trunc(r["score"])}
' f'{trunc(r["detailed"])}' f'{trunc(r["overview"])}' f'{trunc(r["physical"])}{cost}') return "".join(rows) def by_view(tag, view: str): return tag.get("data-view") == view def leaderboard(soup, D: dict) -> None: frag = lambda s: BeautifulSoup(s, "html.parser") n = len(D["models"]) lb = one(soup.find(id="leaderboard"), "leaderboard") one(lb.find("p", class_="section-desc", attrs={"data-view": "t2s"}), "t2s desc").string = \ f"Research Track · 20 scenes · {n} coding-agent configurations · re-run in October 2026." one(lb.find("p", class_="bar-mobile-hint"), "mobile hint").string = "Swipe to view every configuration →" one(lb.find("div", class_="bar-scroll"), "bar scroll")["aria-label"] = "All model configurations" one(lb.find(id="bars-t2s"), "bars-t2s")["aria-label"] = f"Text-to-Scene scores for the {n} Code4Scene configurations" # the charts name the task, not the track (user, 2026-10-17: "Research Track?不要这个吧 … 下面那个图上") one(lb.select_one('.bar-heading h3 span.q[data-view="t2s"]'), "bar qualifier").string = "Text-to-Scene" # the bar note: one per view card = one(lb.find(class_="bar-card"), "bar card") for note in card.find_all("p", class_="note"): note.decompose() card.append(frag( '

Sorted by score. Bar labels are scores ×100, rounded down; the table truncates ' 'to three decimal places. One color per model provider.

' '')) # the Pareto: median cost for this tab pc = one(lb.find(class_="pareto-card"), "pareto card") one(pc.find("p", class_="figure-subtitle", attrs={"data-view": "t2s"}), "pareto t2s subtitle").string = \ "Quality against the median cost of one case · Text-to-Scene, 20 scenes" one(pc.find("svg", attrs={"data-mode": "t2s"}), "pareto t2s svg")["aria-label"] = \ "t2s score against median cost per case. More expensive runs on the left; logarithmic scale." for cap in pc.find_all("p", class_="figure-caption"): cap.decompose() miss = "".join(f" {r['name']} has no point: {r['cost_none']}." for r in D["models"] if r["cost"] is None) basis ="".join(f" {r['name']} is {r['cost_basis']}." for r in D["models"] if r["cost_basis"]) pc.append(frag( '

Solid: the frontier — nothing cheaper scores higher. One point per ' f'configuration, at its evaluated reasoning effort. Cost is the median USD per case.{html.escape(miss + basis)}

' '')) # the ranking one(lb.find(id="ranking-count"), "ranking count").string = f"{n} configurations" t2s = one([d for d in lb.find_all("div", attrs={"data-view": "t2s"}) if d.find("table", class_="board")], "t2s ranking")[0] one(t2s.find("p", class_="board-note"), "t2s board note").string = \ ("Research Track, 20 scenes, re-run in October 2026 with every agent in the same sandbox. Case score = 0.2 × " "Detailed + 0.6 × Overview + 0.2 × Physical; a case without a valid scene scores 0. Detailed, Overview and " "Physical are means of the per-case scores. Cost is the median per case." + "".join(f" *{r['name']} is {r['cost_basis']}." for r in D["models"] if r["cost_basis"])) th = one(t2s.find("th", attrs={"data-k": "c_t2s"}), "t2s cost header") th.string = "Median cost" tbody = one(t2s.find("table", class_="board").tbody, "t2s tbody") tbody.clear() tbody.append(frag(table_rows(D))) rc = one(lb.find(class_="ranking-card"), "ranking card") for old in rc.find_all("p", class_="gallery-link"): old.decompose() rc.append(frag(f'')) def compare_card(D: dict, videos: list[str]) -> str: byname = {m["name"]: m for m in D["models"]} for name in VIDEO_MODELS: one(byname.get(name), f"video model {name} on the leaderboard") items = [] for cs in videos: cid = cs + "-long" brief = D["briefs"][cid] items.append({"id": cs, "title": TITLES[cs], "brief": brief, "mp4": f"media/t2s/compare/{cs}.mp4?v={V}", "poster": f"media/t2s/compare/{cs}.jpg?v={V}", "gallery": f"gallery/cases.html#{cs}", "scores": [[n, trunc(byname[n]["cases"][cid]["score"])] for n in VIDEO_MODELS]}) first = items[0] picker = "".join(f'' for i, it in enumerate(items)) scores = "".join(f'
  • {html.escape(n)}{s}
  • ' for n, s in first["scores"]) payload = json.dumps(items, ensure_ascii=False).replace("' f'
    {picker}
    ' '
    ' f'

    Swipe to see all four agents →

    ' '
    Text-to-Scene · re-run, October 2026' f'

    {html.escape(first["title"])}

    ' f'

    {html.escape(first["brief"])}

    ' f'' f'Compare all {len(D["models"])} agents on this scene ↗' '

    Each tile: the build as a time-lapse, then an orbit of the scene the agent saved. ' 'Chips: the case score ×100, rounded down.

    ' f'' '') def cases_section(soup, D: dict, videos: list[str]) -> None: frag = lambda s: BeautifulSoup(s, "html.parser") sec = one(soup.find(id="cases"), "cases") one(sec.find("p", class_="section-desc", attrs={"data-view": "t2s"}), "cases t2s desc").string = \ (f"Four agents build the same scene: a time-lapse of each build, then an orbit of the result. Pick a scene, or open " f"the gallery for all {len(D['models'])} configurations on all 20 scenes.") grid = one(sec.find("div", class_="case-grid", attrs={"data-view": "t2s"}), "t2s case grid") feature = one(grid.find("article", class_="case-feature"), "Industrial Harbor showcase") feature.extract() grid.clear() grid.append(frag(compare_card(D, videos))) grid.append(feature) # the explorer button: Image-to-Scene's; Text-to-Scene gets the gallery btn = one(sec.find("a", class_="btn", href="cases.html"), "case explorer button") wrap = btn.parent wrap["data-view"] = "i2s" wrap["style"] = "text-align:center;margin-top:2rem;display:none" for old in sec.find_all("div", class_="gallery-cta"): old.decompose() gal = frag('') wrap.insert_before(gal) def takeaways(soup) -> None: tk = one(soup.find(id="takeaways"), "takeaways") one(tk.find("p", class_="section-desc"), "takeaways desc").string = \ ("What the paper's evaluation shows across its 14 agents. Every chart below is drawn from the paper's numbers, " "not the October 2026 Text-to-Scene re-run above; hover for values.") def nav(soup, prefix: str = "") -> None: n = one(soup.find("nav", attrs={"aria-label": "Main navigation"}), "main nav") old = n.find("a", href=(prefix + "gallery/", prefix + GALLERY)) if old: old["href"] = prefix + GALLERY return vis = one(n.find("a", href=re.compile(r"index\.html#cases$")), "Visualization link") a = soup.new_tag("a", href=prefix + GALLERY) a.string = "Gallery" vis.insert_after(a) def scripts_and_styles(soup) -> None: for t in soup.find_all(["script", "link"]): a = "src" if t.name == "script" else "href" u = t.get(a) or "" if re.match(r"assets/(buildingbench\.css|leaderboard-bars\.js|pareto\.js|leaderboard-controls\.js|" r"takeaway-bars\.js|paper\.js)", u): t[a] = re.sub(r"\?v=.*$", "", u) + f"?v={V}" if not soup.find("script", src=re.compile(r"assets/compare-player\.js")): soup.body.append(soup.new_tag("script", attrs={"type": "module", "src": f"assets/compare-player.js?v={V}"})) def index(D: dict, videos: list[str]) -> None: p = ROOT / "index.html" soup = BeautifulSoup(p.read_text(), "html.parser") leaderboard(soup, D) cases_section(soup, D, videos) takeaways(soup) nav(soup) scripts_and_styles(soup) p.write_text(str(soup)) def other_pages() -> None: """The gallery in the main navigation; the case explorer says which round its scenes are from.""" for name in ("cases.html",): p = ROOT / name soup = BeautifulSoup(p.read_text(), "html.parser") nav(soup) if name == "cases.html": desc = one(soup.find("p", class_="section-desc"), "explorer desc") old = desc.find("a", href=("gallery/", GALLERY)) if old: old["href"] = GALLERY else: desc.append(BeautifulSoup( ' These are the paper’s scenes and scores. For the October 2026 Text-to-Scene re-run, every agent’s ' f'scene on every case is in the gallery.', "html.parser")) for t in soup.find_all("script", src=re.compile(r"^assets/paper\.js")): t["src"] = f"assets/paper.js?v={V}" p.write_text(str(soup)) def media(src: Path, videos: list[str]) -> None: out = ROOT / "media/t2s/compare" out.mkdir(parents=True, exist_ok=True) for f in out.iterdir(): if f.stem not in videos: f.unlink() for cs in videos: for ext in ("mp4", "jpg"): s = one((src / "grids" / f"{cs}-compare.{ext}").exists() and src / "grids" / f"{cs}-compare.{ext}", f"{cs} {ext}") if not (out / f"{cs}.{ext}").exists() or (out / f"{cs}.{ext}").read_bytes() != s.read_bytes(): shutil.copyfile(s, out / f"{cs}.{ext}") CSS_START, CSS_END = "/* t2s re-run: comparison videos, gallery link (scripts/t2s_sandbox_round.py) */", "/* end t2s re-run */" CSS = """ .case-grid .case-compare{grid-column:1/-1;display:flex;flex-direction:column} .cmp-picker{display:flex;flex-wrap:wrap;gap:6px;padding:14px 16px;border-bottom:1px solid var(--line-soft)} .cmp-picker button{font:500 12px/1.2 Inter,Arial,sans-serif;padding:7px 12px;border:1px solid var(--line);border-radius:999px;background:#fff;color:var(--text-p);cursor:pointer;transition:border-color .15s,background .15s,color .15s} .cmp-picker button:hover{border-color:var(--green);color:var(--green)} .cmp-picker button[aria-selected=true]{background:var(--green-dark);border-color:var(--green-dark);color:#fff} .cmp-picker button:focus-visible{outline:2px solid var(--green);outline-offset:2px} .cmp-scroll{overflow-x:auto;background:#fff} .cmp-scroll:focus-visible{outline:2px solid var(--green);outline-offset:-2px} .cmp-video{display:block;width:100%;height:auto;aspect-ratio:1920/588;background:#fff} .cmp-swipe{display:none;margin:8px 16px 0;font-size:11px;color:var(--text-muted)} .case-card .meta.cmp-meta{border-top:1px solid var(--line-soft)} .case-card .meta.cmp-meta h4{font-size:17px} .case-card .meta p.cmp-brief{max-width:980px;margin-top:2px;font-size:12.5px;line-height:1.6;color:var(--text-p);display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden} .case-card .meta p.cmp-brief.open{display:block} .cmp-more{margin-top:4px;padding:0;border:0;background:none;font:600 11px Inter,Arial,sans-serif;color:var(--green);cursor:pointer} .cmp-scores{list-style:none;display:flex;flex-wrap:wrap;gap:6px 22px;margin:12px 0 0;padding:0;font-size:12px;color:var(--text-p)} .cmp-scores b{margin-left:7px;font:500 12.5px 'IBM Plex Mono',monospace;color:var(--text-h)} .gallery-link a{color:var(--green);font-weight:600} @media(max-width:760px){.cmp-video{width:760px;max-width:none}.cmp-swipe{display:block}.case-card .meta p.cmp-brief{font-size:11.5px} .cmp-picker{flex-wrap:nowrap;overflow-x:auto;scrollbar-width:none;padding:12px}.cmp-picker::-webkit-scrollbar{display:none}.cmp-picker button{flex:none}} @media(max-width:650px){.site-header nav{gap:10px}} /* six items since the gallery joined the menu */ """ def css() -> None: p = ROOT / "assets/buildingbench.css" s = p.read_text() block = CSS_START + CSS + CSS_END if CSS_START in s: s = s[:s.index(CSS_START)] + block + s[s.index(CSS_END) + len(CSS_END):] else: s = s.rstrip("\n") + "\n" + block + "\n" p.write_text(s) def other_assets() -> None: """Version keys for the files this change edits, so cached copies are not mixed with new ones.""" for name, pat in (("assets/pareto.js", r"(data/pareto\.json\?v=)\w+"), ("assets/chart-interactions.js", r"(leaderboard-details\.json\?v=)\w+")): p = ROOT / name s, k = re.subn(pat, rf"\g<1>{V}", p.read_text()) assert k == 1, name p.write_text(s) for name in ("assets/pareto.js", "assets/takeaway-bars.js"): p = ROOT / name s, k = re.subn(r"chart-interactions\.js\?v=\w+", f"chart-interactions.js?v={V}", p.read_text()) assert k == 1, name p.write_text(s) # sortable tables: a cell without a value (a cost that was not recorded) sorts last in either direction p = ROOT / "assets/paper.js" s = p.read_text() old = ('rows.sort(function (a, b) { var x = +a.cells[ci].getAttribute("data-v"), y = +b.cells[ci].getAttribute("data-v"); ' 'return asc ? x - y : y - x; });') new = ('rows.sort(function (a, b) { var p = a.cells[ci].getAttribute("data-v"), q = b.cells[ci].getAttribute("data-v"); ' 'if (p === "" || q === "") return (p === "") - (q === ""); var x = +p, y = +q; return asc ? x - y : y - x; });') assert old in s or new in s, "paper.js sort" p.write_text(s.replace(old, new)) def main() -> None: ap = argparse.ArgumentParser(description=__doc__.split("\n")[0]) ap.add_argument("--src", type=Path, required=True) ap.add_argument("--videos", default=",".join(VIDEO_ORDER), help="comparison videos to show, in order") a = ap.parse_args() videos = [v for v in a.videos.split(",") if v] assert videos and all(v in VIDEO_ORDER for v in videos), videos D = load(a.src) write_data(D) write_bars(D) write_pareto(D) write_details(D) media(a.src, videos) index(D, videos) other_pages() css() other_assets() print(f"snapshot {D['when']}: {len(D['models'])} final configurations") for r in D["models"]: print(f" {r['name']:<28} {trunc(r['score'])} D {trunc(r['detailed'])} O {trunc(r['overview'])} " f"P {trunc(r['physical'])} {'$%.2f' % r['cost'] if r['cost'] is not None else '–':>7}" f"{' Pareto' if r['front'] else ''}{' open' if r['open'] else ''}") for x in D["left_out"]: print(" left out:", x) print("videos:", ", ".join(videos)) if __name__ == "__main__": main()