feat: name the BIOS each run used

Record version 5: every repetition and variant carries bios_used, staged:<dest> when a load line of the core names the file, builtin when a fallback line says it ran its own BIOS, unknown otherwise. An opened file is no proof. The oracle gains fallback_patterns and load_patterns per core, and a record judged again carries judged_at and judged_by.
This commit is contained in:
Abdessamad Derraz committed 2026-10-28 05:18:38 +01:00
1 parent 7c3e6ced09
commit 5c8da8a424
5 files changed
+194 -14

No files matched your search

+96 -4
View File
@@ -2,7 +2,7 @@
"$schema": "https://json-schema.org/draft/2020-12/schema", "$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://abdess.github.io/retrobios/api/v1/schemas/core-probe-record.schema.json", "$id": "https://abdess.github.io/retrobios/api/v1/schemas/core-probe-record.schema.json",
"title": "RetroBIOS core probe record", "title": "RetroBIOS core probe record",
"description": "One line of measurements/<emulator>.jsonl: one run of scripts/core_probe.py, a libretro core executed headless, in full, with what is needed to judge and reproduce it. Every repetition, its core logs and per-frame hashes, and the raw timing values are in the line. From record_version 4 every repetition and every variant carries the boot oracle's verdict (boots, bios_error, crash, hang, unknown) with the evidence that decided it.", "description": "One line of measurements/<emulator>.jsonl: one run of scripts/core_probe.py, a libretro core executed headless, in full, with what is needed to judge and reproduce it. Every repetition, its core logs and per-frame hashes, and the raw timing values are in the line. From record_version 4 every repetition and every variant carries the boot oracle's verdict (boots, bios_error, crash, hang, unknown) with the evidence that decided it. From record_version 5 each also names the BIOS the core ran (`bios_used`), and a record judged again from an earlier one's runs carries `judged_at` and `judged_by`.",
"type": "object", "type": "object",
"additionalProperties": false, "additionalProperties": false,
"required": [ "required": [
@@ -24,9 +24,10 @@
"record_version": { "record_version": {
"enum": [ "enum": [
3, 3,
4 4,
5
], ],
"description": "3: records before the boot oracle. 4: every repetition and variant carries `verdict` and `verdict_evidence`, and `inputs.oracle` names what the verdicts were judged against." "description": "3: records before the boot oracle. 4: every repetition and variant carries `verdict` and `verdict_evidence`, and `inputs.oracle` names what the verdicts were judged against. 5: they also carry `bios_used`."
}, },
"id": { "id": {
"type": "string", "type": "string",
@@ -416,6 +417,9 @@
}, },
"verdict_evidence": { "verdict_evidence": {
"$ref": "#/$defs/verdict_evidence" "$ref": "#/$defs/verdict_evidence"
},
"bios_used": {
"$ref": "#/$defs/bios_used"
} }
} }
} }
@@ -521,6 +525,36 @@
} }
} }
} }
},
"judged_at": {
"type": "string",
"format": "date-time",
"description": "When the runs of the record it supersedes were judged again, offline, against the current oracle. recorded_at stays the time the runs were made."
},
"judged_by": {
"type": "object",
"additionalProperties": false,
"required": [
"script",
"commit",
"script_sha256"
],
"properties": {
"script": {
"const": "scripts/core_probe.py"
},
"commit": {
"type": [
"string",
"null"
],
"pattern": "^[0-9a-f]{40}$"
},
"script_sha256": {
"$ref": "#/$defs/sha256"
}
},
"description": "The core_probe that judged the runs again: frontend names the one that ran them."
} }
}, },
"allOf": [ "allOf": [
@@ -565,7 +599,10 @@
"if": { "if": {
"properties": { "properties": {
"record_version": { "record_version": {
"const": 4 "enum": [
4,
5
]
} }
} }
}, },
@@ -605,6 +642,35 @@
} }
} }
} }
},
{
"if": {
"properties": {
"record_version": {
"const": 5
}
}
},
"then": {
"properties": {
"variants": {
"items": {
"required": [
"bios_used"
],
"properties": {
"repetitions": {
"items": {
"required": [
"bios_used"
]
}
}
}
}
}
}
}
} }
], ],
"$defs": { "$defs": {
@@ -850,6 +916,9 @@
} }
}, },
"description": "How a worker that did not finish ended: its exit status or the signal that killed it, or the timeout after which the probe killed it." "description": "How a worker that did not finish ended: its exit status or the signal that killed it, or the timeout after which the probe killed it."
},
"bios_used": {
"$ref": "#/$defs/bios_used"
} }
} }
}, },
@@ -925,6 +994,10 @@
"items": { "items": {
"$ref": "#/$defs/verdict" "$ref": "#/$defs/verdict"
} }
},
"bios_log": {
"type": "string",
"description": "The log line that names the BIOS the core ran."
} }
} }
}, },
@@ -1011,11 +1084,30 @@
"type": "string" "type": "string"
}, },
"description": "Log lines by which the core says it loaded the console's own BIOS: they add the bios stage when its screens are hidden." "description": "Log lines by which the core says it loaded the console's own BIOS: they add the bios stage when its screens are hidden."
},
"fallback_patterns": {
"type": "array",
"items": {
"type": "string"
},
"description": "Log lines after which the core runs its built-in BIOS."
},
"load_patterns": {
"type": "array",
"items": {
"type": "string"
},
"description": "Log lines naming the BIOS file the core loaded, in a group named path."
} }
} }
} }
], ],
"description": "What the verdicts were judged against: the reference frames of this content and core, the core's error patterns, error screens and BIOS load patterns, from the oracle file at its sha256. Null when the run had no oracle entry." "description": "What the verdicts were judged against: the reference frames of this content and core, the core's error patterns, error screens and BIOS load patterns, from the oracle file at its sha256. Null when the run had no oracle entry."
},
"bios_used": {
"type": "string",
"pattern": "^(staged:.+|builtin|unknown)$",
"description": "The BIOS the core ran, from its log: staged:<dest> for the staged file a load line names, builtin when a fallback line says the core ran its own BIOS (OpenBIOS, HLE), unknown when the log says neither. An opened file is no proof."
} }
} }
} }
+54 -7
View File
@@ -1007,7 +1007,7 @@ def run_variant(args: argparse.Namespace, base: dict[str, Path], variant: Varian
state_out.parent.mkdir(parents=True, exist_ok=True) state_out.parent.mkdir(parents=True, exist_ok=True)
rep = run_once(args, system_dir, scratch, state_out, png_dir) rep = run_once(args, system_dir, scratch, state_out, png_dir)
oracle = oracle_for(args.oracle_data, args.content_sha1, rep.get("library_name")) oracle = oracle_for(args.oracle_data, args.content_sha1, rep.get("library_name"))
rep["verdict"], rep["verdict_evidence"] = judge(rep, oracle) judge_repetition(rep, oracle)
if rep.get("exit"): if rep.get("exit"):
flags.append(f"repetition {index}: {rep['verdict_evidence']['detail']}") flags.append(f"repetition {index}: {rep['verdict_evidence']['detail']}")
if not rep.get("loaded"): if not rep.get("loaded"):
@@ -1023,15 +1023,14 @@ def run_variant(args: argparse.Namespace, base: dict[str, Path], variant: Varian
if rep.get("observables") != reference: if rep.get("observables") != reference:
differing = sorted(k for k in (reference or {}) if (rep.get("observables") or {}).get(k) != reference[k]) differing = sorted(k for k in (reference or {}) if (rep.get("observables") or {}).get(k) != reference[k])
flags.append(f"repetition {index} disagrees with repetition 0 on {', '.join(differing)}") flags.append(f"repetition {index} disagrees with repetition 0 on {', '.join(differing)}")
verdict, evidence = variant_verdict(reps) out = {
return {
"label": variant.label, "label": variant.label,
"system_files": {dest: sha for dest, sha in sorted(staged.items())}, "system_files": {dest: sha for dest, sha in sorted(staged.items())},
"repetitions": reps, "repetitions": reps,
"flags": flags, "flags": flags,
"verdict": verdict,
"verdict_evidence": evidence,
} }
judge_variant(out)
return out
# -------------------------------------------------------------------------- # --------------------------------------------------------------------------
@@ -1274,7 +1273,9 @@ def oracle_for(data: dict, content_sha1: str | None, library_name: str | None) -
screens = [{"sha1": s["sha1"], "shows": s["shows"]} for s in core.get("error_screens") or ()] screens = [{"sha1": s["sha1"], "shows": s["shows"]} for s in core.get("error_screens") or ()]
loaded = [p["pattern"] for p in core.get("bios_patterns") or ()] loaded = [p["pattern"] for p in core.get("bios_patterns") or ()]
return {"references": references, "error_patterns": patterns, "error_screens": screens, return {"references": references, "error_patterns": patterns, "error_screens": screens,
"bios_patterns": loaded} "bios_patterns": loaded,
"fallback_patterns": [p["pattern"] for p in core.get("fallback_patterns") or ()],
"load_patterns": [p["pattern"] for p in core.get("load_patterns") or ()]}
def sampled(rep: dict) -> list[tuple[int, str]]: def sampled(rep: dict) -> list[tuple[int, str]]:
@@ -1355,6 +1356,52 @@ def _without_the_game(rep: dict, oracle: dict, frames: list[tuple[int, str]]) ->
"and no error screen"} "and no error screen"}
BUILTIN = "builtin"
SYSTEM_PLACE = "<system>/"
def bios_used(rep: dict, oracle: dict) -> tuple[str, str | None]:
"""Which BIOS the core ran, from its log, and the line that says so.
`staged:<dest>` when a load pattern names the file under the system
directory, `builtin` when a fallback pattern says the core went to its
own BIOS (OpenBIOS, HLE), `unknown` otherwise. The last such line wins:
a core that tries a file and then falls back logs both, in that order.
A file the core opened is no proof: Beetle PSX hashes every file of the
folder before it decides.
"""
found: tuple[str, str | None] = ("unknown", None)
for line in rep.get("logs") or ():
text = line["text"]
if any(re.search(pattern, text) for pattern in oracle.get("fallback_patterns") or ()):
found = (BUILTIN, text)
for pattern in oracle.get("load_patterns") or ():
match = re.search(pattern, text)
path = match.groupdict().get("path") if match else None
if path:
found = (f"staged:{path.removeprefix(SYSTEM_PLACE)}", text)
return found
def judge_repetition(rep: dict, oracle: dict) -> None:
"""The verdict, its evidence and the BIOS used, written into the repetition."""
verdict, evidence = judge(rep, oracle)
used, line = bios_used(rep, oracle)
if line:
evidence["bios_log"] = line
if verdict == BOOTS and used == BUILTIN:
evidence["detail"] += ", on the core's built-in BIOS"
rep["verdict"], rep["verdict_evidence"], rep["bios_used"] = verdict, evidence, used
def judge_variant(variant: dict) -> None:
"""The variant's verdict and BIOS: what every repetition agrees on."""
reps = variant["repetitions"]
variant["verdict"], variant["verdict_evidence"] = variant_verdict(reps)
used = {rep.get("bios_used", "unknown") for rep in reps}
variant["bios_used"] = used.pop() if len(used) == 1 else "unknown"
def variant_verdict(reps: Sequence[dict]) -> tuple[str, dict]: def variant_verdict(reps: Sequence[dict]) -> tuple[str, dict]:
"""The verdict all repetitions agree on, else unknown.""" """The verdict all repetitions agree on, else unknown."""
verdicts = [rep["verdict"] for rep in reps] verdicts = [rep["verdict"] for rep in reps]
@@ -1363,7 +1410,7 @@ def variant_verdict(reps: Sequence[dict]) -> tuple[str, dict]:
evidence = {"rule": "repetitions_agree", evidence = {"rule": "repetitions_agree",
"detail": f"{len(reps)} of {len(reps)} repetitions: {first['detail']}", "detail": f"{len(reps)} of {len(reps)} repetitions: {first['detail']}",
"repetitions": verdicts} "repetitions": verdicts}
for key in ("frame", "sha1", "stages", "pattern", "log", "exit_status", "signal"): for key in ("frame", "sha1", "stages", "pattern", "log", "exit_status", "signal", "bios_log"):
if key in first: if key in first:
evidence[key] = first[key] evidence[key] = first[key]
return verdicts[0], evidence return verdicts[0], evidence
+3 -2
View File
@@ -27,9 +27,10 @@ from collections.abc import Iterable
from dataclasses import dataclass from dataclasses import dataclass
from pathlib import Path from pathlib import Path
RECORD_VERSION = 4 RECORD_VERSION = 5
# 3: before the boot oracle. 4: every repetition and variant carries a verdict. # 3: before the boot oracle. 4: every repetition and variant carries a verdict.
RECORD_VERSIONS = (3, 4) # 5: and the BIOS the core ran.
RECORD_VERSIONS = (3, 4, 5)
RECORDS_DIR = "measurements" RECORDS_DIR = "measurements"
ID_PATTERN = re.compile(r"^\d{4}-\d{2}-\d{2}-[0-9a-f]{12}$") ID_PATTERN = re.compile(r"^\d{4}-\d{2}-\d{2}-[0-9a-f]{12}$")
SLUG_PATTERN = re.compile(r"^[a-z0-9]+(?:[-_][a-z0-9]+)*$") SLUG_PATTERN = re.compile(r"^[a-z0-9]+(?:[-_][a-z0-9]+)*$")
+23 -1
View File
@@ -218,6 +218,27 @@ class OracleTests(unittest.TestCase):
self.assertEqual((verdict, evidence["stages"]), ("boots", ["bios", "game"])) self.assertEqual((verdict, evidence["stages"]), ("boots", ["bios", "game"]))
self.assertEqual(core_probe.judge(self.rep(frames((0, self.TITLE))), oracle)[1]["stages"], ["game"]) self.assertEqual(core_probe.judge(self.rep(frames((0, self.TITLE))), oracle)[1]["stages"], ["game"])
def test_the_bios_used_comes_from_the_log(self) -> None:
oracle = {**self.ORACLE, "fallback_patterns": [r"^Firmware is missing: "],
"load_patterns": [r"^Firmware found: (?P<path>.+)$"]}
cases = [
(["Searching for firmware checksum: 0555", "Firmware is missing: scph5501.bin"], "builtin"),
(["Firmware found: <system>/scph5501.bin", "Firmware SHA1: 0555"], "staged:scph5501.bin"),
# A file tried, then the fallback: the last line decides.
(["Firmware found: <system>/a.bin", "Firmware is missing: b.bin"], "builtin"),
(["nothing about the BIOS"], "unknown"),
]
for logs, expected in cases:
self.assertEqual(core_probe.bios_used(self.rep([], logs=logs), oracle)[0], expected, logs)
rep = self.rep(frames((10, self.TITLE)), logs=["Firmware is missing: scph5501.bin"])
core_probe.judge_repetition(rep, oracle)
self.assertEqual((rep["verdict"], rep["bios_used"]), ("boots", "builtin"))
self.assertTrue(rep["verdict_evidence"]["detail"].endswith("on the core's built-in BIOS"))
self.assertEqual(rep["verdict_evidence"]["bios_log"], "Firmware is missing: scph5501.bin")
variant = {"repetitions": [rep, {**rep, "bios_used": "staged:a.bin"}]}
core_probe.judge_variant(variant)
self.assertEqual(variant["bios_used"], "unknown")
def test_the_bios_screens_alone_are_not_a_boot(self) -> None: def test_the_bios_screens_alone_are_not_a_boot(self) -> None:
# A console that shows its logo and then refuses the disc never reached the game. # A console that shows its logo and then refuses the disc never reached the game.
verdict, evidence = core_probe.judge( verdict, evidence = core_probe.judge(
@@ -516,7 +537,8 @@ class FakeCoreTests(unittest.TestCase):
self.assertEqual(record["variants"][3]["system_files"], self.assertEqual(record["variants"][3]["system_files"],
{"card.bin": hashlib.sha1(b"\x00").hexdigest()}) {"card.bin": hashlib.sha1(b"\x00").hexdigest()})
self.assertEqual(record["inputs"]["oracle"]["error_patterns"], ["BIOS not found"]) self.assertEqual(record["inputs"]["oracle"]["error_patterns"], ["BIOS not found"])
self.assertEqual(record["record_version"], 4) self.assertEqual(record["record_version"], 5)
self.assertEqual({v["bios_used"] for v in record["variants"]}, {"unknown"})
self.assert_schema(record) self.assert_schema(record)
def test_a_crash_and_a_hang_are_verdicts(self) -> None: def test_a_crash_and_a_hang_are_verdicts(self) -> None:
+18
View File
@@ -331,6 +331,24 @@ class ReadAndIndex(Workspace):
self.assertTrue(any("measurements/beta.jsonl:1" in e and "kind" in e for e in errors), errors) self.assertTrue(any("measurements/beta.jsonl:1" in e and "kind" in e for e in errors), errors)
self.assertIsNotNone(Draft202012Validator) self.assertIsNotNone(Draft202012Validator)
def test_a_version_5_record_names_the_bios_it_ran(self) -> None:
try:
from jsonschema import Draft202012Validator
except ImportError:
self.skipTest("jsonschema is not installed")
schema = json.loads((REPO_ROOT / "schemas" / "core-probe-record.schema.json").read_text())
record = mr.seal({**full_record("alpha", "card", "2026-10-28T10:00:00+00:00"), "record_version": 5})
errors = [e.message for e in Draft202012Validator(schema).iter_errors(record)]
self.assertIn("'bios_used' is a required property", errors)
for variant in record["variants"]:
variant["bios_used"] = "builtin"
for rep in variant["repetitions"]:
rep["bios_used"] = "staged:card.bin"
self.assertEqual([e.message for e in Draft202012Validator(schema).iter_errors(record)
if "bios_used" in e.message], [])
record["variants"][0]["bios_used"] = "the other region's"
self.assertTrue(list(Draft202012Validator(schema).iter_errors(record)))
def test_a_version_4_record_needs_its_verdicts(self) -> None: def test_a_version_4_record_needs_its_verdicts(self) -> None:
try: try:
from jsonschema import Draft202012Validator # noqa: F401 from jsonschema import Draft202012Validator # noqa: F401