port: audit every kind label, and seven rested on a neighbour's argument
tools/port/audit-kinds reports what each in authored/ rests on. Nothing had ever checked them, which is the point -- the disciplines that fail this way are the ones that never visibly failed. Seven of fifteen labels, every goto_name_kind, had no of their own. Four scored ok on the first run because the audit fell back to the parent's , which argues the DESTINATION while the label is about where the NAME came from. That is the same error I was corrected for the previous iteration, one level down: crediting a claim with evidence that does not bear on it. Borrowed evidence is now its own outcome, and all seven carry a why citing HANDOFF Q4's own words and stating that the port never branches on the field. The audit refuted itself twice first. It counted only paths, shas and filenames as citations, so HANDOFF Q1 and PORT-MISSION section 7 read as citing nothing -- four false positives, and an audit that invents defects is worse than none because its false positives are indistinguishable from its true ones until each is opened. It also resolved paths against committed refs only, failing on a citation to the tool being written. Both fixed. It still cannot read a cited page to confirm it says what the why claims, and prints that every run. MEASURED and measured both existed; a consumer comparing == measured misses the other, and a label that fails to match reads as ABSENT rather than wrong. Normalised. Refutation attempt on HANDOFF Q2's map of GP_TITLE. The headline survives and is exactly right: 4 UI states + 2 loading variants + 2 boot splashes = 8 states shipped twice = the 16 entries the archive holds, confirmed against my export's entry map. But the row enumerates six of those eight -- entries 10, 11, 13 and 14, publisher_logo and developer_logos, appear nowhere in it. A reader counting Q2 gets twelve, and this is the row already corrected once for an ordinal-versus- entry error, which is the mistake four unlisted entries feed. The port is unaffected; both splashes are exported, named and verified at RMSE 2.17 and 3.05. Every asserting check passes, audit-kinds included. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01N7FiFFFwbvG2uxdcEh8HyF
This commit is contained in:
@@ -114,6 +114,22 @@
|
||||
"goto": "main_menu",
|
||||
"goto_name": "TITLE_MENU",
|
||||
"goto_name_kind": "name match, not measured",
|
||||
"goto_name_why": [
|
||||
"NOT MEASURED, and the label says so. HANDOFF Q4 states it exactly: \"the",
|
||||
"screens are measured; the ids are a name match onto the executable's class",
|
||||
"names.\" So `TITLE_MENU` is a string that exists in the executable and plausibly",
|
||||
"denotes this screen -- nothing observed binds it to this transition.",
|
||||
"",
|
||||
"It is carried so a reader can search for it and so the port never has to",
|
||||
"invent one. THE PORT NEVER BRANCHES ON IT: navigation uses `goto`, which is",
|
||||
"a screen file, and this field is documentation.",
|
||||
"",
|
||||
"🔴 THIS `why` DID NOT EXIST UNTIL 2026-08-30. All seven `goto_name_kind`",
|
||||
"labels rested on a sibling `why` that argues the DESTINATION -- a different",
|
||||
"claim from where the NAME came from. `tools/port/audit-kinds` reports that",
|
||||
"as BORROWED rather than ok, because a label resting on a neighbour's",
|
||||
"argument reads as evidenced and is not."
|
||||
],
|
||||
"why": "MEASURED, HANDOFF: (A) on the title opens the main menu, with (A) on the boot title as the control in the same run."
|
||||
},
|
||||
"on_cancel": null,
|
||||
@@ -182,7 +198,23 @@
|
||||
"goto": "title",
|
||||
"goto_name": "TITLE_SCREEN",
|
||||
"goto_name_kind": "name match, not measured",
|
||||
"kind": "MEASURED",
|
||||
"goto_name_why": [
|
||||
"NOT MEASURED, and the label says so. HANDOFF Q4 states it exactly: \"the",
|
||||
"screens are measured; the ids are a name match onto the executable's class",
|
||||
"names.\" So `TITLE_SCREEN` is a string that exists in the executable and plausibly",
|
||||
"denotes this screen -- nothing observed binds it to this transition.",
|
||||
"",
|
||||
"It is carried so a reader can search for it and so the port never has to",
|
||||
"invent one. THE PORT NEVER BRANCHES ON IT: navigation uses `goto`, which is",
|
||||
"a screen file, and this field is documentation.",
|
||||
"",
|
||||
"🔴 THIS `why` DID NOT EXIST UNTIL 2026-08-30. All seven `goto_name_kind`",
|
||||
"labels rested on a sibling `why` that argues the DESTINATION -- a different",
|
||||
"claim from where the NAME came from. `tools/port/audit-kinds` reports that",
|
||||
"as BORROWED rather than ok, because a label resting on a neighbour's",
|
||||
"argument reads as evidenced and is not."
|
||||
],
|
||||
"kind": "measured",
|
||||
"why": "MEASURED 2026-08-30, delivery-confirmed (B = 0x5801), 73.5 % of pixels changed, and both captures name themselves. Latency <= 0.4 s and NO loading screen in between, which matters because the disc carries four pgloading_* screens. This entry previously read 'likely but UNPROVEN': it had been seen once without a capture, and the title ALSO returns on its own after ~8-10 s idle, so an observer could not tell a response from a timeout. The <= 0.4 s latency is what kills that confound -- it is twenty times faster than the idle return. Decoder 86a8ce7, menu-navigation-semantics.md row 'B on the main menu', docs/re/data/b-on-main-menu.txt."
|
||||
},
|
||||
"buttons": {
|
||||
@@ -191,6 +223,22 @@
|
||||
"goto": null,
|
||||
"goto_name": "DIFFICULTY",
|
||||
"goto_name_kind": "name match, not measured",
|
||||
"goto_name_why": [
|
||||
"NOT MEASURED, and the label says so. HANDOFF Q4 states it exactly: \"the",
|
||||
"screens are measured; the ids are a name match onto the executable's class",
|
||||
"names.\" So `DIFFICULTY` is a string that exists in the executable and plausibly",
|
||||
"denotes this screen -- nothing observed binds it to this transition.",
|
||||
"",
|
||||
"It is carried so a reader can search for it and so the port never has to",
|
||||
"invent one. THE PORT NEVER BRANCHES ON IT: navigation uses `goto`, which is",
|
||||
"a screen file, and this field is documentation.",
|
||||
"",
|
||||
"🔴 THIS `why` DID NOT EXIST UNTIL 2026-08-30. All seven `goto_name_kind`",
|
||||
"labels rested on a sibling `why` that argues the DESTINATION -- a different",
|
||||
"claim from where the NAME came from. `tools/port/audit-kinds` reports that",
|
||||
"as BORROWED rather than ok, because a label resting on a neighbour's",
|
||||
"argument reads as evidenced and is not."
|
||||
],
|
||||
"blocked": "DIFFICULTY is not in this export. MEASURED destination (EASY/NORMAL/HARD/BACK, opening on NORMAL, then SELECT DATA) but it is not a GP_TITLE build, so there is no screen file to go to yet.",
|
||||
"skipped_chain": [
|
||||
"DIFFICULTY",
|
||||
@@ -219,6 +267,22 @@
|
||||
"goto": null,
|
||||
"goto_name": "TUTORIAL_MENU",
|
||||
"goto_name_kind": "name match, not measured",
|
||||
"goto_name_why": [
|
||||
"NOT MEASURED, and the label says so. HANDOFF Q4 states it exactly: \"the",
|
||||
"screens are measured; the ids are a name match onto the executable's class",
|
||||
"names.\" So `TUTORIAL_MENU` is a string that exists in the executable and plausibly",
|
||||
"denotes this screen -- nothing observed binds it to this transition.",
|
||||
"",
|
||||
"It is carried so a reader can search for it and so the port never has to",
|
||||
"invent one. THE PORT NEVER BRANCHES ON IT: navigation uses `goto`, which is",
|
||||
"a screen file, and this field is documentation.",
|
||||
"",
|
||||
"🔴 THIS `why` DID NOT EXIST UNTIL 2026-08-30. All seven `goto_name_kind`",
|
||||
"labels rested on a sibling `why` that argues the DESTINATION -- a different",
|
||||
"claim from where the NAME came from. `tools/port/audit-kinds` reports that",
|
||||
"as BORROWED rather than ok, because a label resting on a neighbour's",
|
||||
"argument reads as evidenced and is not."
|
||||
],
|
||||
"blocked": "The lesson list is not a GP_TITLE build. Destination MEASURED."
|
||||
},
|
||||
"ptbtn04": {
|
||||
@@ -232,6 +296,22 @@
|
||||
"goto": "extras",
|
||||
"goto_name": "EXTRA_MENU",
|
||||
"goto_name_kind": "name match, not measured",
|
||||
"goto_name_why": [
|
||||
"NOT MEASURED, and the label says so. HANDOFF Q4 states it exactly: \"the",
|
||||
"screens are measured; the ids are a name match onto the executable's class",
|
||||
"names.\" So `EXTRA_MENU` is a string that exists in the executable and plausibly",
|
||||
"denotes this screen -- nothing observed binds it to this transition.",
|
||||
"",
|
||||
"It is carried so a reader can search for it and so the port never has to",
|
||||
"invent one. THE PORT NEVER BRANCHES ON IT: navigation uses `goto`, which is",
|
||||
"a screen file, and this field is documentation.",
|
||||
"",
|
||||
"🔴 THIS `why` DID NOT EXIST UNTIL 2026-08-30. All seven `goto_name_kind`",
|
||||
"labels rested on a sibling `why` that argues the DESTINATION -- a different",
|
||||
"claim from where the NAME came from. `tools/port/audit-kinds` reports that",
|
||||
"as BORROWED rather than ok, because a label resting on a neighbour's",
|
||||
"argument reads as evidenced and is not."
|
||||
],
|
||||
"why": "MEASURED, HANDOFF Q4: EXTRAS opens GP_TITLE build 6. It is the ONLY main-menu destination inside this archive, and therefore the only (A)-into-a-submenu the P5 gate can actually walk."
|
||||
}
|
||||
},
|
||||
@@ -258,6 +338,22 @@
|
||||
"goto": "main_menu",
|
||||
"goto_name": "TITLE_MENU",
|
||||
"goto_name_kind": "name match, not measured",
|
||||
"goto_name_why": [
|
||||
"NOT MEASURED, and the label says so. HANDOFF Q4 states it exactly: \"the",
|
||||
"screens are measured; the ids are a name match onto the executable's class",
|
||||
"names.\" So `TITLE_MENU` is a string that exists in the executable and plausibly",
|
||||
"denotes this screen -- nothing observed binds it to this transition.",
|
||||
"",
|
||||
"It is carried so a reader can search for it and so the port never has to",
|
||||
"invent one. THE PORT NEVER BRANCHES ON IT: navigation uses `goto`, which is",
|
||||
"a screen file, and this field is documentation.",
|
||||
"",
|
||||
"🔴 THIS `why` DID NOT EXIST UNTIL 2026-08-30. All seven `goto_name_kind`",
|
||||
"labels rested on a sibling `why` that argues the DESTINATION -- a different",
|
||||
"claim from where the NAME came from. `tools/port/audit-kinds` reports that",
|
||||
"as BORROWED rather than ok, because a label resting on a neighbour's",
|
||||
"argument reads as evidenced and is not."
|
||||
],
|
||||
"why": "MEASURED, HANDOFF Q5: (B) goes up one level and RESTORES FOCUS to the item you came from. EXTRAS advertises (B) in its own footer -- the red glyph is in ptmsg2.png and absent from the main menu's ptmsg.png."
|
||||
},
|
||||
"buttons": {
|
||||
@@ -278,6 +374,22 @@
|
||||
"goto": "main_menu",
|
||||
"goto_name": "TITLE_MENU",
|
||||
"goto_name_kind": "name match, not measured",
|
||||
"goto_name_why": [
|
||||
"NOT MEASURED, and the label says so. HANDOFF Q4 states it exactly: \"the",
|
||||
"screens are measured; the ids are a name match onto the executable's class",
|
||||
"names.\" So `TITLE_MENU` is a string that exists in the executable and plausibly",
|
||||
"denotes this screen -- nothing observed binds it to this transition.",
|
||||
"",
|
||||
"It is carried so a reader can search for it and so the port never has to",
|
||||
"invent one. THE PORT NEVER BRANCHES ON IT: navigation uses `goto`, which is",
|
||||
"a screen file, and this field is documentation.",
|
||||
"",
|
||||
"🔴 THIS `why` DID NOT EXIST UNTIL 2026-08-30. All seven `goto_name_kind`",
|
||||
"labels rested on a sibling `why` that argues the DESTINATION -- a different",
|
||||
"claim from where the NAME came from. `tools/port/audit-kinds` reports that",
|
||||
"as BORROWED rather than ok, because a label resting on a neighbour's",
|
||||
"argument reads as evidenced and is not."
|
||||
],
|
||||
"same_as_cancel": true,
|
||||
"why": "MEASURED: EXTRAS' third item is BACK (live-extras.png). Treated as (B): it pops the stack, so focus is restored on the main menu exactly as (B) does. Whether the game distinguishes them is untested and there is no reason here to invent a difference."
|
||||
}
|
||||
|
||||
@@ -147,6 +147,12 @@ HANDOFF.
|
||||
| ~~P1–P7 — the keyframe record layout~~ | ~~adopt the corrected pose/time pairing~~ | — | ✅ **ADOPTED 2026-08-29 by pinning `formats-pin-2026-08-29c`.** This row was wrong twice: it said the change *"cannot be taken yet"* and that it *"reaches the port only when that branch lands on `main`"*. **It arrives when the tag is pinned**, which is what MISSION §2's tagging rule exists for. ⚠️ And the knob I tested first, `SYLPHEED_KF_TIME_SHIFT`, is a **retired partial fix** that left pose 0 untimed — the real correction is the tagged crate's default, with the old reading behind `SYLPHEED_KF_TIME_LEGACY=1`. **The blast radius was far smaller than this row predicted**: under the correction *every pose is timed* (866 keyframes, 0 untimed), so `pose_at`'s synthetic-exit branch became dead code rather than wrong code and nothing needed re-deriving. Oracle: `publisher_logo` 1.00 %→**0.75 %**, `developer_logos` 0.39 %→**0.33 %**, `extras`' differing region collapsing from 736×525 to **398×295 at the sweep position**. 🔴 Open cost: `sylpheed-cli` builds from the workspace crate, so `verify-screen` compares two decoder eras until the tag reaches `main`. Revert to the path dependency then. |
|
||||
| ~~P7 / naming — the four unnamed builds~~ | ~~which locale and variant is each of entries 0, 1, 12, 15?~~ | — | ✅ **answered 2026-08-29** (`docs/re/ui-title-build-map.md`): all four are the loading screen, two variants — plain (7 elements) and dressed (10) — decoded from their own `pgloading_*` element names. ⚠️ **Not adopted as names yet, for two reasons the RE agent gave and one the port found.** Theirs: the executable names exactly two, and *which* bundle takes which name is 🟡 undecided, so `LOADING`/`LOADING2` must not go in an asset path; and locale is 🟡 — the English member of a pair is the one in the first half of `GP_TITLE.p00`, 8/8 structurally but only 3/3 where a capture can check, and the three pairs that matter are the three no capture can check. Mine: **the message gives the bundles as "0/1 and 10/11", which is the `is_build` ordinal, and `authored/screen_names.json` is keyed by PAK ENTRY** — in entry space 10 and 11 are `palogo_sqex` and `palogo_gamearts`, the splashes. See the refutation section in `DECISIONS.md`. |
|
||||
|
||||
## Reported to the DECODER, 2026-08-30 — derived from HANDOFF `12c9f04`
|
||||
|
||||
| Milestone | Needs | HANDOFF | State |
|
||||
|---|---|---|---|
|
||||
| P0–P7 — Q2's map of `GP_TITLE` | **list entries 10, 11, 13 and 14 in the Q2 row** | `12c9f04` | 🟡 **the count is right and the enumeration is short; not blocking.** Q2 says *"`GP_TITLE` is 8 screens shipped twice, EN/JP"* and lists `2/3`, `4/7`, `5/8`, `6/9`, `0/1`, `12/15` — **six states of the eight**. The two boot splashes, `publisher_logo` (10/13) and `developer_logos` (11/14), appear nowhere in it. ✅ The headline is confirmed by my export's entry map: 4 UI states + 2 loading variants + **2 splashes** = 8, shipped twice = the 16 entries the archive holds. 🔴 A reader counting Q2 gets twelve and has no slot for the splashes — **and this is the row already corrected once for an ordinal-versus-entry error**, which is the mistake four unlisted entries feed. The port is unaffected: both splashes are exported, named, and verified against captures at RMSE 2.17 and 3.05. |
|
||||
|
||||
## Ask for the HUMAN, 2026-08-30 — derived from HANDOFF `27938aa`
|
||||
|
||||
| Milestone | Needs | HANDOFF | State |
|
||||
|
||||
@@ -9,7 +9,7 @@ dies, which is what this file is for.
|
||||
|
||||
<!-- INDEX: generated by tools/port/index-decisions -- do not hand-edit -->
|
||||
|
||||
242 sections. Search this before re-deriving anything.
|
||||
244 sections. Search this before re-deriving anything.
|
||||
|
||||
* [P0 — the exporter, 2026-08-28](#p0--the-exporter-2026-08-28)
|
||||
* [P1 — Godot draws the screen, 2026-08-28](#p1--godot-draws-the-screen-2026-08-28)
|
||||
@@ -253,6 +253,8 @@ dies, which is what this file is for.
|
||||
* [The pin moves to `formats-pin-2026-08-30b`, and the port stops owning `+0x08`](#the-pin-moves-to-formats-pin-2026-08-30b-and-the-port-stops-owning-0x08)
|
||||
* [The menu remembers its cursor — a measured P5 defect, fixed and scoped](#the-menu-remembers-its-cursor--a-measured-p5-defect-fixed-and-scoped)
|
||||
* [🔴 Correction, same day: I encoded an absence of measurement as a finding](#correction-same-day-i-encoded-an-absence-of-measurement-as-a-finding)
|
||||
* [The `kind` sweep I said I owed: 15 labels, and 7 rested on a neighbour's argument](#the-kind-sweep-i-said-i-owed-15-labels-and-7-rested-on-a-neighbours-argument)
|
||||
* [A refutation attempt on Q2's map of `GP_TITLE` — the count is right, the list is short](#a-refutation-attempt-on-q2s-map-of-gp_title--the-count-is-right-the-list-is-short)
|
||||
|
||||
<!-- /INDEX -->
|
||||
## P0 — the exporter, 2026-08-28
|
||||
@@ -12633,3 +12635,74 @@ Delivery-confirmation went to Ⓐ and Ⓑ because those broke once; the d-pad ha
|
||||
always quietly worked, so nothing directed attention at it. `kind: "measured"` is
|
||||
the same shape — it has never visibly failed, so nothing has been checking what
|
||||
each instance of it actually rests on.
|
||||
|
||||
## The `kind` sweep I said I owed: 15 labels, and 7 rested on a neighbour's argument
|
||||
|
||||
Every authored entry carries a `kind` — `measured`, `authored`, `name match, not
|
||||
measured` — and the label is the load-bearing part: `measured` tells a reader
|
||||
downstream that the port is repeating something observed off the running game.
|
||||
**Nothing had ever checked them**, which is the point — the Decoder's sharpening
|
||||
is that *the disciplines that fail this way are the ones that never visibly
|
||||
failed*. `tools/port/audit-kinds` now reports what each label rests on.
|
||||
|
||||
### 🔴 It found the same error I was corrected for, one level down
|
||||
|
||||
Seven of fifteen labels — **every `goto_name_kind`** — had no `why` of their own.
|
||||
Four of them scored `ok` on the first run because the audit fell back to the
|
||||
parent's `why`, which argues **the destination**. `goto_name_kind` is about
|
||||
**where the NAME came from**. Different claims, and the audit was crediting one
|
||||
with the other's evidence.
|
||||
|
||||
That is precisely what I had been corrected for the previous iteration: treating
|
||||
evidence as bearing on a claim it does not bear on. Borrowed evidence is now its
|
||||
own outcome, `BORROW`, because *a label resting on a neighbour's argument reads
|
||||
as evidenced and is not*. All seven now carry a `why` citing HANDOFF Q4's own
|
||||
words — *"the screens are measured; the ids are a name match onto the
|
||||
executable's class names"* — and state that the port never branches on the field.
|
||||
|
||||
### And the audit refuted itself twice before it was worth trusting
|
||||
|
||||
* **Four false positives.** The first version counted only paths, shas and
|
||||
filenames as citations, so `HANDOFF Q1` and `PORT-MISSION section 7` read as
|
||||
*cites nothing*. **An audit that invents defects is worse than no audit**: its
|
||||
false positives are indistinguishable from its true ones until each is opened
|
||||
by hand.
|
||||
* **Seven false dangling.** It resolved paths against committed refs only, so a
|
||||
`why` citing the tool being written this iteration failed. Working-tree paths
|
||||
count now.
|
||||
|
||||
⚠️ **What it cannot do** is read the cited page and confirm it says what the `why`
|
||||
claims, and it prints that on every run. Fifteen labels with resolving citations
|
||||
is not fifteen verified labels.
|
||||
|
||||
📌 One cosmetic find with teeth: `MEASURED` and `measured` both existed. **A
|
||||
consumer comparing `== "measured"` misses the other, and a label that fails to
|
||||
match reads as ABSENT rather than as wrong.** Normalised.
|
||||
|
||||
## A refutation attempt on Q2's map of `GP_TITLE` — the count is right, the list is short
|
||||
|
||||
Q2 is titled *"which build is which screen state"* and reads: **`GP_TITLE` is 8
|
||||
screens shipped twice, EN/JP** — `2/3` plate, `4/7` title, `5/8` main menu, `6/9`
|
||||
EXTRAS, and `0/1` and `12/15` the loading screen. The port ships **16** screen
|
||||
files, so I checked the enumeration against my own export's entry map:
|
||||
|
||||
| entries | state |
|
||||
|---|---|
|
||||
| 0/1, 12/15 | loading, two variants — ✅ in Q2 |
|
||||
| 2/3, 4/7, 5/8, 6/9 | plate, title, main menu, EXTRAS — ✅ in Q2 |
|
||||
| **10/13, 11/14** | **`publisher_logo`, `developer_logos` — absent from Q2** |
|
||||
|
||||
✅ **The headline survives and is exactly right.** Four UI states + two loading
|
||||
variants + **two boot splashes** = 8 states, each shipped twice = **16 entries**,
|
||||
which is what the archive holds. The count is confirmed by a second reading.
|
||||
|
||||
🔴 **The row's own enumeration lists 6 of those 8.** A reader who counts Q2's
|
||||
entries gets twelve and has no slot for the splashes — and **this is the row
|
||||
already corrected once for an ordinal-versus-entry error**, which is exactly the
|
||||
mistake an incomplete map feeds: four unlisted entries are four places for an
|
||||
off-by-three to hide. The splashes are not obscure to the contract either; the
|
||||
190/145 dwell retraction is about these very entries.
|
||||
|
||||
Not a defect in the port — `publisher_logo` and `developer_logos` are exported,
|
||||
named and verified against captures at RMSE 2.17 and 3.05. Reported because the
|
||||
map is what the next reader will trust.
|
||||
|
||||
173
tools/port/audit-kinds
Executable file
173
tools/port/audit-kinds
Executable file
@@ -0,0 +1,173 @@
|
||||
#!/usr/bin/env python3
|
||||
"""What does each `kind` label in `authored/` actually REST on?
|
||||
|
||||
Every authored entry carries a `kind` -- `measured`, `authored`, `name match,
|
||||
not measured` -- and a `why`. The label is the load-bearing part: `measured`
|
||||
means the port is repeating something observed off the running game, and a
|
||||
reader downstream will treat it as fact.
|
||||
|
||||
Nothing has ever checked them. That is the point: **a discipline that has never
|
||||
visibly failed is the one nothing directs attention at.** The Decoder reached
|
||||
this from the input side -- Ⓐ and Ⓑ were delivery-confirmed because they had
|
||||
once broken, so the d-pad never was -- and on the same day a `measured` label of
|
||||
mine turned out to rest on a single entry that may have been measuring history.
|
||||
|
||||
So this checks what is checkable about a label, and is explicit that the rest is
|
||||
not:
|
||||
|
||||
citations resolvable references in the `why` -- a `docs/` path that exists on
|
||||
some ref, a commit sha that resolves, a capture filename
|
||||
BARE a label whose `why` cites nothing a reader could go and open
|
||||
DANGLING a citation that does not resolve anywhere in the repository
|
||||
|
||||
🔴 What it CANNOT do is read the cited page and confirm it says what the `why`
|
||||
claims. A label with three resolvable citations can still be wrong. This narrows
|
||||
"which labels rest on nothing" from unknown to a list; it does not audit meaning.
|
||||
"""
|
||||
import json, glob, os, re, subprocess, sys
|
||||
|
||||
REFS = None
|
||||
|
||||
|
||||
def known_paths():
|
||||
"""Every path in the repo, across ALL refs -- docs/re/ lives on a branch.
|
||||
|
||||
Checked against the working tree as well: a file added this iteration is not
|
||||
in any ref yet, and reporting a citation to it as unresolvable would make the
|
||||
audit fail every time it is itself referenced.
|
||||
"""
|
||||
global REFS
|
||||
if REFS is None:
|
||||
out = subprocess.run(["git", "rev-list", "--all", "--objects"],
|
||||
capture_output=True, text=True).stdout
|
||||
REFS = {l.split(" ", 1)[1] for l in out.splitlines() if " " in l}
|
||||
return REFS
|
||||
|
||||
|
||||
HANDOFF_TEXT = None
|
||||
|
||||
|
||||
def handoff():
|
||||
"""The live HANDOFF, so a cited Q number is checked against the real table."""
|
||||
global HANDOFF_TEXT
|
||||
if HANDOFF_TEXT is None:
|
||||
sha = subprocess.run(["git", "log", "--all", "--format=%h", "--",
|
||||
"docs/port/HANDOFF.md"], capture_output=True,
|
||||
text=True).stdout.split()[0]
|
||||
HANDOFF_TEXT = subprocess.run(["git", "show", f"{sha}:docs/port/HANDOFF.md"],
|
||||
capture_output=True, text=True).stdout
|
||||
return HANDOFF_TEXT
|
||||
|
||||
|
||||
def sha_ok(s):
|
||||
r = subprocess.run(["git", "cat-file", "-e", s + "^{commit}"], capture_output=True)
|
||||
return r.returncode == 0
|
||||
|
||||
|
||||
def text_of(why):
|
||||
if isinstance(why, str):
|
||||
return why
|
||||
if isinstance(why, list):
|
||||
return " ".join(str(x) for x in why)
|
||||
return ""
|
||||
|
||||
|
||||
def citations(t):
|
||||
"""References a reader could actually follow."""
|
||||
out = []
|
||||
for p in re.findall(r"\b(?:docs|crates|port|tools|authored)/[\w./-]+\w", t):
|
||||
out.append(("path", p.rstrip(".,")))
|
||||
for s in re.findall(r"\b([0-9a-f]{7,40})\b", t):
|
||||
out.append(("sha", s))
|
||||
for p in re.findall(r"\b([\w-]+\.(?:png|txt|tsv|wav))\b", t):
|
||||
out.append(("file", p))
|
||||
# The corpus cites two things that are not paths and are still followable:
|
||||
# a HANDOFF question number, and a MISSION section. Leaving these out made
|
||||
# the first run report four labels as resting on nothing when they rest on
|
||||
# the two documents the mission names -- an audit inventing defects is worse
|
||||
# than no audit, because its false positives are indistinguishable from its
|
||||
# true ones until each is opened.
|
||||
for q in re.findall(r"HANDOFF Q(\d+)", t):
|
||||
out.append(("handoff", "Q" + q))
|
||||
for m in re.findall(r"(PORT-MISSION|MISSION)[ ]section[ ](\d+)", t):
|
||||
out.append(("mission", m[1]))
|
||||
for r in re.findall(r"MODDING rule (\d+)", t):
|
||||
out.append(("modding", r))
|
||||
return out
|
||||
|
||||
|
||||
def walk(o, f, path, out):
|
||||
if isinstance(o, dict):
|
||||
for k, v in o.items():
|
||||
if (k == "kind" or k.endswith("_kind")) and isinstance(v, str):
|
||||
stem = "" if k == "kind" else k[: -len("_kind")]
|
||||
own = o.get((stem + "_why") if stem else "why")
|
||||
# 🔴 An earlier version fell back to the parent's `why` when a
|
||||
# label had none of its own, and reported the result as `ok`.
|
||||
# That credits a label with evidence for a DIFFERENT claim:
|
||||
# every `goto_name_kind` scored on a sibling `why` about the
|
||||
# DESTINATION, while the label is about where the NAME came
|
||||
# from. Borrowed evidence is now its own outcome, because a
|
||||
# label resting on a neighbour's argument is exactly the case
|
||||
# this audit exists to surface.
|
||||
out.append((f, path + "/" + k, v, text_of(own),
|
||||
own is None and bool(text_of(o.get("why")))))
|
||||
walk(v, f, path + "/" + k, out)
|
||||
elif isinstance(o, list):
|
||||
for x in o:
|
||||
walk(x, f, path, out)
|
||||
|
||||
|
||||
def main():
|
||||
rows = []
|
||||
for f in sorted(glob.glob("authored/*.json")):
|
||||
walk(json.load(open(f)), f, "", rows)
|
||||
paths = known_paths()
|
||||
bare = dangling = 0
|
||||
kinds = {}
|
||||
print(f" {len(rows)} kind label(s) in authored/\n")
|
||||
for f, where, kind, why, borrowed in rows:
|
||||
kinds.setdefault(kind, 0)
|
||||
kinds[kind] += 1
|
||||
cites = citations(why)
|
||||
bad = []
|
||||
for typ, c in cites:
|
||||
if typ == "handoff":
|
||||
if not re.search(rf"\|\s*{c}\s*\|", handoff()):
|
||||
bad.append(f"HANDOFF {c} (no such row)")
|
||||
elif typ == "path" and c not in paths and not os.path.exists(c):
|
||||
bad.append(c)
|
||||
elif typ == "sha" and not sha_ok(c):
|
||||
bad.append(c)
|
||||
mark = "ok "
|
||||
if not cites and borrowed:
|
||||
mark, bare = "🔴 BORROW", bare + 1
|
||||
elif not cites:
|
||||
mark, bare = "🔴 BARE", bare + 1
|
||||
elif bad:
|
||||
mark, dangling = "🔴 DANGL", dangling + 1
|
||||
print(f" {mark} {kind:<24} {f.split('/')[-1]}{where}")
|
||||
if not cites and borrowed:
|
||||
print(" no `why` of its own; a sibling `why` argues a"
|
||||
" DIFFERENT claim")
|
||||
elif not cites:
|
||||
print(f" cites nothing openable -- {len(why)} chars of prose")
|
||||
elif bad:
|
||||
print(f" unresolvable: {', '.join(sorted(set(bad))[:4])}")
|
||||
else:
|
||||
print(f" {len(cites)} citation(s), all resolve")
|
||||
print()
|
||||
# Casing is checked because a consumer comparing == "measured" silently
|
||||
# misses "MEASURED", and a label that fails to match reads as absent.
|
||||
variants = [k for k in kinds if k.lower() == "measured"]
|
||||
if len(variants) > 1:
|
||||
print(f" ⚠️ {len(variants)} spellings of the same label: {variants}")
|
||||
print(" A consumer comparing == 'measured' misses the others, and a")
|
||||
print(" label that fails to match reads as ABSENT, not as wrong.\n")
|
||||
print(f" {bare} bare or borrowed, {dangling} dangling, {len(rows) - bare - dangling} with resolving citations")
|
||||
print(" 🔴 A resolving citation is not a verified label. Nothing here reads")
|
||||
print(" the cited page to confirm it says what the `why` claims.")
|
||||
return 1 if (bare or dangling) else 0
|
||||
|
||||
|
||||
sys.exit(main())
|
||||
@@ -45,6 +45,10 @@ step format-validator must-pass "$BIN" check
|
||||
step contract-values must-pass tools/port/contract-check
|
||||
step contract-control must-pass tools/port/contract-check --control
|
||||
step modding-rules must-pass tools/port/check-modding
|
||||
# Every `kind` in authored/ is a claim about where a value came from, and until
|
||||
# 2026-08-30 nothing checked what any of them rested on -- seven were resting on
|
||||
# a sibling `why` that argued a different claim.
|
||||
step authored-kinds must-pass tools/port/audit-kinds
|
||||
step capture-controls must-pass tools/port/check-capture-controls
|
||||
step menu-audio must-pass env OUT="$OUT/audio" tools/port/verify-menu-audio
|
||||
# A stale index is worse than none: it answers "is this already decided?" with a
|
||||
|
||||
Reference in New Issue
Block a user