Commit af60c81a by PLN (Algolia)

feat(opal26): all four open cuts settled by ear — and the one nobody had counted

PLN made the four calls in the boundary lab. Merged into `verified`, so the
ear-boundary file now covers #1 and #3-#13 and #15; `unjudged` is empty. The raw
export is committed next to it as provenance rather than being consumed and
thrown away.

  #11  3557.33   takeover was +0.13s  -> the machine had it
  #12  3680.10   takeover was -8.00s  -> "here the right cut is mid :)"
  #13  3887.25   ear-only, no candidate could exist
  #15  4547.57   ear-only, no candidate could exist

Worth recording that #11 and #12 disagree about WHICH fraction is right: on one
the takeover was the cut, on the other the midpoint was, 8 s earlier. That kills
any remaining hope of a single universal fraction and confirms the tool's shape
was the right call — offer the candidates, let the ear pick, do not average.

The unnoticed gap: #13's END. The set is butt-joined, so that edge is the
boundary INTO Desire (nominal 4180.0), which was never verified because Desire
itself is cut from the release — it looked like work that no longer mattered.
It is the opposite: with Desire dropped, that edge is where the last official
track ends, and if it is late then Desire's intro bleeds into the end of the
record. Rendered it as a fifth boundary; the builder now reads
`cut_from_release` and labels such an edge "END of #13 Vague de CRIME" with an
on-screen explanation, so the next gig cannot lose the same edge the same way.

Also made bounds-smoke pick its subject by content instead of position: it broke
the moment #11 became settled and lost its candidate buttons, which is a data
change, not a regression. It now prefers a boundary that still has a machine
candidate and falls back to the nominal marker every boundary carries. 11/11
against the production server.
parent e5fb534b
...@@ -113,6 +113,11 @@ def main() -> int: ...@@ -113,6 +113,11 @@ def main() -> int:
v = verified.get(str(tno), {}) v = verified.get(str(tno), {})
c = cands.get(str(tno), {}) c = cands.get(str(tno), {})
# A boundary into a track that is CUT from the release is not a start at
# all — it is the previous track's END, and it still has to be right or
# the dropped track's intro bleeds into the last one that ships. Label it
# as what it is, or it reads as work that no longer matters.
cut = ear.get("cut_from_release", {}).get(str(tno))
marks = [] marks = []
# order matters: this is the audition order, best-first. `takeover` came # order matters: this is the audition order, best-first. `takeover` came
# within 0.7 s on both boundaries that also had ear answers — n=2, on the # within 0.7 s on both boundaries that also had ear answers — n=2, on the
...@@ -126,9 +131,12 @@ def main() -> int: ...@@ -126,9 +131,12 @@ def main() -> int:
if v.get("alt_start") is not None: if v.get("alt_start") is not None:
marks.append({"key": "alt", "label": "ear alt", "t": float(v["alt_start"])}) marks.append({"key": "alt", "label": "ear alt", "t": float(v["alt_start"])})
prev_title = clean_title(by_track.get(tno - 1, {}).get("title", "?"))
out_rows.append({ out_rows.append({
"track": tno, "track": tno,
"title": clean_title(nxt["title"]), "title": clean_title(nxt["title"]),
"cutFromRelease": bool(cut),
"role": (f"END of #{tno - 1} {prev_title}" if cut else "start"),
"fromTitle": clean_title(by_track.get(tno - 1, {}).get("title", "?")), "fromTitle": clean_title(by_track.get(tno - 1, {}).get("title", "?")),
"nominal": nominal, "nominal": nominal,
"clipStart": t0, "clipStart": t0,
...@@ -138,7 +146,7 @@ def main() -> int: ...@@ -138,7 +146,7 @@ def main() -> int:
"marks": marks, "marks": marks,
"settled": v.get("start") is not None, "settled": v.get("start") is not None,
"noCandidate": not marks or all(m["key"] in ("ear", "alt") for m in marks), "noCandidate": not marks or all(m["key"] in ("ear", "alt") for m in marks),
"note": c.get("note") or v.get("note") or "", "note": (cut.get("note", "") + " " if cut else "") + (c.get("note") or v.get("note") or ""),
}) })
doc = { doc = {
......
...@@ -28,7 +28,7 @@ ...@@ -28,7 +28,7 @@
}, },
"7": { "7": {
"start": 2248.8, "start": 2248.8,
"note": "first 21s are still Take Five Drops the xfade" "note": "first 21s are still Take Five Drops \u2014 the xfade"
}, },
"9": { "9": {
"start": 2954.4, "start": 2954.4,
...@@ -42,11 +42,11 @@ ...@@ -42,11 +42,11 @@
"start": 2726.4, "start": 2726.4,
"note": "piment until 0:02, xfade starts, but unfortunate MIDI events make a clean single start only at 0:06.4; PLN also floats xfading it ourselves from 0:10.9 in postprod", "note": "piment until 0:02, xfade starts, but unfortunate MIDI events make a clean single start only at 0:06.4; PLN also floats xfading it ourselves from 0:10.9 in postprod",
"alt_start": 2730.9, "alt_start": 2730.9,
"alt_note": "0:10.9 only if we craft our own crossfade" "alt_note": "0:10.9 \u2014 only if we craft our own crossfade"
}, },
"1": { "1": {
"start": 1.45, "start": 1.45,
"note": "GO, but 'trim leading silence tho' (PLN). The amount is now MEASURED, not detected: the head is literal digital silence every sample is exactly zero up to sample 64038 (= 1.452109 s @ 44.1 kHz), and the first audible frame peaks at -28 dBFS. Cutting at 1.45 keeps a ~2 ms guard before the first transient so the attack cannot be clipped.", "note": "GO, but 'trim leading silence tho' (PLN). The amount is now MEASURED, not detected: the head is literal digital silence \u2014 every sample is exactly zero up to sample 64038 (= 1.452109 s @ 44.1 kHz), and the first audible frame peaks at -28 dBFS. Cutting at 1.45 keeps a ~2 ms guard before the first transient so the attack cannot be clipped.",
"measured": { "measured": {
"first_nonzero_sample": 64038, "first_nonzero_sample": 64038,
"first_nonzero_s": 1.452109, "first_nonzero_s": 1.452109,
...@@ -55,54 +55,25 @@ ...@@ -55,54 +55,25 @@
"method": "sample-exact scan; NOT a threshold and NOT a detector estimate", "method": "sample-exact scan; NOT a threshold and NOT a detector estimate",
"date": "2026-08-16" "date": "2026-08-16"
} }
} },
},
"unjudged": [
11,
12,
13,
15
],
"_candidates_comment": [
"UNCONFIRMED machine proposals, kept separate from `verified` on purpose —",
"nothing here is a boundary until PLN's ear says so. From cut_lens3 run 2",
"(cut_candidates_v3run2.json). On the two boundaries that also have ear",
"answers, u0.85/`takeover` landed within 0.7 s (#8 2726.25 vs 2726.4;",
"#10 3268.3 vs 3269.0) — encouraging, but that is n=2 on the cases that were",
"already easy, so it is a reason to audition `takeover` FIRST, not to trust it.",
"#13 and #15 are absent by exhaustion: zero voting orbits across three gates",
"and two estimators. Those two are ear-only; see cut_lens3.py's docstring."
],
"candidates": {
"11": { "11": {
"title": "Ghosts in the Toilets", "start": 3557.33,
"nominal": 3560.2, "note": "takeover is the right cut time here. \u2014 ear call via boundary lab 2026-08-16 (source: playhead); machine takeover 3557.2 was +0.13s"
"takeover": 3557.2,
"onset": 3515.7,
"mid": 3554.2,
"voters": 3,
"note": "all five fractions inside 3.5 s => SHORT crossfade; nominal looks ~3 s late"
}, },
"12": { "12": {
"title": "Mafia", "start": 3680.1,
"nominal": 3681.6, "note": "here the right cut is mid :) \u2014 ear call via boundary lab 2026-08-16 (source: mid); machine takeover 3688.1 was -8.00s"
"takeover": 3688.1,
"onset": 3667.1,
"mid": 3680.1,
"voters": 3,
"note": "span 21 s => LONG crossfade; nominal sits mid-span, so the takeover is later than the current cut"
}, },
"13": { "13": {
"title": "Vague de CRIME", "start": 3887.25,
"nominal": 3889.1, "note": "ear call via boundary lab 2026-08-16 (source: playhead)"
"note": "NO CANDIDATE — timbral lens structurally blind here (shared sounds across the switch). Ear-only."
}, },
"15": { "15": {
"title": "REVOLUTION", "start": 4547.57,
"nominal": 4547.4, "note": "ear call via boundary lab 2026-08-16 (source: playhead)"
"note": "NO CANDIDATE — same as #13. Ear-only."
} }
}, },
"unjudged": [],
"cut_from_release": { "cut_from_release": {
"14": { "14": {
"title": "Desire", "title": "Desire",
......
{
"gig": "opal-festival-2026",
"_comment": [
"Ear calls from the boundary lab. Times are absolute MASTER seconds.",
"These are PLN's answers — merge them into the `verified` block of",
"judge_specs/<gig>_boundaries_ear.json, which is the ground truth."
],
"decided": {
"11": {
"start": 3557.33,
"source": "playhead",
"note": "takeover is the right cut time here."
},
"12": {
"start": 3680.1,
"source": "mid",
"note": "here the right cut is mid :)"
},
"13": {
"start": 3887.25,
"source": "playhead",
"note": ""
},
"15": {
"start": 4547.57,
"source": "playhead",
"note": ""
}
},
"skipped": []
}
\ No newline at end of file
...@@ -54,7 +54,15 @@ ok('waveform canvas rendered') ...@@ -54,7 +54,15 @@ ok('waveform canvas rendered')
// 3 — the clip is the RIGHT clip. Two independent checks, because either alone // 3 — the clip is the RIGHT clip. Two independent checks, because either alone
// lies: the mount could 404 (caught by the fetch) or fall through to the // lies: the mount could 404 (caught by the fetch) or fall through to the
// wrong directory and serve *a* file (caught by the displayed duration). // wrong directory and serve *a* file (caught by the displayed duration).
const first = doc.boundaries[0] // Pick the subject by CONTENT, not by position: as boundaries get settled their
// candidate buttons disappear, and a test pinned to boundaries[0] starts failing
// on data changes rather than on regressions. Prefer one that still has a
// machine candidate; fall back to the nominal marker every boundary carries.
const firstIdx = Math.max(0, doc.boundaries.findIndex((x) => x.marks.length > 0))
const first = doc.boundaries[firstIdx]
const probe = first.marks[0] ?? { key: 'nominal', label: 'nominal', t: first.nominal }
await p.$$eval('nav button', (n, i) => n[i].click(), firstIdx)
await p.waitForTimeout(600)
const head = await p.request.get(new global.URL(first.url, PAGE).href) const head = await p.request.get(new global.URL(first.url, PAGE).href)
const bytes = Number(head.headers()['content-length'] ?? 0) const bytes = Number(head.headers()['content-length'] ?? 0)
head.ok() && bytes > 500_000 head.ok() && bytes > 500_000
...@@ -70,23 +78,23 @@ Math.abs(shownS - want) <= 1 ...@@ -70,23 +78,23 @@ Math.abs(shownS - want) <= 1
: fail(`transport shows ${shownDur} (${shownS}s), expected ${want}s`) : fail(`transport shows ${shownDur} (${shownS}s), expected ${want}s`)
// 4 — markers are drawn, one per candidate plus the nominal // 4 — markers are drawn, one per candidate plus the nominal
const wantMarks = first.marks.length + 1 const wantMarks = first.marks.length + 1 // candidates/ear + the nominal
const marks = await p.$$eval('[part="region-content"], .wavesurfer-region', (n) => n.length) const marks = await p.$$eval('[part="region-content"], .wavesurfer-region', (n) => n.length)
marks >= wantMarks marks >= wantMarks
? ok(`${marks} markers drawn (>= ${wantMarks} expected)`) ? ok(`${marks} markers drawn (>= ${wantMarks} expected)`)
: fail(`expected >= ${wantMarks} markers, saw ${marks}`) : fail(`expected >= ${wantMarks} markers, saw ${marks}`)
// 5 — clicking a candidate button records a call IN MASTER TIME, inside the clip // 5 — clicking a candidate button records a call IN MASTER TIME, inside the clip
await p.getByRole('button', { name: /^takeover · / }).first().click() await p.getByRole('button', { name: new RegExp('^' + probe.label + ' · ') }).first().click()
await p.waitForTimeout(400) await p.waitForTimeout(400)
const callTxt = await p.textContent('section:has(textarea) .tnum') const callTxt = await p.textContent('section:has(textarea) .tnum')
const secs = Number(/\(([\d.]+)s/.exec(callTxt ?? '')?.[1]) const secs = Number(/\(([\d.]+)s/.exec(callTxt ?? '')?.[1])
Number.isFinite(secs) && secs >= first.clipStart && secs <= first.clipEnd Number.isFinite(secs) && secs >= first.clipStart && secs <= first.clipEnd
? ok(`call recorded at ${secs}s master (inside [${first.clipStart}, ${first.clipEnd}])`) ? ok(`call recorded at ${secs}s master (inside [${first.clipStart}, ${first.clipEnd}])`)
: fail(`call "${callTxt}" is not a master time inside this clip`) : fail(`call "${callTxt}" is not a master time inside this clip`)
Math.abs(secs - first.marks.find((m) => m.key === 'takeover').t) < 0.01 Math.abs(secs - probe.t) < 0.01
? ok('call equals the takeover candidate exactly — no clip/master drift') ? ok(`call equals the ${probe.label} marker exactly — no clip/master drift`)
: fail(`call ${secs} != takeover ${first.marks.find((m) => m.key === 'takeover').t}`) : fail(`call ${secs} != ${probe.label} ${probe.t}`)
// 6 — audio actually PLAYS. The media element is owned by wavesurfer and never // 6 — audio actually PLAYS. The media element is owned by wavesurfer and never
// enters the DOM, so querySelector('audio') is null by construction — that // enters the DOM, so querySelector('audio') is null by construction — that
......
...@@ -31,6 +31,8 @@ interface Boundary { ...@@ -31,6 +31,8 @@ interface Boundary {
marks: Mark[] marks: Mark[]
settled: boolean settled: boolean
noCandidate: boolean noCandidate: boolean
cutFromRelease?: boolean
role?: string
note: string note: string
} }
interface BoundsDoc { gig: string; title: string; pad: number; boundaries: Boundary[] } interface BoundsDoc { gig: string; title: string; pad: number; boundaries: Boundary[] }
...@@ -173,7 +175,17 @@ export function BoundaryLab({ gig }: { gig: string }) { ...@@ -173,7 +175,17 @@ export function BoundaryLab({ gig }: { gig: string }) {
<p className="font-mono text-xs text-ink-faint"> <p className="font-mono text-xs text-ink-faint">
{b.fromTitle} <span className="text-magenta"></span> {b.title} {b.fromTitle} <span className="text-magenta"></span> {b.title}
</p> </p>
<h2 className="text-xl font-semibold">#{b.track} {b.title}</h2> <h2 className="text-xl font-semibold">
#{b.track} {b.title}
{b.settled && <span className="ml-2 align-middle text-xs font-normal text-emerald-300">settled</span>}
</h2>
{b.cutFromRelease && (
<p className="mt-2 rounded border border-sky-500/30 bg-sky-500/10 px-3 py-2 text-xs text-sky-200">
<strong>{b.role}.</strong> {b.title} is cut from the release, so this edge is not a
track start — it is where the last official track <em>ends</em>. It still has to be
right, or {b.title}&rsquo;s intro bleeds into the end of the record.
</p>
)}
{b.noCandidate && ( {b.noCandidate && (
<p className="mt-2 flex items-start gap-2 rounded border border-amber-500/30 bg-amber-500/10 <p className="mt-2 flex items-start gap-2 rounded border border-amber-500/30 bg-amber-500/10
px-3 py-2 text-xs text-amber-200"> px-3 py-2 text-xs text-amber-200">
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment