diff --git a/scripts/generate_pack.py b/scripts/generate_pack.py index 0c2738a0..2ad95233 100644 --- a/scripts/generate_pack.py +++ b/scripts/generate_pack.py @@ -131,54 +131,6 @@ def _write_generated_member(zf: zipfile.ZipFile, arcname: str, text: str) -> Non zf.writestr(info, text) - - - - - - - - -def _pack_member_groups( - config: dict, - pack_systems: dict, - emulators_dir: str, - db: dict, - base_dest: str, - emu_profiles: dict | None, - target_cores: set[str] | None, - source: str, -) -> dict[str, list[tuple[str, str]]]: - """Group every candidate of a pack by system, as (destination, name). - - Covers the platform baseline and the core extras, because a narrowing pass - that saw only one of the two would compare an incomplete set. - """ - groups: dict[str, list[tuple[str, str]]] = {} - for sys_id, system in pack_systems.items(): - members = groups.setdefault(sys_id, []) - for fe in system.get("files", []): - dest = sanitize_pack_path(fe.get("destination", fe.get("name", ""))) - if dest: - members.append((dest, fe.get("name", ""))) - if source == "platform": - return groups - # find_undeclared_files reports the profile's display name, not its key, - # so index both: on a key-only lookup nearly every core extra fell into a - # single bucket and lost its per-system grouping. - emu_systems = _emulator_systems_index(emu_profiles) - for fe in _collect_emulator_extras( - config, emulators_dir, db, set(), base_dest, emu_profiles, - target_cores=target_cores, include_all=(source == "truth"), - ): - dest = sanitize_pack_path(fe.get("destination", fe.get("name", ""))) - if not dest: - continue - for sys_id in emu_systems.get(fe.get("source_emulator", ""), ["_extras"]): - groups.setdefault(sys_id, []).append((dest, fe.get("name", ""))) - return groups - - def _narrowings( source: str, regions: list[str] | None, @@ -576,8 +528,10 @@ def _select_variants( """ region_drops: set[str] = set() region_fallbacks: list[str] = [] - if regions: - region_index = region_mod.build_region_index(emu_profiles or {}) + # One grouping for both passes: a file judged in its own system by one + # pass must not land in every system of its profile for the other. + region_groups: dict[str, list[tuple[str, str]]] = {} + if regions or one_per_slot: region_groups, _extra_dests = platform_region_groups( config, pack_systems, @@ -589,6 +543,8 @@ def _select_variants( include_extras=(source != "platform"), include_all=(source == "truth"), ) + if regions: + region_index = region_mod.build_region_index(emu_profiles or {}) region_drops = region_mod.resolve_region_drops( region_groups, region_index, regions ) @@ -598,13 +554,9 @@ def _select_variants( slot_undecidable: list[str] = [] if one_per_slot: - members_by_system = _pack_member_groups( - config, pack_systems, emulators_dir, db, base_dest, - emu_profiles, target_cores, source, - ) slot_groups = { sys_id: [(d, n) for d, n in members if d not in region_drops] - for sys_id, members in members_by_system.items() + for sys_id, members in region_groups.items() } slot_drops, slot_undecidable = slot_mod.resolve_slot_drops( slot_groups, slot_mod.build_slot_index(emu_profiles or {}) diff --git a/tests/test_pack_grouping.py b/tests/test_pack_grouping.py new file mode 100644 index 00000000..3b6bfcac --- /dev/null +++ b/tests/test_pack_grouping.py @@ -0,0 +1,79 @@ +"""--one-per-slot and --region read one grouping of a pack. + +The slot pass put each core extra under every system of its profile. PicoDrive +ranks its Mega CD BIOSes within sega-megacd; spread across sega-megadrive, +sega-32x and the rest, they shared a tier with unranked files, the tier was +ruled undecidable, and the keep overrode the drop. +""" + +from __future__ import annotations + +import os +import sys +import unittest +from pathlib import Path + +REPO_ROOT = Path(__file__).resolve().parents[1] +sys.path.insert(0, str(REPO_ROOT / "scripts")) + + +class SlotPassReadsTheRegionGrouping(unittest.TestCase): + def test_ranked_mega_cd_alternatives_are_decided(self): + if not (REPO_ROOT / "database.json").is_file(): + self.skipTest("database.json not built") + import generate_pack as gp + from common import load_database, load_emulator_profiles, load_platform_config + + previous = os.getcwd() + os.chdir(REPO_ROOT) + self.addCleanup(os.chdir, previous) + db = load_database("database.json") + profiles = load_emulator_profiles("emulators") + config = load_platform_config("retroarch", "platforms") + drops, _fallbacks, _undecided = gp._select_variants( + config, config["systems"], "emulators", db, + config.get("base_destination", ""), profiles, None, "full", None, True, + ) + names = {os.path.basename(d) for d in drops} + for name in ("jp_mcd1_9111.bin", "eu_mcd1_9210.bin"): + self.assertIn(name, names) + + def test_one_grouping_function(self): + source = (REPO_ROOT / "scripts" / "generate_pack.py").read_text(encoding="utf-8") + self.assertNotIn("def _pack_member_groups", source) + + +class VariantGroupSpansSystems(unittest.TestCase): + def test_us_request_withdraws_the_other_mega_cd_lists(self): + """PicoDrive files US under sega-segacd and EU/JP under sega-megacd. + + find_bios (platform/libretro/libretro.c:1296) fills one slot from one + of three lists. Grouped by system, sega-megacd had no US member and was + kept whole as a fallback. + """ + if not (REPO_ROOT / "database.json").is_file(): + self.skipTest("database.json not built") + import generate_pack as gp + import region as region_mod + from common import load_database, load_emulator_profiles, load_platform_config + + previous = os.getcwd() + os.chdir(REPO_ROOT) + self.addCleanup(os.chdir, previous) + db = load_database("database.json") + profiles = load_emulator_profiles("emulators") + config = load_platform_config("retroarch", "platforms") + drops, _fallbacks, _undecided = gp._select_variants( + config, config["systems"], "emulators", db, + config.get("base_destination", ""), profiles, None, "full", + region_mod.parse_requested("us"), False, + ) + names = {os.path.basename(d) for d in drops} + for name in ("jp_mcd1_9111.bin", "eu_mcd2_9306.bin"): + self.assertIn(name, names) + for name in ("us_scd2_9306.bin", "us_scd1_9210.bin"): + self.assertNotIn(name, names) + + +if __name__ == "__main__": + unittest.main()