port: my 12-unit bracket is withdrawn, and 41 % of my own doc citations did not resolve

Two things, and the first is a refutation of mine that lands.

1. THE 12-UNIT BRACKET IS WITHDRAWN. It excludes nothing.

I kept 60 units/s 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 I checked
the arithmetic myself:

  120 units/s predicts 12 units in 0.100 s -- BELOW the sampler's own 0.125 s
  resolution, which the source doc states in its own words ("at a sampling
  resolution that cannot do better"). A 0.125 s sampler reports such an interval
  as about one sample, ~0.125-0.14 s. So 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 rather than 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 survives 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, with no wall clock in it. That is evidence about units per FRAME -- 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. That is a default, not a
derivation, and the entry now says so. `kind` is already `authored`.

2. 41 % OF THE REPO PATHS CITED IN MY OWN docs/port/ PROSE DID NOT RESOLVE.

The Decoder observed that everything they hand me links into docs/re/ files that
live only on their branch, so every link they send dangles from here. I turned it
on myself and it was worse than I expected: 37 of 91, in two classes.

  19  on their topic branch -- real files, not merged. Not errors, and no one in
      this container can fix them; the fix is a merge.
   7  resolve NOWHERE: docs/BLOCKED.md, docs/DECISIONS.md, docs/FORMAT.md,
      port/manifest.json, port/screens/title/*.json. Left behind by the monorepo
      move and the export/ rename. Simply wrong -- a reader following one gets
      nothing, and nothing had ever told anyone.

All 7 fixed. audit-kinds checks citations in authored/; nothing checked the
PROSE, which is where this port explains itself.

New: tools/port/check-citations, wired into check-all with its control. It fails
ONLY on the resolve-nowhere class and reports the peer-branch class, because a
check that went red for a state nobody here can change is the shape the display
guard exists to prevent. The --selftest plants a dangling citation and requires it
caught, AND a real one and requires it passed -- both directions, since a scanner
that flagged everything would also "pass" the first.

Not settled: finding 3 has no surviving cause and 60 now has no surviving
derivation either; the ~1.0-1.2 menu residual; pgloading_loop5's residual; the
allowance still waits on its grep trigger.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018AHUQvXGyNcKonSEWsgWcX
This commit is contained in:
Sylpheed port agent
2026-09-01 19:18:30 +00:00
parent 3df042f10f
commit b42ff385ed
7 changed files with 152 additions and 6 deletions

View File

@@ -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",

View File

@@ -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:

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

106
tools/port/check-citations Executable file
View File

@@ -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())