1. 24 Sep, 2026 3 commits
    • midiviz: the focus column holds instead of breathing; the wordmark sharpens · 05db016f
      PLN: 'its weird moving a djf makes the col grow then shrink over time' —
      width on a decay curve was motion with no meaning; the played column now
      HOLDS 2x while in use, hands cause the only transitions, uniform after
      FOCUS_HOLD of silence. Wordmark: two tight glow rings instead of a wide
      fog, tighter tracking, condense floored at 0.75 — nine ExtraBold glyphs
      are ~9.4em and three columns ~3.9em, so the rest goes to height. oscfeed
      says 'first trigger received (dN)' once, so 'is Tidal even sending' is a
      journalctl away.
      PLN (Algolia) authored
    • status: six buckets for gig day, and the page stops inventing a doors time · f07fe81d
      Rebuilt against tonight's findings. Buckets Corpus/Audio/Surface become
      Rig/Perf/Mix, which is what the night actually produced; Set 100%, Rehearsal
      35% (six of seventeen, moved up from last), Rig/Perf/Mix gated on one pair of
      hands, Observability 90%. Timeline is three stops, all today. Counters compute
      from the clock and repaint every 60s.
      
      The part worth keeping: no clock time appears on this page except the sourced
      17:00. The previous version said doors at 19:00 and a rebuild changed it to
      18:00 by reasoning from 'end of talks -> doors'. Both were guesses. There is no
      entry for this gig in Perso/www/content/lives/2026 -- the canonical home for
      venue, title, lineup and times -- and TODO_GIG.md never states one, so the rule
      applies: never invent gig metadata, ask. The set now reads 'time tbc' and every
      countdown anchors on the sound test.
      
      That is enforced mechanically rather than by intention: the page's silent
      self-test sweeps every derived string at 30-minute steps from 15 Sept to 5 Oct
      and FAILS on any clock time that is not 17:00. Guessing a doors time back in
      now breaks a test instead of quietly misinforming him at a glance. The two old
      guesses survive only inside the comment that explains why they were wrong.
      
      Verified: 420px frame -> scrollWidth 405 (no horizontal scroll), 1440px -> 3+3
      grid with no orphan card, self-test PASS and silent, zero external requests,
      HTTP 200 over localhost.
      PLN (Algolia) authored
    • gig-log: it was never ten sessions in a row, and the real count tells a better story · 967c2e84
      A subagent counting cc records per log caught the claim; confirmed
      independently. Nine straight zeros Sep 6-22 (no mbind at all -- the 'Launch
      Control XL' name era), then 35 in gig-20260923-101102, then 0 in the 14-hour
      gig-20260923-103812, then 145 tonight.
      
      So the September name fix WORKED and the 10:11 session proves it. The 14-hour
      session recorded nothing for a different reason: it bound a live address that
      later died under it when the driver republished. Two failures, not one, and
      only the second -- liveness -- is still open.
      
      This is also independent evidence for the corrected diagnosis: a resolver that
      could only ever pick the wrong port would not have produced 35 records an hour
      earlier. Worth having in the file, because 'ten sessions of silence' invites
      the wrong fix.
      PLN (Algolia) authored
  2. 23 Sep, 2026 27 commits
    • set: two leftovers from the rehearsal stopped their tracks compiling · 710b78d7
      Both were mid-thoughts from tonight, and each killed EVERY dN in its block
      because a .tidal track is one do block.
      
      liquid_nite:46 had '$ midiOn "^58" (iter 4 . )' -- a dangling composition
      operator, so the section typed as a function-to-function instead of a
      Pattern ValueMap endomorphism. Dot dropped, keeping both lines he added
      tonight (the new ^58 iter and the ^90 ply he uncommented). PLN authorised
      this one after closing the editor.
      
      piment_bresilien:96 had 'once $ "90s_synatm:10"', the line he used to
      audition the sample whose missing bank was tonight's d9 fix. 'once' is not in
      scope in this BootTidal. Commented rather than deleted, with the reason
      inline, because the audition itself is worth keeping.
      
      Proof: xargs the 17 paths from armada/setlist_thu24.txt into
      silent-eval --seeded -> 'OK - every declared orbit emits events', 17/17.
      The earlier 15-track run that found the second one was reading backlog.md's
      list, not the setlist: passing an unquoted $VAR to a command in zsh does not
      word-split, so silent-eval received one argument and fell back to its
      default.
      PLN (Algolia) authored
    • gig: liquid_nite does not compile, and it is track 3 of the set · 99ec9556
      The full gate caught what the fast one skips. An uncommitted edit to
      liquid_nite.tidal left '$ midiOn "^58" (iter 4 . )' -- a dangling composition
      operator, so the section types as a function-to-function instead of a
      Pattern ValueMap endomorphism, and since it is one do block EVERY dN in the
      track is dead. He played it fine at 00:19 (driver logged eval=cycle 0 four
      times) and edited it after, so it is a thought left mid-air at bedtime.
      
      Both fixes verified with silent-eval --seeded on COPIES, without touching his
      file, because which one he wants is musical: dropping the dot keeps both of
      tonight's additions and compiles; reverting the file also compiles but discards
      them, and needs Pulsar closed on that buffer first.
      
      Also triages the rest of the 43-check run, and says plainly which failures are
      mine. preload-covers-set now FAILS because tonight's parser fix made five banks
      visible that the generated preload was never built for -- the gate is right and
      that file gets regenerated WITH him, not unilaterally. And the 379/hour xrun
      warning is my own churn: every burst timestamps to a command I ran, while the
      last five idle minutes are zero.
      PLN (Algolia) authored
    • tide-table: gig-eve rehearsal notes, six tracks and four ear-signals · 42251f09
      PLN played the arc to take_5_drops and called it: 'not bad i am confident'.
      That is the only confidence signal this set gets before doors, so it is
      logged with what it covers (six of seventeen) and what it does not.
      
      Four reactions paired with their mechanical causes, which is the point of this
      corpus: piment_bresilien's silent d9 (a bank off SuperDirt's load path, plus a
      parser that read 90s_synatm as s_synatm and 808bd as bd and so warmed the
      WRONG banks for five of seventeen tracks); crackles he heard in one listen
      that four logged sessions had not surfaced, attributed by xrun_by to Ardour's
      GUI thread on a realtime policy; salut_nu having no backdrop because
      defaultScene ships empty, which reads as the same taste as 'the midi mon is
      beautiful' -- the visual is part of the instrument; and the DJF third row
      landing despite his own bezel finding, which refines that finding rather than
      contradicting it.
      PLN (Algolia) authored
    • perf: the cap is thermal-pilot's rung-1 floor, and gig-up now reads knobs not wishes · d81b9a9f
      Proven with the rig's own hand at 01:12, after being wrong about it twice.
      thermal-mode-apply performance wrote cap=100 and EPP=balance_performance; six
      seconds later thermal-pilot logged 'drift: live knobs no longer match rung 1
      -- reasserted' and put back 65 and balance_power. thermald was already STOPPED
      at the time and the cap still moved, so thermald is innocent and I owe it a
      restart -- the box has had no thermal daemon since 00:40 on my bad call.
      
      It is not a leftover, it is the design: rung_knobs 1 is '0
      balance_power' with RUNG1_PCT defaulting to 65, and profile_params performance
      is '1 2 2600' -- floor rung 1, ceiling rung 2, noise brake at 2600 rpm. The
      fan idles near 2800, so the brake is always armed: six climbs to rung 2
      tonight, six brakes back within five seconds. Under this profile 65% is the
      floor and no hand-written value survives it, which means the mangled paste
      cost nothing.
      
      Suspected upstream cause, and it may close for a cable: the helper reported
      PL1=25W PL2=50W on a '45W USB-C PD source' while thermal-mode.conf records
      STOCK PL1=45W/PL2=90W. Half the power budget means hotter for the same work,
      which is what keeps the fan over the brake. Test the Dell barrel charger
      first.
      
      ensure_perf used to print 'thermal-mode performance holding' sourced from
      MODE= in the conf file, while the gate twelve lines below in the same log
      FAILED on max_perf_pct=65. Both were correct -- the file records the last
      intent, not the live posture. It now asserts the gear, waits out the pilot's
      reassert, reads the four knobs, and either says VERIFIED or names the owner
      and the reason. No sudo line to paste and no memory to rely on, which is what
      was asked for.
      
      tools/fix-ardour-rt.sh closes the Ardour item as a script rather than
      instructions: dry-run default, --apply, --revert, idempotent, backs up, and it
      patches BOTH writers because perf-audio:565/573 re-arms what parvagues-protect
      stops. Verified against the live root files (all four patterns match exactly
      once) and applied to copies first: both still parse, the prio>0 guard is in,
      and scsynth/sclang/pipewire priorities are untouched.
      PLN (Algolia) authored
    • midiviz: the wordmark is its own lens, bolted to the DJFs · 2ecba716
      Syne instanced at 800 (Qt renders a variable font's default instance no
      matter the weight asked — the first cut read Regular), glow baked once per
      geometry, word on 'l' separate from the wave on 'w'. Bolted to D1-D3 per
      PLN — 'over the djfs'. The wave yields on shrink: clamped to the space
      right of the word's span, overlap is impossible by construction.
      PLN (Algolia) authored
    • gig-log: the MIDI leg was a stale address, not the wrong port · b0170751
      Correcting my own reading from forty minutes ago, because the fix differs.
      The first call was that find_seq_port had bound the driver's FB port, since it
      matches port names by substring and 'ParVagues LCXL3 FB' contains 'ParVagues
      LCXL3'. Then measured it: aseqdump -l lists SOURCES only, the FB port is an
      input, and it does not appear in that listing at all. The resolver could never
      have picked it. 131:0 was the right port -- at 10:38:12, fourteen hours ago.
      
      The fault is one word in the docstring. 're-resolved every connect' only fires
      when the aseqdump connection drops, and it never dropped. The driver's ALSA
      client id moves on every republish -- 131 to 130 to 133 tonight alone -- and
      when the client underneath disappears aseqdump keeps running, subscribed to an
      address that no longer exists. No drop, no reconnect, and 16227 sample rows
      with zero cc rows. A dead seq subscription looks exactly like a quiet surface,
      which is why ten sessions passed without anyone noticing.
      
      The matcher hardening here (exact name beats substring, FB excluded like HUI)
      is kept because it is more precise, but it is NOT the fix and the comment says
      so. The real fix is liveness -- rebind when the bound address leaves
      aseqdump -l -- and that is post-gig. Tonight's session is rebound to 133:0.
      PLN (Algolia) authored
    • boot: MIDIIn.connectAll stopped returning, and it took the whole rig's sound · afcbf091
      A plain 'systemctl --user restart parvagues-sc' produced no sound at all for
      thirteen minutes. sclang printed 'MIDI Destinations:' and then blocked
      forever -- State S, 1s of CPU in 97s, wchan futex_wait_queue -- so it never
      reached 'Booting server' and scsynth never existed. The watchdog then
      restarted the unit into the same wall twice and tripped StartLimitBurst, so
      the unit also refused to start until reset-failed.
      
      Isolated with a three-line probe: MIDIClient.init returns, connectAll does
      not, and connecting every source to inport 0 one at a time returns and
      reaches ALL CONNECTED. Reproduced on a CLEAN MIDI world -- System, Midi
      Through, LCXL3 1, PipeWire, nothing of ours -- so it is not contention, not a
      stale client, not the port count, and not the driver.
      
      connectAll pairs source i with inport i, so it wants as many SC input ports
      as the box has MIDI sources and walks off the end. This rig has exactly one
      input worth having, because every MIDIFunc below listens on inport 0. So ask
      for that: same sources, one funnel, no index arithmetic. scsynth now boots in
      6 seconds.
      
      Found while restarting SC to pick up the 90s_synatm link and s.latency 0.1 --
      which is precisely the restart TODO_GIG.md schedules for the START of the
      rehearsal. The boot path had not been exercised since Sunday 17:57 and it was
      broken; discovering that at 5pm Thursday instead of 00:55 Wednesday would
      have cost the gig.
      PLN (Algolia) authored
    • midiviz: the wave is a watermark, not a backdrop · 95e82f0a
      Behind the matrix it survived only in the margins — the cells' floor tints
      are opaque on purpose, so 'behind' meant invisible. Painted over the
      finished reading now, under the scanlines, brand magenta as atmosphere.
      PLN (Algolia) authored
    • midiviz: the set itself paints the lens — Tidal HL feed + the ParVagues wave · ec613576
      BootTidal.hs mirrors every superdirtShape packet to port 57130, oscfeed
      listens, and a trigger on orbit N lights that orbit's column and the cells
      that affect it — family filters and mutes included. The edge is a ring, not
      a fill: it says 'the set just played through me', never a value, never a
      touch. The wave (brand magenta, vendored from the www SSOT asset) sits
      behind everything on 'w'; the feed toggles on 'h' and is on by default.
      Column glow keeps its 30% slower curve.
      PLN (Algolia) authored
    • gig: the double-press is real, and gig-log has been binding the feedback port · b68e343e
      Two findings from one question. PLN asked whether a track change made him
      press ^41 twice; it does, and carry_values() is explicit about why -- the
      latch is cleared in the driver and no CC is sent, so SuperDirt keeps the old
      127 while the driver believes the gate is shut. Press one re-sends 127 and
      nothing moves. The driver's journal has the exact event at 00:38:41, two
      latches cleared, ten seconds before love_first loaded.
      
      The docstring's reason is the inverted part: it clears the latch 'so the LEDs
      stop lying', but the gate is still open in SC after the change, so the lit
      LED was the truth. Post-gig fix, because this is the mute path: carry the
      latch for buttons the new track also maps, clear-and-emit-0 for the ones it
      does not. Thursday's workaround is to close gates before switching, which
      the journal already grades -- 0 latches cleared is a clean switch.
      
      Verifying it from the MIDI log was impossible because there is none: 16227
      sample records, 0 cc records, the tenth session running. find_seq_port
      matches port names by substring, the driver publishes 'ParVagues LCXL3 ' and
      'ParVagues LCXL3 FB' as a pair, and the resolver bound the FB input. Nothing
      sends corpus CCs to an RtMidi input, so the reader subscribed and read
      silence. The name was fixed in September; the direction never was.
      PLN (Algolia) authored
    • samples: leading digits are part of a bank name, and the truncation resolved · eab26896
      piment_bresilien's d9 plays nothing: it asks for "90s_synatm:10" and that
      bank is not on SuperDirt's load path. The samples were never lost -- they sit
      in Samples/90sArchive/output/90s_synatm, symlinked from samples-links/, and
      SuperDirt reads exactly three roots: Dirt-Samples, Samples/extra and the drum
      machines. samples-links is not one of them. Fixed with a link into extra/.
      
      The reason nothing had ever flagged it is worse than a miss. WORD required a
      letter first, so extract_names read "90s_synatm:10" as s_synatm and "808bd"
      as bd -- and bd, hc and sd are all REAL Dirt-Samples folders. The truncation
      did not fail loudly, it resolved to the WRONG bank, and the preload warmed
      that one instead. Measured across the Thursday set before changing anything:
      5 of 17 tracks affected, gaining 808bd (x3), 808cy, 808hc, 808sd and
      90s_synatm, losing the phantom bd, hc and sd. \d* then a mandatory letter, so
      bare numbers and the mini-notation's <8 16> still produce no names.
      
      442 tests pass; the one failure is the pre-existing fold-orbits false positive
      in the parser ratchet (it matches pw-link node names, not Tidal) -- confirmed
      by re-running the ratchet with this change stashed.
      PLN (Algolia) authored
    • gig: two writers re-arm the Ardour FIFO, and the AC has to test both · 7360021e
      perf-audio:565 does the same chrt -f -p 80 on Ardour that parvagues-protect
      does, plus 573 for every child at 79, and perf-watch reasserts it on any
      drift by itself -- the Bridge journal caught it doing exactly that at
      09:20:15 on Sep 23. So an AC that only tests the pv-protect edit passes at
      home and fails at the venue.
      
      Also marks the epistemic line, because the next reader will act on this
      hours before a gig: the xrun counts and their attribution are measured, the
      kill mechanism is consistent with every measurement but was never
      reproduced, and it cannot be reproduced on demand -- it needs one >200ms
      uninterrupted GUI burst. What makes the xrun half plausible is that
      sched_rt_runtime_us sits at the default 950000/1000000, so RT tasks passing
      95% of a period get every RT task on that runqueue throttled for the
      remaining 50ms, which is 2.3 quanta at 1024/48k.
      PLN (Algolia) authored
    • gig: the launch that printed green had already had Ardour killed · b227c7aa
      The desktop icon brought Ardour up at 23:59 and the shell reported it Killed
      ten seconds later, three lines before gig-up said 'gig-up done'. One root
      cause under three complaints:
      
      parvagues-protect's ardour:80 target promotes Ardour's GUI thread to
      SCHED_FIFO/80. Its audio thread already gets RT from the backend the right
      way (AudioEngine 1, FIFO|RESET_ON_FORK at 83, above the GUI), so the
      promotion buys nothing and costs two things. RLIMIT_RTTIME is inherited and
      differs by launcher -- gnome-shell hands children 200000us, a systemd user
      unit hands them unlimited -- so an Ardour started from the icon carries a
      200ms realtime budget, and loading the session blows it: SIGXCPU then
      SIGKILL, with no kernel log line, which is why the journal looked clean.
      And a GUI thread at 40-85% of a core on a realtime policy misses deadlines:
      xrun_by.ardour went 0->46 in six minutes, and it is chronic, not new --
      5172, 4852, 1114 and 987 ardour xruns in the Sep 10/18/20/22 sessions.
      
      d1 alone on the speakers was never a second bug; it is what WirePlumber
      does with SuperCollider:out_1/2 when there is no Ardour to route through.
      
      Also: the DJF overlay's third row was an empty string, which is a row paying
      no rent. It now carries travel from the bypass detent as a signed percent,
      each side normalised against its own throw so 100% means end stop. The
      cutoff stays on row 2 -- row 3 is the bezel row.
      
      Findings in TODO_GIG.md with Thursday reordered; story in armada/tasks/046.
      PLN (Algolia) authored
    • docs(gig): the rehearsal did not happen, so Thursday 5-6pm is the only sound test · 8dbc068b
      Recorded as a decision rather than an omission — he saw the venue, could not
      soundcheck, and chose at midnight not to run the arc. The consequence is that
      one hour between the talks ending and doors is the entire empirical test of a
      two-hour set, so the plan protects that hour: every machine step moves before
      5pm, and the hour itself is ears only.
      
      Two orderings that matter and are easy to get backwards: plug the UMC BEFORE
      gig-up, because Ardour restores its own saved ports and does not follow the
      PipeWire default sink; and check the MIDI log is actually capturing during the
      hour, because nine previous sessions were empty while their headers said
      otherwise.
      PLN (Algolia) authored
    • docs(status): the gate went green after the page was built · ccf0c584
      Three things had gone stale within the half hour: the push had landed, so the
      third move is now the live-fader blind spot it was hiding (check-mix reads the
      SAVED session and Ardour OSC is still off, so the Master needs setting and
      saving); the MIDI log is no longer 'binds at last' but capturing, verified
      against real cc records in corpus numbering; and the footer carries the gate's
      own receipt with the gate's own caveat attached, because 0 fail proves the set
      BOOTS correct and says nothing about whether the mixer IS correct.
      PLN (Algolia) authored
    • docs(gig): green gate, and the rehearsal is the whole remainder · 5b0ad50d
      0 fail / 3 warn / 39 ok with the rig warm, 132/132 banks, 0 xruns per hour —
      the 457/hour this morning was the dead NVIDIA sink, not the CPU.
      
      Five learnings archived from the morning, all one shape: a check that asks the
      wrong question reports success. A resolver matching hardware by product name
      outlived the hardware in three files at once; a capability check that asked
      'is the tool installed' wrote midi:true over nine empty sessions; a probe
      gated on a precondition it did not need skipped the entire setup window; an
      availability flag structurally incapable of being false hid a disconnected
      cable for months; and a unit's start time is the only thing that distinguishes
      running code from readable code.
      PLN (Algolia) authored
    • docs(status): five buckets settled, the rehearsal is the only one still open · a606d992
      Rebuilt for Wed 23. Audio, surface and observability all moved this morning:
      the laptop card is back on HiFi and the NVIDIA pin is retired, the LCXL3
      driver's 1077 silent failures are explained and surface-state.json is live for
      the first time, and the MIDI log binds a port at last after nine dead sessions.
      
      The countdown computes from the real clock at every stop, so the page cannot
      go stale into the gig; checked at five instants from today through three days
      after.
      PLN (Algolia) authored
    • gig-log: upgrade to the better MIDI port while bound, not only when it dies · 6ec3ca09
      Answering 'now if i reboot, will gig log be part of any parvagues run?' — yes,
      gig-log is enabled and starts at login. But lcxl3-driver is linked, not
      enabled, by the on-demand policy rig_units.py owns. So at boot this reader
      binds the board's own DAW port and the driver appears later, and the loop
      re-resolved only once aseqdump exited. 24:1 does not exit, so a whole set
      would have been recorded from the raw board.
      
      Translating the numbers is not enough on its own: with the driver up, rows B
      and C are switched to RELATIVE, so the raw port carries encoder DELTAS, and
      writing those down as absolute values would be a confident wrong record — in
      the one file whose entire job is to be trustworthy after the fact.
      
      So a bound reader now asks, between events and at most every 15s, whether
      something better appeared, and rebinds if so. 'Better' is preference_rank
      against the authored order, not a judgement, and an unplaceable name ranks
      last so we can never downgrade into it. A port pinned with --midi-port is
      never second-guessed.
      PLN (Algolia) authored
    • gate: report a unit that is running code older than the code on disk · fd2e5001
      'how can i watch yesterday's build? Every new release should restart bro. that
      was wasted time' — a pass lost to a midiviz restarted the night before, which
      therefore contained none of that night's four commits. systemd said active
      (running) the whole time, correctly: a unit holds whatever the interpreter read
      at ExecStart, and from outside there is nothing to distinguish it from a unit
      on current code.
      
      Comparing the unit's ActiveEnterTimestamp to its ExecStart file's mtime is the
      only honest test, and it is deliberately not a git comparison: the question is
      'is the process executing what I can read', so an uncommitted edit counts.
      
      ADVISE, never BLOCK — a stale lens is not a reason to refuse a launch. --fix
      restarts only the inaudible login units; SuperDirt and the two painters are
      reported and left alone, since gig-up's generation-aware chooser owns when
      those cycle.
      PLN (Algolia) authored
    • gig-log: the MIDI leg had never once bound a port · f67cf28e
      find_seq_port defaulted to the literal string 'Launch Control XL' — the
      ORIGINAL board's product name. The LCXL3 advertises itself as 'LCXL3 1', so
      the resolver matched nothing from the day the hardware changed. All nine
      recorded sessions from 2026-09-06 onward contain zero cc records, and every
      one of their headers says "midi": true, because available() only asks whether
      aseqdump is installed: the reader existed, reported itself on, and bound
      nothing. run() re-resolved every 5s exactly as designed, forever, against a
      name that could not match.
      
      Now it walks the same authored preference midimon and midiviz use, translated
      port first, because corpus numbering is the only numbering a log is worth
      mining in. A night that falls through to the board's own DAW port is
      translated through lcxl_grid.V3_TO_V2 on the way into the window, so the log
      is always in corpus numbers and a reader months later never has to know which
      port that night happened to bind — and an 'mbind' record now states the port,
      the name and whether translation was applied, since the nine silent sessions
      happened precisely because nothing recorded that the answer was none.
      
      Coalescing is unchanged and keeps v0/v1/lo/hi/n, which is what makes 'minute
      23:30 the bass is too saturated' answerable from the file.
      
      Also adds tools/check-stale-units.py: a unit running three-day-old code is
      indistinguishable from a healthy one from outside, and comparing its start
      time to its ExecStart file's mtime is the only honest test.
      PLN (Algolia) authored
    • audio: a default sink with no receiver is now one line, not a morning · 7b33c2a3
      PLN on the NVIDIA card: 'it NEVER served me, 100% of the time'. He is right,
      and the reason it kept getting chosen was a saved pin to pro-audio — a profile
      whose 'available' flag is unconditionally yes, because it maps raw PCMs and
      asks no questions about cables. So four sinks reported perfectly healthy while
      every ELD on the card read monitor_present 0 and every connector read
      disconnected. Sound was not muted or misrouted, it was converted and discarded.
      
      The pin is retired (that card is now off, and WirePlumber's own findBestProfile
      will not go back to pro-audio by name). The check that would have said so in
      one line now exists, keyed to the ELD rather than to the profile's own opinion.
      
      Narrow on purpose: it accuses only a sink that leaves the box over a video
      cable, reusing fold-orbits' predicate, because the sof card carries Speaker AND
      an HDMI output — a check keyed on the CARD would have accused the laptop
      speakers while sound was coming out of them.
      
      The first resolver called realpath() on /proc/asound/cardN, which is a plain
      directory, matched nothing, and made the detector dead code that reported
      success. A test now asserts it reads sysfs.
      PLN (Algolia) authored
    • midiviz: only the DAW dialect may be translated, and the pip still warns on both · 73bb18f1
      Shipping the translation on raw_port was too wide. That flag means 'the picture
      comes from the board, not the driver', which is true in DAW mode AND in custom
      mode -- but only DAW mode has a fixed, known numbering we may rewrite. Custom
      mode speaks whatever the user assigned, so translating it would have been a
      second wrong picture with the opposite sign.
      
      Not hypothetical: midiviz's reader was on 24:0 'LCXL3 1 MIDI In' at the moment
      this was written (its aseqdump child, PPID 7049), i.e. exactly the port the
      wide rule would have mistranslated.
      
      So two flags for two questions. raw_port keeps the warning pip on any raw LCXL3
      read; v3_dialect authorises the rewrite and only DAW satisfies it.
      PLN (Algolia) authored
    • perf: one owner for the CPU knobs — the Bridge drives thermal-mode, the watcher is gone · cfda4841
      The Bridge's perf.py shelled out to its own root script (perf-audio) and ran a
      watcher that reasserted its last choice every 30 s, on resume and on every
      charger edge. gig-up armed it with 'standard' = --optimize = governor
      performance, which pins EPP and runs every idle wakeup at 4-5 GHz. On
      2026-09-23 it undid a Silent click from the GNOME panel within 31 s and held
      the package at 96-100 C at ~5 % load all morning.
      
      - MODES now name thermal-mode-apply arguments (silent, performance); set_mode
        runs the exact-args sudoers form; detect_mode reads /etc/thermal-mode.conf.
      - run_watcher/reconcile/desired-file removed: thermal-mode-reassert.service
        already re-applies the persisted mode on resume and charger events.
      - gig-up asserts thermal-mode performance (turbo on, powersave governor, EPP
        balance_performance, RT audio) instead of demanding governor=performance.
      - rig-doctor and gig-preflight point at thermal-mode-apply, not perf-audio.
      PLN (Algolia) authored
    • audio: the default sink is an HDMI port with no monitor on it · e534a059
      Measured, not inferred: every ELD on the NVIDIA card reads monitor_present 0
      and every one of its DP connectors is disconnected — the connected display is
      on the Intel iGPU. The default sink is therefore a transmitter with no
      receiver, which is why 'no audio card' is an accurate description rather than
      a preference complaint.
      
      Records what it does and does not threaten. The gig's own path is the UMC202HD,
      which is unpinned and currently unplugged, so it is untouched. What this blocks
      is TODAY's rehearsal, whose monitor route is Jack Out on the card that is off —
      and that rehearsal is the last unrun gate.
      PLN (Algolia) authored
    • gate: stop gating the painter probe on the audio engine · a74949b4
      The one probe that asks whether the LCXL is actually being painted carried
      needs=("scsynth",), so it SKIPPED for the whole of every cold run — which is
      exactly the setup window, when the rig is cold by definition. The painter
      translates hands to CCs and lights LEDs; it has nothing to do with SuperDirt.
      
      It now also reports NRestarts, because a unit retrying forever is invisible
      from outside. StartLimitIntervalSec=0 is deliberate (bind within 5s of a
      replug) and the cost of it is that 1077 consecutive failures look exactly
      like a healthy idle unit. The counter is the only place that shows.
      PLN (Algolia) authored
    • surface: the v3 map belongs to the grid, so any reader of the board is right · 56fa3937
      The translation from the LCXL3's DAW-mode numbering to the corpus's lived
      inside lcxl3-driver.py, which made the driver the only process able to read
      the board correctly. Everything else that fell through to the raw port drew
      v3 indices into v2 cells: row C's knobs lit row B, the faders lit nothing at
      all and rained instead, E5-E8 lit E1-E4. One cause, four symptoms, and a
      picture confident enough that the drawing looked like the bug.
      
      The table is now lcxl_grid.V3_ROWS / V3_TO_V2 and midiviz translates for
      itself whenever it is on the raw port, so the cells and the rain are correct
      whether or not the driver is up. The driver keeps its own copy: it is the
      surface's nervous system and this is the day before a gig. Two tests hold the
      two copies together instead.
      PLN (Algolia) authored
  3. 22 Sep, 2026 10 commits
    • surface state: do not publish a fader nobody has reported, and never autofix a… · c625c25c
      surface state: do not publish a fader nobody has reported, and never autofix a card someone switched off
      
      Two holes the same shape -- a tool stating something it has not observed.
      
      The driver seeds row D and A1-A4 to 0 because those are Ardour's faders and it
      has no readback for them. Publishing that seed would have midiviz draw eight
      faders resting at zero on a mix whose faders are up: the picture lying about
      rest, in the row the layer was built to be honest about. state_write now applies
      the same value_known discrimination paint_cell already used, so an unobserved
      Ardour control is omitted and the cell stays a dot until Ardour echoes a
      position or PLN moves it.
      
      check-audio's 'pinned off' finding is now report-only. It is unreachable today
      (the laptop codec offers nothing better than off), but the calm-evening
      WirePlumber restart that brings its HiFi verb back also makes this branch fire --
      and a --fix acting on it would switch the codec on while skipping the step that
      confirms the DMIC guard matched a real node, reopening the capture path whose
      probe hung the DSP in July. fixable=False is that procedure, in code.
      
      Also: the morning list said Thursday. The first real press of the launcher icon
      is not a gig-day experiment.
      PLN (Algolia) authored
    • midiviz: a pip for 'this is the board, not the driver' · 9f8eab2e
      The window had no way to say it was reading the untranslated stream. With
      lcxl3-driver down there is no ParVagues LCXL3 port, the preference falls through
      to the board's own DAW port, and v3 numbering puts row C's knobs on row B's
      cells and the faders on no cell at all -- a coherent, confident, wrong picture,
      with the port label in the header as the only tell.
      
      Scoped to LCXL3: an original LCXL has no translator in front of it, so its raw
      port IS the right port and a pip there would be a false alarm on a desk that
      works. Set on the rebind tick, which already holds the resolved label, so it
      costs no extra subprocess. Drawn beside the paused and stream-broken pips, no
      words, like everything else in that header.
      PLN (Algolia) authored
    • docs(gig): the launcher had never launched, and Thursday morning's order · bc0aee3b
      Corrects the tooling-pass section's acceptance note: 'window closes silently =
      GO' described the bug, not the check. Adds the morning sequence (start the
      surface driver, press the icon once, read check-audio, then the 2h arc) and
      three learnings -- the positive-marker rule for any wrapper that closes on
      success, the wrong-port-draws-a-confident-wrong-picture class, and the fence
      that stops a profile repair becoming a silence.
      PLN (Algolia) authored
    • test(gate): the loud-probe assertion was reading the weather, not the table · bc721c6e
      It asked plan(audio=True) and then asserted a property of the probe table. The
      loud probe needs=('scsynth',), so the test passed with the rig up and failed
      with it down -- a verdict that depends on the box is the one thing a gate's own
      suite may not have. force=True asks the table the question the docstring says
      it is asking.
      PLN (Algolia) authored
    • midiviz: the whole surface has a resting position, and the rain says what to type · 64c29a05
      Two things PLN asked for, and one correction to how step 3 was planned.
      
      State covers all 48 cells now, not just the three family filters -- but only
      where the event layer cannot speak: a cell whose last event value equals the
      surface value is skipped entirely, track included. That keeps one fact to one
      picture, and what is left is exactly the gap an edge-triggered stream leaves
      behind -- a control untouched since launch, and a control the surface moved
      while this window was not listening. The family filters stay exempt, since a
      left-anchored bar lies about a centre detent whoever draws it.
      
      E needed nothing: the driver already owns the latch, because v3 DAW buttons are
      momentary, so it publishes 127 until the next press. This just draws it, which
      is why it cannot drift out of step with the board.
      
      The dirty-cell repaint the design doc was waiting on was the wrong screw. The
      widget repaints whole frames and its budget is made of drawText calls, not
      fillRects; the layer needed a rule about when NOT to draw, not a cheaper way to
      draw.
      
      And the gutter rains corpus tokens now -- ^42, the exact string a .tidal file
      types -- for controls the grid owns, where before only un-gridded CCs rained at
      all. Rate-gated per control, because the relative rows send a message per
      encoder click and an ungated sweep fills all 32 heads with one repeated word.
      The lanes are computed from the text width, so a three-glyph token in a
      five-glyph gutter cannot be drawn off the edge.
      PLN (Algolia) authored
    • launcher: a clean exit is not a launch, so stop letting one look like the other · 89d80cc7
      The only thing ~/.cache/parvagues/gig-up.log has ever contained is gig-up's own
      usage text. --help prints the options and exits 0, the window script saw a zero
      and closed itself, and the result was indistinguishable from a successful boot:
      a terminal that flashes and vanishes. Nothing had been started, which is how it
      was noticed at all -- the LCXL stayed dark, because no launch ever reached the
      paint step.
      
      The exit code cannot carry this; --help exiting 0 is correct. So the fence is
      whether the launcher reached its own last line: every real path prints
      'gig-up done', while usage, an unknown option and any early exit do not. On a
      zero with no marker the window now holds, names the symptom (no SuperDirt, no
      Ardour, no colours on the LCXL) and exits 2.
      
      --quiet is exempt by construction: ok() is a no-op there, because that mode
      exists so the Bridge captures only WARN/FAIL. The test suite holds both ends of
      the coupling, so rewording gig-up's last line fails loudly instead of turning
      every real launch into a held window.
      PLN (Algolia) authored
    • audio: a checker for the profile pin, and a fix that refuses to be a silence · 6b23c9fb
      Adds tools/check-audio.py and wires it into the gate as 'audio profiles',
      advisory: a card pinned to Pro Audio is a preference, not a reason to refuse a
      launch, but it IS the reason pavucontrol offers no Speaker / Headphones / HDMI.
      
      The ranking is a faithful port of WirePlumber's own findBestProfile, including
      its refusal to ever select pro-audio -- without that, a card someone pinned
      there would read as correctly set.
      
      --fix counts the sinks it would leave behind and stops at zero. On this box the
      textbook repair (clear the pin, take WirePlumber's pick) would have switched off
      the only four sinks present, since the laptop codec is pinned off and the USB
      interface is unplugged. A fix that trades a wrong profile for no audio is not a
      fix, and the fence has a test with that case in it.
      
      Also records a finding the write-up turned up: the config fragment in
      ~/.config/wireplumber/wireplumber.conf.d/ is 0.5 format on an 0.4.17 install, so
      neither it nor the DMIC guard written alongside it in July has ever been in
      force. The tool flags that class -- a file that looks exactly like protection
      and does nothing.
      PLN (Algolia) authored
    • docs(audio): only Pro Audio is a saved pin, and the laptop card lost its HiFi profile · 7808c63f
      Two causes, one symptom. WirePlumber's own policy script never picks pro-audio
      (it skips it by name), so both cards sitting on it can only come from the saved
      default-profile file -- which is consulted before the automatic pick and is
      therefore why it never self-corrects.
      
      The laptop codec is a different story: ALSA UCM still lists its HiFi verb and
      the kernel brought Jack Out / Speaker / HDMI up clean at boot, so the profile
      list PipeWire built at enumeration is what is short. That needs a device
      re-probe, not a setting.
      
      Names the risk before the fix: pro-audio is the profile that opens the capture
      PCM which hung the DSP in July, and the node guard written then is no longer on
      disk.
      PLN (Algolia) authored
    • midiviz: test the state layer's PIXELS, and name the zero-zone guess · 80f2115a
      Two gaps in the commit before this one.
      
      `--selftest` writes no state file, so `_paint_state` returned at its first line
      through all three themes and all four window shapes. The previous message cited
      that PASS next to the new layer, which reads as coverage it did not have: the
      drawing had executed in exactly one place, a throwaway script, which is how a
      whole layer stays green while never running. There is now a differential render
      test — paint offscreen, read the pixels back, assert WHICH SIDE of the detent
      filled: left for a low filter, right for a high one, neither for one at rest,
      and nothing at all in the five columns this slice does not own.
      
      Getting that test right took three attempts, all three failing on the widget
      rather than the layer. Asserting two x positions in a cell are equal is never
      safe here — the CRT texture draws vertical lines every few pixels — and the
      strip's TRACK spans the full cell, so "this pixel changed" is true across the
      whole row regardless of value. The version that holds blits the texture off (the
      one overlay not under test) and compares flanks, plus asserts low and high are
      mirror images of each other rather than merely different.
      
      Second: the zero zone was a bare `abs(val - 64) <= 2`. It is now ZERO_ZONE, at
      module scope, with the reason it is a guess written next to it — rows B/C are
      relative and the driver integrates with a clamp (`max(0, min(127, cur + d))`),
      so a knob swept to an end and brought back loses every click it spent against
      the clamp and settles NEAR 64 rather than on it. Too tight shows a sliver of LOW
      where his hand says ZERO; too loose hides a real nudge off centre, which is the
      worse failure because the layer exists to stop the picture lying about rest.
      Narrow to start, and it wants a hand on the board rather than another argument.
      
      Suites 529 passed, 2 failed (both pre-existing).
      PLN (Algolia) authored
    • docs: the receipt for everything gig prep deliberately parked · b9d83bcd
      Eleven items, each with the acceptance test it is waiting on and the reason it
      was left rather than done. Ordered by what it costs to carry, not by effort.
      
      The two with a real deadline are at the top: retiring tools/gig-up.sh, which is
      only still present because nothing is deleted before its replacement has run for
      real AND because it is the transcription check's reference; and folding
      gig_gate's scaffolding into gig-preflight, which was refused this week for
      buying tidiness rather than behaviour.
      
      Written now rather than after the gig because the reasons are what rot. The list
      of files is recoverable from git; 'we left this because a corpus rewrite with
      Pulsar open destroyed a rehearsal edit once' is not.
      PLN (Algolia) authored