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

+25
View File
@@ -86,6 +86,7 @@ def _validate_yaml_directory(
out.extend(_save_dir_without_library_name(path, data))
out.extend(_systems_outside_profile(path, data))
out.extend(_doubled_paths(path))
out.extend(_group_requirement_split(path, data))
return out
@@ -122,6 +123,30 @@ def _systems_outside_profile(path: Path, document: object) -> list[str]:
return out
def _group_requirement_split(path: Path, document: object) -> list[str]:
"""A group whose members disagree on required.
The members of a group are one need, so required states the set's and
reads the same on each. A true on the first name only wrote the search
order into required, and the other names read as optional files.
"""
from alternatives import groups_of
if not isinstance(document, dict):
return []
seen: dict[str, set[bool]] = {}
for entry in document.get("files") or []:
if not isinstance(entry, dict):
continue
for group in groups_of(entry):
seen.setdefault(group, set()).add(bool(entry.get("required", True)))
return [
f"{path.relative_to(ROOT)}: group {group} mixes required true and false"
for group, values in sorted(seen.items())
if len(values) > 1
]
def _checks_without_values(path: Path, document: object) -> list[str]:
"""A check an entry declares with nothing to compare against.