feat: publish board sets and sets with no bios

The games index now names, per arcade core, the game sets that load no
BIOS set and every BIOS set, so a set on a card the DAT does not know is
told apart from one that needs nothing. Each board default core of a rom
folder gets its own fallback set, and index.json maps every folder name
a setup uses to its systems, for a games folder on a computer.
This commit is contained in:
Abdessamad Derraz committed 2026-11-03 18:17:03 +01:00
1 parent d955e86a5b
commit b69b33178b
41 files changed
+146 -51

No files matched your search

+48 -11
View File
@@ -311,27 +311,42 @@ def build_setups(
counts["profile"] += 1
if resolution.systems:
counts["resolved"] += 1
def fallback_keys(chosen: str | None) -> list[str]:
"""The fallback sets of the folder's systems under one core,
computed once per platform."""
keys = []
table = fallbacks.setdefault(platform, {})
for system in resolution.systems:
key = f"{system}|{chosen or ''}"
if key not in table:
table[key] = fallback_set(manifest, required[platform], profiles, system, chosen)
keys.append(key)
return keys
sets_known = manifest is not None and bool(resolution.systems)
boards = {}
for board, spec in sorted((row.get("board_defaults") or {}).items()):
boards[board] = {"core": spec["core"]}
if cores.resolve(spec["core"]):
boards[board]["profile"] = cores.resolve(spec["core"])
board_profile = cores.resolve(spec["core"])
if board_profile:
boards[board]["profile"] = board_profile
if board_profile in arcade:
boards[board]["arcade"] = board_profile
if sets_known:
# A card moves between boards, and the installer reads
# it on a computer: each board's core has its set.
boards[board]["fallback"] = fallback_keys(board_profile)
if boards:
published["board_defaults"] = boards
if profile and profile in arcade:
published["arcade"] = profile
elif row.get("arcade"):
published["arcade"] = True
if manifest is not None and resolution.systems:
keys = []
table = fallbacks.setdefault(platform, {})
for system in resolution.systems:
key = f"{system}|{profile or ''}"
if key not in table:
table[key] = fallback_set(manifest, required[platform], profiles, system, profile)
keys.append(key)
if sets_known:
keys = fallback_keys(profile)
published["fallback"] = keys
if any(table[k]["files"] for k in keys):
if any(fallbacks[platform][k]["files"] for k in keys):
counts["fallback"] += 1
published["source_ref"] = row["source_ref"]
folders.append(published)
@@ -476,6 +491,27 @@ def _entry(text: str) -> dict:
}
def folder_systems(setups: dict[str, dict]) -> dict[str, list[str]]:
"""Every folder name a setup gives a system, casefolded, with the systems
any setup reads there, and each system id under its own name.
What a games folder on a computer is read with when its setup names no
folder of its own (RetroArch): a name two setups give two systems keeps
both.
"""
names: dict[str, set[str]] = {}
for doc in setups.values():
for row in doc["folders"]:
systems = row["systems"]
if not systems:
continue
for name in [row["folder"], *(row.get("aliases") or [])]:
names.setdefault(str(name).casefold(), set()).update(systems)
for system in systems:
names.setdefault(system.casefold(), set()).add(system)
return {name: sorted(systems) for name, systems in sorted(names.items())}
def render(output: Path = OUTPUT, inputs: Inputs = Inputs()) -> tuple[dict[Path, str], dict]:
"""Every published file's text, keyed by its path, and the coverage."""
resolver = SystemResolver.from_repo(inputs.emulators, inputs.platforms)
@@ -514,6 +550,7 @@ def render(output: Path = OUTPUT, inputs: Inputs = Inputs()) -> tuple[dict[Path,
containers = _load_yaml(inputs.games / "headers.yml").get("containers")
if containers:
index["containers"] = containers
index["folders"] = folder_systems(setups)
files[output / "index.json"] = dumps(index)
coverage = {"setups": setup_cov, "systems": system_cov, "arcade": sorted(arcade)}
return files, coverage
+7 -1
View File
@@ -21,7 +21,9 @@ when it differs from the name (naomi_cart.cpp:75).
Output: games/arcade/<profile>.json. CHD games are listed, not resolved: a
CHD is game content. A ``romof`` pointing outside the DAT is listed with its
target, never guessed.
target, never guessed. The games that load no BIOS set (``no_bios``) and
every BIOS set (``bios_sets``) are listed too: a set name on a card that
none of the lists holds is one the core's DAT does not know.
"""
from __future__ import annotations
@@ -202,6 +204,8 @@ def build_index(sets: dict[str, SetEntry], marker: str) -> dict:
"devices": {k: sorted(v) for k, v in sorted(devices.items())},
"chd": sorted(n for n, e in games.items() if e.disks),
"missing_parents": dict(sorted(missing.items())),
"no_bios": sorted(set(games) - with_bios - set(missing)),
"bios_sets": sorted(bios_names),
"counts": {
"sets": len(sets),
"games": len(games),
@@ -321,6 +325,8 @@ def flycast_index(source: str) -> dict:
"devices": {},
"chd": sorted(chd),
"missing_parents": dict(sorted(unknown.items())),
"no_bios": [],
"bios_sets": sorted(set(archive_of.values())),
"counts": {
"sets": len(games) + len(archive_of),
"games": len(games),