diff --git a/scripts/generate_pack.py b/scripts/generate_pack.py index 9086ea29..733d4bd8 100644 --- a/scripts/generate_pack.py +++ b/scripts/generate_pack.py @@ -555,6 +555,7 @@ def _select_variants( slot_drops, slot_undecidable = slot_mod.resolve_slot_drops( slot_groups, slot_mod.platform_slot_index(config, emu_profiles or {}, target_cores), + held, ) region_drops = region_drops | slot_drops if slot_undecidable: diff --git a/scripts/slot.py b/scripts/slot.py index f79c11ce..5d16840a 100644 --- a/scripts/slot.py +++ b/scripts/slot.py @@ -106,12 +106,14 @@ def lookup_slot(index: dict[str, dict], destination: str, name: str) -> dict | N def resolve_slot_drops( groups: dict[str, list[tuple[str, str]]], index: dict[str, dict], + held: set[str] | None = None, ) -> tuple[set[str], list[str]]: """Destinations to skip, and the groups no declared order can decide. Returns (drops, undecidable). A group is decided only when every candidate declares a priority and exactly one holds the lowest; anything else keeps - every candidate. + every candidate. ``held`` names the destinations the collection holds: a + candidate it lacks ships nothing, so it neither wins nor displaces one. """ keep: set[str] = set() drop: set[str] = set() @@ -123,6 +125,8 @@ def resolve_slot_drops( tiers: dict[tuple[str, tuple[str, ...]], list[tuple[str, int | None]]] = {} for group_id, members in groups.items(): for destination, name in members: + if held is not None and destination not in held: + continue entry = lookup_slot(index, destination, name) if entry is None: keep.add(destination) diff --git a/tests/test_slot.py b/tests/test_slot.py index 402c2cdb..0d5a70ee 100644 --- a/tests/test_slot.py +++ b/tests/test_slot.py @@ -297,5 +297,35 @@ class AnUnrankedDeclarationIsADisagreement(unittest.TestCase): self.assertEqual(drops, {"bios/b.bin"}) + +class AnAbsentRankOneDisplacesNothing(unittest.TestCase): + """--one-per-slot received no `held`: a rank-1 file the collection lacks + dropped the rank-2 and rank-3 files it holds, leaving the slot empty.""" + + def test_the_best_held_file_wins(self): + from slot import build_slot_index, resolve_slot_drops + + index = build_slot_index({"picodrive": {"files": [ + {"name": "us_scd2_9306.bin", "priority": 1}, + {"name": "SegaCDBIOS9303.bin", "priority": 2}, + {"name": "us_scd1_9210.bin", "priority": 3}, + ]}}) + group = {"sega-mega-cd": [ + ("bios/us_scd2_9306.bin", "us_scd2_9306.bin"), + ("bios/SegaCDBIOS9303.bin", "SegaCDBIOS9303.bin"), + ("bios/us_scd1_9210.bin", "us_scd1_9210.bin"), + ]} + held = {"bios/SegaCDBIOS9303.bin", "bios/us_scd1_9210.bin"} + drops, _ = resolve_slot_drops(group, index, held) + self.assertEqual(drops, {"bios/us_scd1_9210.bin"}) + + def test_the_builder_passes_held(self): + from pathlib import Path + + source = (Path(__file__).resolve().parents[1] / "scripts" / "generate_pack.py").read_text() + compact = " ".join(source.split()) + self.assertRegex(compact, r"resolve_slot_drops\( slot_groups, slot_mod\.platform_slot_index\([^)]*\), held, \)") + + if __name__ == "__main__": unittest.main()