The recipe snapshot kept only the archives a profile or platform names,
so provenance_report --missing named no set for dumps a public romset
carries (fm7740sx holds the FM77AV40SX DICROM, EXTSUB and KANJI2 ROMs).
The importer now also keeps the sets holding a catalog entry the
collection lacks. Reimported from mame0289 and the cached FBNeo DATs:
MAME 1726 to 2148 recipes, FBNeo 104 to 111, none dropped.
_enrich_hashes repeated "ground truth" five times with capitalised
insistence for a three-step lookup, and the MAME ZIP rebuild listed
three guarantees of which two were the same.
Quick Install was the one Title Case heading left in the published
text. The Files sentence was glued to the paragraph above it, two
paragraphs before the table whose column it describes. It now names
the column and sits just above the table.
The triage, --changed-only and --strict counted refs only. Prose that
--realign-prose would move or refuse blocks every recale and bump, and
blastem read 0 to review. Each report now carries that reading. The
triage also gets a header, a closing count, and one sampling notice per
run instead of one per profile.
The RetroPie target scraper read master and the EmuDeck one the default
branch, while both platform files are pinned on a commit: a module flag
changed in between filtered packages the pinned list does not describe.
Both now read the revision pinned_revision returns, which also accepts the
codeload URL the RetroPie scraper records. EmuDeck Windows stays on the
emudeck-we default branch: the platform file pins no revision of it.
generate_pack refreshed the data directories before probing its output
lock. The refresh waits for every run holding a tree, and a pipeline holds
them all: a second build sat silent for hours instead of being refused.
The output lock is now taken first, and a refresh that has to wait for a
tree names it.
kronos tags the ST-V BIOS epr-19854.ic8 taiwan, mednafen_stv tags it
asia-ntsc, the SMPC area Mednafen names for Taiwan (ss.cpp:1119,
2363-2364). As siblings under asia they never matched, so a request in
one spelling dropped the file tagged in the other, and --region
taiwan,us dropped the Saturn sega_101.bin. AREA_TERRITORIES relates
each area to the territory it serves, china for asia-pal included, in
region.py and its installer copy.
One unparsable commit in a profile's history stopped the walk and
marked every older prose citation unknown, which refused the recale and
bump for good (lindbergh-loader, df7346b3). The revision is now passed
over unless the pins differ on its two sides while the field's
citations changed across it.
_fetch_asset hashed the cache copy, then hashed the path again for the
release check. A copy another run promoted in between answered the second
question, and the caller was told the cache held nothing when it held
the file asked for. Both checks now read one snapshot, taken while the
inode stays the same.
A source_commit string from before the field was keyed by mode pinned
the standalone repository, yet was read as a writing pin for every mode.
xenia-edge then saw its libretro pin move under unchanged prose, and
the recale and bump were refused.
refresh_data_dirs printed one line per entry and no total, a dry run named
the cached tag only, and cutting a quoted ETag at 12 characters kept one
quote. The run now ends on refreshed / up to date / failed counts, a dry
run shows the remote tag beside the cached one, and tags lose their quotes
before they are cut.
The role a part writes (source or upstream) was kept per path, first
met first kept, so a later part naming the other role was read in the
other repository. In prose the word source made a citation external
and the word upstream was dropped. The role now travels on each part.
check_freshness --only native rebuilds the truth model to compare it, and
the hash conflict notes generate_truth reports opened the freshness output.
They are dropped there, and any other stderr line still passes through.
The coreinfo area compared .info names only: a firmwareN_path added to a
profiled core's .info came out OK. check_freshness now compares each
profiled .info's git blob with platforms/_core_info.json, recorded on
2026-10-21 from the current listing, and --record-coreinfo CORE records
one as reviewed.
diff_platform compared only version, files and core lists: a rescrape
that changed launchers, dual_mode_cores or a system's core, native_id or
data_directories came out identical. Every other field is now compared,
without the revision read (source) and the source_ref lines that move with it.
The buildbot system row was planned as refresh_data_dirs.py --key
'buildbot system', which always exits 1 on an unknown key. A data row now
runs only when its subject is a _data_dirs.yml entry; the rest is shown
for a human to add.
scraper_cli wrote a platform YAML with no system when the parse came back
empty, and exited 0. It now exits 1 before writing, unless the scraper
declares lists_files = False (RetroPie, whose files are inherited); RetroPie
in turn raises when its archive declares no package.
The MAME and FBNeo hash scrapers cached an empty parse for 24 hours and
reported OK. Both now raise before writing, and an empty cache is never
served as fresh.
--emulator accepts a buildbot core name from a profile's cores:, and a
mistyped target gets the closest names instead of every target. diff_truth
shows destinations and reports unpaired entries as evidence, not removals.
The verify summary counts discrepancies, a shared destination names both
systems, a truth hash conflict names both cores, and pipeline steps carry
their place in the real run instead of N/8. Every GitHub API caller takes
the token from github_token(), which falls back to gh auth token.
Troubleshooting, the FAQ and the download page sent people who only ran
the installer to verify.py, which reads the repository, not their BIOS
folder. Download links now name the exact asset of the recorded release,
the --dest example names the BIOS folder on the card, the slice layout
left with the last release that used it, and the shown verify output is
what the tool prints.
A fork's GITHUB_TOKEN is read-only, so the comment never arrived and a
valid file turned red. The report now goes to the job summary for every
pull request, and the comment and labels run only where the token can
write. validate_pr points an oversized file to an issue instead of a
platform setting a contributor cannot use.
RetroPie's YAML listed no standalone_cores, so every package was laid
out as a libretro core and the standalone files of amiberry, dolphin,
hatari, mame and ppsspp never reached the pack. Packages without the
lr- prefix (bar the retroarch frontend) are now standalone, and names
shipped both ways are dual-mode.
quasi88 writes its list "N88KNJ2.ROM, and N88SUB.ROM". Extended with
N88EXT0.ROM, which QUASI88kai reads, it came back as "N88SUB.ROM and
N88EXT0.ROM": every list was rewritten in one idiom. A list that puts a
comma before its final and keeps it.
EmuDeck passes Complex_4627v1.03.bin as xemu's flash ROM
(emuDeckXemu.sh:60-61), and xemu's entry Complex_4627.bin states no hash,
only the sizes it maps. The layer matched entries by name or content
only, so without the alias the default name was still asked for. A
hashless entry that checks a size now takes a launcher file of a size
it accepts, when no other entry of that core accepts that size too.
The removal candidates looked for a reading in the profiles only, so a
name the platform's own launch code opens, links or copies was listed
as read by no core: Batocera's DuckStation generator walks 22 names
(duckstationGenerator.py:501-515). Every name a launcher record names
is now a reading on that platform. Recalbox's unexplained entries drop
from 20 to 13, RetroBat's from 24 to 20, EmuDeck's from 25 to 22.
The launcher layer packed a file under the launcher's name whatever the
platform installs: ROCKNIX's xemu config reads hdd/xbox_hdd.qcow2, and
the profile marks that image installed_on rocknix, yet the pack brought
one. A place whose entry the emulator installs on that platform is now
filled by the install, as validation.deliverable has it for the pack.
A names_searched list is one place the launcher fills with the first name it finds, so a core file it would never open once another name of the list is held is not reported missing. A place no declared file and no packed core file fills receives, under the launcher's name, a file the core accepts by content. The layer is applied inside find_undeclared_files, which verify and the pack builder both read, and a test reads their source to keep it there.
The scraper reads each component's prepare, launcher and functions scripts at the components commit the BIOS list comes from, and the presets they copy: the fixed paths set_setting_value writes (melonDS seven, xemu three, Dolphin GBA), the folders handed over, the dir_prep links (LRPS2 pcsx2/bios back to bios/, Dolphin and PrimeHack GC regions under saves/), package copies into bios/, RetroArch core options naming a file (pcsx2_bios, DuckStation per region), XRoar romlists, the PICO-8 exec and the RPCS3 and Vita3K firmware downloads.
platforms/retrodeck.yml is regenerated at fcd0d729: only the launchers section is new.
The scraper walks each package's functions the way RetroPie runs them in one shell (assignments, arrays, for loops, brace expansion, $md_id, calls into other packages) and records every link, copy, move and setting that names $biosdir, the addEmulator flags (sdltrs -romfile3), the BIOS read outside $biosdir (basilisk --rom, gngeopi -B) and the launch scripts that exit without their file (fs-uae kick13.rom, reicast dc_boot.bin).
Each package also gets a package record with its rp_module_repo and the systems it registers, which maps a package to the build it compiles. The schema gains package, repo, systems and the moves and package kinds. platforms/retropie.yml is regenerated at 46128cd2: only the launchers section is new.
The scraper now records, from configgen at the pinned tag, the names each Recalbox launcher tries and where it hands them: the Hatari TOS cascades (standalone, hatari and hatarib cores), GSplus, Amiberry kickstarts, the PCSX2 region choice, the Dolphin IPL presence test, xemu, the mupen64plus 64DD IPL, the Mini vMac boot disk and the o2em_bios option. The system_directory libretroRetroarch gives each core, and the per-system overrides after it, are passes_path records.
Recalbox starts its emulators from a Python configgen too. The parsed source, the record and the ast helpers move to configgen_launchers so both readers use them. Batocera's extractors are unchanged.
The scraper reads the EmuScripts at the commit checkBIOS.sh is pinned to: the BIOS paths changeLine, iniFieldUpdate and RetroArch_setConfigOverride write, the ares and melonDS configs whose paths sed rewrites, and the bios/ directories linked to Flycast, Azahar, the Switch emulators and shadPS4. The script to emulator mapping moves beside the extractors so both read one table.
The scraper reads the start scripts and the configs each package installs at the tag the BIOS list is pinned to: configs that name a BIOS path, the yabasanshiro search, directories handed to DuckStation and the PCSX2 builds, and the ln -s commands that put bios/ where an emulator reads. A package that ships one config per device gets a device field only where the values differ, as melonDS does on AMD64.
The scraper reads emulatorLauncher's generators and es_features.cfg at the commit the BIOS list is pinned to: Ares, Hatari, Mesen, Amiberry, WinUAE, PCSX2, the PSX BIOS cascades, Xemu, openMSX and DuckStation. Each extractor raises when its construct is gone.
The scraper parses the configgen generators with ast at the pinned tag: DuckStation and its legacy build, Hatari, xemu, melonDS, mupen64plus 64DD, x16emu, GSplus, and the o2em and neocd core options. An extractor that no longer recognises its code raises instead of writing an empty list.
"No core opens it" claimed more than the search shows: what was
checked is that no profile of a core the platform runs names the
entry by name, path or hash. The reason and the page sentence now
say that.
The page named the entries a change touched and never the values, so
a maintainer could not check a correction. The report now carries
changes, one object per change with kind, entry, field, old and new,
read from the platform entry and from the value the render writes
(written() per format: record value for System.dat and RomM, one md5
where a single one is checked). The existing label lists are derived
from it. The page writes "md5 X becomes Y" and names each required
flag with its new value. A test parses the original and updated
System.dat and es_bios.xml and checks the list equals the diff.
Where the truth names no value the platform lacks, hashes() still put
the truth's values first and rewrote the list in another order:
Recalbox's msx/CARTS.SHA md5 attribute came out with its two values
swapped, a diff line that changes nothing the frontend checks. The
platform's own list now stands when it already holds every value.
The export keeps every entry a platform declares. An entry the truth
does not pair was still read in 1189 of 2776 cases (alias, hash, a
type: directory tree, a core outside the resolved set), so absence of
a pairing proves nothing. Each report now lists, under
removal_candidates, the unpaired entries that no profile of the cores
the platform runs answers by name, path tail, hash, directory or data
directory, with the cores examined and a reason. A core the platform
names without a profile, or a ROM set under a core that opens sets by
name, leaves the verdict undetermined. The exports page states the
count and lists the entries per platform.
The rom line renderer upper-cased every crc, so Dinothawr.zip's
683ed4ad, the one lower-case crc libretro writes, changed with no
correction behind it. A line whose values match ours now keeps its
bytes, a corrected crc follows the case of its own line, and an added
line keeps the file's upper case. An empty truth hands System.dat back
byte for byte for RetroArch and Lakka.
A changed element was rendered again, losing its alignment and the
place of its attributes, and an element whose md5 list was only
reordered (neogeo.zip, the same 24 digests) was rewritten too: Recalbox
accepts any digest of the list (Bios.cpp:109-130), so the order is not
content. A changed attribute is now replaced where it stands, one
dropped goes with its space, one added goes before />, and each system's
opening tag keeps its own indentation. An empty truth gives back the
cached es_bios.xml byte for byte.
An entry with one md5 to correct was written again whole: atarist lost
its eight blank lines, every '"x"},' became '"x" },', and most of the
819 lines Batocera's export changed were layout. Each item now keeps
its own text and only the md5 inside it changes; an addition follows
the last item in its indentation and spacing. The patched entry must
read as the one that would have been written, or that one is written.
Batocera's diff to the original drops to 273 lines, ROCKNIX's to 3.
RetroBat separates every system after the first with a blank line.
json.dumps dropped all 100 of them: 100 hunks of noise in a diff meant
to show corrections. The separator is written again where the original
has it, and an added system takes it too where the original separates
them all. An empty truth gives back the cached file byte for byte.