diff --git a/docs/re/README.md b/docs/re/README.md index 9d0f7398..19affd03 100644 --- a/docs/re/README.md +++ b/docs/re/README.md @@ -18,6 +18,22 @@ A wrong-but-confident note is worse than no note: someone builds on it and the b for weeks. Every entry therefore carries an explicit **confidence** and its **evidence**. This mirrors the project method — *measure the oracle, never infer; refute before believing.* +### Captures are local-only (issue #49) + +The repository carries **code, tooling and docs**. Screenshots and savegame +blobs are game-derived, so since 2026-09-19 they live in `docs/re/captures/` +on disk and are **gitignored** — the pages' relative links still resolve on a +machine that has them, and nothing ships. + +Derived measurements (`csv`, `tsv`, `txt`, `log`, `json`, `jsonl`, `npy`) are +our own numbers rather than game content, and stay tracked — they are what most +claims here actually rest on. + +`tools/re/check-capture-citations` enforces both halves: a cited capture must be +**present**, and a game asset must **not be tracked**. ⚠️ A fresh clone has no +captures, so its citations will not resolve until the captures are copied in; +that is expected, and the checker is a local gate rather than a CI one. + ### Clean-room firewall - ✅ Allowed: behaviour descriptions, field offsets/types, formulas, state machines, diff --git a/docs/re/captures/.gitignore b/docs/re/captures/.gitignore new file mode 100644 index 00000000..171e6d69 --- /dev/null +++ b/docs/re/captures/.gitignore @@ -0,0 +1,13 @@ +# Game-derived assets stay on disk and ship nowhere (issue #49). +# +# The pages' relative links still resolve on a machine that has the captures, +# so the evidence stays followable where it exists — it is simply not committed. +# `tools/re/check-capture-citations` enforces both halves: a cited capture must +# be PRESENT here, and an asset must NOT be tracked. +# +# Derived measurements (csv, tsv, txt, log, json, jsonl, npy) are our own +# numbers rather than game content, and remain tracked. +*.png +*.jpg +*.jpeg +*.bin diff --git a/tools/re-capture/.gitignore b/tools/re-capture/.gitignore new file mode 100644 index 00000000..ed6a92f8 --- /dev/null +++ b/tools/re-capture/.gitignore @@ -0,0 +1,3 @@ + +# Game-derived digit templates for ob_read.py — pixels from the game (issue #49). +ob_digits.png diff --git a/tools/re/check-capture-citations b/tools/re/check-capture-citations index e691e7c8..9e755bec 100755 --- a/tools/re/check-capture-citations +++ b/tools/re/check-capture-citations @@ -10,10 +10,17 @@ largest thing in the repository, and the one place a file can be added, never cited, and never noticed. -Two failures, which are opposites and must not be conflated: +⚠️ 2026-09-19, issue #49: **game assets are no longer committed.** Screenshots +and savegame blobs live in the working tree and are gitignored, so the pages' +relative links still resolve on a machine that has them while nothing ships. +That inverts half of this check — "is it committed?" became "is it PRESENT?", +and a NEW failure appeared: an asset that IS tracked. Both are below. - * a page cites a capture that **is not committed** — a reader following it +Three failures, which are opposites and must not be conflated: + + * a page cites a capture that **is not present** — a reader following it gets nothing. That is an error, exactly as in `check-citations`. + * a game asset that **is tracked by git** — issue #49 says it must not be. * a capture that **no page cites** — not an error. It may be evidence a page should have cited, and deleting on that basis would silently ratify the omission. Reported, counted, never failed on. @@ -65,7 +72,16 @@ def committed() -> tuple[set[str], set[str]]: proposed. `tools/port/check-citations` uses the working tree for exactly this reason; so does this. """ - files = {l for l in git("ls-files", ROOT).splitlines() if l} + # 🔴 NOT `git ls-files`: since #49 the assets are deliberately untracked, so + # the index no longer knows they exist. Presence is a question about the + # working tree, and asking git would report every screenshot as missing and + # fail on all 203 citations. + files = set() + for dirpath, _dirnames, filenames in os.walk(ROOT): + for fn in filenames: + if fn == ".gitignore": + continue + files.add(os.path.join(dirpath, fn).replace(os.sep, "/")) dirs = set() for f in files: parts = f.split("/") @@ -74,6 +90,24 @@ def committed() -> tuple[set[str], set[str]]: return files, dirs +ASSET_SUFFIXES = (".png", ".jpg", ".jpeg", ".bin") + + +def tracked_assets() -> list[str]: + """Game assets that are committed — forbidden since issue #49. + + The repo carries code, tooling and docs. A screenshot that sneaks back in + is invisible in review (a binary shows as "Bin 0 -> 1234567 bytes") and is + permanent once merged, because removing it later needs a history rewrite. + So this is the half of the check that has to be loud. + """ + out = [] + for l in git("ls-files").splitlines(): + if l.lower().endswith(ASSET_SUFFIXES): + out.append(l) + return out + + def cited() -> set[str]: raw = git("grep", "-rhoE", CITE_ERE, "--", *SEARCH, SELF).splitlines() out = set() @@ -141,7 +175,7 @@ def selftest() -> int: real_dir = next(iter(dirs), None) real_file = next(iter(files), None) if not real_dir or not real_file: - print("selftest: 🔴 no captures committed — nothing to test against") + print("selftest: 🔴 no captures present — nothing to test against") return 2 cases = [ ("planted dangling caught", "docs/re/captures/no-such-file-anywhere.png", False), @@ -168,7 +202,15 @@ def selftest() -> int: print(" %-34s %s" % ("own fixtures not counted", "🔴 FAILED" if fixture_counted else "ok")) - ok = gathering_ok and not fixture_counted + # 🔴 THE #49 RULE NEEDS ITS OWN TICK. Presence now comes from the working + # tree, so a checkout with the captures present looks identical whether or + # not they are tracked — only this assertion can tell the difference. + stowaways = tracked_assets() + print(" %-34s %s%s" + % ("no game asset tracked", "ok" if not stowaways else "🔴 FAILED", + "" if not stowaways else " (%d tracked)" % len(stowaways))) + + ok = gathering_ok and not fixture_counted and not stowaways for name, path, want in cases: got = resolves(path.rstrip(".,;:)`"), files, dirs) mark = "ok" if got == want else "🔴 FAILED" @@ -188,19 +230,33 @@ def main() -> int: print(f) return 0 - print("captures committed : %d" % len(files)) + stowaways = tracked_assets() + print("captures present on disk : %d" % len(files)) print(" cited by a page or a tool : %d" % (len(files) - len(orphans))) print(" cited by nothing : %d (reported, not failed —" % len(orphans)) print(" an orphan may be evidence a page owes)") + rc = 0 if dangling: - print(" 🔴 cited but NOT committed: %d" % len(dangling)) + print(" 🔴 cited but NOT present : %d" % len(dangling)) for d in dangling: print(" %s" % d) - print("\n🔴 a reader following those gets nothing. Commit the capture, fix the") + print("\n🔴 a reader following those gets nothing. Restore the capture, fix the") print(" path, or drop the citation.") - return 1 - print(" 🔴 cited but NOT committed: 0") - return 0 + rc = 1 + else: + print(" 🔴 cited but NOT present : 0") + + if stowaways: + print(" 🔴 game assets TRACKED : %d (issue #49 — code, tooling, docs only)" % len(stowaways)) + for a in stowaways[:10]: + print(" %s" % a) + if len(stowaways) > 10: + print(" … and %d more" % (len(stowaways) - 10)) + print("\n🔴 run `git rm --cached` on those; they stay on disk and stay ignored.") + rc = 1 + else: + print(" 🔴 game assets TRACKED : 0") + return rc if __name__ == "__main__":