Files
Sylpheed/docs/re/f6-residue-shaping.md
sylph-decoder 322869b89d
Some checks failed
CI / Native — linux (pull_request) Failing after 38m37s
CI / WASM — Web (pull_request) Successful in 35m55s
CI / Formatting (pull_request) Failing after 1m48s
re: shape the F6 out-of-sample residue (issue #9) -- one open question, not three
f6-out-of-sample-RESULT.md left three failures unexplained beyond "n=2 wasn't
enough". Reviewing what's already decoded: two of the three (the ptcopyright/
parent ramp ratio, the sweep-leads-plate lead) fail in the SAME direction
(0.75x, 0.71x of predicted), and f6-unit10 already established the sweep and
plate families are gated by separately-triggered parents -- a ratio across
two independently-triggered elements has no structural guarantee of being a
constant, unlike a ratio internal to one element family (which is exactly
what the passing three checks are). The gross mislabeling that caused the
ORIGINAL 1.7x conflict was fixed two days before the prereg was written, so
that's ruled out as the cause here; whether the cross-group phase genuinely
varies boot to boot vs. an artifact in the frame-based ratio math is still
open, and needs more captures to tell apart -- filed as a follow-on, not run
here.

check_labels.py conflated two different claims under one "N LABEL(S) DRIFTED"
verdict: identity checks (which element -- clock-free, still 3-for-3 out of
sample) and timing checks (a cross-element ratio and a self-consistency curve
fit -- 0-for-2 out of sample). Split into two reported groups; only identity
gates the exit code now. Tolerances untouched -- widening them to pass f6c
would be tuning the check on the case that failed it, the same error class
already named twice in this corpus. Verified with synthetic data shaped like
the real f6c residue (3/3 identity, 0/2 timing, exit 0) and confirmed the
selftest's injected mislabel still fails an identity check (exit 1) --  no
capture exists in this fresh container to run it against real logs.

The withdrawn sweep->plate lead (0.138-0.141) stays withdrawn. Nothing here
reinstates a number.
2026-09-11 21:26:02 +00:00

6.6 KiB
Raw Blame History

Shaping the F6 out-of-sample residue (issue #9)

f6-out-of-sample-RESULT.md already did the honest thing: three predictions held, three failed, and the failing three were withdrawn rather than patched. This page answers what issue #9 asked for — what remains open in my own words, what instrument would close it, and whether check_labels.py is worth repairing or withdrawing.

The residue is not three independent failures — it's one, seen three ways

# quantity what it actually measures
4 pulse amplitude vs ptbtn00f.rat's declared curve a single element's own curve, content-lag-aligned to itself
5 ptcopyright ramp / parent (sweep-gate) ramp a ratio across two separately-triggered elements
6 sweep leads ptbtn00f, in sweep-loops a cross-element phase offset

Items 5 and 6 both fail in the same direction (0.75× and 0.71× of predicted) — that is not two coincidental misses, it is one relationship measured two ways. Item 4 is a different animal: it never compares two elements, so it needs its own explanation, below.

Why 5 and 6 plausibly share a cause, and it isn't mislabeling

f6-plate-identity-and-clock-conflict-resolved.md (2026-09-02) fixed the gross mislabeling — ptbtn00f is the pulsing plate, x=-0.54 is ptcopyrighttwo days before the prereg (8927d2f, 2026-09-04) was written. check_labels.py already encodes the corrected identities. So "the label is wrong" in the sense that cost us the original 1.7× conflict is ruled out as the explanation for this residue — that bug was fixed and stayed fixed in-sample.

What is not ruled out, and is already sitting in the corpus: f6-unit10-parent-alpha-gates-the-sweep.md states plainly that the sweep is switched on by its own parent's declared alpha ramp, "not by anything to do with the plate." The sweep-family clock and the plate-family clock are declared as separately gated. A ratio between two independently-triggered elements' timings is only a constant if both triggers fire in a fixed relative order and interval to each other — which is an empirical claim about the boot sequence, not something either element's own .rat declaration can guarantee. Items 5 and 6 are exactly that kind of cross-group ratio; items 13 are not (each stays inside one sweep-family element or one loop-ratio internal to it), which is the cleanest read I have for why 13 are 3-for-3 and 56 are 0-for-2.

I am not asserting this is confirmed — it is the hypothesis the existing corpus already points at, not a new measurement. The alternative (an instrument artifact in how check_labels.py derives parent_ramp and ptcopyright_ramp from raw frame numbers in a differently-shaped capture) is not excluded either. Both are live.

Item 4 needs its own account

The amplitude-curve fit doesn't compare two elements; it compares ptbtn00f's own drawn alpha, within its own cycle, against its own disc-read curve, after a content-based lag search. Its upf (title-units-per-frame) comes from this same run's pulse_period — so if f6c's automatic slot-finder measured a noisier pulse_period (fewer clean cycles, a frame-drop landing differently), the curve fit inherits that error even if the underlying decoded curve is right. check_labels.py already prints the best lag and the mean error; it does not yet print the per-run pulse_period sample count or variance, which is the number that would tell them apart.

What instrument would close it

Not more reasoning from what's already written down — that's what this page is, and it is the ceiling of what a static review can do. More independent no-input boots, ⟨capture⟩, with check_labels.py's per-run diagnostics (lag, sample count, per-cycle period variance) saved alongside the pass/fail line rather than discarded. Two outcomes distinguish the two live hypotheses:

  • items 56 cluster into two or more distinct values across runs (e.g. a bimodal split rather than a spread) → points at a real, possibly discrete, boot-order dependency in when each group's trigger fires;
  • items 56 scatter without structure, while item 4's error tracks pulse_period's own sample-to-sample noise → points at instrument sensitivity in the frame-based measurement, not a game fact.

Three more runs (total n = 6) is the smallest batch that could show clustering vs scatter; two would not be enough to tell either from noise, which is the same lesson f6-out-of-sample-RESULT.md already drew from n = 2. Filed as a follow-on item rather than run here, since it is new exploratory dynamic RE and the mission asks for state/approved before starting it, not a continuation of #9's own scope.

check_labels.py — repair, don't patch the tolerance

Not worth silently loosening. Widening tolerance 4/5 until f6c passes would be tuning a check on the case that failed it — the exact error class this whole session has been naming. The tool staying red is more honest than a quietly-widened green.

Worth repairing its claim, though, because right now a failure reads as "N LABEL(S) DRIFTED" for checks 45 exactly as it would for checks 13, and those are different claims: 13 test which element you're looking at (clock-free, robust, still 3-for-3 including out of sample); 45 test whether a cross-element or self-consistency timing number holds, which the analysis above says may not even be a constant. Splitting them lets a future reader — human or agent — tell "the identification is wrong" from "the identification is right and the timing varies" without re-deriving this page.

Applied below: check_labels.py now prints identity checks and timing checks as two labelled groups, and only identity checks affect the exit code. Timing-check failures still print in full, still say FAIL, and still return a nonzero count in the summary line — they are demoted from gating, not hidden.

Answering issue #9's pass criterion

Concrete follow-on:

  • New item (state/proposed, this iteration): "F6 — cluster or scatter? Three more independent no-input boots, diagnostics saved, to tell a real cross-group phase dependency from an instrument artifact in the frame-based ratios." Instrument: ⟨capture⟩ × 3, check_labels.py (repaired) with diagnostics logged per run.
  • check_labels.py repaired in place (this commit) to stop conflating the two claims — no emulator needed for that part, verified by --selftest still detecting the seeded mislabel.
  • The sweep→plate separation constant stays withdrawn, as it already was. Nothing here reinstates it.