diff --git a/scripts/generate_site.py b/scripts/generate_site.py index 68905eb1..d7fc8f67 100644 --- a/scripts/generate_site.py +++ b/scripts/generate_site.py @@ -417,7 +417,7 @@ def generate_home( "[Systems](systems/index.md){ .md-button } " "[Emulators](emulators/index.md){ .md-button } " "[Cross-reference](cross-reference.md){ .md-button } " - "[Gap Analysis](gaps.md){ .md-button } " + "[Gap analysis](gaps.md){ .md-button } " "[Dump provenance](provenance.md){ .md-button } " "[Data & API](data.md){ .md-button } " "[Contributing](contributing.md){ .md-button .md-button--primary }", @@ -3334,7 +3334,7 @@ def _render_problem_files( if total_problems > 0: lines.extend([ - "## Problem Files", + "## Problem files", "", f"{len(missing_files)} missing, {len(untested_files)} untested, " f"{len(mismatch_files)} hash mismatch.", @@ -3374,7 +3374,7 @@ def _render_problem_files( if mismatch_files: lines.extend([ - f'### Hash Mismatch ' + f'### Hash mismatch ' f"{_count(len(mismatch_files), 'file')}", "", "Platform says OK but emulator validation disagrees.", @@ -3476,7 +3476,7 @@ def generate_gap_analysis( ) lines = [ - "# Gap Analysis", + "# Gap analysis", "", "Unified view of BIOS verification, file provenance, and coverage gaps.", "", @@ -3513,7 +3513,7 @@ def generate_gap_analysis( # ---- Verification per platform ---- lines.extend([ - "## What Each Pack Contains", + "## What each pack contains", "", "| Platform | On its BIOS list | Files its emulators load | Checked by |", "|----------|-----------------:|-------------------------:|------------|", @@ -3559,7 +3559,7 @@ def generate_gap_analysis( "from its source code " "([how each one works](wiki/verification-modes.md)).", "", - "## Corroboration Against Emulator Source", + "## Corroboration against emulator source", "", "| Platform | On its BIOS list | Documented in a profile | Content the code checks |", "|----------|-----------------:|------------------------:|------------------------:|", @@ -3623,7 +3623,7 @@ def generate_gap_analysis( ) lines.extend([ - "## Platform Lists vs Emulator Source", + "## Platform lists vs emulator source", "", "Platform file lists are scraped as-is from each upstream project. " "Emulator profiles are read from source code. The two do not always " @@ -3691,7 +3691,7 @@ def generate_gap_analysis( ) lines.extend([ - "## Core Complement", + "## Core complement", "", f"Files loaded by emulators but not declared by any platform. " f"{total_undeclared:,} files across {len(emulator_gaps)} emulators, " @@ -3714,7 +3714,7 @@ def generate_gap_analysis( "| GitHub release assets (large files) |", f"| missing | {src_totals['missing']} | Not available, needs sourcing |", "", - "### Per Emulator", + "### Per emulator", "", "| Emulator | Undeclared | bios | data | release | Missing |", "|----------|----------:|-----:|-----:|--------:|--------:|", @@ -3792,7 +3792,7 @@ def generate_gap_analysis( if src_missing_details: req_src = [m for m in src_missing_details if m["required"]] lines.extend([ - f"### Files to Source ({_count(len(src_missing_details), 'file')}, " + f"### Files to source ({_count(len(src_missing_details), 'file')}, " f"{len(req_src)} required" + ( f", {_count(entries, 'entry', 'entries')}: " @@ -3856,7 +3856,7 @@ def generate_gap_analysis( if all_unsourceable: lines.extend([ - "## Acknowledged Gaps", + "## Acknowledged gaps", "", f"{len(all_unsourceable)} files documented as unsourceable " "(verified from source code).", @@ -4494,7 +4494,7 @@ def generate_mkdocs_nav( {"Systems": system_nav}, {"Emulators": emu_nav}, {"Cross-reference": "cross-reference.md"}, - {"Gap Analysis": "gaps.md"}, + {"Gap analysis": "gaps.md"}, {"Dump provenance": "provenance.md"}, {"Data & API": "data.md"}, {"Wiki": wiki_nav}, diff --git a/tests/test_prose_style.py b/tests/test_prose_style.py index f91396e6..0b0b7f2c 100644 --- a/tests/test_prose_style.py +++ b/tests/test_prose_style.py @@ -111,6 +111,37 @@ def _yaml_prose(text: str) -> str: return "\n".join(parts) +# Words a heading keeps capitalized: names of platforms, systems and programs. +_HEADING_KEEP = set("""Batocera Recalbox Lakka Dolphin Redump Linux Windows Android Python Steam Deck +macOS Termux Flatpak Switch Raspberry Pi Mega Drive Genesis Saturn Dreamcast Amiga Atari Sega +Nintendo Sony Neo Geo Lynx Jaguar Commodore Apple Mac Spectrum Sinclair Amstrad MSX GameCube Wii +Game Boy Advance Famicom Disk System Super Master PlayStation Vita Xbox Arcade English French +Spanish I Lua Unity Qt Kodi Discord Pages Actions Docker Windows Mac Ubuntu Debian Arch +SteamOS Chihiro NAOMI Triforce Lindbergh Model PinMAME ScummVM Dinothawr Cemu Citra Yuzu Ryujinx +Mednafen Beetle Stella Hatari Caprice Vice Fuse Daphne Hypseus Singe Supermodel Flycast Redream +DuckStation SwanStation Opera Gearsystem Picodrive Snes9x Nestopia Mesen Gambatte SameBoy +mGBA melonDS DeSmuME Azahar Eden Sudachi Torzu Xemu Cxbx PCSX2 AetherSX2 NetherSX2 ARMSX2 +RPCS3 Vita3K Zinc Raine FBNeo FinalBurn Neo MAME Clrmamepro RomVault""".split()) + + +def _sentence_case(title: str) -> str: + words = title.split(" ") + out = [] + starts = True + for w in words: + core = w.strip("()[],:.") + if starts: + # The first word of a heading or of a clause after ":", "." or " - ". + head, sep, tail = core.partition("-") + if sep and re.fullmatch(r"[A-Z][a-z]+", tail) and tail not in _HEADING_KEEP: + w = w.replace(core, f"{head}-{tail.lower()}", 1) + elif re.fullmatch(r"[A-Z][a-z]+(-[A-Z]?[a-z]+)*", core) and core not in _HEADING_KEEP: + w = w.replace(core, core.lower(), 1) + out.append(w) + starts = w.endswith((":", ".")) or w == "-" or re.fullmatch(r"\d+[a-z]?\.?", w) is not None + return " ".join(out) + + class NoGeneratedTypography(unittest.TestCase): def test_no_dash_arrow_ellipsis_or_banner_glyphs(self): offenders = [] @@ -187,6 +218,17 @@ class NoClauseSemicolons(unittest.TestCase): ] self.assertEqual(offenders, [], "\n".join(offenders[:20])) + def test_wiki_headings_use_sentence_case(self): + """The gaps page and two wiki pages titled in Title Case ("What Each + Pack Contains") beside sentence-case pages ("Quick install").""" + offenders = [] + for page in sorted((ROOT / "wiki").glob("*.md")): + text = FENCED.sub("", page.read_text(encoding="utf-8")) + for title in re.findall(r"(?m)^#{1,4} (.+)$", text): + if _sentence_case(title) != title: + offenders.append(f"{page.name}: {title}") + self.assertEqual(offenders, [], "\n".join(offenders)) + def test_yaml_prose_and_comments(self): files = [p for p in _tracked() if p.suffix in (".yml", ".yaml")] self._check(files, _yaml_prose) diff --git a/wiki/advanced-usage.md b/wiki/advanced-usage.md index 4c134824..60f3ae7e 100644 --- a/wiki/advanced-usage.md +++ b/wiki/advanced-usage.md @@ -1,8 +1,8 @@ -# Advanced Usage +# Advanced usage Fine-grained control over pack generation, hardware filtering, truth analysis, and verification. -## Custom Packs +## Custom packs ### Build from hash @@ -188,7 +188,7 @@ under the name the platform files the target by, whichever alias was typed: never overwrite the full one. -## Pack Source Variants +## Pack source variants A pack is built from two file sources: the platform's own declared list (layer 1) and the requirements of the emulator profiles that apply to it @@ -223,7 +223,7 @@ visible. Variants compose with `--split`, `--target` and `--manifest`. -## Hardware Target Filtering +## Hardware target filtering ### What targets are @@ -291,7 +291,7 @@ treat `--target ` as a no-op: the output is identical to runn without `--target`. -## Truth Generation and Diffing +## Truth generation and diffing ### What truth is @@ -360,7 +360,7 @@ makes md5 and core required on every element, and RomM compares the file size before any hash, so an entry missing either could never verify. -## Emulator-Level Verification +## Emulator-level verification ### Per-emulator checks @@ -410,7 +410,7 @@ emulator profiles. Files without ground truth rely solely on platform-level veri JSON output (`--json`) always includes the full per-emulator detail regardless of verbosity. -## Offline Workflow +## Offline workflow ### Full offline pipeline diff --git a/wiki/community-tools.md b/wiki/community-tools.md index 4411b86b..a0f04e2c 100644 --- a/wiki/community-tools.md +++ b/wiki/community-tools.md @@ -4,7 +4,7 @@ Projects built on RetroBIOS data. --- -## BIOS Preservation Tool +## BIOS preservation tool **Author:** [monster-penguin](https://github.com/monster-penguin) **Repository:** [github.com/monster-penguin/BIOS-Preservation-Tool](https://github.com/monster-penguin/BIOS-Preservation-Tool) diff --git a/wiki/release-process.md b/wiki/release-process.md index 6da9edae..043b9f3e 100644 --- a/wiki/release-process.md +++ b/wiki/release-process.md @@ -1,4 +1,4 @@ -# Release Process +# Release process This page documents the CI/CD pipeline: what each workflow does, how releases are built, and how to run the process manually. @@ -23,7 +23,7 @@ maintainer's machine and uploaded with `gh`, see [cutting a release](#cutting-a-release): the packs weigh 25 GB, more than a hosted runner should rebuild and re-upload. -## deploy-site.yml - Deploy Documentation Site +## deploy-site.yml - Deploy documentation site **Trigger.** Push to `main` when any of these paths change: `platforms/`, `emulators/`, `provenance/`, `wiki/`, `schemas/`, `tests/`, `docs_assets/`, diff --git a/wiki/testing-guide.md b/wiki/testing-guide.md index 388b432f..5a952c00 100644 --- a/wiki/testing-guide.md +++ b/wiki/testing-guide.md @@ -1,4 +1,4 @@ -# Testing Guide +# Testing guide This page covers how to run, understand, and extend the test suite. diff --git a/wiki/verification-modes.md b/wiki/verification-modes.md index 451445ce..951718af 100644 --- a/wiki/verification-modes.md +++ b/wiki/verification-modes.md @@ -1,10 +1,10 @@ -# Verification Modes +# Verification modes Each platform verifies BIOS files differently. `verify.py` replicates the native behavior of each platform so that verification results match what the platform itself would report. -## Existence Mode +## Existence mode **Platforms**: RetroArch, Lakka, RetroPie @@ -26,7 +26,7 @@ Lakka and RetroPie inherit this behavior through platform config inheritance (`inherits: retroarch` in the platform YAML). -## MD5 Mode +## MD5 mode **Platforms**: Batocera, RetroBat, Recalbox, EmuDeck, RetroDECK, RomM, ROCKNIX, MiSTer FPGA @@ -127,7 +127,7 @@ The 208 `games/` files shipped by the `Distribution_MiSTer` repository carry no URL and install themselves, so they are not part of the pack. -## SHA1 Mode +## SHA1 mode **Platforms**: BizHawk @@ -141,7 +141,7 @@ via `compute_hashes()` and compares case-insensitively. | File missing | MISSING | CRITICAL | WARNING | -## Emulator-Level Validation +## Emulator-level validation Independent of platform verification mode, `verify.py` runs emulator-level validation from `validation.py`. This layer uses data from emulator profiles (YAML files in @@ -199,7 +199,7 @@ This catches cases where a file has the right name but wrong content, which exis platforms cannot detect. -## Severity Matrix +## Severity matrix `compute_severity()` maps the combination of status, required flag, verification mode, and HLE fallback to a severity level. @@ -221,7 +221,7 @@ is still included in packs (better accuracy with the real BIOS), but its absence actionable. -## File Resolution Chain +## File resolution chain Before verification, each file entry is resolved to a local path by `resolve_local_file()`. The function tries these steps in order, returning the first @@ -259,7 +259,7 @@ the divergence is printed as a discrepancy: an error in an upstream BIOS list must not remove a file the frontend would have loaded. -## Discrepancy Detection +## Discrepancy detection When platform verification passes but emulator validation fails, the file has a discrepancy. This happens most often in existence-mode platforms where any file with the right name is