diff --git a/scripts/slot.py b/scripts/slot.py index f08049ad..f79c11ce 100644 --- a/scripts/slot.py +++ b/scripts/slot.py @@ -23,6 +23,8 @@ The scale is only meaningful inside one emulator's candidate set. When two profiles rank the same file differently -- pcsx1 walks scph1001 first while DuckStation prefers scph5501 -- the pack serves both cores, so neither order may win: the file is marked conflicting and its group stays undecidable. +The same holds when one profile ranks a file and another declares it with no +rank: the second core reads it by its own rule, which no rank expresses. """ from __future__ import annotations @@ -62,10 +64,11 @@ def build_slot_index(profiles: dict) -> dict[str, dict]: entry = index.setdefault( key, {"rank": None, "conflict": False, "regions": set(), - "emulators": []}, + "emulators": [], "ranked_by": set(), "unranked_by": set()}, ) entry["regions"] |= {str(r) for r in (f.get("region") or [])} rank = f.get("priority") + entry["unranked_by" if rank is None else "ranked_by"].add(emu_name) if rank is not None: current = entry["rank"] if current is not None and current != rank: @@ -75,6 +78,14 @@ def build_slot_index(profiles: dict) -> dict[str, dict]: ) if emu_name not in entry["emulators"]: entry["emulators"].append(emu_name) + # A core that declares the file without a rank loads it by a rule of its + # own: Genesis Plus GX opens bios_CD_U.bin by name for a US disc, and + # PicoDrive's rank 4 for it would let its rank-3 file replace it. + for entry in index.values(): + if entry["ranked_by"] and entry.pop("unranked_by") - entry["ranked_by"]: + entry["conflict"] = True + entry.pop("unranked_by", None) + entry.pop("ranked_by") return index diff --git a/tests/test_slot.py b/tests/test_slot.py index 9cd29878..402c2cdb 100644 --- a/tests/test_slot.py +++ b/tests/test_slot.py @@ -260,5 +260,42 @@ class TestRepoProfiles(unittest.TestCase): self.assertEqual(undecidable, ["sony-playstation|japan"]) + +class AnUnrankedDeclarationIsADisagreement(unittest.TestCase): + """ROCKNIX --one-per-slot dropped the required bios_CD_U.bin: PicoDrive + ranks it 4 behind its own us_scd1_9210.bin, while Genesis Plus GX opens + bios_CD_U.bin by name and ranks nothing.""" + + def test_the_file_genesis_plus_gx_opens_is_kept(self): + from slot import build_slot_index, resolve_slot_drops + + profiles = { + "picodrive": {"files": [ + {"name": "us_scd1_9210.bin", "priority": 3}, + {"name": "bios_CD_U.bin", "priority": 4}, + ]}, + "genesis_plus_gx": {"files": [{"name": "bios_CD_U.bin"}]}, + } + index = build_slot_index(profiles) + drops, undecidable = resolve_slot_drops( + {"sega-mega-cd": [("bios/us_scd1_9210.bin", "us_scd1_9210.bin"), + ("bios/bios_CD_U.bin", "bios_CD_U.bin")]}, + index, + ) + self.assertEqual(drops, set()) + self.assertEqual(undecidable, ["sega-mega-cd"]) + + def test_one_core_ranking_alone_still_decides(self): + from slot import build_slot_index, resolve_slot_drops + + index = build_slot_index({"picodrive": {"files": [ + {"name": "a.bin", "priority": 1}, {"name": "b.bin", "priority": 2}, + ]}}) + drops, _ = resolve_slot_drops( + {"g": [("bios/a.bin", "a.bin"), ("bios/b.bin", "b.bin")]}, index + ) + self.assertEqual(drops, {"bios/b.bin"}) + + if __name__ == "__main__": unittest.main()