diff --git a/docs/port/BLOCKED.md b/docs/port/BLOCKED.md index 191ea789..319137f8 100644 --- a/docs/port/BLOCKED.md +++ b/docs/port/BLOCKED.md @@ -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`. | +## Closed on my own tooling, 2026-08-31 — derived from HANDOFF `abeea3b` + +| Milestone | Needs | HANDOFF | State | +|---|---|---|---| +| ~~all — checkers that pass on an empty input~~ | ~~liveness guards~~ | `abeea3b` | ✅ **SWEPT AND FIXED.** Every one of my tools reported clean when it examined **nothing**: `audit-kinds` exited **0** on a tree with no `authored/*.json`; `verify-transcode-fidelity` would call every transcode faithful with no videos in the manifest; `check-claims` exited **1** from a `FileNotFoundError` in the withdrawal hook — *"a refuted claim is still being asserted"* as the diagnosis for **a wrong directory**, a real failure with a fabricated reason and the third of that family. All three now exit **2**, and both self-tests assert the liveness case as subprocesses (`check-claims --control` is six cases). 📌 None of these tools was ever wrong on real input; what none could do was tell *"I checked and it was fine"* from *"I checked nothing"*. | + ## Half-answered, 2026-08-31 — derived from HANDOFF (today's `DIFFICULTY` delivery) | Milestone | Needs | HANDOFF | State | diff --git a/docs/port/DECISIONS.md b/docs/port/DECISIONS.md index 97197bcd..3f483f64 100644 --- a/docs/port/DECISIONS.md +++ b/docs/port/DECISIONS.md @@ -9,7 +9,7 @@ dies, which is what this file is for. -274 sections. Search this before re-deriving anything. +276 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) @@ -285,6 +285,8 @@ dies, which is what this file is for. * [Settled: a submenu resets to its OWN OPENING ITEM, not to its top item](#settled-a-submenu-resets-to-its-own-opening-item-not-to-its-top-item) * [Their refutation attempt on `extras/initial_focus` — checked against the bytes, twice](#their-refutation-attempt-on-extrasinitial_focus--checked-against-the-bytes-twice) * [Menu focus does not survive a reboot — and the reach matters more than the result](#menu-focus-does-not-survive-a-reboot--and-the-reach-matters-more-than-the-result) +* [Liveness: every one of my tools passed on an empty input](#liveness-every-one-of-my-tools-passed-on-an-empty-input) +* [Their `ring_row.py` defect, and why it did not reach me](#their-ring_rowpy-defect-and-why-it-did-not-reach-me) ## P0 — the exporter, 2026-08-28 @@ -13909,3 +13911,58 @@ an offset, essentially no scaling). **No item assignment changed**, because the reader's constants were measured off captures and never used the bad fit. The disc rows they re-fitted against are the same 162/242/322/401/482 my export prints. + +## Liveness: every one of my tools passed on an empty input + +The Decoder generalised my empty-band case into the rule I now keep: **a control +that only compares two things cannot tell you the comparison is happening.** An +empty band list, a blank frame, an empty register — each makes a checker +**agreeable rather than wrong**, and agreeable is indistinguishable from correct +in a log. + +Swept my own tools against inputs that contain nothing: + +| tool | before | now | +|---|---|---| +| `audit-kinds` | **exit 0** — printed *"0 kind label(s)"* and reported clean | **exit 2** | +| `check-claims` | **exit 1** from a `FileNotFoundError` inside the withdrawal hook | **exit 2**, via a preflight | +| `verify-transcode-fidelity` | manifest with no videos → loop never runs, *"every transcode faithful"* having compared none | **exit 2** | + +🔴 **The `check-claims` case is the one worth naming.** Run from the wrong +directory it died in the hook and exited **1** — which in that script's own +vocabulary means *"a refuted claim is still being asserted"*. **A real failure +with a fabricated diagnosis**, the same shape as my control anchoring at the +wrong document two iterations ago, and the third instance of that family. A +preflight now names the roots it needs and calls their absence a **harness** +fault. + +✅ Both self-tests gained the liveness case, driven as subprocesses so the real +exit code is read: `audit-kinds --selftest` runs itself in an empty directory and +requires 2; `check-claims --control` is now **six** cases — clean 0, unmarked 1, +marked 0, outside-root 0, empty register 2, **nothing to scan 2**. + +📌 What makes this worth an iteration rather than tidying: **none of these tools +was wrong.** Each produced correct output on real input, every time it ran. What +they could not do is tell the difference between *"I checked and it was fine"* and +*"I checked nothing"* — and every green line I have quoted in this file was the +first of those only because the directory happened to be right. + +## Their `ring_row.py` defect, and why it did not reach me + +Their liveness self-test found that `main_menu_item(ring_row(f)) is not None` was +being used as a main-menu test, and **a TITLE frame passes it** — the gutter +carries a bright cluster at y=243, inside tolerance of row 0, so the title reads +as `NEW GAME`. Glyph count separates them cleanly (714 against the menu's 327); +the ring row alone does not. + +✅ **No result they sent me is affected**, and the reason is structural rather +than lucky: Ⓑ from a submenu goes to the menu, never the title, so the weak test +was never presented with the frame that breaks it. **The test was weaker than +they were trusting it to be, not wrong in what it produced** — which is precisely +the state a self-test exists to expose *before* a screen sequence changes and it +starts mattering. + +⚠️ I have not re-derived their focus results, and I am not treating this as a +reason to. Several of my authored values rest on them; what I have instead is +their statement of the exposure and the structural argument for why it did not +fire. Recorded as that, not as verification. diff --git a/tools/port/audit-kinds b/tools/port/audit-kinds index fa4fbca8..2c1a1507 100755 --- a/tools/port/audit-kinds +++ b/tools/port/audit-kinds @@ -139,6 +139,15 @@ def selftest(): means anything. """ paths = known_paths() + # The liveness case belongs in the self-test too, driven as a subprocess so + # its real exit code is read rather than reasoned about. + empty = os.path.join(os.environ.get("TMPDIR", "/tmp"), "audit-kinds-liveness") + os.makedirs(empty, exist_ok=True) + got = subprocess.run([sys.executable, os.path.abspath(__file__)], cwd=empty, + capture_output=True).returncode + print(f" harness: an empty tree -> exit {got} (want 2) " + f"{'✅' if got == 2 else '🔴 examined nothing and reported clean'}") + live_ok = got == 2 cases = [ ("bare", "no citation of any kind here, just prose", "BARE"), ("ok", "see tools/port/audit-kinds for the method", "ok"), @@ -158,8 +167,11 @@ def selftest(): if got != want: bad += 1 print() + if not live_ok: + bad += 1 if bad: - print("🔴 the classifier cannot tell grounded labels from ungrounded ones.") + print("🔴 the classifier cannot tell grounded labels from ungrounded ones,") + print(" or it reports clean on an empty tree.") print(" Exit 2: nothing this tool has reported clean is trustworthy.") return 2 print("the classifier separates bare, dangling and grounded citations") @@ -172,6 +184,18 @@ def main(): rows = [] for f in sorted(glob.glob("authored/*.json")): walk(json.load(open(f)), f, "", rows) + # 🔴 LIVENESS. Run against a tree with no `authored/*.json` this printed + # "0 kind label(s)" and exited 0 -- examined nothing, reported clean. The + # Decoder's generalisation of my empty-band case, which is more general than + # either instance: **a control that only compares two things cannot tell you + # the comparison is happening.** An empty input makes a checker AGREEABLE + # rather than wrong, and agreeable is indistinguishable from correct in a + # log. + if not rows: + print("🔴 no `kind` labels found at all -- this audit examined NOTHING.") + print(" Exit 2: the harness is broken (wrong directory, renamed files),") + print(" not the corpus.") + return 2 paths = known_paths() bare = dangling = 0 kinds = {} diff --git a/tools/port/check-claims b/tools/port/check-claims index e3bb93b0..d2fa1260 100755 --- a/tools/port/check-claims +++ b/tools/port/check-claims @@ -23,7 +23,7 @@ set -euo pipefail cd "${PROJECT_DIR:-/work}" WINDOW=400 # characters either side of a hit in which the marker must appear -fail=0; total_marked=0 +fail=0; total_marked=0; scanned=0 # 🔴 THE MARKER IS AN EXPLICIT SENTINEL, NOT A KEYWORD. # @@ -130,6 +130,13 @@ if [ "${1:-}" = "--control" ]; then got=$(CLAIMS_REGISTER="" "$0" >/dev/null 2>&1; echo $?) if [ "$got" = "2" ]; then printf ' %-26s exit 2 ✅\n' "empty register" else printf ' %-26s exit %s, wanted 2 🔴\n' "empty register" "$got"; ok=1; fi + # Sixth case: a tree with nothing to scan. It used to die in the withdrawal + # hook and exit 1 -- "a refuted claim is still being asserted" -- for a wrong + # directory. Liveness and diagnosis are both asserted here. + _empty="${TMPDIR:-/tmp}/claims-liveness-root"; mkdir -p "$_empty" + got=$(cd "$_empty" && PROJECT_DIR="$_empty" "$OLDPWD/$0" >/dev/null 2>&1; echo $?) + if [ "$got" = "2" ]; then printf ' %-26s exit 2 ✅\n' "nothing to scan" + else printf ' %-26s exit %s, wanted 2 🔴\n' "nothing to scan" "$got"; ok=1; fi echo [ $ok -eq 0 ] && echo "the register check fails when it must, and says so distinctly" \ || echo "🔴 the control machinery itself is broken" @@ -172,6 +179,25 @@ fi # never a defect count. echo echo "withdrawal-time hook -- correction sections that registered nothing:" +# 🔴 PREFLIGHT. Run from the wrong directory this used to die inside the +# withdrawal hook with a FileNotFoundError and exit **1** -- which in this +# script's own vocabulary means "a refuted claim is still being asserted". A real +# failure with a fabricated diagnosis, the same shape as my control anchoring at +# the wrong document. The roots it needs are named here and their absence is a +# HARNESS fault with its own code. +for _root in docs docs/port authored tools/port; do + [ -d "$_root" ] || { + echo "🔴 \`$_root\` is not here -- this check cannot scan anything." >&2 + echo " Exit 2: wrong directory or a bad checkout, not a dirty corpus." >&2 + exit 2 + } +done +[ -f docs/port/DECISIONS.md ] || { + echo "🔴 docs/port/DECISIONS.md is missing -- the withdrawal hook has nothing" >&2 + echo " to read. Exit 2: the harness is broken, not the corpus." >&2 + exit 2 +} + REG="$REGISTER" python3 - <<'HOOK' import os, re reg = [r.strip() for r in os.environ["REG"].split("\n") if r.strip()] @@ -235,6 +261,7 @@ PY # clause. A register matching EXACT wording does not protect the documents that # rewrite most, and a capital letter is the cheapest rewrite there is. done < <(grep -ril -- "$claim" docs/ crates/ port/ tools/ authored/ 2>/dev/null || true) + scanned=$((scanned + hits)) if [ "$bad" -eq 0 ]; then printf ' %-42s %d file(s), %d occurrence(s) suppressed\n' "$claim" "$hits" "$marked" total_marked=$((total_marked + marked)) @@ -243,6 +270,17 @@ PY fi done <<< "$REGISTER" +# 🔴 LIVENESS. A register full of claims and a tree with nothing in it reports +# clean: the grep matches no files, every row scores 0, and the run passes having +# READ NOTHING. Wrong directory, renamed docs, a bad checkout -- all produce a +# green line. The Decoder's rule for the family: a control that only compares two +# things cannot tell you the comparison is happening. +if [ "$scanned" -eq 0 ]; then + echo "🔴 no file anywhere contains any registered claim -- this check READ" >&2 + echo " NOTHING. Exit 2: the harness is broken, not the corpus." >&2 + exit 2 +fi + echo printf ' %d occurrence(s) were SUPPRESSED by a neighbouring `%s`.\n' "$total_marked" "$MARKER" echo " That number is the size of what this check chose not to look at. A" diff --git a/tools/port/verify-transcode-fidelity b/tools/port/verify-transcode-fidelity index c4192dc1..6c5d75d0 100755 --- a/tools/port/verify-transcode-fidelity +++ b/tools/port/verify-transcode-fidelity @@ -338,6 +338,13 @@ def main(): return 2 man = json.load(open("export/manifest.json")) names = [v["name"] for v in man.get("videos", [])] + # 🔴 LIVENESS, the same shape as the empty band list one line up. With no + # videos in the manifest the loop never runs, `fail` stays 0 and this reports + # every transcode faithful -- having compared none. + if not names: + print("🔴 the manifest lists NO videos -- nothing was compared.") + print(" Exit 2: the harness is broken, not the transcodes.") + return 2 control = "--control" in sys.argv fail = 0 print(f" window {WINDOW_S:.0f} s from t=2 s, mono {RATE} Hz, pass at "