- 21 Aug, 2026 11 commits
-
-
check-drift flagged 13 uncommitted .tidal files as "the stale-buffer signature" and its remedy section led with `git checkout -- <file>`. I nearly relayed that. All 13 were PLN's own work: the ^43/^44 -> ^41 gMask-retirement migration (37857225 freed ^41 for d1's gate) plus real musical edits — new octersubbus and squizbus lines, `slow 2 $ ply 2` reworks, gain 1.4->1.6, a sample change from n "74" to n "25". `git checkout --` would have destroyed a set the night before the gig, on the advice of a check that was confident and wrong. The script's own design comment already had the right principle: "drift is not control numbers changed, it is control numbers moved AWAY FROM THE GRID". Its per-file test just cannot see direction — it counts CHANGED ^NN lines (>= 4) and ANDs that with a global grid failure. Both were true here for innocent reasons: the grid is unaligned because the migration is HALF-APPLIED, and those files are the half that is done. Volume of change cannot separate a repair from a regression. Only direction can, and migrate-columns --plan already knows it: 0 pending moves for a file means its edits ARE the alignment. So: the DRIFT message now names both hypotheses with the one command that distinguishes them, the grid failure reports migrate-columns' pending count (20 here) and spells out that UNALIGNED is not REGRESSED, and the destructive remedy is behind an explicit "look at the diff first" with this incident as the reason. A gate that recommends data loss on ambiguous evidence is worse than no gate. Still exits non-zero, honestly: 9 knobs are genuinely out of column and 20 moves are genuinely pending. That is a real finding about an incomplete migration, and completing it is a decision about muscle memory that belongs to the person whose hands are on the desk — not something to apply the night before a gig.
PLN (Algolia) authored -
Two gate/monitor fixes found while clearing gig-up blockers the night before a gig. ABSENT IS NOT BROKEN. check-audio-graph hard-FAILED the moment Ardour's Master was on the internal codec, which made the whole pre-gig gate NO-GO while PLN was rehearsing on the laptop jack with the UMC in a bag. PLN: "no umc atm playing on the jack now. we need to be tolerant to various setups." He is right, and the repo already knows the rule — section 3 of this same script downgrades to warn when the interface is absent, and rig.py opens with "REPORT ABSENT AS ABSENT". Section 2 just never got the memo. Severity now depends on whether the UMC EXISTS. No UMC on the bus: warn, say plainly that the last hop cannot be proven until the interface is plugged in, and tell him to re-run at the venue. UMC present and Master still on the codec: that is the real regression this check was written for — Ardour restores its own saved ports and does NOT follow the PipeWire default sink, so it will happily ignore an interface sitting right there. Result: OK (3 warnings) instead of a NO-GO on a legitimate setup. A DEEP QUEUE IS NOT A BUFFER, IT IS A DELAY. PLN: "when i move faders up/down quickly, [it] lags by 500ms+, almost 1s, behind ahah". Chased this to the wrong end twice, worth recording. First guess was fork+exec per LED write — measured it: 2.8 ms p50, 8 ms p99, and --bench shows the coalescer at p99 18 ms with ZERO overrun, so the LED path was never the problem. Faders have no LEDs at all (row D), so a fader sweep does no LED writes; and the Pulsar HUD has no MIDI tap, so it cannot lag on MIDI either. Second guess was aseqdump block-buffering its stdout into a pipe (the exact bug this repo documents for Python) — disproved with an isolated aseqdump client fed by aseqsend: median 40 ms inter-arrival at a 25/s send rate, last event landing BEFORE the last send. It flushes per event. The real seam is the Bridge's SSE fan-out: `queue.Queue(maxsize=512)`. A fader sweep is ~400 CC/s, so 512 deep is 1.3 s of backlog — and once it fills it STAYS full, because drop-oldest holds the buffer AT capacity. Every event the browser renders is then 512 events stale, permanently. The drop policy was already right; the DEPTH was the latency. 512 was picked as "generous" and is really a latency budget nobody priced. Now 64 (~160 ms at that rate) — jitter absorption rather than a queue. Paired with the browser-side coalescing from 72937cc7, the consumer drains far faster than it fills, so it should rarely be reached. Verified: 79/79 bridge tests, check-audio-graph OK on the jack.
PLN (Algolia) authored -
gig-up's "tools executable" gate has been NO-GO on this box, and the reason is the trap the repo already has a lesson about: chmod fixes YOUR tree, not the repo. parvagues-protect.sh and install-protect.sh both ran fine here and were recorded 100644, so a fresh clone gets two non-executable protect scripts — and the gate that catches it was being read as noise because it never went green. Fixed with `git update-index --chmod=+x`, which is the half that was skipped. lcxl-path.py (new in 7bda1ccd) had the same defect and gets the same treatment. Note the gate checks BOTH `-x` on disk and the index mode. Only the index half was failing, which is exactly the failure a local chmod hides.
PLN (Algolia) authored -
Follow-up to 72937cc7. Having found that the Bridge panel could not see the LED watcher, I went looking for what else was watching the surface. Nothing was — and one of the things that claimed to be could not fail. THE FALSE-GREEN GATE. The pre-gig check for "LCXL -> SC" was: aconnect -l | grep -A3 "Launch Control XL" | grep -q "128:" `grep -A3` matches each of the THREE lines containing "Launch Control XL" and prints three lines after each; the last window runs off the end of the LCXL block and into the next client header, which is literally `client 128: 'SuperCollider'`. So the string "128:" was in the haystack whether or not a single wire existed. This gate has been passing since it was written, for a reason unrelated to what it measures. It would have said green with the desk connected to nothing. Proved rather than argued: recorded the live graph, stripped only the Midi Through -> SC edge from it, and ran both gates against the doctored file. Old gate PASSED. New gate reported "LCXL -> Midi Through, but Through does NOT reach SC" and exited 1. tools/lcxl-path.py replaces it by walking the graph. It has to: the real path here is TWO hops (LCXL 20:0 -> Midi Through 14:0 -> SC in2), so a direct-only test would be wrong in the other direction. It takes an optional recorded-graph file argument precisely so the gate can be tested for FAILURE without unplugging hardware at a venue — the reason the old one was never caught is that nobody could cheaply watch it fail. TWO UNITS NOBODY STARTED. lcxl-leds-watch and midi-autoconnect were both `disabled`, so neither came up at login; they ran only when started by hand. Both now join the auto-start loop. A NEW GATE. "The surface is wired" and "the surface is lit" are different daemons and different failures, and only the first was ever checked. The new "LCXL LEDs" soft check reads: if the board is on the bus, the watcher must be active. Deliberately conditional on the board — at a kitchen table an unplugged desk is normal and must not warn, but at the venue this is the difference between playing blind and not. Verified both new gates in BOTH directions: green as the rig stands, and red with the watcher stopped / the Through edge cut. A gate observed only passing is indistinguishable from the one this commit deletes.
PLN (Algolia) authored -
PLN, the night before a gig: "i dont see midi feedback visuel anymore on the LCXL fix this first plz". The hardware was fine. The daemon was gone. The journal had the whole story and nobody was reading it: lcxl-leds --watch: no LCXL sequencer port; retrying in 30s (x8) Stopping LCXL LED watcher... Stopped LCXL LED watcher. The board was unplugged, the watcher retried, then it was STOPPED — and `Restart=always` does not resurrect a unit somebody stopped. On replug the board came back and the daemon did not. Three independent holes let that become a silent, session-long dark surface: 1. `lcxl-leds-watch` was MISSING from rig.py's SERVICES table, so the Bridge panel showed a fully green rig over a dark board. That is precisely the failure the module's own docstring opens with ("A GREEN UNIT IS NOT SOUND"), one table row away. It was also in neither gig-up.sh nor converge: NOTHING on this box checked whether the surface was lit. 2. The unit was `linked`, not `enabled` — it never started at login. It only ever ran because something started it by hand. 3. `midi-autoconnect` was `disabled` too. The wiring it enforces (LCXL 20:0 -> Midi Through 14:0 -> SuperCollider in2) happened to be intact, so knobs still worked and the fault stayed invisible. But the prescribed fix for the LED stall IS a replug, and a replug drops those connections with nothing to re-apply them. The two failures compound: the remedy for one silently triggers the other, mid-set. Fixed: both units enabled, and the watcher is on the panel with a state that `active` cannot express. It retries forever by design, so "alive" is not "lit" — _leds_state() cross-checks the board via procfs and reports the third case honestly. No board is ABSENT, not broken; board present with the daemon down is "plugged in but DARK, start this", the one combination that was silently wrong. All five services now read green for a reason each, not by omission. Also, the daylight ramp. PLN: "maybe brightness can be leveraged, top brightness always would make more readable signals even in day perfs." The six-step unipolar ramp spent THREE steps on dim shades — exactly the budget that vanishes outdoors. The replacement keeps five steps and puts every one at full brightness by using the two mixed hues the old ramp never touched: red 15 (g0,r3) -> orange 31 (g1,r3) -> amber 63 (g3,r3) -> yellow 62 (g3,r2) -> green 60 (g3,r0) Every value has a component at maximum, so nothing depends on a brightness difference to be legible. It costs one step and gains three readable ones — the same trade he already made for the DJ filters in July ("i agree on clarity > resolution"), which have been all-full-brightness ever since. This just brings the unipolar knobs in line. LCXL_DIM_RAMP=1 restores the old ramp, so a dark-stage revert is one env var and a restart, not a code change. And the gear's MIDI monitor: "super noisy ... cant we do way more dense". "Control change" spent 14 characters saying "CC", and a single fader sweep prepended ~100 near-identical rows. Events now abbreviate (CC/ON/OFF/PB/...) and consecutive events from the same control coalesce into ONE row that updates in place with a x-count — a 100-event sweep is one line, one DOM write per event, no node churn. Row height 20px -> 15px on top of that. Verified: 79/79 bridge tests pass; /api/rig reports all five services up with per-service proof; ramp asserted to have max(g,r)==3 at every step.PLN (Algolia) authored -
PLN, 2026-08-21: "i dont use the webui most of the time, tray indicator is the only ui i use for parvagues gear bro". The gearbox shipped with an engine, a CLI and a web panel — and its one everyday surface still said Silent/Cool/Standard/ Extreme/Normal, still wrote governor/EPP/platform_profile itself through `sudo perf-audio`, and was DISABLED at login. So the surface he uses was the one surface that had not moved. What this does * tools/bridge/gearbox.py — the client both faces speak. Names the three gears and the three trims once, reads the persisted state unprivileged, and builds the argv for a switch. rig.py's gear() now delegates to it, so the tray and the web panel cannot drift on what a gear is called. * perf-tray.py — the mode list is gone. In its place: gear (off/standard/ aggressive) and trim (quiet/auto/cool) as two INLINE radio blocks under their own headers, because the separation is the design. A reader who sees one flat list keeps believing there is a single "how fast" dial and that quiet and cool are two words for it; two headers teach the model every time the menu opens. * It no longer writes any thermal knob itself. Switching goes `sudo -n` to the arbiter, which is the point — a tray reaching around it would rebuild the four-writer mess GEARBOX.md removed, in one click. * The desired-mode bookkeeping is gone with it. The gearbox persists the gear and re-asserts it from a udev hook on power-source changes and a system-sleep hook on resume; a second bookkeeper in the tray could only ever disagree. * The checked gear is read from the FILE, never inferred from sysfs. Inference (perf.detect_mode) cannot tell a deliberate gear from power-profiles-daemon having moved the same knobs, which it does by design since the gearbox cooperates with ppd rather than masking it. * "Gear ▸" (the apps and services) is now "Rig ▸": gear is the thermal lever now, and the web panel already calls that collection the rig. * perf-tray.service was never tracked — it lived only in ~/.config. Now in the repo, so the unit and the script it runs can no longer drift apart. The icon carries the lever, so it reads with the menu shut Width = gear, colour = trim, drawn as the badge's border. One element for two axes because the panel renders this at ~22 px and any design with two separate marks turns to mush there. Width is the cue that has to survive a dark room and a peripheral glance ("am I in the gig gear?" is the mid-set question), so gear gets shape and trim, which is set-and-forget, gets colour. Off draws no ring: a bare badge is the honest picture of nothing steering the machine. First attempt used 2 px / 5 px, which looked right in a mockup and VANISHED on the panel — 64→22 px scaling turns a 2 px stroke into 0.7 px of grey. Rendering every combination at 20/24/32 px before believing it gave 6 px / 11 px, and then forced the temperature numeral and the sparkline to shrink with the ring, since an 11 px border over a 30 px numeral clips the digits at exactly the size where aggressive is the gear whose temperature you most want to read. Verified on hardware, through the real menu over its D-Bus interface (Plasma's own protocol), not by calling the Python directly: * live tooltip reads "gear: Standard · Cool · cap 15/35W" * the menu exports two radio groups, correctly checked, headers live * clicking Aggressive -> GEAR=aggressive, RAPL 45 W / 115 W * clicking Standard -> GEAR=standard, back to 15/35 W * clicking Cool -> TRIM=cool, fans 0 -> ~2900 rpm * a CLI-side gear change is picked up by the tray within one refresh * enabled and running under graphical-session.target, so it survives logout Harness caveat worth writing down: the very FIRST dbusmenu Event sent to a never-opened menu landed on the wrong item (asked for Aggressive, got trim Auto). Every subsequent one mapped exactly, from both dbus-send and gdbus. Reading it as provisional ids on a menu Qt has not yet had to show — a human cannot click an item without opening the menu first, which is what the later probes did. 18 tests for gearbox.py (79 in the bridge suite, all green). They defend two things, neither arithmetic: the sudoers contract (the argv IS a security boundary — `--trim cool` must stay two words, and `performance`/`--boot`, both real helper verbs, must never be reachable from a face that only knows gears), and the honest-unset rule (never-engaged is not the same state as off, which is why off had to become an explicit gear).PLN (Algolia) authored -
TWO OWNERS BECOME ONE. gig-up --converge called `powerprofilesctl set performance` while the gearbox wrote the same governor/EPP/platform_profile through thermal-mode, with no arbiter — whichever ran last won, and ppd re-asserts itself on AC transitions, so a converge done on battery was undone by plugging in for the set. converge now drives the gearbox. It deliberately does NOT touch trim: quiet-vs-cool is a room-by-room call (a mic'd quiet room wants Quiet even at a gig) and converge cannot know which room it is in. The resolver rejects a PRE-gearbox thermal-mode rather than calling a verb it does not have, and falls back to ppd when there is no gearbox at all — a rig check must never fail because a sibling repo is missing. THE PANEL, the GUI half of the button whose CLI half converge already was. converge owns services and refuses to open windows ("a script that opens windows under someone is a script they stop trusting"), leaving Pulsar and Ardour reported-but-not-launched. The Bridge is a thing PLN clicked, so it is allowed to open his apps. Converge runs off the request thread: a cold SuperDirt boot warms 51 banks and gig-up waits up to 100s for it, which held open as an HTTP request would read as a hung dashboard. The list under the button carries gig-up's two hard-won rules: * A GREEN UNIT IS NOT SOUND. 2026-08-01 had parvagues-sc.service active while scsynth was dead. `systemctl start` is a no-op on an active unit, so that state reads `broken` and says "needs restart, not start" — reporting it as down would send the reader to a button that cannot fix their problem. * ABSENT IS NOT DOWN. A thing you cannot start is a different problem from a thing you can, and collapsing them wastes the reader's time under pressure. Verified live: POST /api/rig converged the rig for real — SuperDirt booted, watchdog and autoroute up, faders restored, setlist and preload regenerated — and the panel tracked it through the poll. The audio ladder then reported 5/5 with scsynth and sclang joining the PipeWire trio. One bug found on the way, kept as a test: `systemctl show --value` emits properties in ITS order, not the caller's, so pairing them positionally swapped LoadState and ActiveState and made every inactive unit report its state as "loaded". Parsed by key now, with Id making each block self-identifying. 56 bridge tests green (13 new). Co-Authored-By:Claude Opus 5 <noreply@anthropic.com>
PLN (Algolia) authored -
Captain's log for the SoundCloud uploader landing. Written for the documentary rather than the board: the story beat is that I spent a session and a half picking a window on `POST /tracks` — killing three hypotheses one at a time, each death feeling like progress — while a real, logged-in, past-the-wall browser sat open with the actual upload form in it. Also records the two catches that were PLN's, not mine: the chip-input tags, and the MUI Select whose Private option my four probes all reported as absent.
PLN (Algolia) authored -
Session of 2026-08-17/18 written back to the board for a cold reader, since the Task API is still unreachable from this session and the scratchpad — which held the release plan and every log — was cleaned mid-session. (The plan regenerates from canonical sources, which is exactly why it is a generated artifact; it now lands in Prod/Opal26_master/release/ rather than /tmp.) A3 rewritten: the SoundCloud uploader WORKS. `POST /tracks` never needed solving — it was the wrong door. Recorded the account's verified state (8 of 15 up, all private, including the 72:41 mix), the two DOM facts that cost a session each, and two defects a re-run will NOT fix by itself: take-five-drops uploaded 24 s short, and ghosts-in-the-toilets carries one malformed tag from before the chip-input fix. Also recorded that api-v2 is a dead end for BOTH create and update, so nobody re-opens it. A5: the YouTube adapter is built, with the finding that decides its architecture — the Data API cannot publish at all from an unaudited project. New EPIC G (Slopmotion release videos) carries the renderer's two landed fixes with their measurements, the one modal still blocking, and the 50-commit pull that inverted the app's mode semantics under us. New EPIC H is what PLN asked for: the long-term setup plan split into PRE-GIG (what must be true before playing again — the 59%-audible kick, orphan orbits, preload staleness, SC supervision, the LCXL v3) and POST-GIG (the release pipeline). Plus H3, the standing lesson: three confident conclusions this session were wrong the same way — a blind instrument reported absence — and PLN's own eyes beat the probes every time.
PLN (Algolia) authored -
The 19 bake-off clips all shipped with the photosensitivity warning sitting on top of the visuals. PLN: "lol all clips have the epilepsy warning on top of the viz xD and all seem to have very low fps, like maybe 5 ?" Extracting a frame showed one cause behind all three symptoms: the warning modal itself; the app's full UI chrome still visible (FX rail, top bar, shortcut strip) because the modal swallowed the "minimal performance shell" click and the `h` keypress; and the apparent ~5 fps, because ~90% of every frame was static chrome behind a dark scrim while 17.6 frames/s were captured faithfully. The frame rate was never the problem — I had measured it correctly and drawn the wrong picture of what was being measured. Seeding localStorage does NOT suppress it: hexa installs a dev-only bridge that persists localStorage to disk through the dev server and calls `hydrateLocalStorageFromDisk()` at boot, overwriting anything an init script seeded. Verified by trapping `Storage.prototype.setItem` and reading the stack — the write came from `localStorageBridge.ts:150`. So use the app's own control: uncheck "Show this warning at startup", click OK, which writes through the bridge and flushes to disk. PARTIAL, deliberately. The new guard now throws rather than capturing, and on the next run it caught a SECOND modal behind the first: "START A SESSION". So the bake-off clips were not slow — the scene had never started. Committing the guard alone because it is strictly better than silently shipping 19 more scrims, and refusing loudly is the behaviour worth having even before the fix is complete. Still to do (see the task board): a `git pull` brought 50 commits that also inverted the mode semantics — `?studio=1` is now stripped and Studio is the DEFAULT, with `?live=1` opting into the full-bleed performance shell; Studio freezes shader time when `studioTransportActive === false`, which is the other half of "no motion"; and the "Minimal performance shell" button this script clicks no longer exists at all.
PLN (Algolia) authored -
Every SoundCloud blocker this week fell to looking at the page instead of reasoning about it, so the probes are kept rather than thrown away — each one encodes a question worth re-asking when SoundCloud changes its uploader again. * `probe_upload_form.py` — enumerate every input/select/textarea/button in the upload frame with its label. This produced the whole selector table the uploader is written against: #title, #trackPermalink, #artist, #primaryGenre, #tags, textarea#description, input#fileInput, and the Upload button. * `probe_upload_privacy.py` — read the radio groups properly. Answer: they are geo (worldwide/exclusiveRegions/blockedRegions) and licensing (all-rights-reserved/commons). NOT privacy. * `probe_upload_legacy.py` — does an older, non-v2 uploader still exist with a Public/Private choice? Written when I wrongly believed the v2 form had none. * `probe_upload_watch.py` — open the real form and poll for state changes, so PLN can click a control and have the script report exactly which element it is. Built after concluding, twice, that a control was absent. * `probe_upload_sharing.py` — drive the MUI Select and VERIFY the trigger changed. Confirms Public -> Private end to end. The lesson the set exists to encode: I reported "the v2 uploader is public-only" after sweeping every input, select and radio in the frame and finding nothing. The control was a MUI Select, whose options are portal-rendered only once the trigger is clicked — so an unmounted menu and an absent feature are indistinguishable to a DOM sweep. PLN pasted the real markup (`li[role=option][data-value="Private"]`) and it drove fine first try. A component library's Select is not `<select>`, and "I searched and found nothing" is a statement about the search.PLN (Algolia) authored
-
- 17 Aug, 2026 5 commits
-
-
Correcting my own previous commit, which concluded hardware GL required headful and made `--gl hw` open a real window per clip. PLN, mid-batch: "stop opening chrome windows lol cant we do this headless". Yes — and the conclusion was wrong because I tested three launch modes and stopped at the first that worked. Five headless modes plus headful, each asked what WebGL actually resolved to: --use-angle=swiftshader -> SwiftShader (Subzero) --use-gl=egl -> SwiftShader (Subzero) --use-angle=vulkan -> SwiftShader (Subzero) --ozone-platform=headless --use-angle=vulkan -> SwiftShader (Subzero) --use-gl=angle --use-angle=gl-egl -> Mesa Intel UHD (CML GT2) HEADFUL -> Mesa Intel UHD (CML GT2) `--use-gl=angle --use-angle=gl-egl` reaches the real GPU with no window at all. It is ONE FLAG WORD from `--use-gl=egl`, which silently degrades to software — which is exactly why the original code read as correct and was not, and why the comment claiming the hw path "falls back by simply measuring worse" survived so long. It never fell back: 3.5 fps and 3.2 fps were the same software backend measured twice, and two numbers that looked like measurement noise were one wrong assumption. Measured, landscape 1920x1080, same 30 s window each time: swiftshader (headless) 3.5 fps captured headful + real GPU 11.4 fps captured, page rendered 17 headless + gl-egl 17.8 fps captured, page rendered 542 of 542 Headless is FASTER than headful — no compositor in the path — and the capture bottleneck disappears with it: captured frames now equal rendered frames, where headful drained 346 of 516. So the earlier "the next lever is the capture path" note was also an artefact of the wrong launch mode. 5x the software baseline, comfortably past the under-5-fps guard, and no windows. Lesson for the file: an early success is not a search. Two of the five modes reach the GPU and three do not, and the three failures are indistinguishable from each other by frame rate alone.PLN (Algolia) authored -
Every bake-off render failed, for two independent reasons, and the second one had been sitting in this file as a comment asserting the opposite of the truth. ## 1. The playwright browser cache is empty now `chromeBinary()` scans `~/.cache/ms-playwright` for a full chromium build and returns `undefined` if it finds none, on the documented theory that this "lets playwright pick". It does not. Playwright reaches for the exact build it shipped against — `chromium_headless_shell-1234` — which was never downloaded, so every render died with "Executable doesn't exist" before rendering a frame. The cache directory is now absent entirely (it held build 1217 back in July). Falls back to `/usr/bin/chromium`: a full build, already driven by the SoundCloud transport, and no ~150 MB pull into PLN's shared cache behind his back. ## 2. `--gl hw` never got hardware GL The old comment claimed swiftshader manages ~7 fps and that `--gl hw` "asks for the real integrated GPU via EGL. Falls back by simply measuring worse." It was not falling back — it never left software. Instead of inferring the backend from frame rate on a machine whose load is a variable, I asked WebGL what it resolved to: headless + --use-angle=swiftshader -> SwiftShader (Subzero) headless + --use-gl=egl -> SwiftShader (Subzero) <- identical HEADFUL -> Mesa Intel UHD (CML GT2) Headless cannot reach this box's GPU whatever the flag. Which explains the symptom exactly: landscape 1920x1080 captured 3.5 fps on swiftshader and 3.2 fps on "hw", tripping the renderer's own under-5-fps guard both times — two numbers that look like noise and were actually the same backend twice. `--gl hw` now means HEADFUL. Measured after: **11.4 fps captured, page rendering 17** — 3.3x, comfortably past the guard. Note the bottleneck has moved: the page now renders faster than the CDP screencast drains, so the next lever is the capture path (frame encoding), not the GPU. Cost is a visible window for the duration of each capture. The July note warned against trusting those fps follow-ups because they were measured at load average 12.5, and it was right to. But the fix was not a quieter benchmark — it was asking a question that load cannot corrupt. Third time this month a "the rig is broken" finding turned out to be the instrument.PLN (Algolia) authored -
PLN: "dont render 2h for now lol render 20x different presets 30s clips then let me watch/listen/grade through. try to do sth gtreat soundreactive". Right call — 19 clips of 30 s is ~10 minutes of realtime capture against 2h25m for the record, so the taste decision gets made on cheap evidence. Also recorded: "slopmotion gave me rights to all packs, we can use any, lets be free and test stuf". Every local pack is fair game, which is worth writing down precisely because it is the opposite of the sample-bank situation next door. ## Experiment design Every clip renders the SAME 30 s window. With the audio fixed, the only variable is the look, so the grades mean something; vary two things and a favourite tells you nothing about why. 18 packs get one clip each at one reactive baseline, and clip 19 is `parvagues` again with reactivity OFF. That control earns its slot — "do something great soundreactive" is unanswerable from 19 all-reactive clips, because nothing shows what the reactivity contributes. parvagues sorts first as PLN's own imagery; the control sorts last so it is graded next to its twin. The reactive baseline is four FX chosen to read differently on a transient — glitch and rgbDelay snap, liquix flows, slowmo pulls time — since under `--triggerHeavy` each becomes kick-synced. Four subtle washes would hide the exact thing being evaluated. ## The window, and a metric that was measuring the wrong thing Reactivity feeds on onsets, so I scored 30 s windows by summed positive spectral flux normalised by level. It picked 69:18 at -23.3 dB — the near-silent seam between Vague de CRIME and REVOLUTION, i.e. PLN's 2 s rest plus a reverb tail. Dividing by the window mean rewards silence: a quiet window with any activity at all beats a loud groove. Using the level-gated answer instead — 1:34, -10.1 dB, inside the techno opener, four-on-the-floor — which is the honest test for kick-synced triggers. Same shape as ranking orbits by level instead of punch. Two preflight checks that exist because of what they would have cost: a pack needs BOTH local loops and a `videoLibrary.json` row (the renderer picks its backdrop from the library, so a pack with 30 files and no row fails at clip 12, after eleven realtime renders), and dead symlinks into Kevin's own machine are skipped rather than attempted. The grading page follows judge.html and bounds.html: local, not an Artifact, because 20 clips is hundreds of megabytes and the viewer sandbox blocks a page-initiated download — and the grades have to come back as a file.
PLN (Algolia) authored -
I had `master yt render` wrap each track in a still image, and picked that still from `output/*.jpg`. PLN: "i dont like these covers, wait where are tehse genai images from? these are not great sources! we wanna use slopmotion for videos remember?" Both objections land, and the first is the one worth recording: * Those nine illustrations are UNTRACKED and have no recorded provenance. `6c3272c9 refactor: Remove covers from git` had deliberately taken covers out of this repo, so "sitting in output/" was evidence against using them, not for it. I built a per-bank rights ledger for the audio this same session — one that refuses a sample bank precisely because its origin is unrecorded — and then reached for pictures off the floor. The discipline has to apply to every asset in a release or it is not a discipline. * Slopmotion already does this properly, already supports `--shape landscape` at 1920x1080 explicitly "for YouTube", and has already produced a real 7m26s landscape render. Nothing needed inventing. So this bridges the release plan to `visuals/slop/render_slop_clip.mjs`, naming its output exactly what `tidal_ears.yt.video_path` produces — imported from the adapter rather than reimplemented, because two copies of a filename rule is how a renderer and an uploader come to disagree about which file is track 7. ## Realtime is the whole scheduling story Slopmotion captures in realtime by design: Hydra's clock is wall time, so frame-stepping runs motion ~3x fast and drifts off the audio it reacts to. A full-length render therefore costs the track's own duration. OPAL-26 measures **2h25m** of headless Chromium for the landscape shape — 1h12m of continuous mix plus 1h13m of tracks. Too long for a shell job that dies with its terminal, so `--unit` writes a `systemd --user` service with `TimeoutStartSec=0`. Renders are resumable, so an interruption costs one clip. Preflight refuses before spending any of that: node >= 22 (system node is 16 and fails), the dev server actually answering on :5173, every audio file present, the playset known to the 24-pack catalog. ## The join that had to stay explicit `clip_ideas.json` agrees with the release plan on neither axis. Its titles are shorter or French ("Sunshine", "La Revolution Sera Samplee") and its `setlist_pos` follows an EARLIER running order — its 4 is Take five Drops where the plan's performance 4 is Am i Doing it Right. So position is not a join and title equality only catches 4 of 6 live ideas. Fuzzy matching would be worse than nothing: it puts a look on the wrong track silently. A two-entry alias table takes it to 6 of 15; the other 9 report no playset and the tool refuses to guess, because which visual goes with which track is PLN's call.PLN (Algolia) authored -
Two probes for the SoundCloud upload flow. `probe_upload_dom.py` is the one that settled it: rather than hypothesise about why /upload showed no file input, it dumps overlays and z-indexes, enumerates every frame, walks shadow roots, prints the main region's text, and tries clicking whatever looks like a drop zone. The answer arrived in one run — 0 file inputs in the top document, 1 in a nested same-origin iframe at /n/upload. `probe_upload_ui.py` now attaches in that frame and listens on the CONTEXT rather than the page, because a frame's fetches never surface on the parent's request event — the same blind spot in a second guise. It reached the real form: inputs named title / trackPermalink / artist, plus an Upload button. It captured zero finalize requests, and that is correct: it deliberately stops short of clicking Upload, and the finalize call only fires on that click. Which raises the better question — with a real form and a real Upload button in front of us, reconstructing `POST /tracks` may be work we never have to do.
PLN (Algolia) authored
-
- 16 Aug, 2026 24 commits
-
-
Writing for a cold reader, because the valuable part of this session is the DEAD hypotheses and those live only in context. POST /tracks -> 400 {"errors":[]} has now survived three explanations and each one cost a real experiment: it is not fingerprinting (identical from curl_cffi and from the page's own fetch), not the envelope ({"track":{…}} is the only shape that parses at all — flat, track[…] form and asset_data are rejected with "Failed to parse track create request"), and not the missing web session (retested after PLN logged in for real; session_ok() True, still 400). Anyone resuming would try those three first, so the board says not to. The one lead left is the consent dialog covering the upload form — session_ok() is True while input[type=file] count is 0 — so probe_upload_ui.py moves out of the scratchpad and into the repo. It attaches a 1s probe to the REAL form and logs every non-GET api-v2 request, which is how we stop reconstructing a shape from publish.py's older API and start copying one that demonstrably works. Also recorded: the multi-platform architecture PLN asked for (one manifest, many adapters, one state file), with the research behind it — Bandcamp has no upload API or CLI at all, RouteNote has no open API but ships a desktop tool, YouTube has a real one and gets chapters free from segments_v4. And the gig-list audit: 38 gigs published, 2026 has four, opal-festival-2026.md still has empty audio/video/archive fields waiting on the upload.PLN (Algolia) authored -
His verdicts, verbatim: "jungle breaks is a open domain cd, h2o is hydrogen oss drum machine, kick risers are from the tidalcycles officials or samples-extra cc0 packs, and vec1/2 come from offered free packs". Recorded against 10 banks rather than 5, because the same source answers more than the bank that prompted the question: org_jungle_breaks shares its CD, h2ogmcp and h2ogmcy are the same Hydrogen GM kit as h2ogmhh, and vec1_acid and vec1_snare are the same VEC pack as vec1_claps. Each carries the source in its note, so the reasoning survives the session that produced it. This is what ranking by leverage was for: five answers moved 60 banks from 19 clear to 29, and the four heaviest gates in the record (14, 9, 9 and 8 tracks) are now open. 31 remain, and the next one down is `snare` at 4 tracks — likely the same CC0 family as kick and risers, but that is a guess and a guess is exactly what this ledger exists to not record.
PLN (Algolia) authored -
PLN: "cant push the single tracks when samples hit, but full mix might well pass!" — which matches the measurement (an isolated track flagged where the 87-minute mix passed). So the gate on a per-track release is per BANK, and this tool answers "what is in here and where did it come from". The verdict stays his, recorded once in rights_ledger.json so it is never re-litigated. THE DISCRIMINATOR IS THE WHOLE COMMIT. "Lives in the Dirt-Samples folder" is not "is a Dirt-Samples bank": 56 of OPAL-26's 60 banks resolve to that directory, including the_revolution, like_sugar, desire, wap and crimewave — because local banks were dropped in beside the quark's own. Classifying by location would have returned a confident, comprehensively wrong LOW-RISK verdict on the most obviously sampled material in the set. That is the same shape as reading a role off a sample's name. The quark is a git checkout, so `git ls-files` settles it exactly: 217 upstream banks, 509 added locally. Tracked = ships with the quark under its license, auto-verdict `dirt_samples`. Untracked = provenance unknown to the tool, so `unknown`, which blocks by design. A bank in the score but not on disk reports UNRESOLVED rather than passing as safe. RANKED BY LEVERAGE, not alphabetically. 41 open banks reads as 41 equal chores; in fact five gate almost the whole record — jungle_breaks (14 tracks), h2ogmhh (9), kick (9), risers (8), vec1_claps (6). Answering those five changes the shape of the release; answering `take5` changes one track. Keystone first. First run on OPAL-26: 60 distinct banks, 19 upstream and clear, 41 open, so all 15 tracks currently block. That is the honest starting position, not a failure — and it is per-track only. The continuous mix remains the lower-risk artefact and is a separate question.
PLN (Algolia) authored -
PLN: "cant push the single tracks when samples hit, but full mix might well pass!" — which matches the measurement (an isolated track was flagged where the 87-minute mix passed). So the continuous mix is not a by-product of the release, it is the artefact most likely to survive, and --include-mix makes it a first- class entry in the upload plan. It is listed FIRST on purpose: if an upload run dies halfway, it dies having landed the important thing. The description carries the tracklist with timecodes, and those come from the CUMULATIVE RENDERED durations rather than from segment arithmetic. The mix is literally these files concatenated, so summing what came out is exact — and it stays exact through effects that lengthen a track, which is not hypothetical here: CRIME carries a 2s rest and REVOLUTION a 650ms reverb tail, so segment maths would have every timecode after track 13 drifting. Timecodes switch to h:mm:ss past the hour. The last four tracks of a 73-minute set are all past 60 minutes, and "69:15" is a number nobody can place. The mix title is composed from canonical FIELDS (title · venue · year), never from a remembered string: "Sunset Forest — Opal Festival 2026 (full set)". The format lives in the code, the facts stay in Web/www.
PLN (Algolia) authored -
`sc album --plan` wants titles, files and tags as JSON. Typing that by hand is exactly how a release ships with the wrong album name — split_bandcamp.py once carried an ALBUM string that was simply wrong and nothing downstream could tell. So build_release_plan.py generates it, and every field is copied from the one place that owns it: gig metadata from Web/www content/lives/{year}/{slug}.md, per-track section/style/sample-banks from that gig's tracks.json, release order and durations from the rendered segments, approval from the ear file's release_signoff. `_provenance` records each source, so a wrong string is traced rather than argued about. TWO REFUSALS, both because the failure they prevent is invisible after upload. 1. NO SIGNOFF, NO PLAN. The ear file's release_signoff is pinned to the segments PLN approved; if it is missing, or the rendered FLACs are NEWER than it, the record on disk is not the record he cleared. An upload is hard to take back, so the check belongs before it and not in a checklist. --force overrides and says so in the log. 2. NO ROW, NO PLAN. The first run joined the tracklist on the raw title and silently missed 4 of 14 — because the canonical names are written in PLN's blog voice ("There's **Something About Drums** <3", "Am i _Doing it Right_", "GHOSTS IN THE TOILETS") while the segments carry the plain form. The plan looked complete: 14 tracks, right durations, right files. Four of them just had empty genre, empty section and NO sample banks — and the sample-bank list is the input to the rights question. A parser miss must never become a quiet gap in the catalog, so an unmatched title now refuses the whole plan and prints both name lists side by side. The fix for the join itself is match_key(): strip emphasis, hearts, case and punctuation for MATCHING ONLY, while every value that ships still comes from the canonical record verbatim. Which surfaced a real question rather than hiding it — the two sources disagree about three names, so --titles chooses, defaulting to canonical, and the diff is printed: "There's Something About Drums" -> "There's Something About Drums <3" 'Perfect' -> 'Perfect <3' 'Ghosts in the Toilets' -> 'GHOSTS IN THE TOILETS' Those decorations are his voice, not markup, and clean_title already knew to keep them. Verified against the shipped audio: 14 tracks, durations matching the rendered files including CRIME's 2s rest (271.1s) and REVOLUTION's reverb tail (205.6s). Every track carries `_rights_checked: false` — the banks are listed, nobody has cleared them.PLN (Algolia) authored -
PLN on the rebuilt seams: "listened to 13... perfect end for crime. revolution starts good. listened to 99... all good perfect ending!" That listen is the one that counted, and it is worth saying why: both clips were audio BUILT FROM his approvals rather than audio he had approved. The 2s rest and the MORE reverb tail existed only as instructions until this pass — a chain of individually-approved edits can still add up to an ending nobody has heard. Recorded in three places, each for a different reader: - `release_signoff` in the ear-boundary file — machine-checkable provenance, pinned to segments_v4 as rendered today. Any later re-render invalidates it; the note says so, so a future uploader can refuse rather than guess. - performance_notes.md — the ear-feedback corpus, per the archivist rule. - TASKS_DUMP.md — A1 closed, the last gate marked CLEARED. Two taste calls banked for future sets: SILENCE IS A MUSICAL ELEMENT, AND IT BELONGS TO THE OUTGOING TRACK. His fix for the CRIME->REVOLUTION seam was not a longer fade or a different cut point — the cut was already approved. It was "add after seam 2s silence in crime file". He wanted the last official track to end on its OWN silence and the encore to start clean. Future set-ending seams should offer a rest as a candidate, not only fades. HE REVISED THE REVERB AFTER LIVING WITH IT. Auditioned in isolation: "pick subtle". Heard as the record's ending: "allez lets use the B version, the more option" — roughly 2.7x the ring (650ms top tap vs 240ms). An effect judged on a bare clip and the same effect judged as an ending are different judgements, so render the A/B IN PLACE next time. Both were a second listen changing the answer, which is the whole argument for keeping the audition loop cheap: neither correction cost more than a re-render.PLN (Algolia) authored -
build_release_joins hardcoded its first closing clip as CLOSE · DRY, "what ships today". That was true exactly once — before any reverb was chosen. The clip is cut from the RENDERED last track, so the moment PLN picked a preset the file he auditioned had the echo baked in and the label said it did not. An audition UI that misdescribes what ships is the one bug this tool cannot have: its entire job is to let the ear check the actual artefact, and a wrong label turns a passed listen into a false clearance. Same family as the earlier allow-list that silently dropped reverb_preset — the output looked clean and was clean, of the wrong thing. Now the first entry is CLOSE · SHIPPED and reads the preset out of the release segments, so it renders as "SHIPPED · MORE tail baked in". Names the artefact from the data that produced it rather than from an assumption frozen at the time the code was written. Wrapped in try/except: a missing segments file drops the suffix rather than blocking the audition. The A/B variants are unchanged and still opt-in behind --close-ab.
PLN (Algolia) authored -
Two ear calls on the OPAL-26 record, one of which needed a new concept. PLN, having heard the CRIME->REVOLUTION seam: "approved #13 seam, but lets add after seam 2s silence in crime file, then seam is start of [the] revolution." And on the closing echo, after living with it: "allez lets use the B version, the more option" — so the ship-preset moves subtle -> more. The silence is the interesting half. The pipeline knew two kinds of answer: `verified` boundaries (move one, both neighbours shift) and `edits` trims (audio is DROPPED, a gap opens, the continuous mix skips it). A rest is neither — it ADDS time. So `pad_end_s` is a third kind, and where it lives is the whole decision: the 2s belongs to CRIME, so it ships inside track 13's file AND at the same place in the continuous mix, and REVOLUTION still begins on its first sample in both forms. Had it been modelled as a gap between tracks it would have existed only in the mix and track 13 would have ended on the hard cut. Implemented with `apad` — the same filter whose one-second-per-second literalism was a BUG two commits ago, where it was misused as reverb headroom and shipped ~2.3s of trailing zeros. Here that literalism is exactly the feature: 2.0 in, 2.0000s out. Silence as an accident of a misunderstood filter is a defect; silence a listener asked for is a rest. Same filter, opposite verdict, and the comment now says so where the next reader will hit it. Ordering matters and is documented: the pad runs LAST, after the fades and after any reverb ring. Padding first would hand the fade silence to act on, and the fade's own start is measured from the un-padded duration. In the reverb graph it attaches after the dry/wet concat, not inside either branch. `expected_dur` now predicts pad + aecho's longest tap, so `verify` still compares against INTENT rather than against whatever came out — the property that caught the apad bug in the first place. `stage_continuous` prints rests separately from gaps, because lumping them together is the exact confusion this commit exists to prevent. Validated, all measured not assumed: - verify ALL OK, 14 tracks x 2 variants, 48000/24 - #13 271.050s = 269.05 + 2.00; #14 205.550s = 204.90 + 0.65 (more's 650ms top tap — subtle's was 240ms, so the duration alone proves which shipped) - sample-exact: the last 2.0000s of CRIME are all zeros and the last non-zero sample sits precisely on the 2.000s mark - continuous 4361.35s both variants, OK against intent - the rebuilt seam-13 audition clip carries 2.0000s of silence ending exactly at the 20.00s seam marker: the rest is CRIME's, the marker is REVOLUTION's first sample Kept deliberately: the 1s duck to 50% stays alongside the new rest. They answer different complaints — the duck softens the cut, the rest separates the encore.PLN (Algolia) authored -
Written as documentary source material: three detectors' numbers, why the best-looking one was the least trustworthy, and the reframing (onset vs takeover) that a failed lens produced.
PLN (Algolia) authored -
PLN (Algolia) authored
-
The reverb tail shipped with `apad=pad_dur=2.5` on the reasoning that ffmpeg would otherwise chop the echo at the last sample. That reasoning was wrong, and the verify stage is what exposed it: REVOLUTION came out 207.64s against a predicted 207.40, and chasing the 0.24s led to the actual behaviour. Measured, four ways: no apad -> 5.240s wet, 0.000s trailing silence (full ring, nothing cut) apad=0.05 -> 5.290s wet, 0.050s trailing silence apad=0.35 -> 5.590s wet, 0.350s trailing silence apad=2.5 -> 7.740s wet, 2.500s trailing silence aecho extends its OWN output by its longest tap (240ms for subtle), so the ring was never at risk. Every second of apad simply became trailing digital silence, one for one. The rendered record therefore ended with ~2.3s of pure zeros — which is precisely the defect PLN flagged at the other end of the record ("trim leading silence tho"), reintroduced by me at the close. Two things worth keeping from how this surfaced. The 0.24s mismatch was real information, and the tempting fix — `atrim` the output back to the predicted length — would have clipped the end of the ring while making the check go green: the prediction was wrong, not the audio. And the fix only became findable because verify compares against a number derived from intent rather than from whatever the renderer happened to produce. expected_dur is now duration + aecho's longest tap, exactly. REVOLUTION: 205.14s.PLN (Algolia) authored -
PLN picked SUBTLE from the three-way close A/B. Promoting it exposed two bugs that would each have shipped something he never heard. 1. The reverb existed TWICE and the copies disagreed. render_release had a single 833ms slap (aecho=0.8:0.5:833:0.35) applied to the WHOLE track; build_release_joins had a three-tap preset applied to the tail only. Setting `reverb_tail_s` would therefore have washed all of REVOLUTION with an echo nobody auditioned. The preset table now lives in render_release and the A/B builder imports it, so what ships is bit-for-bit the chain he compared. 2. apply_boundaries forwarded edit keys through an ALLOW-list, which silently dropped `reverb_preset`; the renderer then saw no preset and fell back to no reverb at all. A missing edit is invisible in the output — you get a clean render of the wrong thing — so it now deny-lists the keys it consumes and forwards everything else. Caught by inspecting the built filter graph before rendering rather than after, which is the only reason this is a commit message and not a re-render. The tail reverb is a split graph, not `-af`: head dry, last 5s through apad then aecho, concatenated. The apad matters — without it ffmpeg chops the ring at the final sample, reintroducing the exact hard stop the reverb exists to soften. Verify accounts for the 2.5s ring, so REVOLUTION is expected at 207.4s not 204.9s rather than being flagged as a mismatch.
PLN (Algolia) authored -
Full pipeline run on the edited segments. All green: split 14 tracks x streaming + club verify every duration exact, 48000/24 continuous 14 segments, 72.6 min, 3 gaps -> 4358.70s both variants The three gaps are the trims, and seeing them enumerated is the point: [ 530.35 .. 531.35] 1.00s the silence PLN wanted cut to 1s [ 892.96 .. 893.00] 0.04s WAP's truncation burst [4156.30 .. 4566.47] 410.17s Desire, plus his CRIME/REVOLUTION trims Verified the WAP fix by measurement rather than by assuming the trim landed: peak in the track's last 100ms is now -55.0 dBFS, down from -15.1, and it decays to -70 instead of being chopped mid-transient. What remains near the end is ordinary music ringing out into the 30ms guard fade, which is the intended result — the truncated sound is gone, not merely quieter. Release-check set rebuilt: 17 clips (open + 13 seams + 3 closes). Smoke passes against the production server.
PLN (Algolia) authored -
PLN: 'maybe with reverb over last 5s so we hear it slightly echo as last sound'. Rendered as an A/B rather than baked in — an echo on the final sound of a record is a taste call, and 'maybe' is not a decision. Two flavours (subtle / more) plus the dry render, so the comparison is three clips in the same view. The wet part is the TAIL ONLY: a global reverb would wash the whole ending, and everything before the last 5s stays identical to dry. The tail gets apad before the echo so the ring has somewhere to go — without it ffmpeg chops at the last sample and the 'echo as last sound' becomes another hard cut, which is the thing being fixed.
PLN (Algolia) authored -
PLN (Algolia) authored
-
The release check turned up four fixes and one boundary move. The pipeline could express none of them, because it only knew about boundaries. It now separates two things that look alike and are not: `verified` a BOUNDARY: moving it shifts both neighbours (butt-joined set) `edits` a per-track TRIM/FADE: the audio is DROPPED, a gap opens, and the continuous mix must skip it too Confusing them silently hands a neighbour a second of someone else's silence — which is the exact artefact these edits exist to remove. Applied, all verified by ratio against the unfiltered master rather than by reading back the command line: #1 Bombe end -1.0s PLN: "trim silence to 1s silence from 00:17". Measured: audio ends 527.90, then 2.0s of -60..-100. #2 WAP end -45ms PLN heard "a sound just at seam ... is an error". +30ms fade The error was at WAP's END, not Drums' head: the last 40ms jump -57 -> -15 dBFS, a sound truncated mid-decay. An 80ms fade only reached -26.8 dB — hidden, not gone — so the burst is now dropped outright and the fade only guards the new edge. #12 Mafia +1.66s boundary MOVE: heard as a seam, it wants more Ghosts #13 CRIME end -10.0s PLN: "cut at 0:10 its better. have fade from 0:09 to duck to 50% 0:10 from 100 to 50% to lower the sudden cut" #15 REVOLUTION start +18.9s PLN: "start revolution at 0:38:9 on a fade of the 2s fade-in drums to not be so sudden" end -15.0s PLN: "end at 0:15" Measured gain curves confirm each: CRIME flat 1.0 then ramping to 0.546 over the last second; REVOLUTION 0.022 -> 1.0 across 2s; WAP 1.0 until the fade. Every duration exact. `continuous` was rewritten as a consequence. It used to cut merged "kept spans" from the master, which was right for boundaries and would have been silently WRONG here: the mix would have played un-faded audio while the tracks were faded, with nothing to flag it. It now cuts the same segments through the same filter chain and concatenates them, so the mix is literally the tracklist and the two cannot drift. Gaps fall out for free. PLN's raw release-check export is committed as provenance. Still open: his "maybe with reverb over last 5s so we hear it slightly echo as last sound" — supported as `reverb_tail_s`, deliberately left off pending an A/B.PLN (Algolia) authored -
PLN: "this becomes our tooling, often a set will be postprocessed as we did — reusable scripts and views plz". So the per-gig finish.sh is replaced by spec-driven tools: a new gig is now a copy of judge_specs/<gig>.json, never a copy of a script. POSTPROD.md documents the whole flow and the trap each stage exists to avoid. render_release.py — split every variant, verify durations and format, and re-render the continuous mixes. Ran clean: 14 tracks x streaming+club, all durations within tolerance, 48000/24. Its `continuous` stage answers PLN's other catch: "imo the master club rec will also have desire removed". A set is released TWICE — as tracks, and as the unbroken mix — and cutting a track had only fixed the first; both continuous masters still played Desire in full. This matters past tidiness: the continuous upload is the lower-risk rights path (an isolated track was flagged on SoundCloud where the 87-minute mix passed), so the mix is the artefact most likely to ship and it has to match the tracklist. The excision derives its kept spans from the same segments the tracks came from, so mix and tracklist cannot disagree. Both variants now render to 4403.65s = 73.4 min, matching the sum of the 14 tracks exactly: [1.45 .. 4166.30] 69.4 min + [4547.57 .. 4786.37] 4.0 min build_release_joins.py — the confirmation pass PLN asked for ("i wanna confirm all tracks start/ends/transitions"). It deliberately does NOT use the master: once Desire is dropped, the seam a listener hears (CRIME straight into REVOLUTION) exists only in the rendered output, and a master-based window there would play six minutes of a track that does not ship. Building from the split FLACs also audits the files that actually ship, so a split error surfaces before upload rather than after. 15 clips: the record's opening, 13 seams, and its closing. apply_boundaries now emits the splitter's native shape — `track` is RELEASE position because tidal_ears.master split uses it for both filename and the `track=N/total` tag; performance order stays as `perf_track`. Emitting performance order would have named the last file "15 - REVOLUTION" in a 14-track album and tagged it 15/14, which stores reject. Both UI sets pass bounds-smoke (11 assertions each). The test now derives its data file from the page's own ?set= — hardcoding it meant it could assert against a different set than the browser had loaded, passing or failing for reasons unrelated to the code.PLN (Algolia) authored -
PLN's last call: "00:31:3 is a decent end of crimewave to cut early on end of the crime sound". Read off the transport, which shows CLIP time, and #14's clip starts at 4135.0 — so master 4166.3. The audio agrees rather than merely permitting it: the crime sound gaps to -71 dB at 4164.5 and 4166.3 sits just past that on its decay, 13.7 s before the nominal 4180.0. Recorded with that mapping written out, because clip-vs-master is the one confusion that would shift a cut by 45 s and look plausible. apply_boundaries.py turns the ear file into segments_v4.json. Made a tool rather than a hand edit because this is the step that shipped a wrong album once, and its three rules are each silently destructive to get wrong: - starts come from the ear, ends from the neighbour (butt-joined), so editing one start moves two tracks; - a track CUT from the release still owns its boundary — Desire is dropped but the edge into Desire is where Vague de CRIME ends, and ignoring it would run the last official track into Desire's intro; - release position is not track number: dropping a track renumbers the album but must not renumber the provenance, so both `n` and `track` are emitted. It refuses to write on overlap, non-positive duration, out-of-master range, or an ear-verified start that failed to reach the output. All green: 14 tracks, 73.4 min kept of a 79.8 min master, 6.4 min dropped (Desire). Remaining unverified edge, for the record: #2's start (531.35) is still the nominal, so #1's end rides on it. It was never flagged in the judge pass. Ear feedback archived in performance_notes.md, including the finding that #11 and #12 disagree about which fraction is right — takeover on one, mid on the other, 8 s apart. Two adjacent cuts, opposite answers: where a cut belongs is a musical judgement about that handover, not a parameter to fit.PLN (Algolia) authored -
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.
PLN (Algolia) authored -
PLN (Algolia) authored
-
The set-judge answers "is this track a keeper". This answers the question that actually blocked the release: where exactly does one track become the next. After three failed detectors (cut_lens3 declared a dead end in 5f7db02a), #13 and #15 are ear-only by nature — the two tracks share their sounds across the switch, so no timbral lens can see the handover. The tool's job is therefore to make one ear call cheap, not to avoid asking. build_bounds_set.py renders a ±45 s FLAC clip per boundary instead of seeking the 817 MB master. A boundary audition is a scrub — dozens of small seeks around one point — and seeking a long FLAC depends on a seektable the browser may not use well; local clips make every seek instant for a few MB total. Clips are re-encoded, never `-c copy`, because FLAC stream-copy snaps to frame boundaries and every marker inside the clip would inherit that offset. Clip time and master time are kept explicitly apart (`clipStart`), converted in exactly two functions. The UI shows each cut's candidates as labelled markers on the waveform, drawn as regions rather than positioned divs so they stay glued to the audio through zoom and scroll. `takeover` leads the list because it landed within 0.7 s on both boundaries that also had ear answers — with the caveat printed on screen, since that is n=2 on the cases that were already easy. Boundaries with NO candidate are shown with an explicit notice rather than omitted: those are precisely the ones needing a human, and hiding them would hide the work. Fixes a real bug in the SHARED player while here: `ws.zoom()` needs a decoded buffer, which the peaks path never has, so it threw "No audio loaded" from an effect and blanked the entire page. Same message as the peaks+url trap, entirely different cause. Now guarded and failed-soft — zoom is a convenience and must never take the tool down. The judge had this latent too. bounds-smoke.mjs, 11 assertions, all passing. Two of them exist because the first version of this test passed while proving nothing: querySelector('audio') is null by construction (wavesurfer owns the element and never puts it in the DOM), so the playback probe would have "passed" forever on a dead transport. It now reads the transport clock and the Pause button — what a human sees. A positional selector also silently picked the header's counter instead of the clock, hence the explicit data-testid.
PLN (Algolia) authored -
Two changes to the ear-boundary ground truth, kept deliberately distinct. #1 was the last GO with an unfinished instruction — PLN said "trim leading silence tho" without an amount. The amount is now measured rather than estimated: every sample is EXACTLY zero up to sample 64038 (1.452109 s @ 44.1 kHz), and the first audible frame peaks at -28 dBFS. That is a sample-exact scan, not a threshold and not a detector, which is why it is allowed into a file whose header forbids detector output. Recorded as start=1.45, keeping a ~2 ms guard so the first transient cannot be clipped, with the full measurement and method stored alongside it for audit. #11 and #12 gain machine PROPOSALS in a new `candidates` block, structurally separate from `verified` so nothing can be mistaken for a confirmed boundary. On the two boundaries that also have ear answers, the `takeover` estimate landed within 0.7 s (#8 2726.25 vs 2726.4, #10 3268.3 vs 3269.0) — which is a reason to audition takeover first, not a reason to trust it: n=2, on the cases that were already easy. #13 and #15 are listed with an explicit NO CANDIDATE and the reason, so a future reader does not go looking for numbers that cannot exist: zero voting orbits across three gates and two estimators, because those transitions share their sounds and a timbral lens is blind to them by construction.
PLN (Algolia) authored -
The Claude-Code Task API is not reachable from this session (TaskList/TaskCreate/ TaskUpdate/TaskGet all absent), so the live board — last seen at 112 tasks — could not be read or rewritten. Rebuilt the tree from durable sources instead: board-archive.md (ids exact, completed through 2026-07-29), the numbered achievement logs, the memory store, and git. Structured as six epics with the work nested underneath: OPAL-26 release (the only one blocking a shipment), cut/boundary detection, the floor problem, rig & hardware, open taste calls, completed. Each item carries the measurement or the quote that justifies it rather than a bare title, so it survives a cold read. Honest about provenance: ids in the #150+ range come from the lost board via session context and cannot be re-verified, so the file says to treat a #17x number as a name, not a key. This session's outcomes are slotted in place: #1's leading silence measured (1.452s), the timbral cut lens declared a dead end with its three-attempt table, and #13/#15 escalated to ear-only calls.
PLN (Algolia) authored -
The medoid + effect-size gate was meant to buy coverage after run 1 returned "no separating orbit" for six of fourteen boundaries. It did the opposite: boundaries with any voting orbit went 8/14 -> 4/14. Requiring cross-side distance to beat within-side spread is a stricter test, not a better-aimed one. The error table is seductive and is not evidence: u=0.85 scores median 0.4s, worst 0.7s — but n=2, and those two boundaries (#8, #10) already had ear answers. Four survivors cannot validate five candidate fractions, and a metric computed on the cases that were already easy measures nothing. The durable finding is the pattern across all three attempts: #13 Vague de CRIME and #15 REVOLUTION have produced ZERO voting orbits in v2, v3 run 1 and v3 run 2 — three gates, two estimators. That is not a tuning failure. Those transitions have no orbit whose timbre differs materially across the switch; the outgoing and incoming tracks share their sounds, so a timbral lens is structurally blind there just as an activity lens is blind to a crossfade. A fourth loosening can only admit noise, because there is no signal in this feature space. Declared a dead end in the docstring rather than tuned again. Two ways out are recorded: change the feature (harmonic/key change, or sample-bank identity from the score, which differs even when the spectrum does not), or accept #13/#15 as ear-only calls — two timestamps, seconds of listening each. Candidates that DID survive are kept in cut_candidates_v3run2.json for #8, #10, #11, #12 as ear-confirmation inputs, not as boundaries.
PLN (Algolia) authored
-