diff --git a/scripts/generate_pack.py b/scripts/generate_pack.py index cedfd162..56815bbc 100644 --- a/scripts/generate_pack.py +++ b/scripts/generate_pack.py @@ -1283,25 +1283,9 @@ def generate_emulator_pack( zip_path = os.path.join(output_dir, zip_name) os.makedirs(output_dir, exist_ok=True) - # One group per system (or explicit variant_group) inside each profile. - # Multi-system cores such as O2EM therefore retain a fallback BIOS for - # every system instead of letting one matching system empty another. - region_drops: set[str] = set() - if regions: - region_index = region_mod.build_region_index(dict(selected)) - region_groups: dict[str, list[tuple[str, str]]] = {} - for emu_name, profile in sorted(selected): - structure = profile.get("pack_structure") - for fe in filter_files_by_mode(profile.get("files", []), standalone): - dest = _resolve_destination(fe, structure, standalone) - if dest: - group_id = _emulator_region_group(emu_name, profile, fe) - region_groups.setdefault(group_id, []).append( - (dest, fe.get("name", "")) - ) - region_drops = region_mod.resolve_region_drops( - region_groups, region_index, regions - ) + region_drops: set[str] = ( + emulator_region_drops(selected, standalone, regions) if regions else set() + ) total_files = 0 missing_files = [] @@ -3436,6 +3420,7 @@ from packextras import ( # noqa: E402,F401 _collect_emulator_extras, _extra_system_ids, platform_region_groups, + emulator_region_drops, _emulator_region_group, ) diff --git a/scripts/packextras.py b/scripts/packextras.py index d720763a..f603c0f4 100644 --- a/scripts/packextras.py +++ b/scripts/packextras.py @@ -600,6 +600,34 @@ def platform_region_groups( groups.setdefault(group_id, []).append((dest, name)) return groups, extra_dests +def emulator_region_drops( + selected: list[tuple[str, dict]], standalone: bool, regions: list[str] +) -> set[str]: + """Destinations an emulator-mode `--region` withdraws. + + One group per system (or explicit variant_group) inside each profile, so + a multi-system core such as O2EM keeps a fallback BIOS for every system + instead of letting one matching system empty another. The emulator pack + and `verify --emulator` both read this, keyed by pack destination. + """ + import region as region_mod + from packpaths import _resolve_destination + from validation import filter_files_by_mode + + region_index = region_mod.build_region_index(dict(selected)) + region_groups: dict[str, list[tuple[str, str]]] = {} + for emu_name, profile in sorted(selected): + structure = profile.get("pack_structure") + for fe in filter_files_by_mode(profile.get("files", []), standalone): + dest = _resolve_destination(fe, structure, standalone) + if dest: + group_id = _emulator_region_group(emu_name, profile, fe) + region_groups.setdefault(group_id, []).append( + (dest, fe.get("name", "")) + ) + return region_mod.resolve_region_drops(region_groups, region_index, regions) + + def _emulator_region_group(emu_name: str, profile: dict, file_entry: dict) -> str: """Stable group ID for regional alternatives within an emulator profile.""" variant = file_entry.get("variant_group") diff --git a/scripts/verify.py b/scripts/verify.py index e496d035..f5012802 100644 --- a/scripts/verify.py +++ b/scripts/verify.py @@ -1242,7 +1242,7 @@ def verify_emulator( """Verify files for specific emulator profiles. A region priority list narrows the report the same way a pack built with the - same list would be narrowed. One group per profile, as in generate_pack. + same list would be narrowed, through the emulator pack's own drop set. """ load_emulator_profiles(emulators_dir) zip_contents = build_zip_contents_index(db) @@ -1265,22 +1265,12 @@ def verify_emulator( dest_to_name: dict[str, str] = {} data_dir_notices: list[str] = [] + # The emulator pack withdraws these; the report withdraws the same. region_drops: set[str] = set() if regions: - import region as region_mod + from packextras import emulator_region_drops - region_index = region_mod.build_region_index(dict(selected)) - region_groups: dict[str, list[tuple[str, str]]] = {} - for emu_name, profile in selected: - members = region_groups.setdefault(emu_name, []) - for fe in filter_files_by_mode(profile.get("files", []), standalone): - nm = fe.get("name", "") - key = fe.get("path") or nm - if key: - members.append((key, nm)) - region_drops = region_mod.resolve_region_drops( - region_groups, region_index, regions - ) + region_drops = emulator_region_drops(selected, standalone, regions) for emu_name, profile in selected: files = expand_directory_entries( @@ -1289,10 +1279,13 @@ def verify_emulator( standalone, ) if region_drops: + from packpaths import _resolve_destination + + structure = profile.get("pack_structure") files = [ fe for fe in files - if (fe.get("path") or fe.get("name", "")) not in region_drops + if _resolve_destination(fe, structure, standalone) not in region_drops ] # Check data directories (only notice if not cached) diff --git a/tests/test_region.py b/tests/test_region.py index 94bd4ec2..88b24adf 100644 --- a/tests/test_region.py +++ b/tests/test_region.py @@ -551,18 +551,38 @@ class TestReportAndBuilderNarrowTogether(unittest.TestCase): tmp.cleanup() def test_one_grouping_pass_serves_both_sides(self): - """A second hand-rolled grouping is how the two drifted apart.""" + """A second hand-rolled grouping is how the two drifted apart. + + Every module is read, not only the two that once held a copy: the + builder was split into several modules and a grouping can move with + it. packextras.py holds the two shared passes, platform and emulator. + """ scripts = Path(__file__).resolve().parent.parent / "scripts" - hand_rolled = 0 - for name in ("generate_pack.py", "verify.py"): - for line in (scripts / name).read_text().splitlines(): - if "region_groups.setdefault(" in line: - hand_rolled += 1 - self.assertLessEqual( - hand_rolled, 2, - "platform region grouping belongs to platform_region_groups; " - "the only other pass is the per-emulator pack shape", - ) + for path in sorted(scripts.rglob("*.py")): + if path.name == "packextras.py": + continue + with self.subTest(module=path.name): + self.assertNotIn( + "region_groups.setdefault(", path.read_text(encoding="utf-8"), + "region grouping belongs to packextras: platform_region_groups " + "or emulator_region_drops", + ) + + def test_emulator_report_keeps_what_the_emulator_pack_keeps(self): + """O2EM: --region us left the Videopac BIOSes in the pack, not in the report.""" + repo = Path(__file__).resolve().parent.parent + if not (repo / "emulators" / "o2em.yml").is_file(): + self.skipTest("no o2em profile") + from verify import verify_emulator + from common import load_database + + db = load_database(str(repo / "database.json")) + full = verify_emulator(["o2em"], str(repo / "emulators"), db) + narrowed = verify_emulator(["o2em"], str(repo / "emulators"), db, regions=["north-america"]) + names = {d["name"] for d in narrowed["details"]} + for kept in ("c52.bin", "g7400.bin", "jopac.bin"): + self.assertIn(kept, names, "a system with no candidate in the region falls back") + self.assertEqual(full["total_files"], narrowed["total_files"]) if __name__ == "__main__":