diff --git a/scripts/slots.py b/scripts/slots.py index 5bc10fc9..efecda9a 100644 --- a/scripts/slots.py +++ b/scripts/slots.py @@ -143,6 +143,12 @@ def profile_claims( continue if entry_mode == "libretro" and is_standalone: continue + # A member of a romset is not a file at the BIOS root: the archive + # holding it is what occupies a destination, and it is declared + # separately. Claiming the member put FBNeo's msx.zip contents + # loose at the platform's root. + if entry.get("archive"): + continue dest = ( (entry.get("standalone_path") or entry.get("path")) if is_standalone @@ -282,6 +288,13 @@ class Collision: resolved: list[str] +def _collision_json(collision: Collision) -> dict: + return { + "destination": collision.destination, + "resolved": list(collision.resolved), + } + + def find_collisions(config: dict, db: dict) -> list[Collision]: """Destinations a platform declares twice and resolves two ways. @@ -421,25 +434,29 @@ def main() -> int: collided[name] = collisions if args.json: - print( - json.dumps( - { - platform: [ - { - "destination": c.destination, - "ships": c.platform_claim.local_path, - "ships_evidence": c.platform_claim.status, - "emulators": c.emulators, - "expected": c.profile_claims[0].local_path, - "expected_evidence": c.profile_claims[0].status, - } - for c in conflicts - ] - for platform, conflicts in found.items() - }, - indent=2, - ) - ) + # --strict gates on collisions too, so a document that carries only + # conflicts hands a consumer an empty answer and a non-zero exit. + payload = { + "conflicts": { + platform: [ + { + "destination": c.destination, + "ships": c.platform_claim.local_path, + "ships_evidence": c.platform_claim.status, + "emulators": c.emulators, + "expected": c.profile_claims[0].local_path, + "expected_evidence": c.profile_claims[0].status, + } + for c in conflicts + ] + for platform, conflicts in found.items() + }, + "collisions": { + platform: [_collision_json(c) for c in platform_collisions] + for platform, platform_collisions in collided.items() + }, + } + print(json.dumps(payload, indent=2)) else: fixable = 0 for platform, conflicts in found.items(): diff --git a/tests/test_slots.py b/tests/test_slots.py index 50b26fc2..b4fc46a0 100644 --- a/tests/test_slots.py +++ b/tests/test_slots.py @@ -179,6 +179,26 @@ class TestConflicts(unittest.TestCase): slots.find_conflicts(self._config("m" * 32), profile, REGIONS_DB), [] ) + def test_a_rom_inside_a_romset_claims_nothing_of_its_own(self): + """The archive occupies the destination, not the ROM it holds. + + FBNeo declares msx.rom and kanji.rom with archive: msx.zip and no + path, so the member was claimed at the platform's BIOS root and + contradicted whatever really lives there. + """ + profile = { + "fbneo": { + "type": "libretro", + "files": [ + {"name": "IPL.bin", "archive": "romset.zip"}, + ], + } + } + self.assertEqual( + slots.find_conflicts(self._config("m" * 32), profile, REGIONS_DB), [] + ) + self.assertEqual(slots.profile_claims(profile, REGIONS_DB), []) + def test_base_destination_prefixes_both_sides(self): conflicts = slots.find_conflicts( self._config("m" * 32),