diff --git a/scripts/sitemeasurements.py b/scripts/sitemeasurements.py
index cc35ef362..4d065b631 100644
--- a/scripts/sitemeasurements.py
+++ b/scripts/sitemeasurements.py
@@ -18,6 +18,7 @@ its line at the last commit that touched measurements/.
from __future__ import annotations
+import html
import json
import os
import re
@@ -146,9 +147,20 @@ def index_item(entry: mr.Stored, runs: int, source: dict, profiles: dict,
"runs": runs,
"record_url": permalink(source["repo"], source["commit"], mr.records_path(entry.emulator), entry.line),
"sqlite": f"downloads/measurements/{entry.emulator}.sqlite",
+ "host_short": short_host(record["host"]),
+ "groups": variant_groups(record),
+ "proves": proves(record["id"], profile),
+ "timing": ({"ms_per_frame": record["timing"]["ms_per_frame"]["median"],
+ "fps": record["timing"]["fps"]["median"],
+ "stdev": record["timing"]["ms_per_frame"]["stdev"]}
+ if record.get("timing") else None),
}
+def with_title(item: dict) -> dict:
+ return {**item, "title": short_title(item)}
+
+
def build_index(stored: list[mr.Stored], source: dict | None, profiles: dict,
system_names: dict[str, str] | None = None) -> dict:
"""One item per (emulator, subject): its latest record not superseded."""
@@ -161,7 +173,8 @@ def build_index(stored: list[mr.Stored], source: dict | None, profiles: dict,
for key in sorted(by_subject):
entries = by_subject[key]
latest = max(entries, key=lambda s: (s.record["recorded_at"], s.line))
- items.append(index_item(latest, len(entries), source or {}, profiles, system_names or {}))
+ items.append(with_title(index_item(latest, len(entries), source or {}, profiles,
+ system_names or {})))
items.sort(key=lambda item: (item["emulator"], item["date"], item["subject"]))
return {
"schema_version": INDEX_SCHEMA_VERSION,
@@ -514,9 +527,14 @@ def build(public: Path, root: Path = ROOT, emulators_dir: Path = ROOT / "emulato
# --------------------------------------------------------------------------
def by_emulator(index: dict) -> dict[str, list[dict]]:
+ """Each emulator's items, with the count of its findings still open."""
+ opened: dict[str, int] = {}
+ for line in (index.get("campaign") or {}).get("open") or ():
+ opened[line["emulator"]] = opened.get(line["emulator"], 0) + 1
out: dict[str, list[dict]] = {}
for item in index.get("items", []):
- out.setdefault(item["emulator"], []).append(item)
+ out.setdefault(item["emulator"], []).append(
+ {**item, "open_findings": opened.get(item["emulator"], 0)})
return out
@@ -532,52 +550,262 @@ def _short(commit: str | None) -> str:
return f"`{commit[:12]}`" if commit else "unknown"
-def tested_item(item: dict) -> list[str]:
- """One test: what ran, the result, the method and host beside it, the record."""
- title = item["claim"] or f"Benchmark of {item['content'] or 'the core alone'}"
- runs = "1 run" if item["runs"] == 1 else f"{item['runs']} runs, the latest shown"
- core = item["core"]
- build = f"{_text(core['library'])} at {_short(core['commit'])}" if core.get("commit") \
- else _text(core["library"])
- lines = [f"- **{_text(title)}**", ""]
- if len(item.get("verdicts") or ()) > 1:
- lines += [f" **Result:** `{item['status']}`.", ""]
- lines += [f" - {_text(v['variant'])}: `{v['verdict']}`. {_text(v['detail'])}."
- for v in item["verdicts"]]
- lines.append("")
+# A verdict or a comparison, as the block says it: words and a chip kind.
+RESULT_WORDS = {
+ "boots": ("works", "success"),
+ "bios_error": ("BIOS error", "danger"),
+ "crash": ("crash", "danger"),
+ "hang": ("hang", "warning"),
+ "unknown": ("unknown", "muted"),
+ "mixed": ("differs", "info"),
+ "same": ("no difference", "muted"),
+ "differs": ("differs", "info"),
+ "inconclusive": ("inconclusive", "warning"),
+ "no verdict": ("no verdict", "muted"),
+ "ran": ("ran", "muted"),
+}
+# Words a subject slug spells that read as one term.
+TERMS = {"bios": "BIOS", "vcd": "Video CD", "cd": "CD", "jp": "JP", "us": "US", "eu": "EU",
+ "cr2": "CR2", "mpeg": "MPEG", "psx": "PSX", "hle": "HLE", "usa": "USA"}
+# The campaign's own check names, as the block says them.
+CHECK_NAMES = {"files": "full set", "none": "no BIOS", "negative": "wrong files",
+ "references": "reference run", "variants": "copies", "alias": "other name",
+ "path": "other path", "bench": "benchmark"}
+TITLE_AXIS_WORDS = 4
+
+
+def _words(text: str) -> list[str]:
+ return re.findall(r"[a-z0-9]+", str(text or "").lower())
+
+
+def short_content(label: str | None) -> str:
+ """A content label cut to the game's name: no source prefix, no
+ edition, region or identity detail."""
+ text = str(label or "").strip()
+ if ":" in text.split("(")[0]:
+ text = text.split(":", 1)[1].strip()
+ for cut in (" - ", " (", ", "):
+ text = text.split(cut, 1)[0]
+ return text.strip()
+
+
+def short_title(item: dict) -> str:
+ """A test's name in a few words: the file it varies, the game, and what
+ the subject adds to them (a build, a region, a check of the campaign)."""
+ thing = Path(str(item.get("variable") or "")).name
+ game = short_content(item.get("content"))
+ known = set(_words(thing)) | set(_words(item.get("content"))) | set(_words(game))
+ known |= set(_words(Path(str((item.get("core") or {}).get("file") or "")).name.split("_libretro")[0]))
+ axis = []
+ for word in _words(item.get("subject")):
+ if word in known or re.fullmatch(r"[0-9a-f]{8}", word):
+ continue
+ issue = re.fullmatch(r"issue(\d+)", word)
+ word = f"issue {issue[1]}" if issue else CHECK_NAMES.get(word, TERMS.get(word, word))
+ if word not in axis:
+ axis.append(word)
+ if len(axis) > TITLE_AXIS_WORDS:
+ axis = [*axis[:TITLE_AXIS_WORDS], "..."]
+ head = ", ".join(part for part in (f"`{thing}`" if thing else "", game) if part)
+ title = f"{head} ({' '.join(axis)})" if axis and head else head or " ".join(axis)
+ return title or str(item.get("subject") or "")
+
+
+def short_host(host: dict) -> str:
+ """The computer as a reader names it: the CPU model and the OS."""
+ cpu = str(host.get("cpu_model") or "unknown CPU")
+ cpu = re.sub(r"\(R\)|\(TM\)|\bCPU\b|@.*$|^\d+(?:st|nd|rd|th) Gen ", "", cpu)
+ cpu = " ".join(cpu.split())
+ system = str(host.get("os") or "")
+ name = "Linux" if "Linux" in system else system.split("-")[0] or "unknown OS"
+ if "microsoft" in system.lower():
+ name += " (WSL2)"
+ return f"{cpu}, {name}"
+
+
+def variant_groups(record: dict) -> list[dict]:
+ """One entry per variant: the result group it falls in, and whether it
+ staged no file. A run that carries verdicts is grouped by verdict, a
+ comparison by the value its observable took."""
+ check = record.get("check") or {}
+ position: dict[str, int] = {}
+ result: dict[str, str] = {}
+ if all("verdict" in v for v in record["variants"]) and (
+ record["kind"] == "run" or check.get("observable") == "frame_hashes"):
+ for variant in record["variants"]:
+ position.setdefault(variant["verdict"], len(position))
+ result[variant["label"]] = RESULT_WORDS[variant["verdict"]][0]
+ groups = {v["label"]: position[v["verdict"]] for v in record["variants"]}
else:
- lines += [f" **Result:** `{item['status']}`. {_text(item['statement'])}.", ""]
- if item.get("method"):
- lines += [f" **Method:** {_text(item['method'])}", ""]
- lines += [
- f" **Ran on:** {_text(item['host'])}, {item['date'][:10]}. Core build: {build}. "
- f"{item['variants']} variant{'s' if item['variants'] != 1 else ''}, "
- f"{item['repetitions']} repetition{'s' if item['repetitions'] != 1 else ''} each"
- + (f", {item['flags']} flag{'s' if item['flags'] != 1 else ''} raised" if item["flags"] else "")
- + ".",
- "",
- f" [Full record]({item['record_url']}), {runs}.",
- "",
+ groups = {}
+ for number, group in enumerate((check.get("result") or {}).get("groups") or ()):
+ for label in group["variants"]:
+ groups[label] = number
+ result[label] = f"result {chr(ord('A') + number % 26)}"
+ varied = check.get("variable")
+ return [{"variant": v["label"], "group": groups.get(v["label"], 0),
+ "result": result.get(v["label"], ""),
+ "absent": bool(varied) and varied in v["system_files"] and v["system_files"][varied] is None}
+ for v in record["variants"]]
+
+
+def proves(record_id: str, profile: dict) -> list[str]:
+ """The profile fields this record is evidence for, as `field of file`."""
+ out = []
+ for entry in profile.get("files") or ():
+ for name, ids in sorted((entry.get("evidence") or {}).items()):
+ if record_id in ids:
+ out.append(f"{name} of {entry.get('name')}")
+ return out
+
+
+# An MDX page reads a brace as the start of an expression, in text and in
+# an attribute alike.
+_BRACES = str.maketrans({"{": "{", "}": "}"})
+
+
+def _attr(value: object) -> str:
+ return html.escape(" ".join(str(value or "").split()), quote=True).translate(_BRACES)
+
+
+def _cell(value: object) -> str:
+ """Record text placed in raw HTML: escaped, code spans kept, a semicolon
+ joining two clauses ending a sentence as on the rest of the site."""
+ text = " ".join(str(value or "").split())
+ text = re.sub(r";\s+(\w)", lambda m: ". " + m[1].upper(), text)
+ return markup.inline_html(text).translate(_BRACES)
+
+
+def _dots(item: dict) -> str:
+ groups = item.get("groups") or []
+ names = "; ".join(f"{g['variant']}: {g['result']}" for g in groups)
+ dots = "".join(
+ f'' for g in groups)
+ return (f''
+ f"{dots}")
+
+
+def _chip(item: dict) -> str:
+ words, kind = RESULT_WORDS.get(item["status"], (item["status"], "muted"))
+ return f'{html.escape(words)}'
+
+
+def _sentence_html(label: str, value: object, markup_ready: bool = False) -> str:
+ """One line of the details, or nothing: an empty value is never printed."""
+ text = (str(value or "") if markup_ready else _cell(value)).strip().rstrip(".")
+ return f"
{label}: {text}.
" if text else ""
+
+
+def _details(item: dict) -> list[str]:
+ core = item["core"]
+ build = _cell(core["library"]) + (f" at {html.escape(core['commit'][:12])}"
+ if core.get("commit") else "")
+ reps = f"{item['variants']} variant{'s' if item['variants'] != 1 else ''}, " \
+ f"{item['repetitions']} repetition{'s' if item['repetitions'] != 1 else ''} each"
+ if item["flags"]:
+ reps += f", {item['flags']} flag{'s' if item['flags'] != 1 else ''} raised"
+ runs = "1 run" if item["runs"] == 1 else f"{item['runs']} runs, the latest shown"
+ lines = [
+ _sentence_html("Claim", item.get("claim")),
+ _sentence_html("Result", item.get("statement")),
+ _sentence_html("Method", item.get("method")),
+ _sentence_html("Proves", ", ".join(item.get("proves") or [])),
+ _sentence_html("Ran on", item["host"]),
+ _sentence_html("Core build", build and f"{build}. {reps}", markup_ready=True),
+ f'Full record, {runs}.
',
+ ]
+ return [line for line in lines if line]
+
+
+def tested_counts(items: list[dict]) -> dict:
+ tests = [i for i in items if i["kind"] != "bench"]
+ return {"tests": len(items), "confirmed": sum(1 for i in items if i.get("proves")),
+ "open": max((i.get("open_findings", 0) for i in items), default=0),
+ "benchmarks": len(items) - len(tests),
+ "last": max((i["date"][:10] for i in items), default="")}
+
+
+def core_label(item: dict) -> str:
+ return Path(str(item["core"].get("file") or "")).name.split("_libretro")[0]
+
+
+def _row(item: dict, page_host: str, main_core: str) -> list[str]:
+ elsewhere = (item.get("host_short") or item["host"]) != page_host
+ title = _cell(item.get("title") or short_title(item))
+ if core_label(item) != main_core:
+ title += f' {html.escape(core_label(item))}'
+ return [
+ '',
+ (""
+ f'{title}'
+ f'{_chip(item)}'
+ f'{_dots(item)}'
+ f'{item["date"][:10]}'
+ + (f' {_attr(item.get("host_short"))}' if elsewhere else "")
+ + "
"),
+ '',
+ *_details(item),
+ "
",
+ " ",
]
- return lines
def tested_section(name: str, items: list[dict]) -> list[str]:
- """The Tested block of an emulator page, empty when nothing was recorded."""
+ """The Tested block of an emulator page, empty when nothing was recorded.
+
+ The answer first: one summary line, then one row per test with its
+ result chip, a dot per variant and its date. What proves it (method,
+ claim, computer, core build, record) waits in each row's details. Tests
+ recorded before the boot oracle fold under one line, and benchmarks get
+ their own table at the end.
+ """
if not items:
return []
- lines = [
- "## Tested",
- "",
- f"This emulator was run in real conditions by the [core probe](../{METHOD_PAGE}). "
- f"Each test below is the latest of its kind. "
- f"[All measurements](../{EXPLORER_PAGE}), "
- f"[these records as SQLite](../downloads/measurements/{name}.sqlite).",
- "",
- ]
- for item in sorted(items, key=lambda i: (i["date"], i["subject"]), reverse=True):
- lines += tested_item(item)
- return lines
+ counts = tested_counts(items)
+ hosts = [i.get("host_short") or i["host"] for i in items]
+ page_host = max(sorted(set(hosts)), key=hosts.count)
+ cores = [core_label(i) for i in items]
+ main_core = max(sorted(set(cores)), key=cores.count)
+ tests = f"{counts['tests']} test{'s' if counts['tests'] != 1 else ''}"
+ summary = (f"{tests} · {counts['confirmed']} confirmed · {counts['open']} open · "
+ f"last run {counts['last']} on {page_host}.")
+ links = (f'How · '
+ f'All tests · '
+ f'SQLite')
+ ordered = sorted(items, key=lambda i: (i["date"], i["subject"], core_label(i)), reverse=True)
+ benches = [i for i in ordered if i["kind"] == "bench"]
+ earlier = [i for i in ordered if i["kind"] != "bench" and i["status"] == "no verdict"]
+ current = [i for i in ordered if i["kind"] != "bench" and i["status"] != "no verdict"]
+ lines = ["## Tested", "", '',
+ f'
{html.escape(summary)} {links}
']
+ if current or earlier:
+ lines.append('
Test'
+ "ResultVariantsDate
")
+ for item in current:
+ lines += _row(item, page_host, main_core)
+ if earlier:
+ lines += ['
',
+ f"{len(earlier)} earlier test{'s' if len(earlier) != 1 else ''}, "
+ "before the boot oracle
"]
+ for item in earlier:
+ lines += _row(item, page_host, main_core)
+ lines.append(" ")
+ if benches:
+ # One line: MDX reads a table split over lines as paragraphs.
+ table = ['
| Benchmark | ms per frame | '
+ "fps | stdev | runs |
"]
+ for item in benches:
+ timing = item.get("timing") or {}
+ title = _cell(item.get("title") or short_title(item))
+ if core_label(item) != main_core:
+ title += f' {html.escape(core_label(item))}'
+ table.append(
+ f'| {title} | '
+ f'{timing.get("ms_per_frame", 0):.3f} | {timing.get("fps", 0):.1f} | '
+ f'{timing.get("stdev", 0):.3f} | {item["repetitions"]} |
')
+ lines.append("".join([*table, "
"]))
+ return [*lines, "
", ""]
def hosts_table(index: dict) -> list[str]:
diff --git a/tests/test_measurements.py b/tests/test_measurements.py
index c7eb2536e..5bf12a97b 100644
--- a/tests/test_measurements.py
+++ b/tests/test_measurements.py
@@ -220,7 +220,8 @@ class ReadAndIndex(Workspace):
keys = {"emulator", "emulator_name", "profiled", "systems", "system_names", "subject", "kind",
"claim", "method", "observable", "variable", "status", "statement", "content",
"variants", "repetitions", "flags", "date", "host", "core", "frontend_commit", "id",
- "runs", "record_url", "sqlite", "verdicts"}
+ "runs", "record_url", "sqlite", "verdicts", "title", "host_short", "groups",
+ "proves", "timing"}
for item in index["items"]:
self.assertEqual(set(item), keys)
alpha = {i["subject"]: i for i in index["items"] if i["emulator"] == "alpha"}
@@ -247,11 +248,9 @@ class ReadAndIndex(Workspace):
self.assertNotIn("differ", statement)
# A check on another observable keeps its comparison.
self.assertEqual(sm.result_of(full_record("alpha", "c", "2026-10-06T10:00:00+00:00"))[0], "differs")
- item = {"claim": "c", "runs": 1, "core": {"library": "Fake 1", "commit": None}, "status": "mixed",
- "statement": "", "verdicts": sm.verdicts_of(record), "method": "m", "host": "h",
- "date": "2026-10-06", "variants": 2, "repetitions": 2, "flags": 0, "record_url": "u"}
- text = "\n".join(sm.tested_item(item))
- self.assertIn("v1: `hang`", text)
+ groups = sm.variant_groups(record)
+ self.assertEqual([(g["variant"], g["result"], g["group"]) for g in groups],
+ [("v0", "works", 0), ("v1", "hang", 1)])
def test_links_name_the_last_commit_that_touched_the_records(self) -> None:
first = self.repository()
@@ -431,12 +430,90 @@ class SitePages(unittest.TestCase):
self.assertEqual(sm.tested_section("beetle_saturn", []), [])
text = "\n".join(sm.tested_section("beetle_saturn", [self.ITEM]))
self.assertTrue(text.startswith("## Tested\n"))
- for expected in ("**Result:** `same`", "**Method:** Twice per card. Every input fixed.", "**Ran on:** Test CPU, 12 CPUs",
- self.ITEM["record_url"], "1 flag raised", "2 runs",
- "../downloads/measurements/beetle_saturn.sqlite", "../wiki/measurements.md",
- "<image>"):
+ for expected in ("no difference", "Method: Twice per card. Every input fixed.",
+ "Ran on: Test CPU, 12 CPUs", self.ITEM["record_url"],
+ "1 flag raised", "2 runs", "downloads/measurements/beetle_saturn.sqlite",
+ "wiki/measurements/", "<image>", "1 test · 0 confirmed · 0 open"):
self.assertIn(expected, text)
+ def item(self, **values: object) -> dict:
+ return {**self.ITEM, "host_short": "Test CPU, Linux", **values}
+
+ def test_summary_counts(self) -> None:
+ items = [self.item(proves=["hle_fallback of a.bin"], open_findings=2),
+ self.item(subject="b", date="2026-10-28T00:00:00+00:00", open_findings=2),
+ self.item(kind="bench", status="measured", subject="c",
+ timing={"ms_per_frame": 1.5, "fps": 660.0, "stdev": 0.01})]
+ self.assertEqual(sm.tested_counts(items), {"tests": 3, "confirmed": 1, "open": 2, "benchmarks": 1,
+ "last": "2026-10-28"})
+ text = "\n".join(sm.tested_section("beetle_saturn", items))
+ self.assertIn("3 tests · 1 confirmed · 2 open · last run 2026-10-28 on Test CPU, Linux.", text)
+ self.assertIn("1.500 | 660.0 | ", text)
+
+ def test_short_titles(self) -> None:
+ cases = [
+ ({"subject": "mpeg-card-lunar-cr2-build", "variable": "mpeg_card.bin",
+ "content": "Redump: Lunar - Silver Star Story Complete (Japan), T-27904G V1.00"},
+ "`mpeg_card.bin`, Lunar (CR2 build)"),
+ ({"subject": "negative-scph5501-bin-mednafen-psx-bust-a-move-2-arcade-edition-usa",
+ "variable": "scph5501.bin", "content": "Bust-A-Move 2 - Arcade Edition (USA)",
+ "core": {"file": "mednafen_psx_libretro.so"}},
+ "`scph5501.bin`, Bust-A-Move 2 (wrong files)"),
+ ({"subject": "issue85-lunar-opening-movie", "variable": None,
+ "content": "Redump: Lunar - Silver Star Story Complete (Japan), T-27904G V1.00"},
+ "Lunar (issue 85 opening movie)"),
+ ({"subject": "with-command-auth-device-and-command-get-9a211715", "variable": "mpeg_card.bin",
+ "content": "Redump: Lunar"}, "`mpeg_card.bin`, Lunar (with command auth device ...)"),
+ ]
+ for item, title in cases:
+ self.assertEqual(sm.short_title(item), title)
+ self.assertEqual(sm.short_host({"cpu_model": "13th Gen Intel(R) Core(TM) i9-13900K",
+ "os": "Linux-6.18.54.1-microsoft-standard-WSL2-x86_64"}),
+ "Intel Core i9-13900K, Linux (WSL2)")
+
+ def test_no_empty_sentence(self) -> None:
+ text = "\n".join(sm.tested_section("beetle_saturn", [self.item(claim=None, method=None, statement="",
+ kind="run", status="ran")]))
+ self.assertNotRegex(text, r"\s*\.|:\s*\.\s|`ran`\. \.")
+ self.assertNotIn("Claim:", text)
+ self.assertNotIn("Method:", text)
+
+ def test_dots_follow_the_result_groups(self) -> None:
+ record = full_record("alpha", "boot", "2026-10-06T10:00:00+00:00", values=("1" * 40, "2" * 40, "1" * 40))
+ record["check"]["variable"] = "card.bin"
+ record["variants"][2]["system_files"] = {"card.bin": None}
+ groups = sm.variant_groups(record)
+ self.assertEqual([g["group"] for g in groups],
+ [i for i, group in enumerate(record["check"]["result"]["groups"])
+ for _ in group["variants"]][:1] + [1, 0])
+ self.assertEqual([g["absent"] for g in groups], [False, False, True])
+ text = "\n".join(sm.tested_section("alpha", [self.item(groups=groups)]))
+ self.assertEqual(re.findall(r'rb-dot rb-dot-(\d)( rb-dot-empty)?"', text),
+ [("0", ""), ("1", ""), ("0", " rb-dot-empty")])
+ self.assertIn('aria-label="3 variants. v0: result A; v1: result B; v2: result A"', text)
+
+ def test_the_details_hold_the_method_and_the_host(self) -> None:
+ text = "\n".join(sm.tested_section("beetle_saturn", [self.item()]))
+ details = text.split('', 1)[1].split("
", 1)[0]
+ self.assertIn("Twice per card", details)
+ self.assertIn("Test CPU, 12 CPUs", details)
+ self.assertIn(self.ITEM["record_url"], details)
+
+ def test_the_beetle_saturn_block_stays_short(self) -> None:
+ stored = mr.read_records(REPO_ROOT)
+ profiles = sm.load_profiles(REPO_ROOT / "emulators")
+ index = sm.build_index(stored, {"repo": REPO_URL, "commit": COMMIT}, profiles)
+ items = sm.by_emulator(index).get("beetle_saturn")
+ if not items:
+ self.skipTest("no beetle_saturn record")
+ block = "\n".join(sm.tested_section("beetle_saturn", items))
+ # What a reader sees before opening anything: the closed details hide their body.
+ shown = re.sub(r'.*?
', " ", block, flags=re.S)
+ shown = re.sub(r'.*? \s*', " ", shown,
+ flags=re.S)
+ shown = re.sub(r"<[^>]+>", " ", shown)
+ self.assertLess(len(re.findall(r"[A-Za-z0-9][\w.'-]*", shown)), 120, shown)
+
def test_hosts_table_on_the_method_page(self) -> None:
page = sm.with_hosts(f"x\n\n{sm.HOSTS_MARKER}\n",
{"items": [self.ITEM, {**self.ITEM, "host": "Test CPU, 12 CPUs, load 1 before, 2 after"}]})
diff --git a/website/src/styles/retrobios.css b/website/src/styles/retrobios.css
index 77831fc28..865b72e04 100644
--- a/website/src/styles/retrobios.css
+++ b/website/src/styles/retrobios.css
@@ -877,3 +877,78 @@ starlight-tabs [role='tab'] { white-space: nowrap; }
@media (min-width: 50rem) {
header .site-title img { height: calc(4rem - 2 * var(--sl-nav-pad-y)); width: auto; }
}
+
+/* The Tested block of an emulator page (scripts/sitemeasurements.py): a
+ summary line, then one row per test, each a whose summary is
+ the row and whose body is the proof. Rows stack into two lines on a
+ phone. A dot per variant, its group in colour and its name in a title
+ and the strip's label; a variant that staged no file is a ring. */
+.sl-markdown-content .rb-tested-summary { color: var(--rb-text-secondary); font-size: 0.9rem; }
+.sl-markdown-content :is(.rb-tested-head, .rb-tested-row > summary) {
+ display: grid;
+ grid-template-columns: 1.25rem minmax(0, 1fr) 8.5rem 9rem 6rem;
+ gap: 0.5rem;
+ align-items: center;
+}
+.sl-markdown-content .rb-tested-head {
+ margin-top: 0.75rem;
+ padding: 0 0 0.25rem;
+ border-bottom: 1px solid var(--rb-border-strong);
+ color: var(--rb-text-secondary);
+ font-size: 0.8rem;
+ font-weight: 600;
+}
+.sl-markdown-content .rb-tested-head::before { content: ""; }
+/* Starlight draws a side rule on every details: a row has a rule below only. */
+.sl-markdown-content details:is(.rb-tested-row, .rb-tested-earlier) { border-inline-start: 0; padding-inline-start: 0; }
+.sl-markdown-content details.rb-tested-row { margin: 0; border-bottom: 1px solid var(--rb-border); }
+.sl-markdown-content details.rb-tested-row:has(> summary:hover) { border-color: var(--rb-border); }
+.sl-markdown-content .rb-tested-row > summary {
+ min-height: 2.25rem;
+ padding: 0.3rem 0;
+ cursor: pointer;
+ font-size: 0.9rem;
+}
+.sl-markdown-content .rb-tested-row > summary::before { background-color: var(--rb-text-secondary); margin: 0; }
+.sl-markdown-content .rb-tested-row > summary:hover { background: var(--rb-surface); }
+.sl-markdown-content .rb-tested-test { font-weight: 600; overflow-wrap: anywhere; }
+.sl-markdown-content :is(.rb-tested-test, .rb-tested-date) small { color: var(--rb-text-secondary); font-weight: 400; }
+.sl-markdown-content .rb-tested-date { color: var(--rb-text-secondary); font-variant-numeric: tabular-nums; }
+.sl-markdown-content .rb-tested-more {
+ margin: 0 0 0.75rem 1.75rem;
+ padding: 0.5rem 0.75rem;
+ border-left: 2px solid var(--rb-border-strong);
+ font-size: 0.875rem;
+}
+.sl-markdown-content .rb-tested-more p { margin: 0.35rem 0 0; }
+.sl-markdown-content .rb-tested-more p:first-child { margin-top: 0; }
+.sl-markdown-content details.rb-tested-earlier { margin-top: 0.75rem; }
+.sl-markdown-content details.rb-tested-earlier > summary { font-size: 0.9rem; color: var(--rb-text-secondary); cursor: pointer; }
+.sl-markdown-content .rb-dots { display: inline-flex; flex-wrap: wrap; gap: 3px; }
+.sl-markdown-content .rb-dot {
+ width: 10px;
+ height: 10px;
+ border-radius: 50%;
+ border: 2px solid var(--rb-dot);
+ background: var(--rb-dot);
+}
+.sl-markdown-content .rb-dot-empty { background: transparent; }
+.sl-markdown-content .rb-dot-0 { --rb-dot: var(--rb-ok-fg); }
+.sl-markdown-content .rb-dot-1 { --rb-dot: var(--rb-info-fg); }
+.sl-markdown-content .rb-dot-2 { --rb-dot: var(--rb-warn-fg); }
+.sl-markdown-content .rb-dot-3 { --rb-dot: var(--rb-err-fg); }
+.sl-markdown-content .rb-dot-4 { --rb-dot: var(--rb-muted-fg); }
+.sl-markdown-content table.rb-tested-bench { margin-top: 1rem; font-size: 0.875rem; }
+.sl-markdown-content table.rb-tested-bench td:not(:first-child) { font-variant-numeric: tabular-nums; text-align: right; }
+@media (max-width: 50rem) {
+ .sl-markdown-content div.rb-tested-head { display: none; }
+ .sl-markdown-content details.rb-tested-row > summary {
+ grid-template-columns: 1.25rem auto auto minmax(0, 1fr);
+ row-gap: 0.2rem;
+ }
+ .sl-markdown-content .rb-tested-row > summary::before { grid-row: 1; }
+ .sl-markdown-content .rb-tested-test { grid-column: 2 / -1; }
+ .sl-markdown-content .rb-tested-result { grid-column: 2; }
+ .sl-markdown-content .rb-tested-date { text-align: right; }
+ .sl-markdown-content .rb-tested-more { margin-left: 0.5rem; }
+}
diff --git a/wiki/measurements.md b/wiki/measurements.md
index b251058d0..a39682963 100644
--- a/wiki/measurements.md
+++ b/wiki/measurements.md
@@ -14,34 +14,35 @@ the emulator's page shows a **Tested** block. Every result is on the
## What a test does
- A script runs the emulator with no window and no sound.
-- It starts a game and runs it for a set time.
-- Each run starts fresh and runs at least twice.
-- Each run gets one result, by comparing its screens with ones we checked by eye.
+- It starts a game and runs it for a set time, at least twice.
+- It compares the screens with ones we checked by eye.
## What a result says
-- **boots**: the game reached a screen it shows when it works.
-- **bios_error**: the emulator said a BIOS is missing or wrong.
-- **crash**: the emulator stopped.
-- **hang**: the screen stayed blank, or the run never ended.
-- **unknown**: none of these could be shown.
-- **measured**: a speed test, given as time per frame.
-- **no verdict**: an older test that only compared screens.
+| Result | Meaning |
+|--------|---------|
+| works | the game reached a screen it shows when it works |
+| BIOS error | the emulator said a BIOS is missing or wrong |
+| crash | the emulator stopped |
+| hang | the screen stayed blank, or the run never ended |
+| unknown | none of these could be shown |
+| no verdict | an older test that only compared screens |
+| ms per frame | a speed test |
## How a file is proven
-The game runs with no BIOS, with the BIOS alone, and with a wrong file under
-its name: zeros, half the file, or another region's BIOS.
+The game runs with no BIOS and with the BIOS alone. Then it runs with a wrong
+file under the BIOS name: zeros, half the file, or another region's BIOS.
-- **Required**: no BIOS fails, the BIOS alone boots.
-- **Built-in stand-in**: no BIOS still boots.
+- **Required**: no BIOS fails, the BIOS alone works.
+- **Built-in stand-in**: no BIOS still works.
- **Checked**: a wrong file is refused.
## When tests run
-A maintainer starts a test run. A clear result updates the emulator's page and
-names the test as proof. Anything else waits on the
-[Measurements](../measurements.md) page for someone to read the code.
+A maintainer starts a test run. A clear result updates the emulator's page.
+Anything else waits on the [Measurements](../measurements.md) page for someone
+to read the code.
## What a result keeps