Without a BIOS, PCSX2 logs and shows 'PCSX2 requires a PlayStation 2 BIOS in order to run'. That line is now one of PCSX2's error patterns, for the next run's verdict: this record was judged without it.
Twelve runs on each of beetle_psx and beetle_saturn: each region's game with every regional BIOS, its own only, the others only and the region pack. One DuckStation check on the US disc with and without scph5501.bin.
Every reference block's run, judged again with the oracle as committed, boots in each repetition. Beetle Saturn's three BIOS lines are BIOS errors, the Japanese menu screens are error screens, and the PlayStation cores carry no error pattern.
Every value a run produced (claim, method, statement, host, flags, variant labels, core, content and file names) is escaped with quotes and its braces made inert for MDX, never read as Markdown. A record link is written only for an http(s) or relative target. Reference runs fold with the tests from before the oracle.
Puzzle Bobble 2 (Japan) and Bust-A-Move 2 (Europe) on the PlayStation cores, and the three Saturn editions on Beetle Saturn, each frame reviewed on its PNG. Beetle Saturn's BIOS error lines, read from ss.c, are its error patterns.
The first runs showed it: beetle_psx reaches the game of the US disc on the Japanese and European BIOS, through a different licence screen, so its frames differ from the full run while the oracle says boots. The interim rule read those frames as a failure. A variant now boots on the same frames as the full run or by its oracle verdict, and otherwise has no answer.
The sandbox bound only /usr, /lib and /bin. On the CI runner setup-python puts Python under /opt/hostedtoolcache, so the worker could not start and every core_probe and campaign run crashed (7 tests, plus their replay without jsonschema). A venv, pyenv or uv do the same. The interpreter's prefix and every link on the way to it are now bound read-only, each at its own path. Reproduced with uv's Python 3.13 outside /usr, 92 tests pass under it and under the system Python.
Merging the card and platform branches left two SHORT_HASH definitions, 8 and 12. The later one silently won, so the platform rows showed 12 characters while their tests expected 8. One constant now, 12, the card anchor's length. The copy-button selector skips buttons that carry their own data-copy, and the tests follow the merged markup.
Each file is a details whose summary is the row: name, folder when it is not the bare name, status chip. The short hash sits on one copy button beside it, since a summary is itself a control. Opened, the evidence runs across the list. A list with no folder drops the column. Code spans in the evidence are plain text.
The lists split by the share of a page they render to (elements against 10,000, bytes against 600,000) instead of Markdown length. With its parts, Batocera goes from 12 pages, 18,854 elements and 1.21 MB to 10 pages, 13,671 and 1.11 MB. A part that folds back into its platform page redirects there, from the record in website/src/data/platform-parts.json.
Reference runs, then the full set, no BIOS and negative variants of Bust-A-Move 2 (USA) on Beetle PSX, Beetle PSX HW, SwanStation, PCSX-ReARMed and PCSX1, each judged by the boot oracle.
A finding line is written again when the records behind it change, so an open finding cites the oracle runs rather than a frame comparison. A contradicted validation shows the checks the runs prove, content and size, beside the profile's list.
MAME's card pages, GroovyMAME and yaps2 read 13,000 to 16,700 words of
cards. Each entry is now a table row (name, members, status, short
hash) that opens to its card in a details block, with the site's filter
box and row counter. Pages from 150 entries and every split part use it.
Visible words: mame-files-1 16,680 to 1,612, groovymame 14,396 to 1,876.
A row costs about five elements, so a part holds 180 cards: MAME goes
from 8 parts to 9, the largest at 567 KB and 8,989 elements.
Each wanted list (TOSEC, No-Intro, Redump) opens on its counts, then one
table folded under a filter box that opens it, in place of a block per
DAT. Only the names are searched: indexed descriptions and hashes put
the PlayStation page ahead of the PlayStation 2 one.
A filtered row stayed visible on a phone: a stacked row's display:block
outweighed the hidden attribute on every stacked table.
standalone_probe now holds the host probe lock for one run, its load check included, as core_probe does, and the docs no longer wrap region_check or standalone_probe in flock.
The install box's select had a rule of its own that dropped the arrow
and read as a text field. It now takes the shared select rule and
--rb-select-arrow in both themes.
core_probe takes probe.lock itself around one execution, load check included, and releases it when the run ends. A campaign pass never holds it between runs, so two passes interleave. RETROBIOS_PROBE_LOCK names another file, RETROBIOS_PROBE_LOCK_HELD says the caller holds it. Tests use their own lock file.
System and emulator file cards show the name, size, download, a short
SHA1 with a copy button, the setups and the emulators. The full hashes
and the other facts sit in one closed fold, each hash with a copy button.
A system's install box shows one command under the OS tabs for the
setup picked in a list, remembered across pages. Without the script the
list of every command shows, open. The Markdown the page budgets are
measured on is unchanged, so no card moves.
Each file of a platform list is one row: name, folder, status chip, a short hash on a button that copies it whole, and a details cell. The details hold every declared hash, the size, the served copy and the readers. A reference's role is one word (glue, port, upstream, build) defined once in a legend instead of a phrase after every reference.
Visible words: Batocera 2,433 to 2,332, AmberELEC 1,301 to 1,299, RomM 1,607 to 1,596, RetroArch 2,207 to 2,088, RetroDECK arcade part 2 19,127 to 2,104. Rows weigh more Markdown, so 38 more systems move to pages of their own under the existing budgets.
source_ref_items() gives each part of a source_ref with the role of the code it cites. _source_ref_markdown() joins them as before, so every other page renders the same text.
Rows sit under the file under test. A title names the game and what sets the test apart from the others of its file (build, clock, region, files tried), never cut. Zero counts leave the summary. A legend explains the dots. The chips say depends on the file and same with every file. Ran on shows the short host.
The Tested block opens on one summary line, then a row per test: a short title, a result chip, a dot per variant and the date. Method, claim, computer, core build and the record wait in the row's details. Tests from before the boot oracle fold under one line, benchmarks get their own table. beetle_saturn shows under 120 words before anything is opened.
Pins and launcher index recomputed for the merged install.py. Mouse modes are now written before the frame, so a screen read back already takes clicks.
core_version is a string the code states, null or keyed by build mode,
and the schema refuses Git, SVN, unknown, master, main, latest, dev and
HEAD, alone or annotated. version_basis is defined per kind with the
fields each one needs, and kind none requires a null version.
tests/test_version_basis.py reads a recorded fixture of forge answers
offline and fails on drift, holds every profile to a basis (tag versions
in describe form, aliases equal to their profile, none as null), checks
that a pin advance reads the version again or is refused, and that the
schema refuses placeholders.
Each standalone profile records where its version is read: the source
at its pin (clk 2026-07-23-136-g9020107, shadps4 0.20.1 WIP, eden
v0.2.0-401-g26a03d2), the APK, binary or archive read (kind package),
or, where no reading can be made, kind unverified with the reason: a
host the tools cannot read, a forge gone or not answering, no build
recorded as read, a pin on another branch. Those keep their value,
unconfirmed.
Each libretro profile records version_basis and core_version is read
there at its pin. snes9x states 1.63 in snes9x.h, nestopia 2.0.0, mgba
0.11.0, vice 3.10, atari800's libretro tree 7.0.0. Cores whose build
takes its version from git carry the nearest tag in describe form
(flycast v2.7-90-ge88b0ce, ppsspp, duckstation, gearcoleco), placeholders
copied from .info files (Git, SVN) are replaced by the code's statement,
and a code that states none is null with kind none.