Host git only fetches into bare mirrors no sandbox ever sees, https only, with no user or system config and overrides emptying hooks, fsmonitor, ssh and proxy commands and every filter driver the mirror defines. Each build is a git archive of the commit and of each submodule commit from its own mirror. Old working-tree clones are deleted. The core copied out must be a regular file inside the build folder, opened without following links.
make runs on a git archive export of the commit and its submodules, the clone and its .git never bound into the sandbox. Host-side git runs with no user or system config, no hooks, no fsmonitor and the file and ext transports refused.
Without a working bubblewrap the pass is refused and nothing third-party runs, probes require the sandbox and builds are refused, unless --allow-unsandboxed is given. A job report holding an alert raises an autorun alert.
bwrap now binds only /usr, the /lib* and /bin links and the files of /etc the loader, fonts, TLS, the clock and user lookups read. Home, /tmp and /mnt are empty, the core, content and staged system directory read-only, the scratch folder the one writable path, every namespace unshared. --require-sandbox refuses to run without it. The worker imports nothing from the repository.
Values a run produced (core output, romfetch errors, profile names) are written as character references inside <code> or as plain text, so backticks, MDX braces and HTML stay text.
core_probe starts its worker with only the --env values, the worker clears its own environment before loading the core, and a working bubblewrap runs it with a read-only tree, no network and the user's credentials masked. The record names the isolation used. A timeout kills the whole process group. campaign.py starts core_probe, git, make and romfetch with the allowlist, make inside the same sandbox. --record refuses a folder that is not a checkout root.
Every step, gate and recomputation runs with an allowlisted environment (PATH, HOME, LANG, LC_*, TZ, TMPDIR and the variables its env: key names). Names holding TOKEN, SECRET or PASS are dropped and refused in the registry. Only the push and the gh calls that read CI get GITHUB_TOKEN.
campaign.py takes buildbot nightlies (or a build of the pinned commit), the test content of tests/campaign/content.yml through romfetch, and runs core_probe for the files, boot, path, alias, variants and bench checks. A fixed rule table settles hle_fallback, required, path and aliases with the record ids as evidence, the rest goes to measurements/findings.jsonl. Resumable state, load, disk and budget guards, retries, content reference counting with an LRU cap, and an autorun job after the nightly.
The runner carried its own copy of the refused vocabulary, which put those words in a tracked file. It now runs the commit-msg hook on each job's subject when the job list loads, and checks only the shape where no hook exists.
Runs the jobs of automation/jobs.yml under one host lock, with load and disk guards, one idle retry on a host misread, artifacts computed twice, explicit-path commits in .cache/autorun/worktree, an opt-in push (--push) and a CI circuit breaker. First jobs: nightly refresh chain, large-files asset dry run, disk cleanup and check, weekly bios integrity. install-timers writes systemd user units without enabling them.
Rehashes every file database.json names. A confirmed mismatch on a committed file git sees as unmodified is restored from its HEAD blob after the blob is checked, then hashed again. Gitignored files are flagged only.
Uploads the large-files assets check_release_assets reports out of date, only when the SHA-1 read twice equals database.json. Dry run by default, the release description is never edited.
Tracked form of the private worktree and tmp cleanup: busy, recent and modified worktrees are kept, branches stay, tmp/acquire and bundles stay, and an entry holding a worktree is left to the worktree rule. Exit 1 under --alert-gb free.
--findings FILE takes a check_freshness.py --json report written earlier, kept to the --only areas, so one full check can drive a refresh of a few areas.
Each line of measurements/<emulator>.jsonl now holds the whole run:
every repetition, core logs, per-frame hashes and raw timings. The gzip
data files, the data schema and the split and join code are gone, and
core_probe --record appends one line. record_version moves to 3.
The seven records are rewritten once: each new line equals its old line
joined with its data file, and rebuilds the first-layout file its id
hashes.
scripts/generate_installer_doc.py rewrites the marked tables of
wiki/installer.md: options from build_parser(), events from EVENT_KEYS,
exit codes from the sys.exit calls, RETROBIOS_PAD values and the keys of
each route from TUI_ROUTES, the one table the bar, the help and the stop
line now read. Prose cells are kept, a row the code gains stops the
script until its text is written, and --check runs in the tests. The
site drops the markers.
The full screen falls back to the line questions where the terminal's
terminfo moves no cursor the ANSI way (vt52, glass terminals), and --tui
names why. The line mode draws no escape code with TERM unset, dumb or
such a terminal. Ctrl-Z typed on the screen stops the run as SIGTSTP
does, the terminal handed back and redrawn on fg. A window too small
names 40x12 and its own size.
Pty tests check the modes, the cursor and the main screen after Ctrl-C,
Ctrl-D, SIGTERM, SIGHUP and Ctrl-Z then fg. Tests from a mutmut run over
the device filter, the card bounds and the WSL paths raise its score
from 62/71 to 71/71. The parser lists its options in the page's order,
with NAME, PATH, NAMES and N as arguments.
The site compiles the index and the SQLite files from measurements/
instead of a pinned clone. The pin, the sync script, the CI clone steps,
the freshness area and its refresh handler are gone. core_probe --record
writes into measurements/ of the checkout by default, and a test fails
when a line or data file HEAD holds changed or vanished.
Each emulator with records gets a Tested block: claim, result, method, computer, core build and the permalink of its record at the pin. A Measurements page filters every test in the browser from the compact index, and the Data page lists measurements.sqlite with its tables. One SQLite file per emulator is linked from its page. wiki/measurements.md says how a test runs, held to the Start here budgets.
Both workflows fetch the pinned commit alone with git before the build, and the deploy fails without it.
measurements-pin.json names the measurements repository and the commit read. sync_measurements.py reads the clone in .cache/measurements at that commit, checks every line against its id and sidecar, and writes the site index and the SQLite databases. --fetch refuses a remote that changed or removed a line the pin holds, --bump moves the pin. validate_schemas checks every line and sidecar against their schemas, check_freshness flags a pin behind the remote head.
A record is now one line of records/<emulator>.jsonl in the measurements repository, under 20 KB, and a gzip sidecar holding every repetition, the core logs and the raw timings, its sha256 in the line. record_version 2, core-probe-data.schema.json for the sidecar. core_probe --record appends to a measurements clone and refuses a directory inside this repository's tracked tree.
The seven records of issue 85 moved to Abdess/retrobios-measurements: each line plus its sidecar rebuilds the first file, and keeps its first id.
The probe skipped run_cli, accepted option prefixes in its shared options, left three options undocumented and was missing from the published schemas. The worker runs under -I, so the script directory is put back on sys.path before importing common.
The PlayStation 2 page holds every word of "playstation bios" and pagefind ignores the words a page has that the query lacks, so its own body matches put it first. A console whose every word another system page's consoles also hold is now written again in an `exact` field (weight 20). File names reach the `files` field as their parts: whole names let "ds" reach twelve dsi_* names on the DSi page. `consoles` weighs 100 so an emulator named after its console (Saturn.emu) comes second, and `aliases` 10 so the Nintendo page keeps "gba bios". The gate runs both directions of the PlayStation, Game Boy, DS and Wii pairs and Saturn.
A repository that answers 404 (TeknoParrotUI) was listed folder by folder,
every listing failed, and the tree read as holding nothing: a fetch without
its cached tree dropped its recorded paths. It is now unread, so the record
keeps what it held.
A check is inconclusive when the compared observable differs between repetitions, a run fails to load or a file changes during it. A disagreement on another observable stays flagged without voiding the comparison. Core log lines name the staged directories instead of the host paths.
A profile keyed by build mode compared a part in every repository and kept
the best outcome: xenia-edge's libretro tree carries emulator.cc offset,
and its lines vouched for standalone refs that had moved 4 lines. A part
of an entry with a mode is read in that build's repository, and a part
naming none in the first build declared, as _realign_part decides.
The home page is the site: named RetroBIOS, part of nothing. A source repository states the core_version of its build when it names a release, never a commit id or a pin, and no placeholder description.
get/ pages join the tab of the setup pages of the same name, so no page is excluded from every tab and the tabs keep their names. Groups filing emulators by first letter are no crumb. A console page carries "<name> BIOS" in its terms field. The search gate runs the console, checker, schema, license and iOS queries.
load_emulator_profiles caches by directory, and the run had loaded the
profiles before writing them, so the record rebuilt after a write kept the
pins just replaced: linapple, then ten profiles in one advance. The cache
is cleared first. The record is rebuilt for those ten.
core_probe.py loads a libretro core with no window, runs it for a
number of frames and reports the files it opened (strace), its log
lines, frame, audio and save-state hashes and its speed. bench repeats
a run after warm-up and gives median, mean, deviation and range; check
swaps one system file between runs and compares one observable.
--record writes a JSON record with the core build, frontend commit,
host and every input by hash, validated against
core-probe-record.schema.json. A busy host is refused, and a
repetition that disagrees with the first is flagged.
_needs_review held every skipped report, so a profile with no source_ref
never moved: anarch sat behind a CI-only commit through every bulk advance.
Its source pin now moves; its upstream pin does not, since nothing was
compared there.
A project that tags without releases (eightyone, higan) was never compared,
and a core_version naming no tag (parallel-launcher 9.0.5 beside v9.0-6)
silenced the passed-release warning. The newest tag stands in when it is a
version number, and an untagged core_version is compared by number within
one scheme. --emulator is repeatable: a stored value kept only the last.
A profile pinned on its release tag read as clean forever: armsx1, armsx2
and x1-box sat on old tags while newer releases were out. --check-version
now names the latest release when it follows both the declared tag and
the pin, unless pin_frozen holds the pin on purpose.
A sitesources run that GitLab rate-limited dropped about twenty trees it
never read from website/src/data/source-paths.json. A tree key names a
pinned sha, so the last record's entry for an unread tree still holds and
is carried over, in sitesources and in profile_sync's refresh.
Refs that still anchor say nothing of what the code added around them:
mame2003_plus's zn.c gained two Taito G-NET BIOS sets while its cited
lines only shifted. --rebase-refs and --bump-commit now refuse a profile
whose cited files gained a file name or a hash literal since the pin, or
could not be read, until --accept-changed says the additions were read.
With this, --all --rebase-refs --fresh is safe to run as is.
citron's taken domain now resolves to an unrelated host that fails TLS,
so the pass reported an upstream error where the profile already states
the forge is gone. Any forge error now agrees with upstream_gone; a rate
limit still does not.