diff --git a/README.md b/README.md index ed1f62cc9..3b4d52cbd 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ First time? Follow the [first install, step by step](https://abdess.github.io/re ## Download BIOS packs -A pack holds everything its platform runs: its own BIOS list plus every file its emulator cores load. Pick your platform, download the ZIP, extract to the BIOS path. Each Download link is a page that keeps its address from one release to the next. The installer above does the same file by file: its screen installs everything, or fewer under **Choose systems**, and `--system`, `--core`, `--region` and `--target` make the same choice without questions. For a bare minimum, build your own pack below. +A pack holds its platform's own BIOS list plus the files its emulator cores load. What no pack carries yet is in the [gap analysis](https://abdess.github.io/retrobios/gaps/). Pick your platform, download the ZIP, extract to the BIOS path. Each Download link is a page that keeps its address from one release to the next. The installer above does the same file by file: its screen installs everything, or fewer under **Choose systems**, and `--system`, `--core`, `--region` and `--target` make the same choice without questions. For a bare minimum, build your own pack below. A pack over 2 GB comes in several parts (`Pack.part1of2.zip`, `Pack.part2of2.zip`), and every part is needed. Each part is an ordinary ZIP: extract them all into the same folder. @@ -208,4 +208,4 @@ The scripts and tooling are released under the [MIT License](LICENSE). The BIOS and firmware files are not covered by that license: they are third-party system software, preserved and provided for personal backup, archival, and interoperability with emulation software. [NOTICE](NOTICE) sets out their status and how to ask for a file to be removed. The reasoning, and where it is weakest, is in [the project's legal position](https://abdess.github.io/retrobios/wiki/legal/). -*Auto-generated on 2026-11-06T05:38:30Z* +*Auto-generated on 2026-11-06T07:24:52Z* diff --git a/scripts/generate_readme.py b/scripts/generate_readme.py index 5418e4889..85b5a9ad1 100644 --- a/scripts/generate_readme.py +++ b/scripts/generate_readme.py @@ -608,7 +608,7 @@ def generate_readme(db: dict, platforms_dir: str, db_path: str = "database.json" "", "## Download BIOS packs", "", - "A pack holds everything its platform runs: its own BIOS list plus every file its emulator cores load. Pick your platform, download the ZIP, extract to the BIOS path. Each Download link is a page that keeps its address from one release to the next. The installer above does the same file by file: its screen installs everything, or fewer under **" + _installer_words().TUI_SYSTEMS_ROW + "**, and `--system`, `--core`, `--region` and `--target` make the same choice without questions. For a bare minimum, build your own pack below.", + f"A pack holds its platform's own BIOS list plus the files its emulator cores load. What no pack carries yet is in the [gap analysis]({SITE_URL}gaps/). Pick your platform, download the ZIP, extract to the BIOS path. Each Download link is a page that keeps its address from one release to the next. The installer above does the same file by file: its screen installs everything, or fewer under **" + _installer_words().TUI_SYSTEMS_ROW + "**, and `--system`, `--core`, `--region` and `--target` make the same choice without questions. For a bare minimum, build your own pack below.", "", "A pack over 2 GB comes in several parts (`Pack.part1of2.zip`," " `Pack.part2of2.zip`), and every part is needed. Each part is an" diff --git a/scripts/release_notes.py b/scripts/release_notes.py index cf3903cae..dcf3613f9 100644 --- a/scripts/release_notes.py +++ b/scripts/release_notes.py @@ -7,7 +7,8 @@ issue 81 happened. Install commands, the parts sentence and the table come from the record and the registry. Each row links the platform's page under get/ on the site, which outlives the release, and the setups the registry does not mark release_pack are named with the command. The maintainer writes -what changed, and that text is appended as given. +what changed, and that text is appended as given once it holds none of the +REFUSED phrases. Usage: python scripts/release_notes.py --whats-new tmp/whats-new.md --output notes.md @@ -102,6 +103,20 @@ def _link(name: str, pack: dict) -> str: return f"[{label}]({release_record.get_page(_owner(name, pack), SITE)})" +# Words the published notes of v2026.10.10 carried by hand: "never from a +# forum list" judged lists other people keep, and "a game never stops on a +# missing file" was a claim the gap analysis contradicts. Text about other +# projects says what changed, not who erred (wiki/release-process.md). +REFUSED = ("their file", "theirs", "of ours", "corrected", "wrong", "drift", + "left out", "never seen", "forum", "never stops", "never fails") + + +def refused_words(text: str) -> list[str]: + """The refused phrases the text holds, case ignored.""" + lowered = text.lower() + return [phrase for phrase in REFUSED if phrase in lowered] + + def render(record: dict, platforms_dir: str, whats_new: str) -> str: packs = record.get("packs", {}) folders = _extract_folders(record, platforms_dir) @@ -124,7 +139,8 @@ def render(record: dict, platforms_dir: str, whats_new: str) -> str: "### Download a pack", "", ("One complete pack for each platform below: the BIOS list the platform" - " publishes plus every file its emulators open. Each link is a page" + " publishes plus the files its emulators open. Files no pack carries yet" + f" are listed in the [gap analysis]({SITE}gaps/). Each link is a page" " that keeps its address from one release to the next. Files is what the pack" " holds once extracted, the count a file manager shows. Extracted size" " is the space those files take, and the download is smaller."), @@ -173,6 +189,12 @@ def main() -> int: if not whats_new.strip(): print(f"Error: {args.whats_new} is empty", file=sys.stderr) return 1 + refused = refused_words(whats_new) + if refused: + print(f"Error: {args.whats_new} says {', '.join(repr(w) for w in refused)}. State what" + " changed and how it was checked, not a verdict on other projects or a" + " promise the gap analysis contradicts", file=sys.stderr) + return 1 write_text_atomic(str(args.output), render(record, args.platforms_dir, whats_new)) print(f"Wrote {args.output} for {record['tag']}, {len(record['packs'])} packs") return 0 diff --git a/tests/test_pack_counts.py b/tests/test_pack_counts.py index 461ed4942..e74ae2d14 100644 --- a/tests/test_pack_counts.py +++ b/tests/test_pack_counts.py @@ -11,12 +11,14 @@ installer fetches and leaves out the data directories a pack also carries. from __future__ import annotations import hashlib +import io import json import sys import tempfile import unittest import zipfile from pathlib import Path +from unittest import mock import yaml @@ -552,6 +554,35 @@ class TheReleaseNotesReadTheRecord(unittest.TestCase): def test_the_notes_never_open_on_the_collection(self): self.assertNotIn("collection", self.render().split("### What changed")[0]) + def test_the_notes_claim_only_what_the_gaps_allow(self): + """v2026.10.10 said a pack holds every file its emulators open, "so a + game never stops on a missing file", and that hashes never come + "from a forum list". The gap analysis lists what no pack carries.""" + text = self.render().split("### What changed")[0] + self.assertNotIn("every file its emulators", text) + self.assertIn("https://abdess.github.io/retrobios/gaps/", text) + self.assertEqual(release_notes.refused_words(text), []) + + def test_a_judging_whats_new_is_refused(self): + self.assertEqual( + release_notes.refused_words( + "- Every hash comes from the emulator's source, never from a forum list.\n" + "- So a game never stops on a missing file."), + ["forum", "never stops"]) + with tempfile.TemporaryDirectory(dir=REPO_ROOT / "tmp") as tmp: + whats_new = Path(tmp) / "whats-new.md" + whats_new.write_text("- Hashes never from a forum list\n", encoding="utf-8") + record = Path(tmp) / "release.json" + record.write_text(json.dumps(self.RECORD), encoding="utf-8") + argv = ["release_notes.py", "--record", str(record), "--whats-new", str(whats_new), + "--platforms-dir", str(REPO_ROOT / "platforms"), + "--output", str(Path(tmp) / "notes.md")] + with mock.patch.object(sys, "argv", argv), \ + mock.patch("sys.stderr", new_callable=io.StringIO) as err: + self.assertEqual(release_notes.main(), 1) + self.assertIn("'forum'", err.getvalue()) + self.assertFalse((Path(tmp) / "notes.md").exists()) + def test_the_release_process_runs_it(self): page = (REPO_ROOT / "wiki" / "release-process.md").read_text(encoding="utf-8") self.assertIn("scripts/release_notes.py", page) diff --git a/wiki/release-process.md b/wiki/release-process.md index 250ad6ad7..3c031fab7 100644 --- a/wiki/release-process.md +++ b/wiki/release-process.md @@ -276,7 +276,10 @@ python scripts/generate_site.py # 5. Write the notes: install commands, the parts sentence and the pack table # come from release.json, and what changed since the previous tag is the -# maintainer's own text in tmp/whats-new.md, appended as written. +# maintainer's own text in tmp/whats-new.md, appended as written. The +# script refuses a text that judges another project ("forum list") or +# promises what the gap analysis contradicts ("never stops"): it says +# what changed and how it was checked. python scripts/release_notes.py --whats-new tmp/whats-new.md --output tmp/notes.md # Create the release as a DRAFT, upload every asset, and only then publish it.