"task":"GUI launcher did not spawn the MIDI lens or the tray",
"task":"GUI launcher did not spawn the MIDI lens or the tray",
"learning":"The launcher (gig-up.sh) and the unit converger (rig_units.py --ensure) disagreed on SuperDirt topology — raw sclang vs parvagues-sc.service — so the launcher never called ensure; the fix is ensure()'s proof-process skip, not another hand-copied unit list."
"learning":"The launcher (gig-up.sh) and the unit converger (rig_units.py --ensure) disagreed on SuperDirt topology \u2014 raw sclang vs parvagues-sc.service \u2014 so the launcher never called ensure; the fix is ensure()'s proof-process skip, not another hand-copied unit list."
},
},
{
{
"date":"2026-09-17",
"date":"2026-09-17",
...
@@ -12,16 +12,16 @@
...
@@ -12,16 +12,16 @@
{
{
"date":"2026-09-18",
"date":"2026-09-18",
"task":"tray GIG UP did nothing + midimon silent",
"task":"tray GIG UP did nothing + midimon silent",
"learning":"The Bridge ran 'gig-up.sh --converge --quiet' but gig-up.sh's arg guard only knew --headphones/--help, so every tray GIG UP died on flag 1 (exit 2, output captured). Flags now parsed: --converge boots SuperDirt + converges units with NO app windows, --quiet mutes ok/info. midimon watches 132:0 (the lcxl3-driver's translated virtual port), not the raw surface — silence there just means the driver had no input yet."
"learning":"The Bridge ran 'gig-up.sh --converge --quiet' but gig-up.sh's arg guard only knew --headphones/--help, so every tray GIG UP died on flag 1 (exit 2, output captured). Flags now parsed: --converge boots SuperDirt + converges units with NO app windows, --quiet mutes ok/info. midimon watches 132:0 (the lcxl3-driver's translated virtual port), not the raw surface \u2014 silence there just means the driver had no input yet."
},
},
{
{
"learning":"Two gig-up.sh exist: root (1-click, owns leds()) and tools/ (the GO/NO-GO gate the Bridge calls, where lcxl3-driver is boot=manual so nobody starts it). The v3 LCXL3 enumerates as 'LCXL3 1', breaking every v2 'Launch Control XL' grep — present/LEDs/path checks all silently misread. lcxl-path must match the driver's virtual PORT name 'ParVagues LCXL3' (under RtMidiOut client), never chase the PipeWire relay mesh (everything reaches everything via 143)."
"learning":"Two gig-up.sh exist: root (1-click, owns leds()) and tools/ (the GO/NO-GO gate the Bridge calls, where lcxl3-driver is boot=manual so nobody starts it). The v3 LCXL3 enumerates as 'LCXL3 1', breaking every v2 'Launch Control XL' grep \u2014 present/LEDs/path checks all silently misread. lcxl-path must match the driver's virtual PORT name 'ParVagues LCXL3' (under RtMidiOut client), never chase the PipeWire relay mesh (everything reaches everything via 143)."
},
},
{
{
"learning":"WirePlumber 0.4.17 ignores wireplumber.conf.d SPA-JSON rules (0.5 syntax) — this box reads Lua from main.lua.d only, and a partial alsa_monitor.rules reassignment clobbers the built-in table by index (dropped the card to profile off). Every wireplumber restart tears down Ardour's JACK client; recovery needed a full Ardour relaunch. pactl/wireplumber restart restored the missing HiFi profile after a UCM probe failure (card showed only off/pro-audio)."
"learning":"WirePlumber 0.4.17 ignores wireplumber.conf.d SPA-JSON rules (0.5 syntax) \u2014 this box reads Lua from main.lua.d only, and a partial alsa_monitor.rules reassignment clobbers the built-in table by index (dropped the card to profile off). Every wireplumber restart tears down Ardour's JACK client; recovery needed a full Ardour relaunch. pactl/wireplumber restart restored the missing HiFi profile after a UCM probe failure (card showed only off/pro-audio)."
},
},
{
{
"learning":"A generated boot file is only as good as the list it is generated FROM. gig-up.sh rewrote preload.scd on every launch from a default naming the PREVIOUS gig (setlist_opal2026.txt): measured 132 banks -> 45, leaving 41 of Thursday's banks to be read off disk mid-set — the exact 'crackle at the venue' failure the check was built for, re-armed by a stale default. Two structural lessons. (1) Name the set ONCE and have every consumer read that; the plan generator and the gate disagreeing about 'the set' is the whole bug. (2) An unconditional write is destructive in the quiet direction: the plan in place was a deliberate 132-bank UNION and regeneration could only narrow it, so gen_preload now asks check-preload first and leaves a covering plan alone. Corollary from the same pass: merging two tools that share a name means diffing what they DO, not just what they check — the root launcher span raw sclang while the other started parvagues-sc.service, so repointing the Bridge at 'one launcher' would have silently taken it off systemd (losing rlimits, the journal, supervision and OOM protection)."
"learning":"A generated boot file is only as good as the list it is generated FROM. gig-up.sh rewrote preload.scd on every launch from a default naming the PREVIOUS gig (setlist_opal2026.txt): measured 132 banks -> 45, leaving 41 of Thursday's banks to be read off disk mid-set \u2014 the exact 'crackle at the venue' failure the check was built for, re-armed by a stale default. Two structural lessons. (1) Name the set ONCE and have every consumer read that; the plan generator and the gate disagreeing about 'the set' is the whole bug. (2) An unconditional write is destructive in the quiet direction: the plan in place was a deliberate 132-bank UNION and regeneration could only narrow it, so gen_preload now asks check-preload first and leaves a covering plan alone. Corollary from the same pass: merging two tools that share a name means diffing what they DO, not just what they check \u2014 the root launcher span raw sclang while the other started parvagues-sc.service, so repointing the Bridge at 'one launcher' would have silently taken it off systemd (losing rlimits, the journal, supervision and OOM protection)."
},
},
{
{
"learning":"A launcher's exit code cannot tell 'it worked' from 'it did nothing'. gig-up.sh --help prints its usage and exits 0, and the window wrapper closed on that zero exactly as it closes on a good launch -- so the rig had never once been started from the icon, and the only evidence was the LCXL staying dark. The log had been written faithfully all along and held nothing but the usage text. Structural lesson: for any wrapper that closes on success, the success test must be a POSITIVE marker the wrapped thing prints on its real path ('gig-up done'), never the absence of an error. And hold the coupling from both ends in tests, because the day someone rewords that last line, every real launch starts reporting a failure."
"learning":"A launcher's exit code cannot tell 'it worked' from 'it did nothing'. gig-up.sh --help prints its usage and exits 0, and the window wrapper closed on that zero exactly as it closes on a good launch -- so the rig had never once been started from the icon, and the only evidence was the LCXL staying dark. The log had been written faithfully all along and held nothing but the usage text. Structural lesson: for any wrapper that closes on success, the success test must be a POSITIVE marker the wrapped thing prints on its real path ('gig-up done'), never the absence of an error. And hold the coupling from both ends in tests, because the day someone rewords that last line, every real launch starts reporting a failure."
"learning":"Tidal already ships 4 OSC targets in BootTidal.hs — mirroring superdirtShape to a 5th port (oscfeed.VIZ_PORT 57130) gives the HL trigger feed without touching SuperDirt's port; and an OSC name/value fold must compare str(k+1) keys, not int, or every named arg silently vanishes."
"learning":"Tidal already ships 4 OSC targets in BootTidal.hs \u2014 mirroring superdirtShape to a 5th port (oscfeed.VIZ_PORT 57130) gives the HL trigger feed without touching SuperDirt's port; and an OSC name/value fold must compare str(k+1) keys, not int, or every named arg silently vanishes."
"learning":"Tidal 1.9 addresses superdirtShape packets /dirt/play, not /play — captured off the wire, the documented address silently dropped every real packet while every synthetic test passed; and font metrics lie about letter-spacing, so a bake must scan its own ink. Synthetic tests share the author's assumptions — one real wire capture beats a hundred self-tests."
"learning":"Tidal 1.9 addresses superdirtShape packets /dirt/play, not /play \u2014 captured off the wire, the documented address silently dropped every real packet while every synthetic test passed; and font metrics lie about letter-spacing, so a bake must scan its own ink. Synthetic tests share the author's assumptions \u2014 one real wire capture beats a hundred self-tests."
},
{
"task":"midiviz extracted to standalone opensource repo",
"learning":"A branding override is a fuzzing harness in disguise: exposing the wordmark to arbitrary strings immediately found an UnboundLocalError in _word_pixmap's fit branch (pad used before assignment) that PARVAGUES could never reach \u2014 the default input only exercises the author's assumptions."
},
{
"date":"2026-09-26",
"task":"Export + master Take103 (AI ENGINEER set, partial: rec joins 3 min in, d8 never armed) and Take104 (Rose Rouge club mix) from Ardour interchange mono pairs; 4 masters (-14/-9) + 13-track split in ~/Work/Sound/Prod/AIEngineer2026_master; segments authored from gig-log track switches; fixed tidal-ears split --segments slug crash (3bafd03)",
"learning":"The Ardour interchange capture IS the export: mono %L/%R pairs + gig-log track timeline replace the DAW export pass and silence-based boundary detection entirely."