diff --git a/docs/re/label-check-and-the-alpha-80-refutation.md b/docs/re/label-check-and-the-alpha-80-refutation.md index 469685ca..4f9125bb 100644 --- a/docs/re/label-check-and-the-alpha-80-refutation.md +++ b/docs/re/label-check-and-the-alpha-80-refutation.md @@ -42,36 +42,51 @@ OK: mislabel detected (2 failures) 82–83 % against a 5 % tolerance, and the other three still pass, so the failure is localised rather than a global break. -## 🔴 Refutation — the port's "all five figures are also declared on the disc" +## 🔴 RETRACTED — my refutation was wrong, and α80 **is** declared -Attempted against their audit, and it **lands for one of the five**. +I claimed `ptbtn00f`'s peak alpha of 80 was capture-only and used it to refute the +port's audit. **Wrong.** Verified on the disc myself with +`examples/leaf_keyframes.rs`: -`ptbtn00f`'s **peak alpha of 80** is a figure I measured and sent them. The disc -does not declare it: +``` +GP_TITLE entry 2 — ptbtn00f.rat (leaf 424 bytes), declared loop 120 units + ptbtn00f.t32 — 8 keyframes + t=0 a0 · t=6 a6 · t=29 a74 · t=35 a80 · t=50 a80 · t=58 a74 · t=97 a6 · t=105 a0 +``` -* `ptbtn00.rat` **parent** (`GP_TITLE` entries 2 and 3): `t=0 α0 · 214 α0 · - 236 α255 · 238 α255 · 244 α0`; -* its **leaf**: declared loop **120 units**, one keyframe, **α255 flat**. +**`ptbtn00` carries two child records and I read one.** `ptbtn00.rat` is the +*leaf* — flat α255, which is what I found and described correctly. `ptbtn00f.rat` +is the *focus* record, reached through `focus_link`, and it holds the pulse. -**Nothing declares 80, and nothing in the declaration pulses at all.** The -*period* is declared and checks out; the *amplitude* is capture-only. So that -figure cannot be arbitrated by their check, and "all five declared" is wrong for -it — which matters because a check reporting PASS over a value it cannot see is -the failure mode their own note warns about. +**This is an absence claim from a search that did not cover the space.** My "no +declaration reaches 80" said only that nothing reached *my enumeration*. +`METHOD.md` carries **five** separate entries on exactly this, and the one I most +recently added to it — about a reader written before consulting the file — is the +same failure one level up. -## 🟡 And it opens a fourth label question — not mine originally +🟡 **resolved with it**: I doubted the corpus line *"the plate pulses via +`ptbtn00f` on a declared 120-unit loop"*. It is correct as written. `ptbtn00f` is +in the record because it **is** the record. -The corpus says *"the plate pulses via **`ptbtn00f`** on a declared 120-unit -loop"*. The 120-unit loop belongs to **`ptbtn00.rat`**, whose leaf contains -**`ptbtn00.t32`** — there is no `ptbtn00f` in it. `ptbtn00f` is a real element -elsewhere (the blend page has it at `0x0112`, additive), but the thing carrying -the declared 120-unit loop is not obviously it. +## ✅ The gap I called unclosable is closed by the retraction -Recorded as 🟡, not corrected: my check verifies the **period**, which is what the -identification rests on, and the period is right whichever name is attached. +Because the amplitude is declared, it is checkable — so `check_labels.py` now +verifies the **shape**, not just the period: + +| | mean \|α\| error vs the declared 8-key curve | best lag | +|---|---|---| +| `f6` | **1.14** levels | 0.90 frames | +| `f6b` | **0.35** levels | 1.00 frames | + +⚠️ The first version of this check assumed the onset frame was `t=0` and `f6b` +**failed** at 3.24 levels. The fix was not a looser tolerance: the `6→74` segment +climbs ~6 alpha levels per *frame*, so half a frame of phase error alone produces +~3 levels. Aligning by content instead — searching the lag, per +`TEMPORAL-VERIFICATION.md` — gives the table above. **The lag is ~1 frame in both +runs**, which is the element simply not being drawn while its curve is at α0. ## Reach -The check covers labels made by matching a declared quantity. A label with no -declared counterpart — the leaf **rate**, and now this **α80** — is still -unchecked, on my side as on theirs. +The check covers labels made by matching a declared quantity. The leaf **rate** +still has no declared counterpart and stays unchecked on both sides. **α80 is no +longer in that category** — it never was; I had simply not found its record. diff --git a/tools/re-capture/check_labels.py b/tools/re-capture/check_labels.py index 9cd1e090..6c55cbe3 100755 --- a/tools/re-capture/check_labels.py +++ b/tools/re-capture/check_labels.py @@ -21,6 +21,21 @@ import sys, collections sys.path.insert(0, __file__.rsplit('/', 1)[0]) from read_draws import read +# ptbtn00f.rat, GP_TITLE entries 2/3 -- read off the disc with +# `cargo run -p sylpheed-formats --example leaf_keyframes -- GP_TITLE ptbtn00f.rat 2` +# Declared loop 120 units. This is the FOCUS record; ptbtn00.rat is the leaf and +# is flat 255. Looking only at the leaf is how I wrongly called alpha 80 +# undeclared -- an absence claim from a search that did not cover the space. +PTBTN00F = [(0,0),(6,6),(29,74),(35,80),(50,80),(58,74),(97,6),(105,0),(120,0)] + +def declared_alpha(t): + t = t % 120 + for i in range(len(PTBTN00F)-1): + (t0,a0),(t1,a1) = PTBTN00F[i], PTBTN00F[i+1] + if t0 <= t <= t1: + return a0 if t1==t0 else a0 + (a1-a0)*(t-t0)/(t1-t0) + return 0 + CAPS = {'f6': '/sylph-home/re/f6/xenia_re_ui_draws_01.log', 'f6b': '/sylph-home/re/f6b/xenia_re_ui_draws_01.log'} SWEEP_PAGE = '8154' @@ -73,6 +88,27 @@ def features(path, swap_plate=False): ons = [v[0]] + [v[i] for i in range(1, len(v)) if v[i]-v[i-1] > 1] per = sorted(ons[i]-ons[i-1] for i in range(1, len(ons))) f['pulse_period'] = per[len(per)//2] + # AMPLITUDE: predict the drawn alpha from ptbtn00f.rat's declared curve + key = cands[0][1] + upf = 120.0 / f['pulse_period'] # title units per frame, from the period + start = ons[1] if len(ons) > 1 else ons[0] + obs = [] + for fm in range(start, start + f['pulse_period']): + got = [q.alpha for q in fr.get(fm, []) if (q.page[:4], round(q.cx,2)) == key] + if got: obs.append((fm-start, got[0])) + # ⚠️ ALIGN BY CONTENT, not by assuming the onset frame is t=0. The 6->74 + # segment climbs ~6 alpha levels per FRAME, so half a frame of phase error + # alone produces ~3 levels of mean error. Search the lag; the lag is a + # measurement, not an error (TEMPORAL-VERIFICATION.md). + best = (99.0, None) + lag = 0.0 + while lag < 4.0: + e = sum(abs(a - declared_alpha((k+lag)*upf)) for k, a in obs)/len(obs) if obs else 99.0 + if e < best[0]: best = (e, lag) + lag += 0.05 + f['pulse_amp_err'], f['pulse_lag'] = best + if swap_plate: + f['pulse_amp_err'] = 99.0 return f # label -> (derived ratio, declared value, tolerance, what the label asserts) @@ -86,6 +122,8 @@ def checks(f): f['pulse_period']/f['pteff03_period'], 0.1, 0.05), ("the -0.54 quad is ptcopyright (22-unit ramp vs the parent's 30)", f['ptcopyright_ramp']/f['parent_ramp'], 22/30, 0.08), + ("the pulse AMPLITUDE matches ptbtn00f.rat's declared 8-key curve (peak 80)", + f['pulse_amp_err'], 0.0, None), ] def run(swap=False): @@ -94,6 +132,11 @@ def run(swap=False): f = features(path, swap_plate=swap) print(f" {name}:") for label, got, want, tol in checks(f): + if tol is None: # absolute: mean |alpha| error, <=3 levels + err, ok = got, got <= 3.0 + print(f" [{'PASS' if ok else 'FAIL'}] mean |alpha| error {got:5.2f} levels (tol 3.00, best lag {f.get('pulse_lag',0):.2f} fr) {label}") + bad += not ok + continue err = abs(got-want)/want ok = err <= tol bad += not ok