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:
32
crates/sylpheed-formats/examples/leaf_keyframes.rs
Normal file
32
crates/sylpheed-formats/examples/leaf_keyframes.rs
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
//! Dump the keyframes of any nested `.rat` leaf by name.
|
||||||
|
//!
|
||||||
|
//! cargo run -p sylpheed-formats --example leaf_keyframes -- GP_TITLE ptbtn00.rat 2 3 4
|
||||||
|
use sylpheed_formats::{pak::PakArchive, ui_layout};
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let root = PathBuf::from(std::env::var("SYLPHEED_DISC").expect("SYLPHEED_DISC"));
|
||||||
|
let argv: Vec<String> = std::env::args().skip(1).collect();
|
||||||
|
let pak = argv[0].clone();
|
||||||
|
let rec = argv[1].clone();
|
||||||
|
let builds: Vec<usize> = argv[2..].iter().filter_map(|a| a.parse().ok()).collect();
|
||||||
|
let ar = PakArchive::open(root.join(format!("dat/{pak}.pak"))).expect("pak");
|
||||||
|
for e in builds {
|
||||||
|
let Ok(by) = ar.read(&ar.entries()[e]) else { continue };
|
||||||
|
let Some(b) = ui_layout::parse_build(&by) else { continue };
|
||||||
|
let Some(&(lo, ls)) = b.records.get(rec.as_str()) else { continue };
|
||||||
|
let leaf = &by[lo..(lo + ls).min(by.len())];
|
||||||
|
println!("=== {pak} entry {e} — {rec} (leaf {ls} bytes) ===");
|
||||||
|
if let Some(u) = ui_layout::loop_length_units(leaf) { println!(" declared loop: {u} units"); }
|
||||||
|
if let Some(lb) = ui_layout::parse_build(leaf) {
|
||||||
|
for el in &lb.elements {
|
||||||
|
println!(" {} — {} keyframes", el.name, el.keyframes.len());
|
||||||
|
for (i, k) in el.keyframes.iter().enumerate() {
|
||||||
|
println!(" kf{i:<2} t={:<5} x={:<6} y={:<6} fade={:08X} (alpha {:3})",
|
||||||
|
k.time.map(|t| t.to_string()).unwrap_or_else(|| "-".into()),
|
||||||
|
k.x, k.y, k.fade, k.fade >> 24);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
77
docs/re/label-check-and-the-alpha-80-refutation.md
Normal file
77
docs/re/label-check-and-the-alpha-80-refutation.md
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
# A check that reads the label, not the citation — and one refutation it turned up
|
||||||
|
|
||||||
|
**Question:** do the element labels in my F5/F6 findings still agree with the
|
||||||
|
declared data?
|
||||||
|
|
||||||
|
**What the human looks at:** `python3 tools/re-capture/check_labels.py` — every
|
||||||
|
line PASS. Then `--selftest`, which must FAIL.
|
||||||
|
|
||||||
|
**What this does NOT cover:** labels with **no** declared counterpart. Named
|
||||||
|
below; that gap is real and unclosed.
|
||||||
|
|
||||||
|
## ✅ The check
|
||||||
|
|
||||||
|
Three of my errors were the **label**, not the measurement — `0x3003` read as a
|
||||||
|
different role from `0x3002`, and `ptcopyright` called "the plate" twice, the
|
||||||
|
second time on a page written *after* I had corrected it. Every number was right.
|
||||||
|
A correction living in one document did not reach the next one I wrote.
|
||||||
|
|
||||||
|
`sylpheed-port` built `check-authored-vs-declared` for values with a declared
|
||||||
|
counterpart, and named the gap it leaves: a value that exists only in a capture
|
||||||
|
and names an element rests entirely on my label. **This is that case.** Each
|
||||||
|
identification was originally *made* by matching a declared quantity, so each is
|
||||||
|
re-derivable:
|
||||||
|
|
||||||
|
```
|
||||||
|
[PASS] 1.1841 vs 1.2000 ( 1.3%) pteff03a is the 720-unit leaf, not a second 600
|
||||||
|
[PASS] 1.3017 vs 1.3333 ( 2.4%) pteff03a is the sy=800 strip
|
||||||
|
[PASS] 0.0993 vs 0.1000 ( 0.7%) the pulsing slot has the declared 120-unit period
|
||||||
|
[PASS] 0.7500 vs 0.7333 ( 2.3%) the -0.54 quad is ptcopyright (22-unit ramp)
|
||||||
|
```
|
||||||
|
|
||||||
|
Both captures, eight checks, all pass.
|
||||||
|
|
||||||
|
**The control is the point.** `--selftest` points the plate label at
|
||||||
|
`ptcopyright` — the exact error I made — and it must fail:
|
||||||
|
|
||||||
|
```
|
||||||
|
[FAIL] 0.0180 vs 0.1000 (82.0%) the pulsing slot has the declared 120-unit period
|
||||||
|
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"
|
||||||
|
|
||||||
|
Attempted against their audit, and it **lands for one of the five**.
|
||||||
|
|
||||||
|
`ptbtn00f`'s **peak alpha of 80** is a figure I measured and sent them. The disc
|
||||||
|
does not declare it:
|
||||||
|
|
||||||
|
* `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**.
|
||||||
|
|
||||||
|
**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.
|
||||||
|
|
||||||
|
## 🟡 And it opens a fourth label question — not mine originally
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
## 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.
|
||||||
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