1. 05 Sep, 2026 40 commits
    • docs(board): the fast-forward promise now carries its proof · efdb09f1
      Two sessions checked each other and each found what the other missed. I
      measured that the tree was not waiting on a commit; the peer re-derived that
      independently, then added the two checks my recommendation lacked -- that HEAD
      is a true ancestor (so it is a real fast-forward, not a merge in disguise) and
      that the branch touches none of PLN's five modified .tidal files.
      
      That second one is the one that mattered. Recommending a checkout across
      somebody's live set prep without measuring the direction is how a repair
      becomes a clobber, and I had asserted the switch was clean on the strength of
      the two tools files alone.
      
      Both verified here before recording them. The caution that remains is real and
      unchanged: the fast-forward swaps code under a running rig.
      PLN (Algolia) authored
    • docs(board): the shared tree is not waiting on a commit that will never come · c4eb90be
      The peer session's 043-open-decisions.md tells a cold reader the main working
      tree can be fast-forwarded once check-mix.py and gig-log.py are committed. They
      are not anyone's in-flight edits: worktree, master and f56775ae all hold the
      identical blob for both files. They read as modified only because the tree's
      HEAD predates 27f2531's shared-ladder refactor, which changed both files to
      import tools/ardour_session.py -- the tree already has the post-refactor
      content.
      
      Left as a precondition, that sentence blocks the fast-forward on a commit
      nobody can make. Recorded with the hashes and the two-command fix, and
      deliberately not run: PLN has folded, the live rig runs from that tree, and
      restarting his audio services unwatched is not a call to make for him.
      PLN (Algolia) authored
    • docs(board): reconcile the board against measured reality, evening of the go · 6678acd1
      PLN authorised the deletes, the CosmicFest tone and the gig page, so the board
      needed to stop describing a world where those were pending.
      
      Every row in the new block was measured today rather than recalled, which
      mattered: the standing 'catalog is STALE at 73 tracks / 23 gigs' warning is
      itself now false (81 / 25, as_of 2026-09-05), and the deletes that the board
      was waiting on a decision for have already run and been confirmed.
      
      The three remaining items all need PLN's screen or ears and nothing else, so
      they are listed as such instead of as work. Includes the verified prep for
      switching the shared checkout off rig-streamline, since that is the reason the
      running midiviz is still the old code.
      PLN (Algolia) authored
    • test(tide-table): the OPAL title join needs the page to be as-performed · 7e966a5b
      Audited what could have consumed the phantom SOUNDCHECK entry positionally.
      Answer: nothing did. The two real consumers of setlist.entries() are safe --
      take-segments.py builds a dict keyed by path stem (a phantom adds one unused
      key) and migrate-columns.py calls tracks(), not entries(). No shipped artifact
      was off by one.
      
      But the audit found a different positional coupling, live and undocumented.
      opal_title_map() joins tracks.json's 1..N performance order against
      segments_v4.json's perf_track. OPAL was played as 15 tracks; the release cut
      Desire, and segments_v4 renumbered around the hole -- its perf_track keys are
      1..13 and 15, with no 14. So the join lines up only while Desire still holds
      performance slot 14.
      
      Measured both ways rather than argued: as-performed yields 14 mappings
      including REVOLUTION; drop Desire to make the page match the release and it
      falls to 13. REVOLUTION's title is LOST -- silently, with nothing mislabelled,
      which is what would have made it hard to notice. PLN chose as-performed today
      as a documentary call about the page; this join quietly depended on the same
      answer, and nobody knew the two were connected.
      
      So: the assumption is written down where the join lives, and three tests hold
      it -- every released track gets a title, the encore by name, Desire maps to
      nothing rather than borrowing a neighbour's, and the hole at perf_track 14 is
      still really there. That last one matters: if segments_v4 is ever renumbered
      densely the reasoning stops holding, and the right outcome is a red test
      rather than a quietly missing track.
      PLN (Algolia) authored
    • feat(midiviz): an X that closes it, a [x] that pins it, and a close that sticks · 46709b26
      PLN: "needs basic menu or at least top right X to close, and its too sticky
      atm if i close i wanna close it i guess maybe X and a [x] where x goes and
      comes and is the 'stick above' feature, but it anyway is all desks always."
      
      No menu -- two glyph targets in the header's right edge, in the same micro
      font as everything else: "[x]" toggles always-on-top (the x IS the state) and
      "X" closes. Dim by default, brighter under the pointer, which is the only
      affordance a frameless window can offer. T toggles the pin from the keyboard;
      Q and Escape already closed it and still do.
      
      Controls win over the drag. The whole surface is a drag handle, so without an
      explicit hit-test first the X would only ever have moved the window.
      
      Targets are laid out from the right edge with the glyphs centred inside them,
      not sized to fit the glyphs. Sizing to the glyph gave an 8px-wide X --
      measured -- which is fine to look at and unhittable mid-set, and at 0.6 scale
      the two targets then had to either overlap or shrink below usable. Deciding
      targets first makes "disjoint and at least 18px" true by construction at every
      zoom, asserted across four scales and three widths.
      
      All-desktops is deliberately untouched: it is a KWin rule keyed on the app id,
      it is unconditional as PLN said, and toggling the pin must not disturb it.
      
      THE STICKINESS WAS NOT THE WINDOW. A clean exit is not a failure so systemd
      never restarted it -- but rig_units.ensure() starts every non-manual unit that
      is not active, and both gig-up and the Bridge watcher converge, so the lens
      came straight back with nothing in the output admitting why. Marking the unit
      "manual" would have answered the complaint by deleting the feature he asked
      for last week.
      
      So a deliberate close is recorded in $XDG_RUNTIME_DIR, ensure() honours the
      latch and says so, and anything that starts the lens on purpose clears it.
      That directory is wiped at logout, which is exactly the right lifetime:
      "always open" is about how a session starts, "closed means closed" is about
      what happens after he acts, and the two only looked contradictory. Closed for
      this session, back at next login, no state to remember to undo. Only a close
      the human asked for latches -- a compositor teardown is not an opinion.
      
      The latch path is a contract between three files that deliberately do not
      import each other, so a test asserts they agree rather than trusting three
      copies of an f-string. That test immediately earned its keep: rig_units.py
      used Path without importing it, on a line only reached once a latch existed.
      
      Also fixes a shadow of my own making: the new painter was called
      _paint_chrome, which is already a method on this widget, so it silently
      replaced the window-chrome painter and the selftest died on the signature.
      Renamed _paint_controls.
      PLN (Algolia) authored
    • fix(tide-table): stale line anchors, not the setlist soundcheck fix, dropped opal-2024 to 3 · 436d327f
      Two unrelated failing tests, investigated in order.
      
      test_agreement_distribution_within_bounds asserted tracks_total == 73, a snapshot
      from before yesterday's catalog rebuild (73 -> 81 tracks). Bumping it to 81 would
      just re-freeze the same brittleness. Replaced it with
      test_tracks_total_matches_the_generated_catalog: assert the live view's
      tracks_total equals the CHECKED-IN catalog.generated.json's n_tracks. That file is
      a 1:1 derivation of this same view, so the two can only disagree on a real
      regression (corpus changed but catalog not regenerated, or the pipeline
      dropped/duplicated a row) — never on honest growth. Verified it still fails by
      corrupting n_tracks and re-running.
      
      test_real_backlog_coverage_does_not_regress: opal-festival-2024's recovered
      tracks fell from 13/14 to 3. The obvious suspect was e8f7066a (yesterday's
      tools/setlist.py soundcheck filter) — ruled out by inspection (e8f7066a touches
      only tools/setlist.py + its test, nothing under armada/tide-table) and by
      bisection: replaying backlog_setlists.py's ANCHORS against every historical
      backlog.md since the anchors were authored (5acf72f7, 2026-06-06) shows
      n_resolved holding at 14 across ten intervening commits and only breaking at
      49e1b78e ("update: post cosmic", 2026-08-23).
      
      The real bug: ANCHORS keyed gigs by a raw 1-based LINE NUMBER pinned once against
      a single backlog.md snapshot. backlog.md is PLN's running journal, not a stable
      document, so every one of those ten commits that inserted lines earlier in the
      file silently shifted every anchor below it. Opal 2024's anchor drifted through
      blank lines and a lucky near-miss for months (the collected span still happened
      to contain the same real content), then 49e1b78e pushed the drift past the
      header entirely into the tail of the PREVIOUS gig's block, collecting 3
      unrelated lines instead of the setlist.
      
      Fix: anchor by the header's exact TEXT, resolved fresh against the live file on
      every build() (resolve_anchors()), instead of a line number frozen at authoring
      time. Self-healing against drift elsewhere in the file; raises loudly if a
      heading is ever actually renamed or duplicated, rather than silently
      mis-anchoring. The three anchors that carried an explicit end-of-block bound
      (opal-2025/Latin-Heritage bleed, mephisteuf's commented block, the 38c3-toilet
      cross-check) now store that bound as a line-count SPAN measured from the anchor,
      since that span lives inside the block and doesn't move when the anchor does.
      
      tools/setlist.py's soundcheck filter (e8f7066a) is untouched and still yields 15
      entries for OPAL 2026 — never the culprit here, just an unrelated same-day
      change that made a plausible but wrong prime suspect.
      
      Full suite: 468 passed, 2 skipped (465 baseline + 2 fixed + 1 new regression
      test), up from 2 failing.
      PLN (Algolia) authored
    • merge(rig): fold claude/rig-streamline into master · 3c210a2a
      Eight commits of rig work that had accumulated on the branch while the shared
      checkout sat on it. Reviewed as a diff, not taken on faith:
      
        perf.sh        stop DWIM-launching Pulsar into /usr/local/sbin -- 183
                       SIGABRTs between 08-19 and 09-05 because BASH_SOURCE resolved
                       to the deploy path, and the perf watcher re-entered every 30s.
        rig_units.py   neither LCXL painter may be auto-started blind; gig-up.sh's
                       generation-aware chooser owns that decision.
        midiviz        its own Wayland app id (desktopFileName, not applicationName),
                       so focus and KWin rules can name this window and no other;
                       plus the CC readout PLN asked for while wiring.
        logs 040/041   the phantom port, and the GIG UP rewire parked behind the
                       Ardour double-launch bug.
      
      The four other branches were already upstream by patch-id.
      
      # Conflicts:
      #	TASKS_DUMP.md
      PLN (Algolia) authored
    • docs(log): 041 — perf redeploy landed, 183 Pulsar crashes to 0 · 1236fe63
      PLN ran the root install at 20:06. Verified rather than assumed: the
      deployed /usr/local/sbin/perf-audio md5 matches the repo copy, the bug's
      fingerprints (nohup pulsar, SCRIPT_DIR) are absent from the deployed
      script, and prioritization is retained.
      
      Behavioural confirmation is the part worth keeping: the Bridge's perf
      watcher still reasserts every cycle, but now only prioritizes -- no
      launch attempts in the journal -- and the last Pulsar SIGABRT in
      coredumpctl is 15:58:44, before the deploy. The counter that ran 183 deep
      since 2026-08-19 has stopped.
      PLN (Algolia) authored
    • docs(archive): the suite that ran zero tests, and the phantom it hid · 1c83d75a
      Structured archive entry. The keeper: two bugs hid each other for a month. One
      file's module-level sys.exit aborted pytest collection for the whole directory,
      so the obvious command ran zero tests while 273 passed file-by-file — and that
      invisibility concealed the file's own month-old red check, whose NAME was a
      complete bug report about a soundcheck being counted as OPAL's first track.
      
      A test nobody can see failing is worse than one that does not exist, because its
      name reads as reassurance. Fixing the harness surfaced a real correctness bug at
      no extra cost.
      PLN (Algolia) authored
    • docs(board): second pre-compact — R2 closed, four decisions, four next moves · cf896762
      Refreshes the resume state. The R1-R5 block from the first pre-compact is now
      partly stale — R2 closed (PLN raised the faders and saved; check-mix reports all
      12 orbits reaching master), both R5 test items fixed, and the suffix question
      answered by evidence rather than taste.
      
      Three things landed after those resume points that a cold reader needs: pytest
      tools/tests/ had been running ZERO tests because one file's module-level
      sys.exit aborts collection, and behind that invisibility the OPAL setlist's
      first track was a soundcheck, so every tool reading it believed the set had 16
      tracks opening on Quand on décolle. Both fixed; suite is 274 green in one
      command.
      
      Separated what is genuinely PLN's from what is merely undone, because that
      distinction is the whole value of this block. Four decisions block everything
      (--go on the 14, /desire specifically, CosmicFest tone, gig page as-performed vs
      as-released) plus one ears question about nine faders moving where four were
      broken. Four next moves need nobody, and the first is a 15-minute audit of
      whether anything consumed the phantom setlist positionally.
      PLN (Algolia) authored
    • docs(board): the suite ran zero tests, and that hid a phantom first track · dfab5ca7
      Two bugs that were hiding each other, which is why both survived a month.
      
      test_setlist.py ended in a module-level sys.exit(), pytest raises that during
      collection, so 'pytest tools/tests/' aborted the whole directory and ran NOTHING
      while 273 tests passed file-by-file. And what that invisibility concealed was
      test_setlist's own failure, on a check whose name states the bug: OPAL's backlog
      opens with '## SOUNDCHECK / -- Quand on decolle', entries() took every item under
      the gig heading, and so every tool reading the setlist believed the set had 16
      tracks opening on Quand on décolle. The recording has 15, opening on Ceci n'est
      pas Une Bombe.
      
      A test nobody can see failing is worse than a test that does not exist, because
      its name is a correct bug report that everyone reads as reassurance.
      
      Whole suite now runs in one command: 274 passed, 0 failed.
      PLN (Algolia) authored
    • fix(setlist): a soundcheck is not a track, and the suite can run again · e8f7066a
      Two bugs that were hiding each other, which is why both survived a month.
      
      THE SUITE RAN ZERO TESTS. test_setlist.py was a plain script ending in a bare
      module-level sys.exit(), and pytest raises that during COLLECTION — so
      'pytest tools/tests/' aborted the whole directory with an INTERNALERROR and ran
      nothing, while 273 tests passed when invoked file-by-file. Every test in this
      repo was invisible to the obvious command. Guarded the exit under __main__ (the
      script still works run directly) and added one assertion so pytest actually sees
      the checks. Whole suite in one command now: 274 passed, 2 skipped, 0 failed.
      
      AND THE THING IT WAS HIDING: test_setlist had been failing all along, on a check
      whose name says exactly what is wrong — 'a deliberation note is not a track (the
      quand_on_decolle phantom)'. OPAL 2026's backlog section opens with
      
          ## SOUNDCHECK
          -- Quand on decolle
      
      and entries() takes every list item under the gig heading, so every one of the
      tools reading this setlist believed the set had 16 tracks opening on Quand on
      décolle. The recorded set has 15 and opens on Ceci n'est pas Une Bombe
      (segments_v3/v4 track 1). The parser now yields 15, in the right order.
      
      The fix is deliberately NOT the section whitelist entries() refuses to build —
      that one tried to enumerate which of PLN's prose headings are part of the set and
      died on '## Livecoding Techno DNB Nu-jazz'. This is the inverse and far narrower:
      naming the one phase that is definitionally not a performance. Three patterns,
      and skips print to stderr, because a dropped track and a skipped soundcheck must
      never look alike.
      
      Fixed the parser, never backlog.md — the backlog is PLN's own file and it was
      right. A soundcheck IS in his set list; it just isn't in his set.
      
      Verified by breaking it: neutered _NOT_THE_SET_RE, watched
      test_backlog_setlist_checks_all_pass fail naming the quand_on_decolle phantom,
      restored, 274 green.
      
          the first track was never the first track.
          sixteen tools agreed with each other
          and none of them agreed with the recording.
      PLN (Algolia) authored
    • fix(tests): orphan-orbits derives its counts, lcxl3-display collects 30 tests · 21f83b50
      Both were pre-existing, unrelated to today's check-mix.py/gig-log.py work
      (spotted while running the suite for that change).
      
      test_orphan_orbits.py: 9 of 41 tests failed (16-row setlist vs a frozen "13",
      and 6 HAND_MEASURED orbit sets vs the parser). Root cause confirmed by git log
      + grep, not a parser bug: the setlist grew from 13 to 16 tracks since the
      fixture was written, and commit 2376e431 (2026-08-01, "d10 is the riser") added
      a d10 safe-riser idiom to 5 of the hand-measured tracks (bombe_dj, wap,
      you_my_sunshine, mafia_sans_serif, desire) after the 2026-07-28 hand
      measurement date; piment_bresilien went the other way (d10->d9, PLN's own
      request, commit 71bb9bc2/5e5a37ad). Fix: the setlist-length assertions are now
      derived from an independent sed-based row count of the setlist file (same
      pipeline check-tracks.sh uses) instead of a literal that rots every time PLN
      adds a track; the HAND_MEASURED dict and the vague_de_crime->bombe_dj orphan
      assertion are re-measured by hand (grep on each file's dN lines, not by
      copying the parser's own output) against the corpus as it stands today. No
      assertion loosened — 41/41 pass, same invariants, current facts.
      
      test_lcxl3_display.py: pytest collected 0 items. Cause: this was written as a
      plain script (module-level `check()` calls, run via `python3
      tools/tests/test_lcxl3_display.py`), like test_setlist.py, with no `test_*`
      function pytest could find — a file that cannot fail under pytest, i.e.
      silent coverage loss. Converted to 30 ordinary pytest test_* functions (one
      assert group per logical check), same assertions, nothing dropped or
      loosened. Verified live: broke rec_line's hour rollover (divmod 3600->3601)
      and watched test_rec_line_formats_hmmss_past_an_hour fail with the exact
      wrong value (REC 1:02:02 vs 1:02:03), then reverted.
      
      Suite: 234 passed/9 failed/2 skipped -> 273 passed/2 skipped (ignoring
      test_setlist.py, a separate plain-script file, out of scope here — it already
      breaks whole-directory pytest collection via a module-level sys.exit and
      predates this fix). test_gig_log.py untouched: 169 passed before and after.
      PLN (Algolia) authored
    • docs(board): CosmicFest is clean, and it settles the suffix question · e72eca1d
      Ran the new generation lens proactively against Cosmic26_master, before that
      release goes anywhere: 12 OK, zero stale, zero strays. The exact opposite of
      OPAL, for two reasons that are lessons rather than luck — only one render
      generation exists on disk, and every permalink is gig-scoped via
      --permalink-suffix, so no CosmicFest upload could collide with the OPAL cut of
      the same score. Four of these titles exist at both gigs.
      
      That answers an open board decision with evidence instead of taste. It asked
      whether to keep OPAL unsuffixed for consistency; the suffixed release has 0
      permalink pathologies and the unsuffixed one has 14. Consistency with a broken
      scheme is not a virtue, so new OPAL uploads get suffixed. The 6 correct
      unsuffixed tracks stay — they are right, and renaming spends a permalink for
      nothing.
      
      Found in passing: CosmicFest is missing exactly 2 tracks, and they are almost
      certainly the two already-known vanishers that logged '✓ live at', passed an API
      check, then 404'd. A plain idempotent re-run converges. Which is the standing
      lesson landing on its feet: verify the shelf AFTER a run, not only during it —
      'N uploaded, 0 failed' was never evidence that N tracks exist, and it took a
      lens three weeks later to notice two were gone.
      PLN (Algolia) authored
    • docs(board): R2 closed — faders up and saved, but nine of twelve moved · 7d33e3e5
      check-mix reports OK, all 12 orbits reach master. Third recurrence of the
      saved--inf bug, closed by PLN's hands.
      
      Two things fell out of verifying it. check-mix resolves the saved session with
      Ardour CLOSED — it reads the file, not the process, so the fader check can run
      in a pre-gig script before anything is launched. And the diff is bigger than the
      fix: orbits 02-06 were in a coherent -8 to -10 dB trim set and are now all near
      unity, so nine faders moved where four were broken. Master is still -0.2 so
      nothing clips, but the balance between orbits shifted ~9 dB on five of them.
      
      Recorded as a QUESTION, not a finding. The LCXL3's faders are absolute-position
      controls and touching one snaps the DAW value to the pot — which also means the
      board's 'v3 retires pot-pickup' claim covers the endless ENCODERS only; the
      eight faders are still absolute and that whole class survives the upgrade. But
      PLN may simply have dragged them on purpose, and a cause nobody asked about is
      not a cause.
      PLN (Algolia) authored
    • docs(log): 041 — midiviz exits with the board, so 'always open' is conditional · 151c3d93
      Found while shutting down: unplugging the LCXL took midiviz's ALSA
      source with it, the process exited cleanly, and Restart=on-failure did
      not fire. The permanent lens is only permanent while the board stays
      plugged -- which is the same resolve-once stale-binding shape as the
      zombie driver this session was opened to fix. lcxl-leds.py already has
      the fix pattern (re-resolve per send, cache invalidated on failure).
      PLN (Algolia) authored
    • docs(log): 041 — park the GIG UP rewire behind the Ardour double-launch bug · e473f7e1
      The rewire (tray Launch Gig -> POST /api/rig, arm-then-confirm) is
      written and loads clean, but testing it end-to-end killed Ardour:
      launchers.launch('ardour') reported 'launched' while Ardour was already
      running, a second instance collided with the first over the session
      lock, and both exited -- taking all 12 orbit links with them.
      
      A false negative in is_running() wired to a tray button is worse than
      the mislabeled button it replaces, so it does not ship until launch()
      fails closed. Parked as a patch beside the note rather than committed
      (unvalidated) or left loose in the worktree (this checkout is shared and
      another session commits into it by pathspec).
      
      Carries the rest of the session's open ends too: the perf.sh redeploy
      that is committed but not live, gig-up's four pre-existing NO-GO
      blockers, perf-tray's 2.6GB RSS, and why gig-down is blocked on the
      Freebox being down.
      PLN (Algolia) authored
    • docs(log): 040 — the phantom port, and what name-based identity cost us · 43a44247
      Captain's log for the gear-failure pass: one zombie lcxl3-driver holding a
      stale binding to an unplugged mk3 explained three of PLN's five symptoms,
      the fourth was a mislabeled tray button, and the fifth was tooling that
      had been running invisibly all along.
      
      Written for mining later — the sharp bit is that name-based identity is
      the actual bug, and it caught the investigation as well as the rig: a
      subagent grepped aconnect for LCXL3, hit the driver's own phantom virtual
      port, and reported the hardware was back.
      PLN (Algolia) authored
    • docs(board): point a cold reader at the truth, not at the oldest theory · 61f7a377
      TASKS_DUMP has accreted amendments since 2026-08-16 and the oldest sections are
      now the least true — but a reader arrives at the top, where A3 still says
      /take-five-drops uploaded '24s short', that every other track 'matched to the
      second', that 8 of 15 are up, and that no artwork exists. All four are wrong,
      and the first two are the exact reasoning that let six stale uploads pass a
      duration check in August.
      
      So: a READ THIS FIRST banner naming what is superseded and sending the reader to
      the resume points at the end, plus an inline stop sign at A3's 'TWO DEFECTS'
      where someone reading that epic top-down actually hits it. The wrong diagnosis
      is kept rather than deleted, because the reasoning being auditable is the point —
      it just no longer gets to be the first thing anyone reads.
      
      A board whose front page is a year of stale confidence is worse than no board.
      Nobody rewrites 1700 lines; they act on line 90.
      PLN (Algolia) authored
    • fix(rig): neither painter may be auto-started blind · 57dc2d91
      lcxl3-driver was policy "login", so rig_units.ensure() started it
      whenever it was not already active -- and ensure() runs inside
      gig-up.sh --converge, which is what the Bridge's RIG UP calls.
      
      With the CLASSIC LCXL plugged that is actively destructive: the v3
      driver has Conflicts=lcxl-leds-watch, so a converge would stop the
      painter that was correctly lighting the classic board, then fail to bind
      (there is no LCXL3 port) and crash-loop at RestartSec=5. That is exactly
      what happened between 11:26 and 14:36 on 2026-09-05 -- 86 restarts, the
      board dark the whole time -- and today's repair would have been undone
      by the next press of GIG UP.
      
      lcxl-leds-watch was already "manual" for the mirror-image reason, stated
      in the comment right below this one. The rule generalises: neither
      painter may be auto-started blind, because "which painter" is a question
      only the plugged hardware can answer. gig-up.sh's leds() is the
      generation-aware chooser -- it greps aconnect for LCXL3 and restarts the
      right unit -- and it now owns the decision outright.
      
      Verified: `rig_units.py --ensure` (dry) reports "nothing to do" with the
      driver inactive and the v2 painter running, instead of queueing a start
      that would have gone dark.
      
      Trade-off worth knowing: on a mk3 rig the driver no longer comes up at
      login, so faders reach Ardour only after gig-up runs. That is the price
      of not guessing the generation, and gig-up is the documented bring-up.
      PLN (Algolia) authored
    • docs(archive): A3-b — the OPAL SoundCloud diagnosis, for the long term · 368747eb
      Structured archive entry for the session, written as standalone learning rather
      than a board line. Fourteen findings, of which the four that will outlive the
      task: SoundCloud accumulates generations and never replaces (so a stale upload
      is permanent until deleted); the wrong cut wore the cover art, which is why
      PLN's ear beat every tool to it; the 'matches to the second' tolerance was the
      bug, because full_duration is millisecond-exact and a loose window made two
      different cuts identical; and roll call cannot find a stray, because a check
      that only looks where it expects a thing never discovers it elsewhere.
      
      Written for a reader months out with no memory of any of it.
      PLN (Algolia) authored
    • docs(log 040 + board): the wrong cut wore the cover art, and the resume points · 33794e36
      Captain's log 040 and the pre-compact resume state.
      
      The log's finding, in one line: OPAL-26 sounded broken on SoundCloud while every
      rendered file was correct to the millisecond, because SoundCloud will not replace
      audio behind a permalink and our uploader is idempotent — so every re-upload
      under a fresh slug ADDED a copy. The whole Aug-12 first cut was still up beside
      the six correct v4 ones. And the tell PLN was actually reacting to: eight of the
      fourteen stale uploads wear cover art and not one of the correct six does. The
      superseded audio looked finished. His ear was reading the artwork.
      
      Two lens lessons in it, both recurring. 'Matches to the second' was a tolerance,
      not a verdict — SoundCloud reproduces full_duration to the MILLISECOND, so at the
      ±1.5s a transcode seems to deserve, the Aug-12 and v4 cuts of /wap are the same
      track, which is how six stale uploads passed a duration check in August. And roll
      call cannot find a stray, because a check that only looks where it expects a
      thing can never find that thing somewhere else.
      
      Plus the git one, which cost four commits: this is a shared checkout, another
      session moved HEAD between two of my commits, and 'git push origin master' from a
      non-master HEAD is a silent no-op that exits 0. The PUSHED in my own log was
      something I wrote myself.
      
      The board now carries R1-R5 written for a reader with zero context: exact
      commands with paths, the artifacts already on disk so nothing is regenerated,
      the three decisions that are PLN's and not a machine's, and the shared-checkout
      hazard spelled out before anyone commits into it again.
      PLN (Algolia) authored
    • fix(perf): stop DWIM-launching Pulsar into /usr/local/sbin · c7ac1741
      183 Pulsar SIGABRTs between 2026-08-19 and 2026-09-05, 80 of them inside
      one 70-minute window, all from this one block.
      
      perf.sh launched Pulsar in "the script's directory",
      $(dirname "${BASH_SOURCE[0]}") -- but perf.sh is deployed root-owned as
      /usr/local/sbin/perf-audio (perf-audio.sudoers, for scoped NOPASSWD), so
      BASH_SOURCE resolved to /usr/local/sbin and Pulsar got /usr/local/sbin as
      its project directory. Coredump argv:
        /opt/Pulsar/pulsar --executed-from=/home/pln --no-sandbox /usr/local/sbin
      
      The Bridge's perf watcher (bridge.py:41 -> perf.py:run_watcher ->
      reconcile -> set_mode) shells out here every ~30s, and every mode flag
      routes through set_priorities, so the retry was unbounded: crash ->
      pgrep finds nothing -> next tick tries again. No backoff, no lock, no
      exit-status check. And the "✓ Pulsar launched" line proved nothing: the
      verification was a 0.5s pgrep presence poll that caught the doomed
      Electron process alive during startup and returned before it aborted.
      
      Fixed by removing the launch, not by resolving the directory better.
      Setting priorities is this script's job; starting applications is not.
      Ardour, two branches up, has always been prioritize-if-present with no
      launch branch -- Pulsar is now symmetric with it. Both the tray and the
      Bridge have real Pulsar launchers that pass the right project directory
      and surface failures. #116 also wants the hot path unable to start
      things by accident, and a root script spawning a GUI editor through
      `sudo -u $USER env ...` every 30s is the opposite of that.
      
      NOT LIVE YET: /usr/local/sbin/perf-audio is a root-owned copy and still
      carries the bug. It needs
        sudo install -m755 -o root -g root perf.sh /usr/local/sbin/perf-audio
      which is outside the sudoers whitelist, so PLN has to run it. Dormant
      until then -- the block only fires when no Pulsar is running.
      PLN (Algolia) authored
    • docs(board): check-mix can see the session now, and it failed on sight · 43e46143
      The argv-only bug is fixed by extracting the 3-rung ladder into
      tools/ardour_session.py, imported by both gig-log and check-mix. gig-log's
      'keep check-mix in sync' comment is gone because there is nothing left to keep
      in sync — one parser per concept satisfied by construction rather than by
      somebody remembering. 169 gig-log tests pass unchanged.
      
      Then it ran for real and reported Tidal 07, 08, 10 and 12 at -inf dB in the
      saved session. Third recurrence of that bug (04-08+12, then 02+10, now these
      four). PLN's parked two-minute check went from unrunnable straight to failing,
      which is the whole argument for wiring a check into a caller: it gets run under
      conditions the unit test never sees.
      
      Also logged: two pre-existing test failures the suite run made visible
      (test_orphan_orbits 16 rows vs 13 expected; test_lcxl3_display collects zero
      tests, and a test file that cannot fail is not a passing test), and the
      uncommitted duplicates left sitting on claude/rig-streamline so whoever rebases
      it does not double-apply them.
      
          the fader was down in the file, not in the room.
          nobody could hear the difference
          because a closed fader and a silent instrument
          look identical on a post-fader meter.
      PLN (Algolia) authored
    • feat(midiviz): a permanent lens — own app id, all desktops, CC numbers · d1dbae36
      PLN, after the first look at it: "the midi mon is beautiful! so lets
      streamline, now it should always oppen, with always on top, all desktops
      displayed when i switch from desk 1 to 2 3 4" -- and then "would help to
      see the chan num: 33 or 53 etc so as i press i can wire".
      
      Always-on-top needed no change: the WindowStaysOnTopHint at :293 is
      unconditional and KWin honours it. The other three did.
      
      IDENTITY. The Wayland app_id comes from desktopFileName, not
      applicationName, so KWin saw resourceClass "python3" -- verified by
      querying workspace.windowList(), not assumed. Two consequences: a window
      rule keyed on the app id would have matched EVERY python GUI on the box,
      and launchers.focus_window("midimon") could never match, so clicking the
      Bridge/tray button on an already-running lens silently did nothing. Set
      desktopFileName + ship a .desktop (which also silences the portal's "App
      info not found" warning), and point the launcher's focus at "midiviz".
      
      ALL DESKTOPS is a compositor property, not a Qt flag, so it lives in a
      KWin rule (~/.config/kwinrulesrc, wmclass=midiviz, desktops= empty with
      desktopsrule=2/Force). Declarative on purpose: it applies at map time,
      every time, with no post-launch retry racing the window's existence.
      Verified on the live window rather than inferred from the key names --
      onAllDesktops=true keepAbove=true.
      
      CC NUMBERS, two layers. Per-cell dim decimals are the map you consult
      while wiring; they render in their own pass because f_micro is already
      current there, and a per-cell setFont would add 96 font switches a frame
      to a path this file keeps to a counted number of drawText calls. They
      are skipped when a cell is too narrow to hold them beside the value. And
      the header now prints the last-touched control as ^53=127 -- decimal,
      with the corpus's caret, so it is the literal token to type into a
      pattern instead of a hex value to convert mid-set.
      
      Autostart as a systemd --user unit bound to graphical-session.target,
      not default.target: it needs WAYLAND_DISPLAY/XDG_RUNTIME_DIR imported
      into the user manager first. Registered "login" in rig_units.py.
      
      Paint path exercised by the offscreen selftest: 116 paints, PASS.
      PLN (Algolia) authored
    • fix(check-mix): resolve the running Ardour session via the 3-rung ladder · 27f23515
      check-mix.py resolved the live session from argv only, so any Ardour
      launched from the GUI's recent-sessions list (bare argv) went unresolved
      and check-mix silently fell back to a guessed path. gig-log.py's FilesLens
      already solved this with a 3-rung evidence ladder (argv > cwd > open fds,
      commit abad7515) and had a sync comment flagging check-mix as needing it.
      
      Extracted the ladder into tools/ardour_session.py — one resolver instead
      of two, per feedback_one_parser_per_concept — and made both gig-log.py and
      check-mix.py import it. gig-log's FilesLens keeps its instance-level
      caching/logging but delegates all evidence-gathering to the shared module;
      the now-unnecessary "keep in sync" comment is gone.
      
      Verified: gig-log's 169 pytest tests still pass unchanged (they exercise
      all 3 rungs against synthetic /proc fixtures). Live: Ardour was running
      ("Tidal Live" session, launched with argv), so check-mix.py resolved it
      for real and reported per-orbit gains — surfacing the same recurring bug
      this fix was meant to catch: Tidal 07/08/10/12 sit at -inf dB in the
      currently SAVED session. Rungs 2 (cwd) and 3 (open-fds) were proven against
      synthetic fixtures matching the live-rig gap, since the running process's
      argv already carried the session path.
      PLN (Algolia) authored
    • docs(board): the gated delete is built, and a shared-checkout incident · 3668e9b9
      master sc delete --manifest exists: dry-run by default, identity verified in
      BOTH directions (a permalink that now resolves to a different sc_id is a
      refusal, not a warning), public tracks refused, a reason required per entry
      because a deletion without provenance is not allowed, batch capped before any
      API call, and a receipt that keeps every field plus the on-disk paths of the
      archived artwork and the source FLAC. The old test-namespace gate was not
      widened — a second gate was added beside it, and a test proves the old one is
      unreachable from the new path.
      
      Dry run: 14 deletable, 0 refused. All private, all uploaded in the same minute
      on 2026-08-12, and every one still has its FLAC in Opal26_master/tracks/, so
      deleting them loses a URL and not a recording. --go is NOT approved yet.
      
      Two caveats recorded loudly rather than buried. The delete TRANSPORT is
      unproven: no DELETE has ever been issued from this codebase against api-v2, and
      a clean dry run is not evidence the deletes will land. And the generator's first
      version lined stale uploads against the newest cut BY TRACK NUMBER — dropping
      Desire renumbered REVOLUTION from 15 to 14, so the manifest confidently compared
      Desire against REVOLUTION's duration. An unnamed reference, again.
      
      Also logged: four of my own commits landed on a concurrent session's branch
      because the shared checkout moved under me, and 'git push origin master' from a
      non-master HEAD is a silent no-op that exits 0. Recovered via worktree
      cherry-pick without touching their branch or the live tree. The worktree rule in
      CLAUDE.md has no orchestrator exemption, which is what I had assumed.
      
          two sessions, one working tree,
          and a push that said yes to a question
          it had never actually been asked.
      PLN (Algolia) authored
    • docs(board): tracks.json is on v4, and two parsers disagree on what a track is · dcbf3558
      The regen moved timecodes by up to 21s (Piment +21.0, REVOLUTION +19.0, Gimme
      Acid +16.7). That file is not just website furniture — Slopmotion cuts visuals
      to it, so the release videos were pointed a third of a minute off the music on
      a third of the record.
      
      The more interesting finding is a parser conflict the regen walked into.
      build_gig_tracksjson.py drafts NEW gig pages and never computes the
      styleDistribution that LiveEvent.js renders; gig-metadata.py regenerates
      existing ones and does. They also disagree outright on REVOLUTION's style
      (jazz vs nujazz). Two parsers, one concept — so the board's line that
      build_gig_tracksjson 'is now the way to make any gig page' is too strong and
      is corrected here rather than left to bite the next regen.
      
      Left explicitly to PLN: whether the OPAL page documents the set as PERFORMED
      (15 tracks, Desire included) or as RELEASED (14). Desire has no v4 boundary
      because it was cut, so its v3 timecode rides through unverified. A page can
      honestly be either thing; only he knows which one he wants a visitor to get.
      PLN (Algolia) authored
    • docs(board): correct my own count — 14 stale uploads, not 1 · d429f162
      The first version of this amendment said one OPAL track on SoundCloud was
      stale. Then I read the account instead of reasoning about it, and it is
      fourteen: the entire Aug-12 first cut is up, thirteen of it under junk
      permalinks (/opal26-wap, /12-mafia-12, /ceci-nest-pas-une-bombe) that roll
      call could never have found, because roll call only looks where it expects a
      track to be. Same error the board made before me, one size smaller.
      
      The finding that explains PLN's ear exactly: eight of the fourteen stale
      uploads carry cover art and not one of the six correct v4 tracks does. The
      wrong cut is the one that looks finished.
      
      Also corrected: 'every other track matches to the second' was never a verdict,
      it was a tolerance. SoundCloud reproduces full_duration to the MILLISECOND, so
      at the ±1.5s a transcode seems to deserve, the Aug-12 and v4 cuts of /wap
      (361.0 vs 361.6) and /perfect (315.0 vs 314.6) are the same track. That is how
      six stale uploads passed a duration check in August.
      
      And two coincidences caught before they could become deletions: /saria-demo
      (2025) and /au-revoir-lord-toyota (2020) match OPAL to 50ms by chance on a
      132-track account. Duration is a strong discriminator and still not provenance.
      
          the ones with the cover art were the wrong ones.
          six months of a set played once, and the tell
          was never in the sound -- it was in which
          of the twenty ghosts looked ready.
      PLN (Algolia) authored
    • docs(claude): the canonical gig-metadata path lost its next/ segment · 577767a3
      CLAUDE.md pointed at ../../Web/www/next/content/lives/, which has not existed
      since www hoisted the app out of next/ in 6e51fff. The correct path is
      ../../Web/www/content/lives/{year}/{slug}.md.
      
      This is a worse bug than a typo in docs. The instruction it sits inside is
      'Mastering != metadata. Never invent gig metadata... Verify there and cite the
      path — if a fact isn't in the canonical source, ask.' A reader who follows the
      documented path gets a false 'not found' from the one source they were told to
      trust, and the honest next step from there — asking, or worse, filling the gap
      from an intermediate script — is exactly the failure the rule was written to
      prevent. A stale read is how a Write becomes a delete.
      
      The tide-table scripts already had it right (build_catalog_view.py's LIVES
      carries a comment naming the hoist commit); only the instruction was behind.
      PLN (Algolia) authored
    • chore: project config and the logo source · 1b03cb03
      - process-palette.json wires the editor's command palette to the rig's own
        entrypoints (Init MIDI Channels -> init_midi.sh, and the rest). The whole
        'one entrypoint, fewer files' goal is only real if the editor knows where
        the entrypoints are, so this is rig config, not editor preference.
      - sketch.properties points the Processing sketch at its main .pde.
      - Logo.xcf is the editable source of the ParVagues mark, from 2025-05. The
        armada design system is documented down to its type stack and its reserved
        brand magenta, and the one file you cannot regenerate from any of that is
        the layered original. 1.2 MB to never have to redraw it.
      
      Deliberately still untracked: TODO.md. It is a paused 2026-06-22 perf/thermal
      board, and TASKS_DUMP.md has been the board since 2026-08-16. Committing it
      would put a second answer to 'what is next' in the tree, which is the shape
      that keeps biting us. PLN's call: fold its two live items into TASKS_DUMP and
      delete it, or keep it as a scratch file outside git.
      PLN (Algolia) authored
    • feat(tide-table): build_delete_manifest — the verdict becomes a delete allowlist · bd3a680f
      `sc_generation_lens` already knows which OPAL-26 uploads are the Aug-12 first
      cut; nothing turned that verdict into something the new `sc delete --manifest`
      could execute. This does, and it is the last checkpoint before a destructive
      call, so it refuses rather than warns.
      
      In: verdicts starting STALE/ORPHAN (right permalink, wrong cut) plus every
      stray (an older cut under another slug). Out, and asserted out: verdict OK
      (that IS the ear-verified cut), NOT UPLOADED (nothing to delete), anything
      without an sc_id, and everything in `coincidences_discarded` — the lens finds
      strays by duration and on a 132-track account two unrelated tracks will share
      a length to 50 ms, which is how /au-revoir-lord-toyota (2020, and PUBLIC) and
      /saria-demo (2025) both matched an OPAL render. The date floor is re-applied
      here instead of trusting the file's own bucket names: two chances to catch a
      mistake whose cost is permanent.
      
      Every entry carries a reason citing the evidence, the artwork the pull saved,
      and the local render the upload came from — all fourteen resolve to a FLAC in
      Opal26_master/tracks/, so deleting these loses a URL and not a recording.
      
      One bug found while writing it, and it is the reason the reason strings matter:
      the first version lined a stale upload up against the newest cut BY TRACK
      NUMBER, and the Aug-12 cut had fifteen tracks where v4 has fourteen. Dropping
      Desire renumbered REVOLUTION from 15 to 14, so the manifest claimed
      "#14 Desire, 388.1s vs v4 205.6s" — 205.6s is REVOLUTION's length. A
      measurement is a comparison; an unnamed reference cites the wrong track. The
      lookup is keyed on the title slug now, and a track absent from the newest cut
      says so instead of borrowing a neighbour's duration.
      
      Dry-run against the live account: 14/14 resolved, both directions, all private,
      nothing refused. Nothing deleted — that is PLN's call.
      PLN (Algolia) authored
    • docs(board): tracks.json is on v4, and two parsers disagree on what a track is · 719e09ae
      The regen moved timecodes by up to 21s (Piment +21.0, REVOLUTION +19.0, Gimme
      Acid +16.7). That file is not just website furniture — Slopmotion cuts visuals
      to it, so the release videos were pointed a third of a minute off the music on
      a third of the record.
      
      The more interesting finding is a parser conflict the regen walked into.
      build_gig_tracksjson.py drafts NEW gig pages and never computes the
      styleDistribution that LiveEvent.js renders; gig-metadata.py regenerates
      existing ones and does. They also disagree outright on REVOLUTION's style
      (jazz vs nujazz). Two parsers, one concept — so the board's line that
      build_gig_tracksjson 'is now the way to make any gig page' is too strong and
      is corrected here rather than left to bite the next regen.
      
      Left explicitly to PLN: whether the OPAL page documents the set as PERFORMED
      (15 tracks, Desire included) or as RELEASED (14). Desire has no v4 boundary
      because it was cut, so its v3 timecode rides through unverified. A page can
      honestly be either thing; only he knows which one he wants a visitor to get.
      PLN (Algolia) authored
    • fix(midi): cut the surface's direct leg to SuperCollider, every 2s · 361a05b9
      SuperDirt's boot runs MIDIIn.connectAll (start_and_midi.scd:19), so SC
      subscribes to every hardware MIDI input it can see -- the LCXL included.
      The surface then fed SC twice: once raw, once through Midi Through. And
      MIDIFunc.cc takes `src` and never checks it (start_and_midi.scd:51), so
      every CC fired the handler twice and every button noteOn double-fired
      into ~lcxlChordCheck. A double noteOn on a toggle is a no-op: that is how
      a mute button silently stops working.
      
      lcxl3-driver's prune() handled this for the v3 board, but only while it
      runs -- and it was stopped today (stale binding to an unplugged mk3,
      Conflicts=-killing the mk1 painter). A hand-run `aconnect -d` fixed it
      live and then SC restarted and re-armed it, which is the whole argument
      for putting it in the reconcile loop that already owns the wiring.
      
      Rule 3 matches the RAW board on type=kernel, never on name alone:
      lcxl3-driver publishes a virtual port called 'ParVagues LCXL3' that
      matches the same name candidates, and cutting the driver's translated
      feed would kill the path we actually want. Only surface->SuperCollider
      links are cut; -> Midi Through and the aseqdump taps that gig-log and the
      Pulsar HUD read all survive, verified live.
      
      Tested by arming the bug (aconnect 20:0 130:3, 20:1 130:4) and watching
      the loop cut both and settle: lcxl-path.py went from "LCXL -> SC direct"
      back to "LCXL -> Midi Through -> SC", logged once, no per-tick spam, and
      gig-log kept counting touched controls.
      
      Also logs PLN's ear-feedback on the classic-LCXL play-test (rose_rouge
      works great; midiviz landed) to the archivist's notes.
      PLN (Algolia) authored
    • docs(board): correct my own count — 14 stale uploads, not 1 · 84e37fec
      The first version of this amendment said one OPAL track on SoundCloud was
      stale. Then I read the account instead of reasoning about it, and it is
      fourteen: the entire Aug-12 first cut is up, thirteen of it under junk
      permalinks (/opal26-wap, /12-mafia-12, /ceci-nest-pas-une-bombe) that roll
      call could never have found, because roll call only looks where it expects a
      track to be. Same error the board made before me, one size smaller.
      
      The finding that explains PLN's ear exactly: eight of the fourteen stale
      uploads carry cover art and not one of the six correct v4 tracks does. The
      wrong cut is the one that looks finished.
      
      Also corrected: 'every other track matches to the second' was never a verdict,
      it was a tolerance. SoundCloud reproduces full_duration to the MILLISECOND, so
      at the ±1.5s a transcode seems to deserve, the Aug-12 and v4 cuts of /wap
      (361.0 vs 361.6) and /perfect (315.0 vs 314.6) are the same track. That is how
      six stale uploads passed a duration check in August.
      
      And two coincidences caught before they could become deletions: /saria-demo
      (2025) and /au-revoir-lord-toyota (2020) match OPAL to 50ms by chance on a
      132-track account. Duration is a strong discriminator and still not provenance.
      
          the ones with the cover art were the wrong ones.
          six months of a set played once, and the tell
          was never in the sound -- it was in which
          of the twenty ghosts looked ready.
      PLN (Algolia) authored
    • docs(claude): the canonical gig-metadata path lost its next/ segment · 111c903e
      CLAUDE.md pointed at ../../Web/www/next/content/lives/, which has not existed
      since www hoisted the app out of next/ in 6e51fff. The correct path is
      ../../Web/www/content/lives/{year}/{slug}.md.
      
      This is a worse bug than a typo in docs. The instruction it sits inside is
      'Mastering != metadata. Never invent gig metadata... Verify there and cite the
      path — if a fact isn't in the canonical source, ask.' A reader who follows the
      documented path gets a false 'not found' from the one source they were told to
      trust, and the honest next step from there — asking, or worse, filling the gap
      from an intermediate script — is exactly the failure the rule was written to
      prevent. A stale read is how a Write becomes a delete.
      
      The tide-table scripts already had it right (build_catalog_view.py's LIVES
      carries a comment naming the hoist commit); only the instruction was behind.
      PLN (Algolia) authored
    • chore: project config and the logo source · eefd7066
      - process-palette.json wires the editor's command palette to the rig's own
        entrypoints (Init MIDI Channels -> init_midi.sh, and the rest). The whole
        'one entrypoint, fewer files' goal is only real if the editor knows where
        the entrypoints are, so this is rig config, not editor preference.
      - sketch.properties points the Processing sketch at its main .pde.
      - Logo.xcf is the editable source of the ParVagues mark, from 2025-05. The
        armada design system is documented down to its type stack and its reserved
        brand magenta, and the one file you cannot regenerate from any of that is
        the layered original. 1.2 MB to never have to redraw it.
      
      Deliberately still untracked: TODO.md. It is a paused 2026-06-22 perf/thermal
      board, and TASKS_DUMP.md has been the board since 2026-08-16. Committing it
      would put a second answer to 'what is next' in the tree, which is the shape
      that keeps biting us. PLN's call: fold its two live items into TASKS_DUMP and
      delete it, or keep it as a scratch file outside git.
      PLN (Algolia) authored
    • docs(notes): archive today's two ear-signals before they evaporate · ec9c7d4b
      Written earlier today by the gear-repair session and left uncommitted, which
      for this particular file is the one failure mode that matters: performance_notes
      IS the archive, so an uncommitted entry is not a note, it is a memory.
      
      Two signals, both about the rig rather than the audio:
      
      - *"ok just played rose_rouge works great!"* — first confirmation that the
        CLASSIC LCXL (mk1, 1235:0061) is a fully viable performing surface once
        lcxl-leds-watch owns the paint. Worth recording precisely because the rig had
        drifted into treating the mk3 as the only real board; the classic is a
        fallback, not a downgrade.
      - *"and the midi mon is beautiful!"* — on first sight of tools/bridge/midiviz.py.
        A taste signal about the rig's own UI, and the follow-up ask (always-open,
        always-on-top, every desktop) says it plainly: midiviz is an instrument of
        the rig, not a debug tool.
      
      CLAUDE.md asks for every reaction captured durably because this corpus feeds
      sampling, effect choices and the auto-mastering heuristics. Durably means
      committed.
      PLN (Algolia) authored
    • feat(tools+tracks): the last loose sources come aboard · f5d34615
      Four kinds of orphan, all real source, none of it generated:
      
      - tools/sweep_state.py — 'the surface needs a sweep', as a fact the rig can
        act on. Companion to the LCXL3 driver thread.
      - armada/tide-table/kick_bass.py — the deterministic kick-vs-bass tiebreaker,
        the one split CLAP cannot do reliably because both live low. Exactly the
        'validate by analysis, never infer role from the name' rule in code.
      - armada/tide-table/calibration.stem.json — joins calibration.json and
        calibration.whole-mix.json, which were already tracked. A calibration set
        missing one of its three members is a trap for whoever reads the other two.
      - install_superdirt.sclang — pins SuperDirt v1.7.2 and recompiles. The boot
        path is exactly where 'simple, non-brittle, one entrypoint' matters.
      - live/collab/benoit/tous_ensemble.tidal and live/midi/nova/nujazz/
        nouvelle_couleur.tidal — two scores. nouvelle_couleur carries a '-- url:'
        header naming its backdrop source, per the scene standard.
      - visuals/{maze,nightly_repairs,there,there0}.js — four hydra sketches.
      
      Python entrypoints staged 100755 via --chmod, not left to the working tree.
      
          the drawer is empty now.
          everything in it was something,
          which is why it took three weeks to look.
      PLN (Algolia) authored
    • feat(mix): the mix/ toolbox lands — ten scripts the index never knew · a14aee74
      Ten shell tools plus FORMAT.md and MIX.md, written 2026-07-29 and untracked
      since: stem-stats, energy-profile, onset-detect, silence-detect,
      transition-detect, mix-meta, mix-render, mix-normalize, mix-master, lufs-check,
      and lib/progress.sh. FORMAT.md defines a <project>.mix.json metadata contract
      whose tools 'read existing metadata and augment (don't overwrite unless told)'.
      
      Committed with the exec bit set IN THE INDEX, which is the part that bites:
      3fdf5b97 had to restore it on 63 scripts, and MIX.md invokes every one of these
      as ./stem-stats. A file that is executable in the working tree and 100644 in
      git is executable exactly until someone else clones it. Note also that
      'git commit -- path' silently drops a staged mode flip (the trap 1667bb02
      documented), so this went in without a pathspec after proving nothing else
      was staged.
      
      mix/take94's 833 MB stayed out (a5a5b37). The scripts are the artifact; the
      take is the input.
      
      ️ Flagged, not resolved: this overlaps ../tidal-ears' mastering CLI, which is
      the canonical recipe per MASTERING.md. Two toolboxes that both master a set is
      the shape feedback_one_parser_per_concept warns about. I am not merging them
      blind — committing first so the overlap is visible in one tree instead of
      half-invisible in an untracked directory. PLN's call which one is the spine.
      
          ten small knives in a drawer nobody opened,
          sharp the whole time.
      PLN (Algolia) authored