fix: replay the builder's held and one-file groups

install.region_drops now skips one-file groups (:file:) and lets no
omitted match displace an installable file, as region.resolve_region_drops
does on the builder side. The property test draws both cases and
compares against the builder.
This commit is contained in:
Abdessamad Derraz committed 2026-10-21 05:15:56 +02:00
1 parent 82acd55ebc
commit c638c12ca8
4 files changed
+95 -20

No files matched your search

+1 -1
View File
@@ -12,7 +12,7 @@
$ErrorActionPreference = "Stop"
$defaultInstallUrl = "https://raw.githubusercontent.com/Abdess/retrobios/main/install.py"
$defaultInstallSha256 = "376edf794195d74f21e598854b5f3e90fb1955513835264bc9326ad4d2acf9c9"
$defaultInstallSha256 = "973d64627af05b239555ab5a83a8902747b7a742bc1176f1b994cf7ac74774f9"
$maximumInstallerBytes = 2MB
$installerExit = 0
$installer = if ($PSScriptRoot) { Join-Path $PSScriptRoot "install.py" } else { $null }
+33 -10
View File
@@ -142,6 +142,8 @@ _REGION_PARENT = {
member: parent for parent, members in REGION_TREE.items() for member in members
}
REGIONS = frozenset({WORLD_REGION} | set(REGION_TREE) | set(_REGION_PARENT))
# Marks a region group named after one file that no system names.
FILE_GROUP = ":file:"
MAX_SELECTION_TAGS = 1000
SELECTION_FIELDS = ("systems", "regions", "region_groups", "region_system_groups")
# The files cores load for systems the platform does not list (game engines,
@@ -2220,7 +2222,10 @@ def _region_rank(file_regions: set[str], requested: list[str]) -> int:
def region_drops(
entries: list[dict], requested: list[str], systems: list[str] | None = None
entries: list[dict],
requested: list[str],
systems: list[str] | None = None,
held: set[str] | None = None,
) -> set[str]:
"""Destinations a region priority list withdraws.
@@ -2228,10 +2233,16 @@ def region_drops(
groups the manifest records. Per group, the best-ranked regional file
stays. A world file beats unmatched regional ones the code does not search
before it (`priority`, lower first). When nothing matches and no world
file exists, every regional file stays, so a group is never emptied. A file with no region never competes and always stays.
file exists, every regional file stays, so a group is never emptied. A
file with no region never competes and always stays.
A core extra competes whatever systems are chosen, while a platform's own
declaration competes only when its system is kept, as in the pack.
``held`` names the destinations the manifest can install. A match it
lacks never displaces a held file: the American BIOS stays under
--region japan when the Japanese one is missing. A group named after a
single file (FILE_GROUP) competes with nothing and keeps nothing.
"""
if not requested:
return set()
@@ -2253,7 +2264,9 @@ def region_drops(
groups.setdefault(group, []).append((set(regions), entry["dest"], priority))
keep: set[str] = set()
drop: set[str] = set()
for members in groups.values():
for group_id, members in groups.items():
if FILE_GROUP in group_id:
continue
world = {dest for regions, dest, _p in members if WORLD_REGION in regions}
ranks = {dest: priority for _regions, dest, priority in members}
regional = [
@@ -2265,12 +2278,19 @@ def region_drops(
if not regional:
continue
matched = [(rank, dest) for rank, dest in regional if rank < len(requested)]
if matched:
best = min(rank for rank, _dest in matched)
keep |= {dest for rank, dest in matched if rank == best}
drop |= {dest for _rank, dest in regional}
elif world:
world_ranks = [ranks[dest] for dest in world]
winners, rivals = matched, world
if held is not None:
winners = [(rank, dest) for rank, dest in matched if dest in held]
rivals = world & held
if winners:
best = min(rank for rank, _dest in winners)
keep |= {
dest for rank, dest in matched
if rank == best or (rank < best and dest not in (held or ()))
}
drop |= {dest for _rank, dest in regional if dest not in keep}
elif rivals:
world_ranks = [ranks[dest] for dest in rivals]
best_world = min(world_ranks) if None not in world_ranks else None
for _rank, dest in regional:
ours = ranks[dest]
@@ -2310,7 +2330,10 @@ def narrow(
files = _filter_by_target(files, cores)
omitted = _filter_by_target(omitted, cores)
if regions:
dropped = region_drops(files + omitted, regions, systems)
# The builder's held set: what the collection serves. An omission
# answers the request but takes no installable file's place.
held = {f["dest"] for f in files}
dropped = region_drops(files + omitted, regions, systems, held)
files = [f for f in files if f["dest"] not in dropped]
omitted = [o for o in omitted if o["dest"] not in dropped]
if systems:
+1 -1
View File
@@ -18,7 +18,7 @@ esac
TEMP_INSTALLER=""
TEMP_DIRECTORY=""
DEFAULT_INSTALL_URL="https://raw.githubusercontent.com/Abdess/retrobios/main/install.py"
DEFAULT_INSTALL_SHA256="376edf794195d74f21e598854b5f3e90fb1955513835264bc9326ad4d2acf9c9"
DEFAULT_INSTALL_SHA256="973d64627af05b239555ab5a83a8902747b7a742bc1176f1b994cf7ac74774f9"
MAX_INSTALLER_BYTES=2097152
cleanup() {
+60 -8
View File
@@ -43,6 +43,7 @@ class OneRegionVocabulary(unittest.TestCase):
self.assertEqual(install.REGION_ALIASES, region.ALIASES)
self.assertEqual(install.WORLD_REGION, region.WORLD)
self.assertEqual(install.REGIONS, region.REGIONS)
self.assertEqual(install.FILE_GROUP, region.FILE_GROUP)
class TheRegionRuleIsTheBuilders(unittest.TestCase):
@@ -72,26 +73,68 @@ class TheRegionRuleIsTheBuilders(unittest.TestCase):
index[dest] = {"regions": regions,
"has_untagged": not regions, "emulators": [],
"priorities": {priority}}
groups.setdefault(f"g{g}", []).append((dest, dest))
if f"g{g}" not in entries[dest]["region_groups"]:
entries[dest]["region_groups"].append(f"g{g}")
# A lone core file no system names gets a group of its own.
group = f"g{g}" if rng.random() < 0.85 else f"e{region.FILE_GROUP}{dest}"
groups.setdefault(group, []).append((dest, dest))
if group not in entries[dest]["region_groups"]:
entries[dest]["region_groups"].append(group)
for entry in entries.values():
if not entry["regions"]:
del entry["regions"], entry["region_groups"]
entry.pop("priority", None)
requested = rng.sample(["north-america", "europe", "japan", "uk", "canada"],
rng.randint(1, 3))
return groups, index, list(entries.values()), requested
held = {dest for dest in entries if rng.random() < 0.7}
return groups, index, list(entries.values()), requested, held
def test_two_thousand_random_platforms(self):
rng = random.Random(20261010)
for trial in range(2000):
groups, index, entries, requested = self._case(rng)
groups, index, entries, requested, held = self._case(rng)
with self.subTest(trial=trial):
self.assertEqual(
install.region_drops(entries, requested),
region.resolve_region_drops(groups, index, requested),
)
self.assertEqual(
install.region_drops(entries, requested, held=held),
region.resolve_region_drops(groups, index, requested, held),
)
def test_narrow_passes_the_installable_files_as_held(self):
"""The builder passes what the collection holds (generate_pack
_select_variants). An omission is the installer's unheld file."""
rng = random.Random(20261021)
for trial in range(500):
groups, index, entries, requested, held = self._case(rng)
files = [dict(e, size=1, cores=None) for e in entries if e["dest"] in held]
omitted = [dict(e, cores=None) for e in entries if e["dest"] not in held]
kept, still_omitted = install.narrow(files, omitted, [], [], requested)
drops = region.resolve_region_drops(groups, index, requested, held)
with self.subTest(trial=trial):
self.assertEqual(
{f["dest"] for f in kept},
{f["dest"] for f in files} - drops,
)
self.assertEqual(
{o["dest"] for o in still_omitted},
{o["dest"] for o in omitted} - drops,
)
def test_a_lone_core_file_leaves_its_systems_drop(self):
"""Dolphin's GC/EUR/IPL.bin is a core extra no system names, plus the
GameCube's own declaration. The builder skips the one-file group, so
--region us drops it."""
entries = [
{"dest": "GC/USA/IPL.bin", "regions": ["north-america"],
"region_system_groups": ["nintendo-gamecube"]},
{"dest": "GC/EUR/IPL.bin", "regions": ["europe"],
"region_groups": [f"dolphin{region.FILE_GROUP}GC/EUR/IPL.bin"],
"region_system_groups": ["nintendo-gamecube"]},
]
self.assertEqual(
install.region_drops(entries, ["north-america"]), {"GC/EUR/IPL.bin"}
)
class Narrowing(unittest.TestCase):
@@ -138,12 +181,21 @@ class Narrowing(unittest.TestCase):
self.assertIn("scph5501.bin", files)
self.assertEqual(omitted, [])
def test_an_unavailable_match_still_wins_as_the_builder_counts_it(self):
def test_an_unavailable_match_never_displaces_a_held_file(self):
"""The builder keeps the held BIOS when the requested one is missing
(region.resolve_region_drops, held). The missing one stays reported."""
files, omitted = self._dests([], [], ["europe"])
self.assertNotIn("scph5500.bin", files)
self.assertNotIn("scph5501.bin", files)
self.assertIn("scph5500.bin", files)
self.assertIn("scph5501.bin", files)
self.assertEqual(omitted, ["scph5502.bin"])
def test_a_missing_japanese_bios_keeps_the_american_one(self):
files = [dict(self.FILES[1])]
omitted = [dict(self.OMITTED[0], dest="scph5500.bin", regions=["japan"])]
kept, still = install.narrow(files, omitted, [], [], ["japan"])
self.assertEqual([f["dest"] for f in kept], ["scph5501.bin"])
self.assertEqual([o["dest"] for o in still], ["scph5500.bin"])
def test_no_match_keeps_every_regional_file(self):
files, _ = self._dests([], [], ["brazil"])
self.assertIn("scph5500.bin", files)