fix: never drop a held file for a missing region

Region filtering ranked every declared file, held or not, so a request
answered only by a missing file dropped the held fallback and emptied
the slot. Only held files now decide what gives way; the missing better
match stays reported.
This commit is contained in:
Abdessamad Derraz committed 2026-10-17 18:31:56 +02:00
1 parent 5cfcb9e746
commit e6c29452f9
6 files changed
+69 -8

No files matched your search

+6 -2
View File
@@ -476,6 +476,7 @@ def _select_variants(
# One grouping for both passes: a file judged in its own system by one
# pass must not land in every system of its profile for the other.
region_groups: dict[str, list[tuple[str, str]]] = {}
held: set[str] = set()
if regions or one_per_slot:
region_groups, _extra_dests = platform_region_groups(
config,
@@ -488,13 +489,14 @@ def _select_variants(
include_extras=(source != "platform"),
include_all=(source == "truth"),
required_only=required_only,
held_out=held,
)
if regions:
region_index = region_mod.platform_region_index(
config, emu_profiles or {}, target_cores
)
region_drops = region_mod.resolve_region_drops(
region_groups, region_index, regions
region_groups, region_index, regions, held
)
region_fallbacks = region_mod.fallback_groups(
region_groups, region_index, regions
@@ -3528,6 +3530,7 @@ def _manifest_selection(
the full manifest withdraws what `--region` withdraws from the pack.
"""
extras: list[dict] = []
held: set[str] = set()
region_groups, _extra_dests = platform_region_groups(
config,
pack_systems,
@@ -3540,12 +3543,13 @@ def _manifest_selection(
include_all=(source == "truth"),
required_only=required_only,
extras_out=extras,
held_out=held,
)
region_index = region_mod.platform_region_index(
config, emu_profiles, target_cores
)
drops = (
region_mod.resolve_region_drops(region_groups, region_index, regions)
region_mod.resolve_region_drops(region_groups, region_index, regions, held)
if regions
else set()
)
+11
View File
@@ -877,9 +877,14 @@ def platform_region_groups(
include_all: bool = False,
required_only: bool = False,
extras_out: list[dict] | None = None,
held_out: set[str] | None = None,
) -> tuple[dict[str, list[tuple[str, str]]], dict[tuple[str, str, str], str]]:
"""Group a platform's pack candidates the way region filtering reads them.
``held_out`` receives the destinations whose file the collection holds:
a missing file is grouped so the report can withdraw it, but it never
takes the slot of a held one (resolve_region_drops).
Returns the groups and, for every core extra, the destination it was
grouped under, keyed by (emulator, name). verify.py needs that mapping to
withdraw from its report exactly what the builder withdraws from the pack:
@@ -898,6 +903,10 @@ def platform_region_groups(
)
if dest:
members.append((dest, file_entry.get("name", "")))
if held_out is not None and db is not None and resolve_local_file(
file_entry, db, dest_hint=dest
)[0]:
held_out.add(dest)
extra_dests: dict[tuple[str, str, str], str] = {}
if not include_extras or db is None:
@@ -938,6 +947,8 @@ def platform_region_groups(
groups.setdefault(group_id, []).append((dest, name))
if extras_out is not None and id(extra) in held:
extras_out.append(extra)
if held_out is not None and id(extra) in held:
held_out.add(dest)
return groups, extra_dests
+3 -1
View File
@@ -582,6 +582,7 @@ def verify_pack_against_platform(
region_index = region_mod.platform_region_index(
config, emu_profiles, target_cores
)
held: set[str] = set()
region_groups, _extra_dests = platform_region_groups(
config,
expected_systems,
@@ -590,9 +591,10 @@ def verify_pack_against_platform(
base_dest,
emu_profiles,
target_cores=target_cores,
held_out=held,
)
region_drops = region_mod.resolve_region_drops(
region_groups, region_index, regions
region_groups, region_index, regions, held
)
with zipfile.ZipFile(zip_path, "r") as zf:
+19 -5
View File
@@ -254,6 +254,7 @@ def resolve_region_drops(
groups: dict[str, list[tuple[str, str]]],
index: dict[str, dict],
requested: list[str],
held: set[str] | None = None,
) -> set[str]:
"""Destinations to skip for a requested region priority list.
@@ -261,6 +262,10 @@ def resolve_region_drops(
A world candidate beats unmatched regional fallbacks the code does not
search before it, while untagged files always survive. If neither a requested nor a world candidate exists, all
regional candidates survive so filtering can never empty a group.
``held`` names the destinations the collection holds. A match it lacks
never displaces a held file: the American BIOS stays in a Japanese pack
when the Japanese one is missing, and the missing one stays reported.
"""
if not requested:
return set()
@@ -284,11 +289,20 @@ def resolve_region_drops(
if not regional:
continue
matched = [(r, destination) for r, destination in regional if r < len(requested)]
if matched:
best = min(r for r, _destination in matched)
keep |= {destination for r, destination in matched if r == best}
winners, rivals = matched, world
if held is not None:
# A missing file answers the request but takes no held file's
# place: only held files decide what gives way.
winners = [(r, d) for r, d in matched if d in held]
rivals = world & held
if winners:
best = min(r for r, _destination in winners)
keep |= {
destination for r, destination in matched
if r == best or (r < best and destination not in (held or ()))
}
drop |= {destination for _r, destination in regional if destination not in keep}
elif world:
elif rivals:
# A world file beats an unmatched regional one, unless the code
# provably looks for the regional one first: DuckStation ranks its
# PS1 images 5 to 50 and its world PS2 images 100, so a request
@@ -296,7 +310,7 @@ def resolve_region_drops(
world_ranks = [
lookup_priority(index, destination, name)
for destination, name in members
if destination in world
if destination in rivals
]
known = None not in world_ranks
best_world = min(world_ranks) if known and world_ranks else None
+3
View File
@@ -967,6 +967,7 @@ def verify_platform(
# every core extra a region run withdraws from the pack.
from packextras import platform_region_groups
held: set[str] = set()
region_groups, region_extra_dests = platform_region_groups(
config,
verify_systems,
@@ -975,11 +976,13 @@ def verify_platform(
config.get("base_destination", ""),
profiles,
target_cores=target_cores,
held_out=held,
)
region_drops = region_mod.resolve_region_drops(
region_groups,
region_mod.platform_region_index(config, profiles, target_cores),
regions,
held,
)
# A destination two systems declare with two hashes ships one file, the