core_probe refuses to record above its load threshold. region_check gave up on the first refusal, so twelve runs ended at once on a host still busy from the previous run. It now tries a refused run again every 15 seconds for up to --max-load-wait seconds, the threshold unchanged.
PCSX2 writes its NVRAM and MEC files beside the BIOS and reported an error on the read-only folder, and both emulators opened an error dialog over the screen for want of a sound device. The BIOS folder is now writable and staged again for each repetition, and both use the null audio backend.
The new probes now refuse abbreviated options, read the GitHub token through github_headers, type every signature and document every option. The site build watches region_check.py and the content list, and validate.yml's tests/** counts as watching the list. The testing guide names the two new test modules.
One catalog name is a PlayStation disc and a Saturn disc. The coverage table counted a record for every system listing its label, so the Saturn row showed the PlayStation cores. A record now counts only where its emulator's profile names the system, runs on games outside the list are named apart, and two ids of one console share one row.
region_check.py runs each region game of content.yml with every regional BIOS, its own only, the others only, and the region-filtered pack, and reads the outcome from the records: the file opened settles region:, the verdicts say whether the BIOS is locked. System pages show the outcome with its records, the Measurements page lists which systems are tested and why the others are not. The campaign text says a maintainer starts each pass.
standalone_probe.py runs the release binary a platform installs (EmuDeck's asset rule for DuckStation and PCSX2), unpacked without running the AppImage runtime, in core_probe's allowlist sandbox with a private Xvfb, strace and core_probe's oracle and record functions. Records go to measurements/<emulator>.jsonl.
The Tested blocks and the explorer show each variant's verdict. A frame comparison from before the oracle shows no verdict. The SQLite variants table gains verdict columns. The method page says what each verdict means and how a file is proven.
Bust-A-Move 2 (USA) on Beetle PSX, Beetle PSX HW, SwanStation, PCSX-ReARMed and PCSX1, each frame reviewed on its PNG. A boot needs a screen of the game: the BIOS screens alone are not one.
campaign.py install-timer writes the autorun units and enables nothing unless --enable is passed. The docs say a contributor who wants a schedule on their own machine installs the timer.
The campaign runs the full set, no BIOS, and per file the file alone with zeros, its first half and other regions' dumps under its name. required, hle_fallback and validation are read from the verdicts, never from frames that differ. Benchmarks run only with --bench. The timer command is gone: a maintainer starts each pass. references lists stable screens for the oracle.
core_probe writes record version 4. Each run is boots, bios_error, crash, hang or unknown, from reference frames, core error patterns and screens, and the process status. A crash or a timeout is a verdict now, rebuilt from the events the worker streamed. --vary accepts @zeros:SIZE and @head:SIZE:PATH, and run takes --repeat.
Record version 4 carries verdict and verdict_evidence on every repetition and variant, verdicts on a check result, and inputs.oracle. Version 3 records stay valid.
A step marked github_token: true receives the token. autorun accepts the key only on check_freshness.py, refresh_stale.py and python -m scripts.scraper.* tracked in this repository, refuses it on any other step and in the campaign job. The nightly steps declare it.
The rules lived only in the local commit-msg hook, which CI and other clones do not have, so autorun could only check a subject's shape there. scripts/commit_rules.py now holds them, the hook calls it, and autorun checks every job subject with it when the job list loads.
Answers code_audit_17 and tui_audit_10: the folder found after o, SHARE
drive roots, one same-choice command, a counted confirm step in line
mode, counts from the narrowed list, read-only advice without sudo,
systems recorded in packs, Ctrl-Z stopping the whole job, and the
wording, layout and docs items.
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.