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
...@@ -7,6 +7,8 @@ ...@@ -7,6 +7,8 @@
{ {
"track": 11, "track": 11,
"title": "Ghosts in the Toilets", "title": "Ghosts in the Toilets",
"cutFromRelease": false,
"role": "start",
"fromTitle": "Gimme Acid", "fromTitle": "Gimme Acid",
"nominal": 3560.2, "nominal": 3560.2,
"clipStart": 3515.2, "clipStart": 3515.2,
...@@ -1417,28 +1419,20 @@ ...@@ -1417,28 +1419,20 @@
], ],
"marks": [ "marks": [
{ {
"key": "takeover", "key": "ear",
"label": "takeover", "label": "ear-verified",
"t": 3557.2 "t": 3557.33
},
{
"key": "mid",
"label": "mid",
"t": 3554.2
},
{
"key": "onset",
"label": "onset",
"t": 3515.7
} }
], ],
"settled": false, "settled": true,
"noCandidate": false, "noCandidate": true,
"note": "all five fractions inside 3.5 s => SHORT crossfade; nominal looks ~3 s late" "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"
}, },
{ {
"track": 12, "track": 12,
"title": "Mafia", "title": "Mafia",
"cutFromRelease": false,
"role": "start",
"fromTitle": "Ghosts in the Toilets", "fromTitle": "Ghosts in the Toilets",
"nominal": 3681.6, "nominal": 3681.6,
"clipStart": 3636.6, "clipStart": 3636.6,
...@@ -2849,28 +2843,20 @@ ...@@ -2849,28 +2843,20 @@
], ],
"marks": [ "marks": [
{ {
"key": "takeover", "key": "ear",
"label": "takeover", "label": "ear-verified",
"t": 3688.1
},
{
"key": "mid",
"label": "mid",
"t": 3680.1 "t": 3680.1
},
{
"key": "onset",
"label": "onset",
"t": 3667.1
} }
], ],
"settled": false, "settled": true,
"noCandidate": false, "noCandidate": true,
"note": "span 21 s => LONG crossfade; nominal sits mid-span, so the takeover is later than the current cut" "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"
}, },
{ {
"track": 13, "track": 13,
"title": "Vague de CRIME", "title": "Vague de CRIME",
"cutFromRelease": false,
"role": "start",
"fromTitle": "Mafia", "fromTitle": "Mafia",
"nominal": 3889.1, "nominal": 3889.1,
"clipStart": 3844.1, "clipStart": 3844.1,
...@@ -4279,122 +4265,1548 @@ ...@@ -4279,122 +4265,1548 @@
0.3099, 0.3099,
0.3024 0.3024
], ],
"marks": [], "marks": [
"settled": false, {
"key": "ear",
"label": "ear-verified",
"t": 3887.25
}
],
"settled": true,
"noCandidate": true, "noCandidate": true,
"note": "NO CANDIDATE \u2014 timbral lens structurally blind here (shared sounds across the switch). Ear-only." "note": "ear call via boundary lab 2026-08-16 (source: playhead)"
}, },
{ {
"track": 15, "track": 14,
"title": "REVOLUTION", "title": "Desire",
"fromTitle": "Desire", "cutFromRelease": true,
"nominal": 4547.45, "role": "END of #13 Vague de CRIME",
"clipStart": 4502.45, "fromTitle": "Vague de CRIME",
"clipEnd": 4592.45, "nominal": 4180.0,
"url": "/audio/bounds/15.flac", "clipStart": 4135.0,
"clipEnd": 4225.0,
"url": "/audio/bounds/14.flac",
"peaks": [ "peaks": [
0.5188, 0.4111,
0.3985,
0.4629,
0.2823,
0.3549,
0.434,
0.4949,
0.514,
0.4691,
0.4186,
0.4695,
0.3523,
0.2904,
0.4747,
0.4687,
0.2649,
0.2399,
0.2086,
0.1803,
0.1235,
0.1536,
0.1768,
0.3064,
0.5047,
0.4635,
0.4935,
0.4398,
0.1583,
0.2824,
0.4501,
0.5449,
0.618,
0.5948,
0.4612,
0.3995,
0.4489,
0.3611,
0.2289,
0.5291,
0.5035,
0.4539,
0.4424,
0.3432,
0.2351,
0.4481,
0.5196,
0.4137,
0.1888,
0.1974,
0.1637,
0.0878,
0.1131,
0.2788,
0.3859,
0.4028,
0.3363,
0.5039,
0.4582,
0.2338,
0.2416,
0.3519,
0.4709,
0.4016,
0.4276,
0.5081,
0.4204,
0.3945,
0.3563,
0.4471,
0.485, 0.485,
0.313, 0.3588,
0.3171, 0.421,
0.6287, 0.3833,
0.7878, 0.3215,
0.7358, 0.2273,
0.7553, 0.3349,
0.4799, 0.4187,
0.5398, 0.3614,
0.5469, 0.2369,
0.4619, 0.1977,
0.4264, 0.1874,
0.0804,
0.086,
0.272,
0.2768,
0.3824,
0.3381, 0.3381,
0.3442, 0.5181,
0.3675, 0.5278,
0.3426, 0.1723,
0.3327, 0.2369,
0.348, 0.3414,
0.5385, 0.4686,
0.5118, 0.5365,
0.4422, 0.479,
0.6848, 0.3661,
0.6632, 0.3928,
0.5464, 0.3107,
0.4606, 0.3544,
0.5289, 0.394,
0.4426, 0.4685,
0.3552, 0.4731,
0.3728, 0.4448,
0.5331, 0.4287,
0.3759, 0.3144,
0.2102,
0.4032,
0.4456,
0.283,
0.1512,
0.1362,
0.1479,
0.0534,
0.1172,
0.3211,
0.3153,
0.4428,
0.3954, 0.3954,
0.5414, 0.5201,
0.5673, 0.5157,
0.4179, 0.1898,
0.2271,
0.3611,
0.3577,
0.406,
0.4059,
0.4278,
0.3687,
0.2467,
0.4201,
0.4643,
0.4163,
0.4875,
0.421,
0.4368,
0.4121,
0.222,
0.35,
0.3247,
0.2388,
0.2116,
0.1579,
0.121,
0.0901,
0.0631,
0.0701,
0.079,
0.4407,
0.3532,
0.4848, 0.4848,
0.4779, 0.4249,
0.3841, 0.2014,
0.2984, 0.1066,
0.3452, 0.3855,
0.3224, 0.3655,
0.3467, 0.4961,
0.3328, 0.5261,
0.3107, 0.3903,
0.3718, 0.4174,
0.2867, 0.3899,
0.2876, 0.3942,
0.5356, 0.2968,
0.37, 0.1571,
0.3132, 0.4671,
0.6437, 0.4663,
0.5921, 0.3779,
0.429, 0.377,
0.4098, 0.2599,
0.6118, 0.1229,
0.5194, 0.4763,
0.3637, 0.4946,
0.3266, 0.1876,
0.5609, 0.1433,
0.4079, 0.1392,
0.3384, 0.1406,
0.0495,
0.1864,
0.2263,
0.3939,
0.4179,
0.4906,
0.5047,
0.3362,
0.2477,
0.355,
0.3757,
0.4442,
0.4203,
0.415,
0.4835,
0.5317,
0.3115,
0.4537,
0.3412,
0.2819,
0.3347,
0.2421,
0.3181,
0.2346,
0.1766,
0.332,
0.2889,
0.1924,
0.1623,
0.1199,
0.0961,
0.0722,
0.1907,
0.2056,
0.338,
0.3445,
0.4774,
0.4844,
0.2215,
0.1921,
0.2747,
0.3547,
0.5128,
0.5615,
0.3528,
0.3901,
0.3334,
0.3593,
0.3719,
0.4083,
0.524,
0.4697,
0.4309, 0.4309,
0.6401, 0.4298,
0.5662, 0.1314,
0.5077, 0.1624,
0.4858, 0.4334,
0.5598, 0.3994,
0.4412, 0.1099,
0.4451, 0.1185,
0.4259, 0.0924,
0.368, 0.0558,
0.3313, 0.0561,
0.3316, 0.1226,
0.3709, 0.138,
0.3314, 0.2697,
0.3229, 0.2182,
0.6128, 0.3506,
0.5084, 0.3429,
0.3685, 0.1885,
0.5828, 0.1935,
0.7073, 0.2729,
0.5217, 0.3006,
0.468, 0.3728,
0.5228, 0.3312,
0.4589, 0.2736,
0.3657, 0.3111,
0.3142, 0.2137,
0.5314, 0.2799,
0.4304, 0.331,
0.3144, 0.3441,
0.6589, 0.3181,
0.6975, 0.3757,
0.4664, 0.3663,
0.4898, 0.3928,
0.4491, 0.0959,
0.4464, 0.1854,
0.3584, 0.1676,
0.3575, 0.2553,
0.3144, 0.2007,
0.3027, 0.1262,
0.3111, 0.1462,
0.1231,
0.0563,
0.0682,
0.0755,
0.158,
0.2147,
0.1793,
0.1713,
0.121,
0.0891,
0.1123,
0.1128,
0.1932,
0.1821,
0.1553,
0.1441,
0.0938,
0.1098,
0.1298,
0.1227,
0.1985,
0.1669,
0.1415,
0.1263,
0.0852,
0.1117,
0.1433,
0.2054,
0.194,
0.1511,
0.1795,
0.159,
0.0556,
0.0537,
0.0565,
0.1806,
0.1825,
0.157,
0.1631,
0.1563,
0.0958,
0.1304,
0.1365,
0.2003,
0.1621,
0.1458,
0.156,
0.0886,
0.119,
0.135,
0.1343,
0.198,
0.0705,
0.0681,
0.0783,
0.0746,
0.1087,
0.1344,
0.1784,
0.1891,
0.1523,
0.151,
0.1541,
0.0425,
0.0988,
0.0939,
0.1523,
0.2009,
0.1561,
0.1266,
0.103,
0.0968,
0.1044,
0.1275,
0.1427,
0.1298,
0.124,
0.1221,
0.0791,
0.0811,
0.1047,
0.11,
0.1444,
0.1259,
0.1097,
0.1025,
0.0529,
0.1144,
0.1387,
0.1378,
0.1641,
0.12,
0.1232,
0.1193,
0.0426,
0.0924,
0.1083,
0.3578,
0.4102,
0.4865,
0.5039,
0.2548,
0.126,
0.4152,
0.3583,
0.5505,
0.6044,
0.4413,
0.4201,
0.41,
0.4275,
0.3346,
0.2325,
0.5386,
0.5505,
0.2113,
0.1893,
0.1517,
0.1076,
0.3425,
0.3194,
0.1494,
0.13,
0.0571,
0.0592,
0.0586,
0.071,
0.0569,
0.2891,
0.3076,
0.3652,
0.3829,
0.2614,
0.0574,
0.2818,
0.2447,
0.401,
0.4385,
0.358,
0.4007,
0.4589,
0.35,
0.3458,
0.1638,
0.373,
0.3943,
0.1336,
0.0718,
0.0557,
0.0567,
0.3883,
0.4323,
0.0984,
0.0852,
0.0654,
0.0619,
0.0354,
0.0568,
0.1426,
0.2569,
0.3887,
0.3948,
0.5336,
0.5152,
0.1093,
0.2965,
0.2807,
0.4062,
0.4476,
0.3961,
0.386,
0.4461,
0.3461,
0.3696,
0.1804,
0.403,
0.2059,
0.1274,
0.0828,
0.0567,
0.0623,
0.2197,
0.2193,
0.1319,
0.0677,
0.0587,
0.0462,
0.0092,
0.0243,
0.0229,
0.0106,
0.0041,
0.0005,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0006,
0.0054,
0.0131,
0.0202,
0.0321,
0.0095,
0.0045,
0.0039,
0.0643,
0.0798,
0.0879,
0.1108,
0.1096,
0.0695,
0.1074,
0.1144,
0.1069,
0.0581,
0.041,
0.044,
0.0352,
0.1154,
0.1718,
0.1856,
0.2026,
0.203,
0.1745,
0.075,
0.0412,
0.0214,
0.0387,
0.108,
0.1205,
0.1445,
0.1313,
0.1057,
0.0461,
0.0303,
0.0279,
0.0846,
0.1128,
0.1201,
0.0769,
0.0167,
0.083,
0.1107,
0.1267,
0.0943,
0.0446,
0.0282,
0.0203,
0.0411,
0.0596,
0.0969,
0.0866,
0.087,
0.0838,
0.054,
0.1077,
0.1074,
0.0854,
0.1593,
0.1518,
0.1466,
0.1432,
0.1964,
0.1866,
0.1963,
0.1175,
0.1032,
0.0672,
0.0362,
0.0255,
0.0142,
0.0079,
0.0061,
0.0058,
0.0086,
0.0099,
0.0049,
0.0056,
0.0066,
0.0101,
0.062,
0.1006,
0.1198,
0.1065,
0.0996,
0.1086,
0.118,
0.0914,
0.0623,
0.0963,
0.0956,
0.0925,
0.1175,
0.1135,
0.11,
0.1003,
0.0977,
0.157,
0.1459,
0.0807,
0.061,
0.0495,
0.0308,
0.0918,
0.101,
0.1293,
0.1388,
0.0933,
0.0892,
0.0868,
0.0401,
0.0532,
0.0926,
0.1015,
0.0879,
0.126,
0.1296,
0.1472,
0.1172,
0.0297,
0.0286,
0.049,
0.0721,
0.093,
0.0786,
0.0992,
0.092,
0.035,
0.0279,
0.034,
0.1158,
0.1311,
0.1209,
0.1238,
0.0634,
0.1065,
0.1326,
0.1196,
0.0893,
0.0631,
0.0668,
0.054,
0.0729,
0.1723,
0.1805,
0.2166,
0.2192,
0.2233,
0.1587,
0.0748,
0.0573,
0.0584,
0.0705,
0.1352,
0.1494,
0.134,
0.1455,
0.0888,
0.0457,
0.0556,
0.0449,
0.1222,
0.1372,
0.1102,
0.1043,
0.0838,
0.1049,
0.1191,
0.1443,
0.0965,
0.0593,
0.0605,
0.0717,
0.0745,
0.1221,
0.1104,
0.1165,
0.1311,
0.0851,
0.0531,
0.1082,
0.0858,
0.1054,
0.1592,
0.1506,
0.1439,
0.1893,
0.1964,
0.1971,
0.0938,
0.117,
0.0865,
0.0471,
0.0247,
0.0228,
0.0065,
0.0047,
0.0023,
0.0043,
0.0052,
0.0044,
0.001,
0.001,
0.0027,
0.0004,
0.0006,
0.0007,
0.0007,
0.0007,
0.0007,
0.0007,
0.0005,
0.0003,
0.0004,
0.0003,
0.0004,
0.0003,
0.0004,
0.0004,
0.0003,
0.0002,
0.0001,
0.0001,
0.0002,
0.0003,
0.0004,
0.0005,
0.0007,
0.001,
0.0015,
0.0019,
0.0023,
0.0026,
0.0027,
0.003,
0.0697,
0.1364,
0.1145,
0.1129,
0.1446,
0.1594,
0.0417,
0.0236,
0.2335,
0.1795,
0.1769,
0.1325,
0.186,
0.2044,
0.1209,
0.0363,
0.0393,
0.0274,
0.0139,
0.2336,
0.0927,
0.0319,
0.0287,
0.021,
0.0188,
0.0161,
0.3639,
0.0959,
0.0324,
0.0379,
0.0255,
0.0129,
0.0141,
0.0211,
0.0113,
0.0163,
0.0064,
0.0055,
0.0073,
0.007,
0.0066,
0.0069,
0.0059,
0.0042,
0.0054,
0.0113,
0.0114,
0.0245,
0.011,
0.0063,
0.0047,
0.0059,
0.0054,
0.0049,
0.0069,
0.0054,
0.0059,
0.0036,
0.2981,
0.3949,
0.6566,
0.5706,
0.6321,
0.6323,
0.6009,
0.5904,
0.9688,
1.0,
0.5894,
0.6538,
0.7373,
0.8741,
0.4937,
0.3794,
0.7034,
0.7638,
0.6719,
0.9284,
0.2349,
0.1362,
0.0843,
0.756,
0.3045,
0.1106,
0.7694,
0.4316,
0.0915,
0.0658,
0.9373,
0.3325,
0.0967,
0.0702,
0.0758,
0.0339,
0.0396,
0.0431,
0.0299,
0.0429,
0.0254,
0.0276,
0.0193,
0.0159,
0.0162,
0.0163,
0.0147,
0.0158,
0.0185,
0.0148,
0.0167,
0.027,
0.0195,
0.0168,
0.0178,
0.0161,
0.0134,
0.0129,
0.0162,
0.0131,
0.0149,
0.0132,
0.0238,
0.3661,
0.5926,
0.5691,
0.5317,
0.5668,
0.4925,
0.5516,
0.9035,
0.9898,
0.7757,
0.4373,
0.6715,
0.7706,
0.4304,
0.3984,
0.6069,
0.1401,
0.6261,
0.7197,
0.18,
0.0969,
0.0957,
0.4966,
0.2084,
0.0772,
0.484,
0.2774,
0.0689,
0.0543,
0.5881,
0.3618,
0.084,
0.06,
0.0647,
0.0259,
0.0233,
0.028,
0.0187,
0.0265,
0.0169,
0.0156,
0.0108,
0.0096,
0.0076,
0.0067,
0.0069,
0.0051,
0.0071,
0.0048,
0.0055,
0.0065,
0.012,
0.006,
0.0046,
0.0058,
0.0052,
0.0027,
0.0044,
0.0042,
0.0034,
0.0034,
0.0047,
0.2687,
0.4179,
0.4571,
0.3395,
0.4635,
0.3062,
0.4478,
0.7297,
0.7461,
0.8239,
0.3718,
0.5558,
0.4146,
0.6181,
0.3011,
0.4565,
0.4692,
0.4988,
0.5892,
0.218,
0.0828,
0.0859,
0.4748,
0.3041,
0.0725,
0.4191,
0.2238,
0.057,
0.0339,
0.0492,
0.4822,
0.1143,
0.049,
0.0379,
0.0301,
0.0179,
0.0136,
0.021,
0.0193,
0.0147,
0.0126,
0.0076,
0.0065,
0.0051,
0.0045,
0.0046,
0.0115,
0.0333,
0.0126,
0.0036,
0.0028,
0.0078,
0.0039,
0.003,
0.0256,
0.0162,
0.0029,
0.003,
0.0028,
0.0023,
0.0023,
0.0011,
0.188,
0.2747,
0.319,
0.313,
0.318,
0.2129,
0.3072,
0.5039,
0.4358,
0.577,
0.3081,
0.3928,
0.3732,
0.4528,
0.256,
0.2732,
0.3867,
0.3981,
0.4854,
0.2165,
0.0686,
0.0708,
0.0433,
0.3913,
0.1615,
0.3996,
0.3004,
0.1845,
0.043,
0.0369,
0.4819,
0.1297,
0.056,
0.0377,
0.0303,
0.0165,
0.0178,
0.021,
0.0128,
0.0361,
0.0186,
0.0112,
0.0066,
0.0056,
0.0051,
0.0046,
0.0039,
0.056,
0.0219,
0.0087,
0.0029,
0.0078,
0.0039,
0.003,
0.0038,
0.0405,
0.0043,
0.0018,
0.003,
0.0022,
0.0023,
0.0012,
0.1517,
0.188,
0.3137,
0.2674,
0.318,
0.3092,
0.3071,
0.3038,
0.5039,
0.5771,
0.3209,
0.3548,
0.3928,
0.4528,
0.256,
0.197,
0.3879,
0.3991,
0.4854,
0.3538,
0.099,
0.0708,
0.0433,
0.3913,
0.1615,
0.06,
0.3996,
0.2242,
0.043,
0.0369,
0.482,
0.1297,
0.0566,
0.0377,
0.0335,
0.0158,
0.0178,
0.021,
0.0139,
0.0365,
0.0348,
0.0111,
0.0075,
0.0056,
0.0051,
0.0046,
0.0045,
0.056,
0.0385,
0.0103,
0.0033,
0.0078,
0.0049,
0.0036,
0.0038,
0.0405,
0.0101,
0.0025,
0.003,
0.0028,
0.0023,
0.0021,
0.0188,
0.188,
0.3191,
0.3131,
0.2982,
0.318,
0.2871,
0.3072,
0.5039,
0.5771,
0.3275,
0.309,
0.3928,
0.4528,
0.256,
0.2327,
0.3879,
0.0922,
0.3991,
0.4854,
0.1304,
0.0669,
0.0708,
0.3913,
0.1615,
0.0612,
0.3996,
0.2242,
0.0476,
0.0337,
0.482,
0.293,
0.0566,
0.0504,
0.0377,
0.0158,
0.0178,
0.021,
0.0139,
0.0361,
0.0349,
0.0112,
0.0075,
0.0066,
0.0051,
0.0045,
0.0046,
0.056,
0.0453,
0.017,
0.0036,
0.0042,
0.0078,
0.0039,
0.0031,
0.0405,
0.0136,
0.0029,
0.003,
0.0028,
0.0022,
0.0023,
0.0032,
0.188,
0.2858,
0.3137,
0.2328,
0.3179,
0.21,
0.3071,
0.5005,
0.5728,
0.5824,
0.2503,
0.3927,
0.3291,
0.4524,
0.233,
0.386,
0.2836,
0.3982,
0.4814,
0.1392,
0.0682,
0.0707,
0.391,
0.2504,
0.074,
0.399,
0.2238,
0.057,
0.0339,
0.2576,
0.4822,
0.1143,
0.0557,
0.0379,
0.0301,
0.0179,
0.0136,
0.021,
0.0279,
0.0378,
0.0109,
0.0075,
0.0065,
0.0051,
0.0045,
0.0046,
0.0202,
0.0096,
0.0052,
0.0036,
0.0028,
0.0078,
0.0039,
0.0777,
0.2321,
0.282,
0.2692,
0.235,
0.1823,
0.1936,
0.2503,
0.5615,
0.4161,
0.4136,
0.5071,
0.4493,
0.4307,
0.3881,
0.4553,
0.4669,
0.2215,
0.2051,
0.5627,
0.4701,
0.4634,
0.3908,
0.5002,
0.5179,
0.2958,
0.5066,
0.3984,
0.3163,
0.2704,
0.6561,
0.3536,
0.2594,
0.4758,
0.3991,
0.2827,
0.2307,
0.805,
0.4445,
0.2937,
0.2249,
0.0807,
0.1142,
0.1381,
0.2205,
0.2318,
0.2218,
0.2067,
0.1989,
0.1822,
0.1724,
0.1962,
0.1869,
0.1598,
0.1448,
0.4701,
0.3948,
0.2742,
0.2374,
0.4638,
0.2979,
0.2236,
0.4226,
0.3451,
0.2638,
0.2153,
0.3797,
0.3178,
0.2134,
0.1836,
0.9568,
0.7413,
0.9007,
0.8004,
0.885,
0.3352,
0.2376,
0.7687,
0.702,
0.8637,
0.665,
0.8779,
0.5638,
0.8239,
0.4635,
0.3359,
0.2775,
0.1969,
0.7841,
0.6039,
0.1941,
0.1736,
0.3715,
0.2004,
0.1799,
0.5044,
0.4109,
0.1885,
0.1649,
0.1125,
0.0802,
0.102,
0.1593,
0.1642,
0.1531,
0.1532,
0.147,
0.1444,
0.1406,
0.1443,
0.147,
0.1342,
0.1406,
0.3144,
0.4164,
0.2807,
0.2404,
0.4335,
0.3341,
0.2567,
0.2167,
0.2039,
0.1442,
0.1602,
0.4374,
0.383,
0.2951,
0.2786,
0.6517,
0.5316,
0.6739,
0.666,
0.6843,
0.6044,
0.3437,
0.4752,
0.601,
0.627,
0.6202,
0.622,
0.4225,
0.5794,
0.4643,
0.608,
0.4425,
0.3235,
0.8252,
0.6322,
0.3171,
0.2863,
0.6133,
0.3778,
0.3011,
0.3114,
0.7826,
0.3533,
0.3019,
0.2812,
0.117,
0.1442,
0.2281,
0.2667,
0.2593,
0.2543,
0.236,
0.2247,
0.212,
0.2058,
0.2067,
0.1868,
0.1864,
0.1736,
0.5637,
0.4024,
0.3281,
0.5637,
0.4652,
0.3432,
0.2859,
0.5759,
0.477,
0.3349,
0.2934,
0.5586,
0.414,
0.3163,
0.2834,
0.4799,
0.5066,
0.4861,
0.5208,
0.5115,
0.3438,
0.2912,
0.4969,
0.542,
0.5557,
0.4694,
0.5527,
0.4842,
0.5162,
0.5785,
0.421,
0.3191,
0.3039,
0.7912,
0.4414,
0.3004,
0.6017,
0.4684,
0.3356,
0.3027,
0.2293
],
"marks": [],
"settled": false,
"noCandidate": true,
"note": "PLN 2026-08-16, CONFIRMED: 'i confirm skip desire stronger set without'. #13 Vague de CRIME becomes the last OFFICIAL track; #15 REVOLUTION remains the Encore (its section in tracks.json). Release is 14 tracks. "
},
{
"track": 15,
"title": "REVOLUTION",
"cutFromRelease": false,
"role": "start",
"fromTitle": "Desire",
"nominal": 4547.45,
"clipStart": 4502.45,
"clipEnd": 4592.45,
"url": "/audio/bounds/15.flac",
"peaks": [
0.5188,
0.485,
0.313,
0.3171,
0.6287,
0.7878,
0.7358,
0.7553,
0.4799,
0.5398,
0.5469,
0.4619,
0.4264,
0.3381,
0.3442,
0.3675,
0.3426,
0.3327,
0.348,
0.5385,
0.5118,
0.4422,
0.6848,
0.6632,
0.5464,
0.4606,
0.5289,
0.4426,
0.3552,
0.3728,
0.5331,
0.3759,
0.3954,
0.5414,
0.5673,
0.4179,
0.4848,
0.4779,
0.3841,
0.2984,
0.3452,
0.3224,
0.3467,
0.3328,
0.3107,
0.3718,
0.2867,
0.2876,
0.5356,
0.37,
0.3132,
0.6437,
0.5921,
0.429,
0.4098,
0.6118,
0.5194,
0.3637,
0.3266,
0.5609,
0.4079,
0.3384,
0.4309,
0.6401,
0.5662,
0.5077,
0.4858,
0.5598,
0.4412,
0.4451,
0.4259,
0.368,
0.3313,
0.3316,
0.3709,
0.3314,
0.3229,
0.6128,
0.5084,
0.3685,
0.5828,
0.7073,
0.5217,
0.468,
0.5228,
0.4589,
0.3657,
0.3142,
0.5314,
0.4304,
0.3144,
0.6589,
0.6975,
0.4664,
0.4898,
0.4491,
0.4464,
0.3584,
0.3575,
0.3144,
0.3027,
0.3111,
0.3753, 0.3753,
0.3726, 0.3726,
0.3098, 0.3098,
...@@ -5695,10 +7107,16 @@ ...@@ -5695,10 +7107,16 @@
0.4376, 0.4376,
0.5623 0.5623
], ],
"marks": [], "marks": [
"settled": false, {
"key": "ear",
"label": "ear-verified",
"t": 4547.57
}
],
"settled": true,
"noCandidate": true, "noCandidate": true,
"note": "NO CANDIDATE \u2014 same as #13. Ear-only." "note": "ear call via boundary lab 2026-08-16 (source: playhead)"
} }
] ]
} }
\ 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