feat: read a search list as one need

A core that loads the first of several names (yabause: saturn_bios.bin,
sega_101.bin, mpr-17933.bin) needs one of them. Profiles wrote this as
required on the first name and optional on the others, so verify and
the gap report called saturn_bios.bin a missing required file while
sega_101.bin was held, and a set held only through a name marked
optional read as a need met by an optional file.

Model: an explicit group on the file entry, mirroring the platform
field. priority was not reused: it is a rank, read by slot.py, region.py
and truth.py inside a (system, region) slot, and that slot is not the
set. px68k-sdl ranks its IPL list and its font list each from 1 in one
system, pom2 ranks one list per machine model, DuckStation ranks 105
images across every region and takes any of them. A rank cannot say
where one list ends. priority stays the order inside a group.

Readers, through alternatives.settle_sets:
- verify --emulator and the emulator pack: a held member fills the
  set, the others leave the report, a set nobody holds is one gap
  under its first member naming the others.
- find_undeclared_files (verify platform, core complement of packs and
  manifests): same rule, a platform declaration or launcher counts as
  answering the set.
- cross_reference: excused members are set aside as other versions.
- slot.py: ranks compare members of one set only.
- archive members: a required set inside an archive is met by one.
- truth: a member gives no requirement of its own, one_of and
  one_of_required carry the set, and the exporters and the diff leave
  such a line's required as the platform wrote it.
- validate_schemas: members of a group share required.
This commit is contained in:
Abdessamad Derraz committed 2026-11-09 17:39:46 +01:00
1 parent a40f8416e1
commit 2c76cc7df9
13 files changed
+599 -41

No files matched your search

+29 -4
View File
@@ -36,6 +36,7 @@ import zipfile
from pathlib import Path, PurePosixPath
sys.path.insert(0, os.path.dirname(__file__))
from alternatives import settle_sets, slot_set_keys
from common import resolve_platform_cores as _platform_cores
from common import (
installed_data_refs,
@@ -1838,6 +1839,10 @@ def generate_emulator_pack(
continue
slots.setdefault(dest, []).append(fe)
# What each destination came to, settled by set once the loop
# is over: a member another member fills is not missing, and a
# set nobody holds is reported once (alternatives.py).
outcomes: list[tuple[list[dict], bool, Callable[[], None] | None]] = []
for dest, alternatives in slots.items():
# Name order, first file wins, case folded: verify replays
# the rule and reports a later profile's file as missing.
@@ -1923,17 +1928,25 @@ def generate_emulator_pack(
held, packed_at.get(dest.lower())
):
# The file already at this path, asked for twice.
outcomes.append((alternatives, True, None))
continue
report_missing(alternatives[0]["name"], dest)
outcomes.append((alternatives, False, functools.partial(
report_missing, alternatives[0]["name"], dest
)))
continue
if chosen is None:
if refusal:
rejected_files.append(refusal)
outcomes.append((alternatives, False, None))
elif all(fe.get("unsourceable") for fe in alternatives):
unsourceable_files.append(alternatives[0]["name"])
outcomes.append((alternatives, False, functools.partial(
unsourceable_files.append, alternatives[0]["name"]
)))
else:
report_missing(alternatives[0]["name"], dest)
outcomes.append((alternatives, False, functools.partial(
report_missing, alternatives[0]["name"], dest
)))
continue
fe, local_path, how = chosen
@@ -1954,8 +1967,11 @@ def generate_emulator_pack(
total_files += 1
if record is not None:
record.files.append(dest)
outcomes.append((alternatives, True, None))
else:
missing_files.append(fe["name"])
outcomes.append((alternatives, False, functools.partial(
missing_files.append, fe["name"]
)))
finally:
if os.path.exists(tmp_path):
os.unlink(tmp_path)
@@ -1969,6 +1985,7 @@ def generate_emulator_pack(
# (but allow same file to be packed under different destinations,
# e.g., IPL.bin in GC/USA/ and GC/EUR/ from same source)
dedup_key_hash = f"{os.path.realpath(local_path)}:{dest}"
outcomes.append((alternatives, True, None))
if dedup_key_hash in seen_hashes:
continue
seen_hashes.add(dedup_key_hash)
@@ -1985,6 +2002,14 @@ def generate_emulator_pack(
if record is not None:
record.files.append(dest)
excused, _standing = settle_sets([
(slot_set_keys(emu_name, entries), entries[0], fills)
for entries, fills, _report in outcomes
])
for position, (_entries, _fills, report) in enumerate(outcomes):
if report is not None and position not in excused:
report()
# The trees after the files, as in a platform pack: a dump the
# profile declares wins over the copy a tree bundles at the same
# path (Dolphin's real dsp_rom.bin over Sys/GC's free one).