tools+re: a check that reads the label, and the alpha-80 refutation
Three of my errors were the label rather than the measurement, and a correction in one document did not reach the next page I wrote. The port built check-authored-vs-declared for values the disc can arbitrate and named the gap: capture-only values that name an element rest entirely on my label. check_labels.py closes that for the cases where the identification was itself made by matching a declared quantity -- 8 checks over two captures, all passing, with a --selftest that points the plate label at ptcopyright (the real error) and must fail. It does, at 82-83% against a 5% tolerance, with the other checks still passing so the failure is localised. Refutation of the port's "all five figures are also declared": lands for one. ptbtn00f's peak alpha of 80 is not declared anywhere -- ptbtn00.rat's parent peaks at 255 and its leaf is one keyframe at 255 flat, with the 120-unit loop declared but no amplitude. The period checks out; the amplitude is capture-only and their check cannot see it. Opens a 🟡 not mine originally: the corpus attributes the 120-unit loop to ptbtn00f, but it belongs to ptbtn00.rat whose leaf is ptbtn00.t32. Left uncorrected since the identification rests on the period, which holds under either name. Adds examples/leaf_keyframes.rs. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Jc4pciRArGHfxGGhEbwp5t
This commit is contained in:
113
tools/re-capture/check_labels.py
Executable file
113
tools/re-capture/check_labels.py
Executable file
@@ -0,0 +1,113 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Re-derive every element LABEL in the F5/F6 findings from captures + disc.
|
||||
|
||||
⚠️ WHY THIS EXISTS. Three of my errors were the label, not the measurement:
|
||||
`0x3003` read as a different role from `0x3002`; `ptcopyright` called "the plate"
|
||||
in the clock-conflict page; and `ptcopyright` called "the plate" AGAIN in the F6
|
||||
page, written after that correction. Every number was right. What it was pointed
|
||||
at was not, and a correction in one document did not reach the next.
|
||||
|
||||
sylpheed-port has `check-authored-vs-declared` for values with a declared
|
||||
counterpart. It cannot cover a value that exists only in a capture and names an
|
||||
element -- that one rests entirely on my label. This is that case.
|
||||
|
||||
Each identification below was originally MADE by matching a declared quantity, so
|
||||
each is re-derivable. If a label drifts, the agreement it was built on breaks.
|
||||
|
||||
python3 check_labels.py # check
|
||||
python3 check_labels.py --selftest # prove it can fail
|
||||
"""
|
||||
import sys, collections
|
||||
sys.path.insert(0, __file__.rsplit('/', 1)[0])
|
||||
from read_draws import read
|
||||
|
||||
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'
|
||||
PTCOPY_X = (-0.54, 0.54, 0.54, -0.54)
|
||||
|
||||
def features(path, swap_plate=False):
|
||||
fr = read(path); fs = sorted(fr)
|
||||
f = {}
|
||||
pairs = []
|
||||
for fm in fs:
|
||||
adds = [q for q in fr[fm] if q.blend == '0x01010101' and q.page.startswith(SWEEP_PAGE)]
|
||||
if len(adds) >= 2: pairs.append((fm, adds[0], adds[1]))
|
||||
def cycles(idx, sign):
|
||||
out = []
|
||||
for i in range(1, len(pairs)):
|
||||
d = pairs[i][idx].cx - pairs[i-1][idx].cx
|
||||
if (sign > 0 and d < -1.0) or (sign < 0 and d > 1.0): out.append(pairs[i][0])
|
||||
return out
|
||||
ca, cb = cycles(1, +1), cycles(2, -1)
|
||||
f['pteff03_period'] = ca[1] - ca[0]
|
||||
f['pteff03a_period'] = cb[1] - cb[0]
|
||||
import math
|
||||
def length(q):
|
||||
pts = [(x*640, y*360) for x, y in q.verts]
|
||||
return max(math.dist(pts[i], pts[j]) for i in range(4) for j in range(i+1, 4))
|
||||
f['pteff03_len'] = sum(length(p[1]) for p in pairs) / len(pairs)
|
||||
f['pteff03a_len'] = sum(length(p[2]) for p in pairs) / len(pairs)
|
||||
# parent ramp: sweep quad A's alpha climbing to full from its cycle start
|
||||
start = ca[0]
|
||||
seq = [(fm, a.alpha) for fm, a, _ in pairs if fm >= start]
|
||||
f['parent_ramp'] = next(fm for fm, al in seq if al >= 239) - start
|
||||
# ptcopyright: fade-in length of the -0.54..0.54 quad
|
||||
cop = [(fm, q.alpha) for fm in fs for q in fr[fm]
|
||||
if tuple(round(v[0], 2) for v in q.verts) == PTCOPY_X]
|
||||
f['ptcopyright_ramp'] = next(fm for fm, a in cop if a >= 255) - cop[0][0]
|
||||
# ptbtn00f: the slot that comes and goes
|
||||
slots = collections.defaultdict(list)
|
||||
for fm in fs:
|
||||
for q in fr[fm]: slots[(q.page[:4], round(q.cx, 2))].append(fm)
|
||||
cands = []
|
||||
for k, v in slots.items():
|
||||
if len(v) < 200: continue
|
||||
gaps = sum(1 for i in range(1, len(v)) if v[i]-v[i-1] > 1)
|
||||
if gaps >= 8: cands.append((gaps, k, v))
|
||||
cands.sort(reverse=True)
|
||||
if swap_plate and len(cands) > 0: # selftest: point the label at ptcopyright instead
|
||||
f['pulse_period'] = f['ptcopyright_ramp']
|
||||
else:
|
||||
v = cands[0][2]
|
||||
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]
|
||||
return f
|
||||
|
||||
# label -> (derived ratio, declared value, tolerance, what the label asserts)
|
||||
def checks(f):
|
||||
return [
|
||||
("pteff03a is the 720-unit leaf (not a second copy of the 600)",
|
||||
f['pteff03a_period']/f['pteff03_period'], 720/600, 0.05),
|
||||
("pteff03a is the sy=800 strip, pteff03 the sy=600",
|
||||
f['pteff03a_len']/f['pteff03_len'], 800/600, 0.06),
|
||||
("the pulsing slot is ptbtn00f (120-unit loop vs the sweep's 600 leaf units)",
|
||||
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),
|
||||
]
|
||||
|
||||
def run(swap=False):
|
||||
bad = 0
|
||||
for name, path in CAPS.items():
|
||||
f = features(path, swap_plate=swap)
|
||||
print(f" {name}:")
|
||||
for label, got, want, tol in checks(f):
|
||||
err = abs(got-want)/want
|
||||
ok = err <= tol
|
||||
bad += not ok
|
||||
print(f" [{'PASS' if ok else 'FAIL'}] {got:.4f} vs {want:.4f} ({err*100:4.1f}%, tol {tol*100:.0f}%) {label}")
|
||||
return bad
|
||||
|
||||
if __name__ == '__main__':
|
||||
if '--selftest' in sys.argv:
|
||||
print("SELFTEST — the plate label deliberately pointed at ptcopyright.")
|
||||
print("A check that cannot fail here would not have caught the real error.\n")
|
||||
bad = run(swap=True)
|
||||
print(f"\n{'OK: mislabel detected' if bad else 'BROKEN: mislabel NOT detected'} ({bad} failures)")
|
||||
sys.exit(0 if bad else 1)
|
||||
print("Element labels in the F5/F6 findings, re-derived from captures + disc:\n")
|
||||
bad = run()
|
||||
print(f"\n{'all labels agree' if not bad else str(bad)+' LABEL(S) DRIFTED'}")
|
||||
sys.exit(1 if bad else 0)
|
||||
Reference in New Issue
Block a user