diff --git a/authored/timing.json b/authored/timing.json index 2679c809..7fe17710 100644 --- a/authored/timing.json +++ b/authored/timing.json @@ -132,7 +132,39 @@ " confirmation. The cleanness may be the failure mode's own signature.", "", "Same family as this port's non-inverting latch check, which passed for the wrong", - "reason until its control failed." + "reason until its control failed.", + "", + "šŸ”“ 2026-09-01 — THE 12-UNIT BRACKET ABOVE IS WITHDRAWN. IT EXCLUDES NOTHING.", + "", + "I kept 60 on the ground that '12 declared units measured at 0.14-0.30 s gives", + "40-86 units/s, so 120 is excluded'. The Decoder refuted it and the refutation", + "holds on arithmetic I checked myself:", + "", + " the source doc says of that number, in its own words, 'at a sampling", + " resolution (0.125 s) that cannot do better'. 120 units/s predicts 12 units in", + " 0.100 s -- BELOW one sample interval. A 0.125 s sampler cannot resolve it and", + " reports about one sample, ~0.125-0.14 s. The 0.14 s low end is the", + " INSTRUMENT'S FLOOR, and 12/0.14 = 85.7 is an upper bound produced by dividing", + " by a floored duration. It is the value 120 predicts once the sampler is", + " accounted for.", + "", + "šŸ”“ AND THE DEEPER ERROR IS MINE, NOT THE ARITHMETIC. I argued the leg survived", + "because it 'never touched a frame count'. True, and INSUFFICIENT: every", + "wall-clock duration off Canary is true/speed_factor, so apparent units/s =", + "true x speed -- and the speed factor is precisely what makes the three routes", + "disagree. I checked the leg for the WRONG CONTAMINANT. Frame-free is not", + "clock-free, and on this emulator clock-free is the property that matters.", + "", + "What actually survives from that leg, and it is the half I did not lead with:", + "the declared 12 units are independently confirmed as SIX FRAMES by", + "screen-transitions.md's 255/6-per-frame ramp. No wall clock in it at all. That", + "is evidence about units per FRAME -- which was never in dispute -- and silent", + "about units per second.", + "", + "SO 60 HAS NO SURVIVING BRACKET. It stays because nothing supports 120 either and", + "moving a shipped timeline on no evidence is worse than leaving it. That is a", + "default, not a derivation, and this entry now says so. `kind` is already", + "`authored`, which is the honest label for a default." ], "kind": "authored", "source": "docs/re/ui-keyframe-time-unit.md, docs/port/HANDOFF.md", diff --git a/docs/port/BLOCKED.md b/docs/port/BLOCKED.md index 1dc6289a..92250902 100644 --- a/docs/port/BLOCKED.md +++ b/docs/port/BLOCKED.md @@ -627,7 +627,7 @@ settled title sits at sd ≤ 0.01. `loop*.rat` is a misleading name — these animate once during build-in and then rest off-screen. The port's own error here was different and is fixed: it settled at the last -*timed* keyframe rather than at the hold. See `docs/DECISIONS.md`. +*timed* keyframe rather than at the hold. See `docs/port/DECISIONS.md`. Kept for the record: diff --git a/docs/port/DECISIONS.md b/docs/port/DECISIONS.md index e582b619..c11095c6 100644 --- a/docs/port/DECISIONS.md +++ b/docs/port/DECISIONS.md @@ -12130,7 +12130,7 @@ fifth instance of that habit in this exchange — and most are not findings: | a hypothetical modding example | `data/mods/sprites/…` | āœ… **Genuine: five stale citations** from the `docs/` → `docs/port/` -reorganisation — `docs/BLOCKED.md` Ɨ4 and `docs/FORMAT.md` Ɨ1. A reader following +reorganisation — `docs/port/BLOCKED.md` Ɨ4 and `docs/port/FORMAT.md` Ɨ1. A reader following them fails. Rewritten. ### šŸ”“ And the one that reached a claim I made last iteration diff --git a/docs/port/FORMAT.md b/docs/port/FORMAT.md index a990d025..f3aeabf4 100644 --- a/docs/port/FORMAT.md +++ b/docs/port/FORMAT.md @@ -147,7 +147,7 @@ for a long time. > grossly (`ptlogo_back2`, 1118Ɨ262, pivot 500,117 where half is 559,131). It is > not a problem for this port — the exporter emits the declared pivot and never > derives one — but it is a claim a consumer should not lean on. Raised in -> `docs/BLOCKED.md`. +> `docs/port/BLOCKED.md`. **`sprite`** / **`focus_sprite`** are paths relative to `export/`. The highlight pairs **by name** on the sprite — `ptbtn01.t32` ↔ `ptbtn01f.t32` — which is 🟔 a @@ -272,7 +272,7 @@ keyframe *k* means the screen spends that time *arriving at* `k+1`. > A consumer that wants the pose after arrival should therefore take **the last > timed keyframe**, not `rest`. `rest` is kept in the format because it is what > the pinned decoders say and removing it would hide the disagreement — see -> `docs/DECISIONS.md`. The format is unchanged at **v2**: no field changed +> `docs/port/DECISIONS.md`. The format is unchanged at **v2**: no field changed > meaning, this is a warning about one of them. **`paint_order`** is back-to-front, as declaration indices, and is a permutation diff --git a/docs/port/PORT-MISSION.md b/docs/port/PORT-MISSION.md index 963b7a5b..b8c75028 100644 --- a/docs/port/PORT-MISSION.md +++ b/docs/port/PORT-MISSION.md @@ -105,7 +105,7 @@ A milestone is done when its **artifact** exists, not when the code compiles. | **P7** | New-game intro video after NEW GAME | Plays, then returns to a defined state | Work the lowest unfinished milestone. When one is blocked on an RE answer, say so -in `docs/BLOCKED.md`, and take the next milestone that is not. +in `docs/port/BLOCKED.md`, and take the next milestone that is not. ## 6. The video problem diff --git a/tools/port/check-all b/tools/port/check-all index 15801f0f..b7aec672 100755 --- a/tools/port/check-all +++ b/tools/port/check-all @@ -129,6 +129,14 @@ step input-control must-pass tools/port/verify-input --control # A stale index is worse than none: it answers "is this already decided?" with a # confident no. That is not hypothetical -- see the entry it was built after. step decisions-index must-pass tools/port/index-decisions --check +# `audit-kinds` checks citations in `authored/`; nothing checked the PROSE, and +# prose is where this port explains itself. A first run found 37 of 91 +# non-resolving -- 7 of them pointing at NOTHING on any ref, left behind by the +# monorepo move and the `export/` rename. Only that class fails; a citation that +# is merely on a peer's unmerged branch is reported, because the fix is a merge +# and nobody in this container can make it. +step doc-citations must-pass tools/port/check-citations +step citations-control must-pass tools/port/check-citations --selftest # A refuted claim asserted outside its correction is a lie the corpus tells a # reader who greps for it. Registered claims must carry an explicit `[refuted]`. # šŸ”“ The register check had NO executable control until 2026-08-31 -- every diff --git a/tools/port/check-citations b/tools/port/check-citations new file mode 100755 index 00000000..53c824b3 --- /dev/null +++ b/tools/port/check-citations @@ -0,0 +1,106 @@ +#!/usr/bin/env python3 +"""Do the repo paths cited in `docs/port/*.md` actually resolve? + + tools/port/check-citations # assert + tools/port/check-citations --selftest # can it fail? + +`audit-kinds` checks citations in `authored/`. Nothing checked the PROSE, and +prose is where this port explains itself. A first run found **37 of 91** +non-resolving, 41 %, in two very different classes: + + * **19 on the Decoder's topic branch** — real files, not merged here. Not + errors. A reader in this checkout still cannot follow them, which is worth + reporting and not worth failing on; the fix is a merge, not an edit. + * **7 that resolve NOWHERE** — `docs/BLOCKED.md`, `docs/FORMAT.md`, + `port/manifest.json`, `port/screens/title/*.json`. Left behind by the + monorepo move and the `export/` rename. Those are simply wrong: a reader + following one gets nothing, and nothing had ever told anyone. + +So the two classes are separated and only the second fails. A check that failed +on the first would be red for a state nobody in this container can fix, which is +the shape the display guard exists to prevent. + +āš ļø THE PEER-BRANCH CLASS IS THE OTHER AGENT'S POINT, TURNED ON MYSELF. They +observed that everything they hand over links into `docs/re/` files that live +only on their branch, so every link they send is dangling from here. The same is +true in reverse and neither of us was counting. +""" +import os +import re +import subprocess +import sys +import glob + +# A repo path with a file extension, optionally in backticks or a markdown link. +CITE = re.compile( + r"`?((?:docs|crates|port|tools|authored|export)/[\w./-]+" + r"\.(?:md|rs|gd|json|txt|py|tsv|csv))`?" +) +PEER_REFS = ("origin/auto/frame-blend-draw-path", "origin/main") + + +def on_a_ref(path: str) -> str | None: + """The first ref that carries `path`, or None.""" + for ref in PEER_REFS: + if subprocess.run(["git", "cat-file", "-e", f"{ref}:{path}"], + capture_output=True).returncode == 0: + return ref + return None + + +def scan(files): + resolves, peer, nowhere = 0, {}, {} + for p in files: + try: + text = open(p, encoding="utf-8").read() + except OSError: + continue + for m in sorted(set(CITE.findall(text))): + if os.path.exists(m): + resolves += 1 + elif (ref := on_a_ref(m)): + peer.setdefault(m, (p, ref)) + else: + nowhere.setdefault(m, p) + return resolves, peer, nowhere + + +def main() -> int: + if "--selftest" in sys.argv: + # šŸ”“ A CHECK THAT CANNOT FAIL IS NOT A CHECK. This plants a citation of a + # path that exists on no ref and requires the scanner to catch it, and a + # citation of a real file and requires it NOT to. Both directions, + # because a scanner that flagged everything would also "pass" the first. + tmp = os.path.join(os.environ.get("TMPDIR", "/tmp"), "check-citations-selftest") + os.makedirs(tmp, exist_ok=True) + bad = os.path.join(tmp, "bad.md") + open(bad, "w").write("see `docs/port/this-file-does-not-exist-anywhere.md`\n") + good = os.path.join(tmp, "good.md") + open(good, "w").write("see `docs/port/PORT-MISSION.md`\n") + _, _, nb = scan([bad]) + r, _, ng = scan([good]) + ok = len(nb) == 1 and len(ng) == 0 and r == 1 + print("selftest: planted dangling caught=%s, real citation passed=%s -> %s" + % (len(nb) == 1, len(ng) == 0 and r == 1, "ok" if ok else "šŸ”“ BROKEN")) + return 0 if ok else 2 + + files = sorted(glob.glob("docs/port/*.md")) + resolves, peer, nowhere = scan(files) + total = resolves + len(peer) + len(nowhere) + print("citations of repo paths in docs/port/*.md: %d" % total) + print(" resolve here : %d" % resolves) + print(" on a peer branch, not merged: %d (reported, not failed)" % len(peer)) + for m, (src, ref) in sorted(peer.items()): + print(" %-52s %s <- %s" % (m, ref.split("/")[-1], os.path.basename(src))) + if nowhere: + print(" šŸ”“ resolve NOWHERE : %d" % len(nowhere)) + for m, src in sorted(nowhere.items()): + print(" %-52s <- %s" % (m, os.path.basename(src))) + print("\nšŸ”“ a reader following those gets nothing. Fix the path or drop the citation.") + return 1 + print(" šŸ”“ resolve nowhere : 0") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main())