# 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`](f6-plate-identity-and-clock-conflict-resolved.md) (2026-09-02) fixed the gross mislabeling — `ptbtn00f` is the pulsing plate, `x=-0.54` is `ptcopyright` — **two 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`](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 1–3 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 1–3 are 3-for-3 and 5–6 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 5–6 **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 5–6 **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 4–5 exactly as it would for checks 1–3, and those are different claims: 1–3 test *which element you're looking at* (clock-free, robust, still 3-for-3 including out of sample); 4–5 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.