diff --git a/docs/re/METHOD.md b/docs/re/METHOD.md index 7848975..eb07535 100644 --- a/docs/re/METHOD.md +++ b/docs/re/METHOD.md @@ -585,3 +585,16 @@ agent's loop prompt, i.e. nowhere durable. See [`README.md`](README.md) for the with the capture". Each step is a filter that costs one query and changes the number by more than an order of magnitude in total. A raw count is almost never the number a consumer needs. +* **Nothing was checking that the docs' cited evidence exists.** A sweep of every + relative link under `docs/` found **16 broken**, and two of them were the + figures backing the UI layout decode's headline claim — the port's foundation, + unreachable from its own page, because a path in `structures/` was written one + directory too shallow. Eleven were wrong relative depth with the target present; + five name files that do not exist. Evidence that cannot be opened is not + evidence, and a link is exactly the kind of thing no one re-reads. + `tools/re-capture/doc_link_check.py` now does it, and also flags targets that + resolve to a **zero-byte** file — which looks correct in every listing. +* **Repair in bulk only when the counts pair.** The fixer rewrote 11 links; the + checker went from 1 038 resolving / 16 missing to 1 049 / 5. +11 and −11 + against 11 edits is the confirmation that the pass did what it said and touched + nothing else. A bulk edit without that arithmetic is a hope. diff --git a/docs/re/data/doc-link-audit.txt b/docs/re/data/doc-link-audit.txt new file mode 100644 index 0000000..5c8c305 --- /dev/null +++ b/docs/re/data/doc-link-audit.txt @@ -0,0 +1,10 @@ +# tools/re-capture/doc_link_check.py -- 2026-08-29, after the repair pass + +1050 link(s) resolve + +5 MISSING target(s): + docs/re/autopilot-knowledge-sources.md -> ../../MEMORY.md + docs/re/challenge-mission-gate.md -> ../../../xenia-canary-native/src/xenia/hid/file/file_input_driver.h + docs/re/entities-live-roster.md -> ../../MEMORY.md + docs/re/mission-freeze-resume-spin.md -> canary-build-verified-env-confound.md + docs/re/stage-drift-is-navigation-not-save.md -> structures/weapon-datasheet-runtime.md diff --git a/docs/re/doc-link-audit.md b/docs/re/doc-link-audit.md new file mode 100644 index 0000000..26d8162 --- /dev/null +++ b/docs/re/doc-link-audit.md @@ -0,0 +1,50 @@ +# ✅ Do the docs' cited artifacts exist? — 11 broken links repaired + +**Status:** ✅ checked mechanically and fixed, with the count verified both ways. + +The brief's rule is *"commit the reference data beside the finding, so the port +can be built without a disc in the loop"*. An answer whose evidence is not +reachable cannot be used. Nothing had ever checked that. + +[`tools/re-capture/doc_link_check.py`](../../tools/re-capture/doc_link_check.py) +walks every markdown file under `docs/`, resolves each relative link, and reports +targets that do not exist — and, separately, targets that exist but are **empty**, +which is the sneakier failure since a zero-byte file looks fine in any listing. + +| | before | after | +|---|---|---| +| links resolving | 1 038 | **1 049** | +| missing targets | **16** | 5 | +| empty targets | 0 | 0 | + +**+11 resolving, −11 missing — the two numbers pair exactly**, which is the check +that the repair did what it claimed and nothing else. + +## 🔴 Two of them were the evidence for the UI decode itself + +`structures/ui-rat-layout.md` is the layout decode the port is built on. Its two +figures — the ones backing *"the tutorial PAUSE menu rebuilds pixel-accurately +from its sprites"* and *"the same method reproduces the main menu"* — were +written as `captures/ui-layout/…` from a file in `structures/`, one directory +too shallow. **The headline evidence for the decode was unreachable from its own +document.** + +## What was wrong, and what still is + +Eleven links had the **wrong relative depth** while their targets existed — a +missing or surplus `../`, or a missing `structures/`. Those are repaired; each +was rewritten only when exactly one candidate path resolved, so nothing was +guessed. + +❔ **Five remain genuinely absent** and are left alone rather than invented: + +| doc | target | +|---|---| +| `autopilot-knowledge-sources.md`, `entities-live-roster.md` | `../../MEMORY.md` — outside the repo | +| `challenge-mission-gate.md` | a header in the separate `xenia-canary-native` tree | +| `stage-drift-is-navigation-not-save.md` | `structures/weapon-datasheet-runtime.md` — never written | +| `mission-freeze-resume-spin.md` | `canary-build-verified-env-confound.md` — never written | + +None is port-relevant: they are mission, entity and emulator-side documents. Two +name documents that do not exist, which is a different problem from a bad path +and is not something to paper over with a link fix. diff --git a/docs/re/entities-live-roster.md b/docs/re/entities-live-roster.md index a6401b5..c068c25 100644 --- a/docs/re/entities-live-roster.md +++ b/docs/re/entities-live-roster.md @@ -79,7 +79,7 @@ looks stuck on the main menu while it is in fact three screens further on. ## Still open -* The **world-unit measurement** ([collisionset](collisionset.md)) — positions +* The **world-unit measurement** ([collisionset](structures/collisionset.md)) — positions in world units against the HUD's own distance readout. This run reached the mission but the flight HUD was not yet up (green 0.03 %, vs 1.3–1.5 % in flight), so no distance readout was available to compare against. diff --git a/docs/re/mission-freeze-resume-spin.md b/docs/re/mission-freeze-resume-spin.md index 5a51557..65f1a1c 100644 --- a/docs/re/mission-freeze-resume-spin.md +++ b/docs/re/mission-freeze-resume-spin.md @@ -264,7 +264,7 @@ and the boot never happens. A Stage 02 run froze after ~4 minutes of flight (screen still `flight`, not GAME OVER), and `gdb_bt.sh` took backtraces of all **79** threads -([`captures/stage02-freeze-gdb-backtraces.txt`](../captures/stage02-freeze-gdb-backtraces.txt)). +([`captures/stage02-freeze-gdb-backtraces.txt`](captures/stage02-freeze-gdb-backtraces.txt)). **Every single one is in a wait.** Not one thread is executing guest code or sitting in a xenia loop: @@ -394,7 +394,7 @@ and the control are all in place; what is missing is one frozen sample. The fourth run froze **9 seconds** into the watcher's window, in flight (`freeze_watch.sh` confirmed the HUD was still on screen), and the probe built for exactly this moment reported **the healthy-run baseline and nothing else** -([`captures/stage02-freeze-stuck-wait-probe.txt`](../captures/stage02-freeze-stuck-wait-probe.txt)): +([`captures/stage02-freeze-stuck-wait-probe.txt`](captures/stage02-freeze-stuck-wait-probe.txt)): ``` FROZEN IN FLIGHT at 9s diff --git a/docs/re/script-runtime-probe.md b/docs/re/script-runtime-probe.md index 2855080..8e709ee 100644 --- a/docs/re/script-runtime-probe.md +++ b/docs/re/script-runtime-probe.md @@ -146,7 +146,7 @@ the encoding: **1 = not yet deployed, 2 = active, 4 = destroyed**. ### ✅ The mission-over branch, observed exactly as disassembled The phase ended at 694.9 s, but **the ordinal did not advance** — and the reason -is the branch [mission-phase-advance](../mission-phase-advance.md) read out of +is the branch [mission-phase-advance](mission-phase-advance.md) read out of `sub_82260710`: ``` diff --git a/docs/re/structures/isl-message-dialogue-link.md b/docs/re/structures/isl-message-dialogue-link.md index a0cfe5c..87168c4 100644 --- a/docs/re/structures/isl-message-dialogue-link.md +++ b/docs/re/structures/isl-message-dialogue-link.md @@ -82,7 +82,7 @@ names had no text to resolve *to*, and the 100 % figure was unreachable. * **Which recording plays.** The caption is the *text*; the voice bank binding is a separate and still-unresolved question — see - [voice-bank-leading-region.md](voice-bank-leading-region.md) and the + [voice-bank-leading-region.md](../voice-bank-leading-region.md) and the known case of a generic line playing against a specific subtitle. * **Which page a `MSG_DEMO_*` id belongs to.** The `DEMO` family is not called from the stage scripts at all — 78 of its ids are multi-page, so something diff --git a/docs/re/structures/mission-objective-counter.md b/docs/re/structures/mission-objective-counter.md index 631a3fc..3bc9d3f 100644 --- a/docs/re/structures/mission-objective-counter.md +++ b/docs/re/structures/mission-objective-counter.md @@ -270,7 +270,7 @@ result of the two. ## ✅ 2026-08-23 (third pass) — the counter is at `0xbdb59668` again, and the refutation above is *refined*, not reversed Run 4, with the hunt automated end to end -([`ob_hunt.py`](../../tools/re-capture/ob_hunt.py) + the HUD reader), produced +([`ob_hunt.py`](../../../tools/re-capture/ob_hunt.py) + the HUD reader), produced **exactly one** surviving address: ``` @@ -322,7 +322,7 @@ With the per-entity searches refuted at both word and bit level counter. It sits at `0xbdb59668`, inside the entity-heap window, so the answer is readable directly: sample ±0x200 around it across one transition and keep the words that move **with** it -([`ob_neighbourhood.py`](../../tools/re-capture/ob_neighbourhood.py), +([`ob_neighbourhood.py`](../../../tools/re-capture/ob_neighbourhood.py), [`captures/ob-counter-neighbourhood-stage02.json`](../captures/ob-counter-neighbourhood-stage02.json)). Control first: over an 8-second interval while the counter sat still, **0 of the diff --git a/docs/re/structures/stage-settings-table.md b/docs/re/structures/stage-settings-table.md index 242cf7e..4816673 100644 --- a/docs/re/structures/stage-settings-table.md +++ b/docs/re/structures/stage-settings-table.md @@ -40,7 +40,7 @@ resolves all twenty-four: | `stage\StageParameter_Test.tbl` | 1 — the developer stage | **That is exactly the 24, and it explains 24 against the 29 stage records** -([`challenge-mission-gate.md`](challenge-mission-gate.md) counts 29: +([`challenge-mission-gate.md`](../challenge-mission-gate.md) counts 29: 16 story + 6 tutorial + 6 challenge + `Test`): the six tutorials do not get one table each, they **share a single `_Tutorial` table**, and `_Test` accounts for the last. 16 + 6 + 1 + 1 = 24. diff --git a/docs/re/structures/ui-rat-layout.md b/docs/re/structures/ui-rat-layout.md index ed256cc..8a607eb 100644 --- a/docs/re/structures/ui-rat-layout.md +++ b/docs/re/structures/ui-rat-layout.md @@ -4,7 +4,7 @@ **reassembled from the disc**: the tutorial PAUSE menu rebuilds pixel-accurately from its sprites placed at the coordinates in their `.rat` records — no fitting, no manual nudging. -![real vs rebuilt](captures/ui-layout/pause-tutorial-real-vs-rebuilt.png) +![real vs rebuilt](../captures/ui-layout/pause-tutorial-real-vs-rebuilt.png) *Left: the running game (Canary screenshot). Right: rebuilt from `GP_PAUSE_MENU.pak` alone. The remaining differences are the animated frame/glow sprites (`*eff*`) that were not @@ -157,7 +157,7 @@ game, twice, in that order — the records were never fitted to the picture. The same method run against `GP_TITLE.pak` reproduces the **main menu**, which is a different screen with a different item count and a different pitch: -![main menu real vs rebuilt](captures/ui-layout/title-mainmenu-real-vs-rebuilt.png) +![main menu real vs rebuilt](../captures/ui-layout/title-mainmenu-real-vs-rebuilt.png) `ptbtn01..05.rat` give X = 542 for all five and Y = 162 / 242 / 322 / 402 / 482 — an **80 px** pitch, where the pause menu used 70. Measured against the screenshot, the sprite diff --git a/docs/re/structures/xbg7-mesh.md b/docs/re/structures/xbg7-mesh.md index 90ca236..9beaf2a 100644 --- a/docs/re/structures/xbg7-mesh.md +++ b/docs/re/structures/xbg7-mesh.md @@ -507,7 +507,7 @@ run scan that builds the candidate list — which does not emit a start for thes resources' real vertex buffers. That is where the remaining 51 live. The ignored test -[`mesh_consistency_disc.rs`](../../crates/sylpheed-formats/tests/mesh_consistency_disc.rs) +[`mesh_consistency_disc.rs`](../../../crates/sylpheed-formats/tests/mesh_consistency_disc.rs) still asserts the target state and now records 63 rather than 125; the remaining cases are where the neighbourhood is itself wrong or absent. diff --git a/tools/re-capture/doc_link_check.py b/tools/re-capture/doc_link_check.py new file mode 100755 index 0000000..2b9ab2b --- /dev/null +++ b/tools/re-capture/doc_link_check.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python3 +"""Do the files the docs cite actually exist in the repo? + +An answer whose evidence is not committed cannot be used by anyone without a +disc and an emulator, which is the whole point of the reference data. This walks +every markdown file under docs/ and resolves each relative link, reporting the +ones that point at nothing. + +Skips external links (http, mailto) and pure anchors. Reports missing targets +and, separately, committed-but-EMPTY files, which are the sneakier failure -- +a link that resolves to a zero-byte file looks fine in every listing. + + doc_link_check.py [docs-root] +""" +import os, re, sys + +ROOT = sys.argv[1] if len(sys.argv) > 1 else "docs" +LINK = re.compile(r"\[[^\]]*\]\(([^)\s]+)\)") + +missing, empty, ok = [], [], 0 +for dirpath, _dirs, files in os.walk(ROOT): + for f in files: + if not f.endswith(".md"): + continue + src = os.path.join(dirpath, f) + try: + body = open(src, encoding="utf-8").read() + except Exception: + continue + for target in LINK.findall(body): + if target.startswith(("http://", "https://", "mailto:", "#")): + continue + path = os.path.normpath(os.path.join(dirpath, target.split("#")[0])) + if not path: + continue + if not os.path.exists(path): + missing.append((src, target)) + elif os.path.isfile(path) and os.path.getsize(path) == 0: + empty.append((src, target)) + else: + ok += 1 + +print(f"{ok} link(s) resolve") +if missing: + print(f"\n{len(missing)} MISSING target(s):") + for s, t in sorted(missing): + print(f" {s} -> {t}") +if empty: + print(f"\n{len(empty)} link(s) resolve to an EMPTY file:") + for s, t in sorted(empty): + print(f" {s} -> {t}") +sys.exit(1 if (missing or empty) else 0)