Commit Graph
429 Commits
Author SHA1 Message Date
Abdessamad Derraz b86a34933c feat: parse gamel machines and computed sizes 2026-08-12 03:15:05 +02:00
Abdessamad Derraz 6719aa415d feat: anchor prose citations beside source refs 2026-08-12 03:11:47 +02:00
Abdessamad Derraz b120528dd7 test: cover the native format exporters
export_native turns the profile data back into each platform's own
file, for a maintainer there to pick up and use, and none of the ten
exporters had a test. Coverage goes from 0 to 83%.

Writing them found the EmuDeck exporter rejecting its own output: its
export skips placeholder entries, its validate looked for them anyway
and reported the omission as a missing hash. Both sides apply the same
filter now.

The placeholder assertion initially passed for the wrong reason twice
over -- the fixture entry had no hash, so it was dropped as incomplete
rather than as a placeholder, and the assertion then read the name
while the exporters write the path. It is pinned on an unmistakable
destination now, and removing the guard fails two tests.
2026-08-12 00:46:04 +02:00
Abdessamad Derraz b5f28d7b7f chore: drop three reads whose result went nowhere
The requirement parser read core, hashMatchMandatory and note from
each bios element and discarded all three, which reads like a dropped
attribute: hashMatchMandatory decides whether a wrong hash is yellow or
red on Recalbox. It is not lost -- the config parser below captures it
for the YAML -- so these were leftovers, and they cost a look to
confirm that.

Two yaml imports left unused by the move to the shared loader go too.
2026-08-11 20:27:53 +02:00
Abdessamad Derraz 0a941acc43 refactor: split the site validator into its two passes
validate_site reached complexity 43 doing three things at once. The
per-page checks and the link resolution are now separate functions and
the caller keeps only the cross-page duplicate check, which cannot run
until every page has been seen.

The first attempt left the link pass calling a closure that had moved,
and running it against the real site did not catch that: no page there
has a broken link, so the error path never ran. The unit test covering
a deliberately broken fragment did.
2026-08-11 20:16:11 +02:00
Abdessamad Derraz 00f10b0379 refactor: extract rename matching from the truth diff
_diff_system reached complexity 42, and its hash-based rename fallback
is the part that stands alone: a platform is free to call a file
whatever it likes, so a name matching nothing is not yet a gap, and
counting one file as both missing and extra invents a discrepancy.

That step is now its own function at complexity 31, with the tests it
never had: pairing on any of the three digests, case folding, non-string
values, and the case where two files simply have no hashes and so are
not evidence of anything. diff_truth output is unchanged.
2026-08-11 19:54:45 +02:00
Abdessamad Derraz 4b8d7baac5 feat: transcribe bios search orders into priority 2026-08-11 19:18:30 +02:00
Abdessamad Derraz 4ecb65b8d9 fix: keep the mame clone map across runs
A clone group is only visible while both copies are on disk, and the
run then deletes the clone, so writing only what this run saw erased
every mapping an earlier run had recorded. One real run took the map
from 69 entries to 1, and the canonical zips silently stopped answering
to the names they stand in for.

The map is merged now, the 68 lost entries are restored, and the one
whose canonical file is no longer in the collection is dropped.
2026-08-11 18:41:04 +02:00
Abdessamad Derraz 8c018849ae fix: return instead of continue in the split profile
The cross-reference loop body became its own function, but one exit
stayed a continue and left the module unparseable, which took the whole
test suite down with it. The other ten continues are inside genuine
inner loops and stand.

Output verified identical to the version before the split.
2026-08-11 18:41:04 +02:00
Abdessamad Derraz 837ea786b0 refactor: separate the dedup decision from the deletion
deduplicate reached complexity 50 by mixing the question of which
copies may go with the work of removing them. The decision is now its
own function: same name in two directories is a true duplicate,
different names only collapse as MAME device clones, and anywhere else
each name may be the one some emulator looks for.

Verified against the real collection: both versions plan the same 99
removals.
2026-08-11 18:12:35 +02:00
Abdessamad Derraz b5e6b68263 refactor: split the per-system platform sections
generate_platform_page reached complexity 49, most of it in the block
rendering one collapsible section per system: each file shows a
different set of hashes, sizes and provenance depending on what the
platform declares. That block is now its own function and the caller
sits at 28. All 534 pages identical apart from timestamps.
2026-08-11 18:07:57 +02:00
Abdessamad Derraz ae7256f56f refactor: split the cross-reference into its two views
generate_cross_reference rendered the same relation twice in one
function, once per platform and once per upstream, and reached
complexity 53. Each view is now its own function and the caller is a
pair of calls.

Verified by generating the site with both versions against the same
data: all 533 pages identical apart from timestamps, with a control run
of the same code twice to confirm the comparison had no drift in it.
2026-08-11 17:58:11 +02:00
Abdessamad Derraz 36a284b93b fix: name and announce every narrowed pack 2026-08-11 17:54:28 +02:00
Abdessamad Derraz 4bd277a7be feat: name every narrowing in the pack filename 2026-08-11 16:56:20 +02:00
Abdessamad Derraz 5b0e6836de refactor: split the problem-files gap section
generate_gap_analysis reached complexity 56. Its problem-files section
is the one that stands alone -- it reads platform_problems and nothing
else -- so it becomes its own function and the caller drops to 43.

The neighbouring sections were tried too and put back: they share
gap_report and the resolved core list, so pulling them out turns a
render pass into an argument-threading exercise for no gain. Verified
by generating the site with both versions against the same data: 529 of
530 pages identical, the odd one a wiki page another run had edited.
2026-08-11 16:52:18 +02:00
Abdessamad Derraz 694f2a74d0 fix: index emulator systems by display name too 2026-08-11 15:56:45 +02:00
Abdessamad Derraz 4b1ab7cb74 fix: separate findable gaps from unsourceable ones
The README said 14 files were 'not in the collection yet', which
implies somebody could still put them there. Nine of them cannot be:
WHDLoad.key is a per-user signed registration, Custom.dat and key.txt
are slots the emulator expects the user to fill, gpib.rom and
dragonfly-2.3.rom have never been dumped, CARTS.CRC belongs to a dead
code path. The profiles already record why, so the two are now counted
apart: five still to be found, nine that cannot be.
2026-08-11 15:56:26 +02:00
Abdessamad Derraz e97c1fbbcc fix: keep unsourceable files from matching homonyms 2026-08-11 14:40:33 +02:00
Abdessamad Derraz 94512b5acf fix: drop manifest entries with no download source
install.py fetches a file from its repo_path or from a release asset.
Resolution can land on a file the database does not index, and the
entry then shipped with neither: a line in the download list that can
only ever fail. Those are recorded as omitted instead, which is what
the installer already knows how to report, and a test holds the
committed manifests to it.

validate_schemas read dist/ while a build was writing it and reported
a half-written pack as 'File is not a zip file'. It takes the shared
lock --verify-packs uses, and says so when a build holds it.
2026-08-11 14:40:33 +02:00
Abdessamad Derraz 85f3f7c393 fix: read the bios preference order the code applies 2026-08-11 14:34:49 +02:00
Abdessamad Derraz a372f2abf3 feat: keep one bios per slot on declared order 2026-08-11 13:34:15 +02:00
Abdessamad Derraz ecbe69760d refactor: check packs through a single path 2026-08-11 11:31:21 +02:00
Abdessamad Derraz d8a0d975d7 feat: judge a ref against every declared repository 2026-08-11 07:25:31 +02:00
Abdessamad Derraz 3bc9e5ec96 feat: attribute a shared path by its declared subject 2026-08-11 05:42:27 +02:00
Abdessamad Derraz d8e4325af2 fix: skip conformance on required-only packs
A required-only build is narrower than the platform declares by
design, like the source-restricted variants already handled, so the
full expectation must not be applied to it. --region is also refused
alongside --manifest-targets, which carries no region dimension.
2026-08-11 05:34:21 +02:00
Abdessamad Derraz 2879f489c4 feat: give every page a link preview
Links to this site get shared on Discord, Reddit and forums, where a
page with no Open Graph tags renders as a grey rectangle. The pages
already carry a per-page title and description, so a theme override
fills the tags from those; mkdocs-material would otherwise only emit
them through its social plugin, which pulls in Pillow and CairoSVG to
render a preview image these pages do not need.
2026-08-11 05:33:42 +02:00
Abdessamad Derraz cc24ff1bea fix: ignore the rendered timestamp when comparing
Decorated site pages carry the generation stamp twice: once as the
markdown footer and once as a rendered element. write_if_changed knew
only the first, so every page was rewritten on every run for the clock
alone.

The comparison is what makes the deploy-site freshness guard a real
staleness check rather than a guaranteed failure, and it had no tests.
2026-08-11 05:23:52 +02:00
Abdessamad Derraz c562459567 refactor: split the emulator page renderer
generate_emulator_page carried a 228-line loop body rendering one file
row from thirty-odd optional fields, which put its complexity at 141,
more than twice the next function in the repository. The row renderer
and its badge strip are now their own functions and the page function
sits at 68.

_forge_sources went from 18 to 32 when it learned to pair each
repository with its own revision; the pairing is now its own function
and the caller is back under the threshold.

Verified by regenerating the site and diffing: all 505 pages are
identical apart from their generation timestamps.
2026-08-11 05:22:25 +02:00
Abdessamad Derraz 0d59979f2a fix: pin the digest order in cached entries
The cached hashes were rebuilt by iterating a set, so their order in
each database entry followed set hashing rather than a declared one. A
run with a warm cache rewrote all 7,850 entries with no content change.
The order is now the one compute_hashes returns, and a test holds a
warm-cache run byte-identical to a --force rehash.
2026-08-11 01:46:46 +02:00
Abdessamad Derraz 0b5c534af4 fix: settle a contributed path before reading it
validate_pr.py inspects paths chosen by whoever opened the pull
request, and it hashed the file before deciding whether it was a
symlink. A link to /dev/zero was read until the job timed out, and a
link out of the checkout was hashed and reported as though its target
had been contributed. The shape is now settled first, and a test that
used to hang the run covers it.

The gate had no tests at all, and neither did the 3DS crypto reached
by dynamic import from validation.py: RSA PKCS#1 v1.5, AES-128-CBC and
ECDSA over GF(2^233), all written by hand. Coverage goes from 0 to 95%
on the curve, 0 to 55% on the gate, 9 to 35% on the rest. The curve
tests check against the published SEC 2 parameters rather than against
the module: the generator satisfies the curve equation and the group
order takes it to infinity.
2026-08-11 01:39:29 +02:00
Abdessamad Derraz 87e19191b6 fix: pair each source repository with its own pin
A profile whose builds live in separate repositories keys source,
upstream and source_commit by build mode. The site flattened the URLs
but read a single scalar revision, so a libretro fork could be pinned
to the standalone commit, and binding the object form into SQLite
failed outright once ymir adopted it. URL and revision are now read as
pairs.

The site also published a sitemap nothing pointed at, so robots.txt is
generated alongside it.
2026-08-11 00:56:00 +02:00
Abdessamad Derraz 24e9b820ae feat: make packs reproducible byte for byte
Two builds of the same pack from the same inputs produced different
archives. Of 67 members, 65 were already identical: only README.txt
and manifest.json differed, both stamped with the wall clock by
writestr and the second carrying a generated timestamp. Generated
members now use the epoch the archive rebuilder already applies, and
the timestamp comes from the database snapshot the pack was built
from, so the same data yields the same bytes.

Install manifests skipped archived platforms, which is why RetroPie
had none; archived means upstream is no longer scraped, not that the
packs stopped shipping. A target-filtered manifest also had no record
of its filter beyond the filename, so it carries one the way a
region-filtered manifest already does.
2026-08-11 00:55:42 +02:00
Abdessamad Derraz dc14089932 fix: keep the database free of stale entries
Two entries claimed bios/Sony/PlayStation 3/PS3UPDAT.PUP with different
SHA1s. Preserving large-file entries matched on path and keyed on
SHA1, so replacing a firmware revision on disk left the old entry
pointing at a path that now serves other bytes. A preserved entry whose
path the scan has already claimed is dropped, and validate_schemas
refuses a database where one path carries two entries.

Separately, a run without --force rebuilt each cached entry from a
hand-written list of four digests and wrote it back without adler32,
so one such run stripped the digest from every file permanently. A
cache entry missing any digest is now a miss.
2026-08-11 00:55:32 +02:00
Abdessamad Derraz 3b8f2d75d5 perf: read yaml through the c loader
Loading the emulator profiles is the most expensive step of every
command here, and all forty call sites used the pure-Python scanner
while libyaml sat unused in the same wheel. One shared yaml_load picks
the C loader when pyyaml ships it: the 375 profiles parse in 0.18s
instead of 1.39s, and verify --platform retroarch drops from 2.17s to
0.73s. The loader class is the same restricted one safe_load uses.

es_bios.xml was parsed straight from the network while install.py
already refused a document declaring entities; both now share one
guard. Scrapers reach it through a single path bootstrap in the
package rather than two ad-hoc ones.
2026-08-11 00:55:16 +02:00
Abdessamad Derraz ab6a3bb26d refactor: single-source the native mode policy
verify.py and generate_pack.py must reach the same verdict on the same
file, and CLAUDE.md calls any divergence critical, but each spelled out
mode == 'existence' in its own words. Both now ask nativemode whether
the frontend reads the file's bytes, which is the one fact the rest
follows from, and a test holds the two answers together.

The BaseScraper contract the wiki asks contributors to implement had no
caller anywhere, so nothing proved compare_with_config, has_changes or
test_connection still worked.
2026-08-11 00:54:46 +02:00
Abdessamad Derraz 958b988015 docs: scope the licence and temper the legal claims
LICENSE covered the whole repository, so the MIT grant read as
covering 9.9 GB of third-party firmware. It now states its scope and
NOTICE describes the files it does not cover, with a removal channel.

The FAQ cited Connectix and Accolade for redistribution when both
decided intermediate copying during reverse engineering, presented fair
use as settled, read section 1201(f) as a general permission, and
listed abandonware among legal doctrines. Each claim is now stated at
the strength it actually has, and the weakest ground is named.
2026-08-11 00:54:10 +02:00
Abdessamad Derraz 902bb4e01e refactor: require a hash before relocating a ref 2026-08-11 00:37:30 +02:00
Abdessamad Derraz 5330ed1950 fix: keep the cache usable when a path is both 2026-08-10 19:10:26 +02:00
Abdessamad Derraz 49f50f51b5 feat: read per-mode sources and pins 2026-08-10 18:18:09 +02:00
Abdessamad Derraz 2b7c5f9a64 feat: follow the branch a port was read from 2026-08-10 17:55:35 +02:00
Abdessamad Derraz dd7faf2b5f feat: attribute a shared path by its cited subject 2026-08-10 17:40:48 +02:00
Abdessamad Derraz 44ba5e45c3 feat: pin the mame upstream revision 2026-08-10 17:37:06 +02:00
Abdessamad Derraz 7ac19f4c9f feat: reach forward by the declared member count 2026-08-10 17:31:59 +02:00
Abdessamad Derraz 4f7aa5f853 feat: read content values from archive members 2026-08-10 17:30:29 +02:00
Abdessamad Derraz 885c4c3c06 refactor: judge a self-checked entry whole 2026-08-10 17:22:00 +02:00
Abdessamad Derraz f430986361 refactor: treat a located value as a recalable move 2026-08-10 17:20:26 +02:00
Abdessamad Derraz 43cf84d5ec refactor: require evidence before calling a ref stale 2026-08-10 17:17:19 +02:00
Abdessamad Derraz f67642095c feat: self-check a profile already on head 2026-08-10 16:55:48 +02:00
Abdessamad Derraz 77a39b2f4e perf: reuse diffs and raise the file size cap 2026-08-10 16:48:59 +02:00
Abdessamad Derraz 77dff6377e fix: keep fbneo per-rom refs and archive names 2026-08-10 16:47:20 +02:00