diff --git a/install/targets/retrobat.json b/install/targets/retrobat.json index 30464cc5..23348313 100644 --- a/install/targets/retrobat.json +++ b/install/targets/retrobat.json @@ -1,156 +1,3 @@ { - "windows": [ - "81", - "a5200", - "abuse", - "arduous", - "atari800", - "azahar", - "bennugd", - "bk", - "bluemsx", - "bsnes", - "bstone", - "cannonball", - "cap32", - "catacombgl", - "cdogs", - "cemu", - "cgenius", - "citron", - "clk", - "corsixth", - "demul", - "devilutionx", - "dhewm3", - "dice", - "dolphin", - "dosbox_pure", - "dxx-rebirth", - "easyrpg", - "ecwolf", - "eduke32", - "eka2l1", - "emuscv", - "etlegacy", - "fake08", - "fallout1-ce", - "fallout2-ce", - "fbneo", - "fceumm", - "flycast", - "freechaf", - "freeintv", - "fury", - "fuse", - "gambatte", - "gearsystem", - "genesisplusgx", - "gong", - "gsplus", - "gw", - "gzdoom", - "hatari", - "hcl", - "hurrican", - "hypseus-singe", - "ikemen", - "ioquake3", - "iortcw", - "jazz2-native", - "lindbergh-loader", - "lowresnx", - "lutro", - "mame", - "mame078plus", - "mednafen_lynx", - "mednafen_ngp", - "mednafen_supergrafx", - "mednafen_wswan", - "melonds", - "mgba", - "minivmac", - "model2emu", - "moonlight", - "mrboom", - "neocd", - "np2kai", - "nxengine", - "o2em", - "openbor6412", - "openjazz", - "openjk", - "openjkdf2", - "openmohaa", - "opera", - "pce_fast", - "pcfx", - "pcsx2", - "pcsx_rearmed", - "pd777", - "picodrive", - "play", - "pokemini", - "potator", - "ppsspp", - "prboom", - "prosystem", - "puae", - "px68k", - "pygame", - "pyxel", - "quasi88", - "raze", - "reminiscence", - "rpcs3", - "ruffle", - "samcoupe", - "sameduck", - "scummvm", - "sdlpop", - "shadps4", - "snes9x", - "solarus", - "sonic-mania", - "sonic2013", - "sonic3-air", - "stella", - "superbroswar", - "supermodel", - "taradino", - "tgbdual", - "theforceengine", - "theodore", - "thextech", - "tic80", - "tr1x", - "tr2x", - "tsugaru", - "tyrian", - "tyrquake", - "uqm", - "uzem", - "vb", - "vecx", - "vice_x64", - "vircon32", - "virtualjaguar", - "vita3k", - "vox_official", - "vpinball", - "wasm4", - "x1", - "x128", - "x16emu", - "xash3d_fwgs", - "xemu", - "xenia-canary", - "xpet", - "xplus4", - "xrick", - "xvic", - "yabasanshiro", - "yquake2", - "zc210" - ] + "windows": null } diff --git a/install/targets/retrodeck.json b/install/targets/retrodeck.json index 8921313d..61dbb1ae 100644 --- a/install/targets/retrodeck.json +++ b/install/targets/retrodeck.json @@ -1,22 +1,3 @@ { - "x86_64-linux": [ - "azahar", - "cemu", - "dolphin", - "duckstation", - "gzdoom", - "mame", - "melonds", - "openbor", - "pcsx2", - "pico-8", - "ppsspp", - "primehack", - "rpcs3", - "ruffle", - "solarus", - "vita3k", - "xemu", - "xroar" - ] + "x86_64-linux": null } diff --git a/install/targets/romm.json b/install/targets/romm.json index 8ed0f324..0a84d631 100644 --- a/install/targets/romm.json +++ b/install/targets/romm.json @@ -1,51 +1,3 @@ { - "browser": [ - "a5200", - "beetle_ngp", - "beetle_pcfx", - "beetle_psx", - "beetle_vb", - "cap32", - "crocods", - "desmume", - "desmume2015", - "dosbox_pure", - "fbalpha2012_cps1", - "fbalpha2012_cps2", - "fbneo", - "fceumm", - "freeintv", - "fuse", - "gambatte", - "gearcoleco", - "genesis_plus_gx", - "handy", - "mame2003", - "mame2003_plus", - "mednafen_pce", - "melonds", - "mgba", - "mupen64plus_next", - "nestopia", - "opera", - "parallel_n64", - "pcsx_rearmed", - "picodrive", - "ppsspp", - "prboom", - "prosystem", - "puae", - "same_cdi", - "smsplus", - "snes9x", - "stella2014", - "vice_x128", - "vice_x64", - "vice_x64sc", - "vice_xpet", - "vice_xplus4", - "vice_xvic", - "virtualjaguar", - "yabause" - ] + "browser": null } diff --git a/platforms/targets/retrobat.yml b/platforms/targets/retrobat.yml index 0b1de6b2..14503d72 100644 --- a/platforms/targets/retrobat.yml +++ b/platforms/targets/retrobat.yml @@ -1,25 +1,9 @@ platform: retrobat source: static -scraped_at: "2026-03-26T00:00:00Z" targets: + # RetroBat runs on Windows alone: every core the platform declares runs on this target, and + # naming it filters nothing. A hand-kept list here fell behind the scraped + # platform and dropped cores the pack ships. windows: architecture: x86_64 - cores: ['81', a5200, abuse, arduous, atari800, azahar, bennugd, bk, bluemsx, bsnes, - bstone, cannonball, cap32, catacombgl, cdogs, cemu, cgenius, citron, clk, corsixth, - demul, devilutionx, dhewm3, dice, dolphin, dosbox_pure, dxx-rebirth, easyrpg, - ecwolf, eduke32, eka2l1, emuscv, etlegacy, fake08, fallout1-ce, fallout2-ce, - fbneo, fceumm, flatpak, flycast, freechaf, freeintv, fury, fuse, gambatte, - gearsystem, genesisplusgx, glide64mk2, gong, gsplus, gw, gzdoom, hatari, hcl, - hurrican, hypseus-singe, ikemen, ioquake3, iortcw, jazz2-native, lindbergh-loader, - lowresnx, lutro, mame, mame078plus, mednafen_lynx, mednafen_ngp, mednafen_supergrafx, - mednafen_wswan, melonds, mgba, minivmac, model2emu, moonlight, mrboom, neocd, - np2kai, nxengine, o2em, odcommander, openbor6412, openjazz, openjk, openjkdf2, - openmohaa, opera, pce_fast, pcfx, pcsx2, pcsx_rearmed, pd777, picodrive, play, - pokemini, potator, ppsspp, prboom, prosystem, puae, px68k, pygame, pyxel, quasi88, - raze, reminiscence, rpcs3, ruffle, samcoupe, sameduck, scummvm, sdlpop, sh, - shadps4, snes9x, solarus, sonic2013, sonic3-air, sonic-mania, steam, stella, - superbroswar, supermodel, taradino, tgbdual, theforceengine, theodore, thextech, - tic80, tr1x, tr2x, tsugaru, tyrian, tyrquake, uqm, uzem, vb, vecx, vice_x64, - vircon32, virtualjaguar, vita3k, vox_official, vpinball, wasm4, wine-tkg, x1, - x128, x16emu, xash3d_fwgs, xemu, xenia-canary, xpet, xplus4, xrick, xvic, - yabasanshiro, yquake2, zc210] + cores: null diff --git a/platforms/targets/retrodeck.yml b/platforms/targets/retrodeck.yml index 7e8741bc..61fdc3be 100644 --- a/platforms/targets/retrodeck.yml +++ b/platforms/targets/retrodeck.yml @@ -1,9 +1,9 @@ platform: retrodeck source: static -scraped_at: "2026-03-26T00:00:00Z" targets: + # RetroDECK runs on x86_64 Linux alone: every core the platform declares runs on this target, and + # naming it filters nothing. A hand-kept list here fell behind the scraped + # platform and dropped cores the pack ships. x86_64-linux: architecture: x86_64 - cores: [azahar, cemu, dolphin, duckstation, gzdoom, mame, melonds, openbor, pcsx2, - pico-8, ppsspp, primehack, retroarch, rpcs3, ruffle, solarus, vita3k, xemu, - xroar] + cores: null diff --git a/platforms/targets/romm.yml b/platforms/targets/romm.yml index 24c2dd4d..83986e0b 100644 --- a/platforms/targets/romm.yml +++ b/platforms/targets/romm.yml @@ -1,7 +1,9 @@ platform: romm source: static -scraped_at: "2026-03-26T00:00:00Z" targets: + # RomM plays in the browser alone, through EmulatorJS: every core the platform declares runs on this target, and + # naming it filters nothing. A hand-kept list here fell behind the scraped + # platform and dropped cores the pack ships. browser: architecture: wasm - cores: [a5200, beetle_ngp, beetle_pcfx, beetle_psx, beetle_vb, cap32, crocods, desmume, desmume2015, dosbox_pure, fbalpha2012_cps1, fbalpha2012_cps2, fbneo, fceumm, freeintv, fuse, gambatte, gearcoleco, genesis_plus_gx, handy, mame2003, mame2003_plus, mednafen_pce, melonds, mgba, mupen64plus_next, nestopia, opera, parallel_n64, pcsx_rearmed, picodrive, ppsspp, prboom, prosystem, puae, same_cdi, smsplus, snes9x, stella2014, vice_x128, vice_x64, vice_x64sc, vice_xpet, vice_xplus4, vice_xvic, virtualjaguar, yabause] + cores: null diff --git a/scripts/check_freshness.py b/scripts/check_freshness.py index 32cda9a4..4cd9b415 100644 --- a/scripts/check_freshness.py +++ b/scripts/check_freshness.py @@ -436,6 +436,14 @@ def check_targets( with path.open(encoding="utf-8") as fh: old = yaml_load(fh) or {} module = scrapers.get(platform) + targets = old.get("targets") or {} + if module is None and targets and all( + (t or {}).get("cores", []) is None for t in targets.values() + ): + # Every core the platform declares runs on these targets: the list + # is the platform's own and has nothing of its own to age. + return [Finding("targets", platform, OK, local="static", + detail="every declared core")] if module is None: # No scraper: a hand-written file has no upstream to diff against, # so only its age and its core names can be reported. diff --git a/scripts/common.py b/scripts/common.py index 5e832784..8069ac91 100644 --- a/scripts/common.py +++ b/scripts/common.py @@ -437,10 +437,14 @@ def load_target_config( platform_name: str, target: str, platforms_dir: str = "platforms", -) -> set[str]: +) -> set[str] | None: """Load target config and return the set of core names for the given target. Resolves aliases from _overrides.yml, applies add_cores/remove_cores. + A target whose cores are null runs every core the platform declares and + returns None, which filters nothing: the platforms with a single target + (RetroBat on Windows, RetroDECK, RomM in the browser) kept a hand list + that dropped 98 RetroBat cores and 300 RetroDECK ones. Raises ValueError if target is unknown (with list of available targets). Raises FileNotFoundError if no target file exists for the platform. """ @@ -470,7 +474,10 @@ def load_target_config( msg += f"\nAliases: {', '.join(sorted(aliases))}" raise ValueError(msg) - cores = set(str(c) for c in targets[canonical].get("cores", [])) + declared = (targets[canonical] or {}).get("cores") + if declared is None: + return None + cores = set(str(c) for c in declared) return apply_target_overrides(cores, overrides, canonical) @@ -515,18 +522,30 @@ def list_available_targets( result = [] for tname, tdata in sorted(data.get("targets", {}).items()): aliases = (overrides.get(tname) or {}).get("aliases", []) - cores = {str(c) for c in tdata.get("cores", [])} + declared = (tdata or {}).get("cores") + # None: the target runs every core the platform declares. + count = ( + None if declared is None + else len(apply_target_overrides({str(c) for c in declared}, overrides, tname)) + ) result.append( { "name": tname, - "architecture": tdata.get("architecture", ""), - "core_count": len(apply_target_overrides(cores, overrides, tname)), + "architecture": (tdata or {}).get("architecture", ""), + "core_count": count, "aliases": aliases, } ) return result +def target_listing_line(row: dict) -> str: + """One --list-targets row, as verify and generate_pack print it.""" + aliases = f" (aliases: {', '.join(row['aliases'])})" if row["aliases"] else "" + count = "all" if row["core_count"] is None else str(row["core_count"]) + return f" {row['name']:30s} {row['architecture']:10s} {count:>4s} cores{aliases}" + + HASH_EXACT_RESOLUTION_STATUSES = frozenset( { "sha1_exact", diff --git a/scripts/generate_pack.py b/scripts/generate_pack.py index a28a39c7..693026e0 100644 --- a/scripts/generate_pack.py +++ b/scripts/generate_pack.py @@ -30,6 +30,7 @@ from pathlib import Path sys.path.insert(0, os.path.dirname(__file__)) from common import resolve_platform_cores as _platform_cores from common import ( + target_listing_line, suggest_names, require_platform, apply_target_overrides, @@ -2897,10 +2898,7 @@ def main(): print(f"No targets configured for platform '{args.platform}'") return for t in targets: - aliases = f" (aliases: {', '.join(t['aliases'])})" if t["aliases"] else "" - print( - f" {t['name']:30s} {t['architecture']:10s} {t['core_count']:>4d} cores{aliases}" - ) + print(target_listing_line(t)) return _validate_args(args, parser) diff --git a/scripts/verify.py b/scripts/verify.py index f03f502f..3172f15d 100644 --- a/scripts/verify.py +++ b/scripts/verify.py @@ -34,6 +34,7 @@ import slots sys.path.insert(0, os.path.dirname(__file__)) from common import ( + target_listing_line, suggest_names, require_platform, archive_entry, @@ -2029,10 +2030,7 @@ def _run_listing(args: argparse.Namespace, parser: argparse.ArgumentParser) -> N print(f"No targets configured for platform '{args.platform}'") return for t in targets: - aliases = f" (aliases: {', '.join(t['aliases'])})" if t["aliases"] else "" - print( - f" {t['name']:30s} {t['architecture']:10s} {t['core_count']:>4d} cores{aliases}" - ) + print(target_listing_line(t)) def main(): diff --git a/tests/test_check_freshness.py b/tests/test_check_freshness.py index 2fc1eb98..beff1a61 100644 --- a/tests/test_check_freshness.py +++ b/tests/test_check_freshness.py @@ -303,6 +303,21 @@ class RepositoryWiringTests(unittest.TestCase): self.assertIn("[ci]", text) self.assertIn("[data]", text) + def test_a_target_of_every_declared_core_does_not_age(self): + """RetroBat, RetroDECK and RomM name one target that runs every core + they declare; with nothing hand-kept, there is no date to watch.""" + import tempfile # noqa: PLC0415 + from pathlib import Path as _Path # noqa: PLC0415 + + with tempfile.TemporaryDirectory(dir=REPO_ROOT / "tmp") as tmp: + targets = _Path(tmp) / "targets" + targets.mkdir() + (targets / "solo.yml").write_text( + "platform: solo\nsource: static\ntargets:\n only:\n cores: null\n") + findings = cf.check_targets(_Path(tmp), _Path(tmp), {}, 1, True) + solo = [f for f in findings if f.subject == "solo"] + self.assertEqual([(f.status, f.detail) for f in solo], [(cf.OK, "every declared core")]) + def test_render_keeps_long_values_whole(self): """The profile_sync row lost the end of its sentence at 32 characters.""" local = "12 profiles behind upstream, 103 refs to re-read" diff --git a/tests/test_target_names.py b/tests/test_target_names.py index 873ef562..7706cb8b 100644 --- a/tests/test_target_names.py +++ b/tests/test_target_names.py @@ -90,12 +90,47 @@ class TheListingCountsWhatTheFilterUses(unittest.TestCase): platform = target_file.stem for row in list_available_targets(platform, platforms): with self.subTest(platform=platform, target=row["name"]): - self.assertEqual( - row["core_count"], - len(load_target_config(platform, row["name"], platforms)), - ) + resolved = load_target_config(platform, row["name"], platforms) + self.assertEqual(row["core_count"], + None if resolved is None else len(resolved)) checked += 1 self.assertGreater(checked, 0) + +class ASingleTargetFiltersNothing(unittest.TestCase): + """CLAUDE.md's rule, held on the real data: RetroBat, RetroDECK and RomM + have one target each, and hand-kept lists dropped 98, 300 and 132 cores + from a pack the user had only asked to name.""" + + def test_the_only_target_resolves_every_declared_core(self): + import glob # noqa: PLC0415 + + import yaml # noqa: PLC0415 + + from common import ( # noqa: PLC0415 + load_emulator_profiles, + load_platform_config, + load_target_config, + resolve_platform_cores, + ) + + platforms = str(REPO_ROOT / "platforms") + profiles = load_emulator_profiles(str(REPO_ROOT / "emulators")) + checked = 0 + for path in sorted(glob.glob(str(REPO_ROOT / "platforms" / "targets" / "*.yml"))): + name = Path(path).stem + if name.startswith("_"): + continue + targets = yaml.safe_load(Path(path).read_text())["targets"] + if len(targets) != 1: + continue + with self.subTest(platform=name): + config = load_platform_config(name, platforms) + only = load_target_config(name, next(iter(targets)), platforms) + self.assertEqual(resolve_platform_cores(config, profiles, target_cores=only), + resolve_platform_cores(config, profiles)) + checked += 1 + self.assertGreaterEqual(checked, 3) + if __name__ == "__main__": unittest.main()