- 23 Sep, 2026 14 commits
-
-
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 -
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 -
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 -
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 -
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 -
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 -
'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 -
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 -
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 -
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 -
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 -
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 -
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 -
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
-
- 22 Sep, 2026 26 commits
-
-
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 -
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 -
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 -
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 -
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 -
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 -
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 -
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 -
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 -
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 -
"as it maintains a state, it should be 'not all same default state' when you look at it between two movements? like, the DJFs are at zero, in the Low, or in the High, and this doesnt really grasp from viewing the midimon" Correct, and it was not a theming problem. This module draws EVENTS, and makes recency a visual axis on purpose: an event's identity is its position, its value is a bar, its recency is brightness. So between two movements every cell decays to the same floor tint, and a filter resting at zero is the same picture as one parked at the top. No palette fixes that — an event stream carries changes, and rest is the absence of a change. A viewer cannot derive it either. The LCXL3 has no readback, and rows B/C run relative (the surface sends v - 64), so the hardware holds no absolute position. Exactly one process integrates those deltas and therefore owns the value, and it is not the viewer: lcxl3-driver's `self.values` is not a cache of something else, it is the only copy. So: two channels, because two physics. Events stay edge-timed off the wire; state is a level, published last-value-wins. driver `~/.cache/parvagues/surface-state.json`, temp file + os.replace, on the existing loop at 10 Hz, written ONLY when the snapshot changed. Every failure swallowed: this is a courtesy to viewers, and a read-only /home must never stop the faders reaching Ardour. midiviz one stat() per frame; an unchanged `seq` means zero parsing and zero repainting, which is the "pay once" property asked for. Absent, unreadable or wrong-shaped file all mean "no state", and the window draws exactly what it drew before this layer existed. Deliberately not a socket or a broker: ~32 integers, idempotent, no history worth queueing. A socket buys a connection, backpressure and a dead-consumer failure mode in exchange for nothing. `cat` is the debugger. First slice is the three family filters, drawn in the cells they physically occupy (C1/C2/C3) rather than a strip of its own — the layout does not move, and this module's founding idea is that identity IS position, so state belongs where the events about it are, which is where his hands are. ZERO IS THE CENTRE DETENT, 64, not 0: gDJF is an lpf and an hpf section both wide open at ch=0.5, so centre is bypass and both ends filter. The event bar is left-anchored, which is exactly the lie — a filter at rest draws a half-width bar that reads as "half of something". The state strip sits above it, centre-anchored, with the detent drawn: a bar growing left of centre is LOW, right is HIGH, bare detent is ZERO. Static at rest, no decay, no pulse — being legible when nothing has moved for a minute is the entire point. Found by looking at it rather than by reasoning: the first version drew into the bottom of the cell, where the event bar already lives, and C1-C3 rendered as mud. Checked at 900x560 and 1600x1000, and 3 units tall beat 2 in sunlight. 16 tests, the load-bearing one being a real round trip: the real state_write and the real SurfaceState.poll over one temp path, so a change to either side that breaks the other fails here and not at a gig. That round trip caught a live bug — `null` is valid JSON of the wrong shape, and the AttributeError from the first .get escaped the handler and would have taken the window down. Suites 526 passed, 2 failed (both pre-existing); --selftest PASS across three themes and four shapes. Takes effect on the next driver restart; the running driver was not touched.PLN (Algolia) authored -
RIG gains the tooling pass and STAGE gains the daylight-readable monitor. The percentages do not move to 100: the launcher's new blocks have never executed, so 'never pressed' is the honest mood line and the press is a YOU row. Dropped 'Restart SC so 132 banks warm' as a separate item — the launcher now boots SuperDirt as parvagues-sc.service, so that IS the launch. Two rows for one action reads as two actions, and the next-3-moves list says it once, with what it buys: starts SC, warms 132 banks, runs 39 checks.
PLN (Algolia) authored -
The archived note said install-protect.sh was never run and therefore "the OOM-protection daemon has never protected anything", reasoning that scsynth and sclang read ok only because they were already at the target so no write was attempted. That inference was sound and the premise was wrong. The unit is installed (Sep 6 23:30) and active since Sep 20, the installed copy carries CAP_DAC_OVERRIDE in both CapabilityBoundingSet and AmbientCapabilities, and the journal holds 21 protections with 0 permission errors over 7 days — each on a process that started at the default, which is the write the note said never happened: sclang[755981] oom:200->-1000, today at 15:04. Matters now because gig prep calls for restarting parvagues-sc to warm the banks, and the unit has no OOMScoreAdjust of its own: this daemon is the only thing between scsynth and DefaultOOMScoreAdjust=200. It re-protects within seconds, so the restart is safe.
PLN (Algolia) authored -
Three complaints from the rehearsal, one root cause each. "unusable in the sun in light mode" — themes are now top-level rows in the right-click menu with painted swatches and a ● / ○ marking the live one, plus a `MIDI ▸` submenu in the tray carrying the same three. Sun is listed first, because the moment you need it is the moment you cannot read the menu to find it. The swatches paint from the Theme ramps rather than the live LUT, so the menu shows what you would get, not what you have. "only drag -> grab atm" — 7px edges and 20px corners hand off to startSystemResize, which is what a tiling WM wants; a manual setGeometry path covers compositors that refuse it, keeping the opposite edge pinned so the window grows where you pull. minimumSize 240x150, corner ticks brighten on hover, and leaveEvent puts them back (without it they stay lit forever). "have it squeeze and expand properly as we make it taller or wider" — the old `_apply_scale` resized the window, so scaling and being resized fought each other. Now `_fit` = min(w/ref, h/ref) clamped to [0.35, 2.4], multiplied by the density knob, and `resizeEvent` re-measures rather than rescaling: idempotent, keyed on the pixel sizes it actually produces, and no resize() inside a resize handler. The reference is the birth size read BACK off the widget, so fit == 1.0 at birth and the look at 1.0x is byte-for-byte what it was. Key `0` resets. FIT_MIN went 0.55 -> 0.35 because 0.55 crushed the layout at 2.4x on a 240x150 tile and printed rows on top of each other — found by screenshotting six window shapes, not by reasoning about it. Tray theme switching is stop -> forget -> launch, and the forget is load-bearing: two relaunches inside SPAWN_GRACE would otherwise close the lens and then refuse to reopen it. It returns early if the stop is not confirmed, so a wedged monitor is never followed by a second one. Variants live in BY_KEY only, never in LAUNCHERS or snapshot(), so they cannot be converged into existence behind your back. Verified on screen (grabs, scaling, colours) before this landed. 75 tests over the three modules; suites 510 passed, 2 failed, both pre-existing. `--theme` is still one-shot, so a converge brings dark back — inherited, not fixed here.
PLN (Algolia) authored -
PLN (Algolia) authored
-
Unifying the two launchers surfaced a divergence neither had been measured against. The root script span raw `sclang` under setsid; the Bridge's RIG UP went through the other gig-up, which started parvagues-sc.service. Repointing the button at one launcher would therefore have taken it OFF systemd. Raw sclang loses, all at once: LimitMEMLOCK=infinity and LimitNICE=-20 (which scsynth inherits), the 10-pipewire-jack.conf drop-in this script hand-rolls as \${wrap}, StandardOutput=journal + SyslogIdentifier (so the post window is parseable, and so the `SuperDirt warmed` probe — which keys on the unit's ActiveEnterTimestamp — can ever say yes), Restart=on-failure bounded 3-in-300s, and the OOM protection: systemd's user manager ships DefaultOOMScoreAdjust=200, and an unprotected scsynth was terminated for memory on 2026-08-15 after four crashes in one afternoon. None of this is new reasoning — it is parvagues-sc.service's own header, written 2026-07-27: "SuperDirt as a managed unit rather than a thing running in a terminal somewhere". This launch path simply predates it, and the divergence was invisible because each launcher was the only one anyone watched. Keeps the 2026-08-01 distinction that cost an evening: a unit that is ACTIVE while scsynth is DEAD needs a restart, because start is a no-op on an active unit. Falls back to raw sclang, loudly, if the unit is not installed.PLN (Algolia) authored -
The gate call hardcodes python3.12 (mido is installed for 3.12 only, and a gate that quietly ran on 3.11 would fail the MIDI probes for the wrong reason). But propagating rc=127 from a missing interpreter would hold a terminal open over the stage for a reason that has nothing to do with the rig — which is already up by the time this runs. Report it, name the fix, let the launch stand.
PLN (Algolia) authored -
Two verbs instead of three names. gig-up.sh DOES; tools/check-gig.py PROVES. The collision was not a fork. `gig-up.sh` (root, 555 lines) launches the rig; `tools/gig-up.sh` (620) proved the set; `tools/gig-preflight.py` (522) already proved the machine. Both gates took --converge and --quiet, so the two paths looked interchangeable and were not: the preload gate sat only on the path not pressed, the readiness report only on the path the Bridge does not call. The gate is now a TABLE, not control flow: 23 probes, each carrying its command, its fix, its severity and its layer. The bash pipelines are kept verbatim — each was written the day a specific failure was found and the pipeline IS the finding. gig-preflight already emitted {check,state,detail,fix} over OK/WARN/FAIL, so the machine layer joined through its own --json with no rewrite. 23 + 16 = 39 checks in one report, one exit code. Measured: 16.5s full, 3.4s --fast, 0.3s machine. Found while wiring it, each verified against the failing leg: 1. The launcher rewrote preload.scd from setlist_opal2026.txt on EVERY launch — the Sept 6 set. Measured 132 banks -> 45, leaving 41 of Thursday's banks to be read off disk mid-set: the failure check-preload's own header calls "debuted as a crackle at the venue". The set is now named ONCE at the top of the launcher and read by both the plan generator and the gate. Generation also converges instead of clobbering — a plan that already covers the set is left alone, because a rewrite can only narrow it. 2. `LCXL present` reported the desk PRESENT with the board unplugged: it grepped all of `aseqdump -l`, whose third column is the PORT name, and lcxl3-driver publishes a virtual output called "ParVagues LCXL3". The gate was matching our own driver. Now anchored on the client name (old: present, new: absent, board off the bus). 3. `setlist compiles` proved backlog.md's 15 tracks, not the 17 being played. --setlist scopes the compile and preload probes to one gig's file, via setlist_samples.read_setlist — never a fresh regex. 4. `SC -> Ardour` was gated on scsynth, so it printed NO-GO on a healthy --headphones rig where there is legitimately no Ardour. Preconditions are per-probe now, and an unmet one is SKIP, never FAIL. Also: gig-preflight's `midi surface` warned that mk3 LEDs were unimplemented, untrue since lcxl3-driver took over painting. Both generations exercised against a recorded graph. And `gig-log preflight` (the armed-global check — gMask sat at 127 for 74 minutes while every cold check was green) lived only in the launcher's report where it could not block. It is a blocking probe now. Enforcement, because a rule in a docstring is not a rule: tools/tests/test_check_gig.py, 39 tests. No probe may mutate anything (read over argv, never the source text — a fix hint is SUPPOSED to say "systemctl restart"); the fence has its own detector; no check may be lost from the port while the old script exists; the launcher may not grow assertions of its own and must end by calling the gate. Suites 510 passed, 2 failed — both pre-existing (fold-orbits' PipeWire regexes tripping the Tidal-parser ratchet, test_gearbox's dash fallback). tools/gig-up.sh is kept, unrun, with a superseded banner: nothing is deleted until its replacement has run on a real launch, and it is the transcription check's reference until then.PLN (Algolia) authored -
The obvious reading is that the two gig-up scripts are forks that drifted. They are not. gig-up.sh is a LAUNCHER (start things in order) and tools/gig-up.sh is a GATE (14 assertions over the repo and the saved session) — two different tools wearing one name. And a third, tools/gig-preflight.py, is already the gate over the live machine; its own docstring says '--quiet (for gig-up.sh)', so the gate was always meant to be called by the launcher and never was. So the merge is a separation, not a diff: gig-up does, check-gig proves, gig-up-window owns the terminal. Three entry points become two, each answering one question, and the launcher ends by calling the gate — which makes today's failure (a gate that existed but was unreachable from the path actually pressed) structurally impossible. Six phases, each independently revertable, and phase 0 is a harness because neither script nor the preflight has any test coverage today. Nothing is deleted until the phase replacing it has run on a real launch: a bad gig-up at a venue is not recoverable under pressure.
PLN (Algolia) authored -
gig-up restored the fader baseline on its own, so the bucket that was 20% and flagged as the critical path is now 85% with three ticks: baseline restored, all twelve orbits reaching master, Master at -0.5 dB instead of -30. What is left there is two judgements, not two repairs. The ribbon moves to REHEARSAL, because the full 2h run really is the only thing left that counts. Moves become: restart SC so the 132 banks warm, run the arc with Ardour recording, A/B the two crush buses that stopped trading. New storage key so ticks against the old moves cannot read as done.
PLN (Algolia) authored -
fix(launch): the guard that could not see Ardour, the gate that was in the other script, the window that never closed Three things PLN hit in one launch. ARDOUR RELAUNCHED OVER A RUNNING ONE. The guard was `pgrep -f 'ardour-[0-9]'`, and `/usr/bin/ardour` is a BASH WRAPPER that execs the real binary out of /usr/lib/ardour8 — so for the first second of a launch the process is `bash /usr/bin/ardour …` and that pattern misses it entirely. Re-run gig-up inside that window and the second Ardour fights the first for the session lock. Now matches the wrapper AND the exec'd name, both bracket-tricked so pgrep cannot match the command line asking the question. Verified against the live box: neither pattern catches tidal-ardour-autoroute, which runs permanently and would otherwise have made the guard always true. THE PRELOAD GATE WAS IN THE SCRIPT NOBODY LAUNCHES. There are two gig-up scripts with separate histories: this one (528 lines, what parvagues.desktop runs) has the readiness gate, cut-gpu, the headphones fold and the fader restore; tools/gig-up.sh (620 lines, what the Bridge's RIG UP runs) has check-preload, --bt-off and check-boot. Neither is a superset, and both accept --converge and --quiet, which is exactly how it stayed invisible. So the path PLN actually presses never checked whether the set's banks are warmed — the failure check-preload.sh's own header calls "debuted as a crackle at the venue". Added here as REPORT ONLY: `--fix` regenerates a load-bearing boot file, and doing that at a venue is the landmine rather than the cure. Output is filtered to the verdict and any MISSING banks; the 66-name "warmed on purpose" roster is dropped, because at a venue the useful output is one line long. THE TERMINAL STAYED OPEN AFTER A CLEAN LAUNCH. `kitty --hold` was a real fence — GNOME's Terminal=true handler is not guaranteed to exist and the boot log is what you want when a launch goes sideways — but holding on SUCCESS left a dead window parked over the rig all night, and on stage every window that is not the music is in the way. tools/gig-up-window.sh tees the log to ~/.cache/parvagues/gig-up.log every time, keeps the previous one as .log.1, and holds the window only on a non-zero exit. A file beats a held window on both counts: it survives the window and it survives success. It is a separate file rather than a longer Exec= line because the desktop spec reserves `" $ & ; | < > ( )` inside Exec, so a pipeline with a conditional read has to be escaped twice and breaks silently on the third edit. Merging the two gig-up scripts is the right fix and is NOT a two-days-out job — design landing separately.
PLN (Algolia) authored -
TWO THINGS, AND THE FIRST ONE IS THE LESSON. wap.tidal never got its d7 -> d6. The compaction wrote it, then Pulsar saved the file from a buffer loaded before the compaction ran and put the old declaration back. My "0 moves left" re-plan was taken BEFORE that save, so the verification was true when it ran and false ten minutes later. Same shape as the third-pass bug, from the other direction: a conformance check is a measurement of a moment, and a moment with an editor open is not a state. PLN has now closed Pulsar, so both plans report 0 moves across all 703 — and this time nothing else is writing. Second: the orphan-orbits HAND_MEASURED table had gone stale, which is exactly what it is for. Five of its ten tracks moved d7 -> d6 (bombe_dj, wap, piment_bresilien, mafia_sans_serif, the_revolution_will_be_sampled); the other five already declared both, so compaction had nothing to close and they are unchanged — which is a check on the tool, not a coincidence. Re-measured with `grep -hoE '^d[0-9]+' | sort -nu`, deliberately NOT with orbits_of(), because the block's own docstring warns that a hand measurement quoting the thing it checks is a tautology. And the original ear report changed orbits without changing meaning. PLN heard crimewave survive vague_de_crime -> bombe_dj; bombe_dj now declares d6, which COVERS the orbit he heard and exposes the one below it. vague_de_crime runs crimewave on both d6 and d7, so the sound is the same and only the carrier moved. The test now asserts {7} and says why — the ghost is a property of the PAIR, which is the whole thesis of the tool. Suites: 456 passed, 2 failed, both pre-existing and unrelated (the fold-orbits tidal-parser ratchet, and test_gearbox's dash fallback from 6fb0f98d).PLN (Algolia) authored -
A conformance check run before the step that breaks conformance proves nothing — the compaction moved 150 orbits and left 202 of their controls behind, and the tool said '0 move' about a file that needed four. Also records the answer to PLN's own d9 note: its attack was riding d12's Ardour-learned level knob, and its mask shares d6's gate with no legal alternative, because d9 has no button slot in the grid at all.
PLN (Algolia) authored -
Five buckets: SET 100%, RIG 95%, SOUND 20% and flagged as the critical path, STAGE 15%, REHEARSAL 40%. Six items carry a YOU badge because six of them are PLN's hands and nothing else. SOUND leads because it is the only thing that can silence the show: seven orbit faders sit at -inf in the saved session, and Ardour's meters are post-fader so that failure is indistinguishable from Tidal emitting nothing. The card says so in one line rather than trusting anyone to remember it. Every counter computes from the clock — a date-math suite asserts the header and all three stops across day-before, day-of and day-after, including "gig played" once it is behind us. Checked at 1440 and 420, and with localStorage throwing: the page still renders, which is the only state a status page must never fail in. New storage key, so ticks from the old move list cannot show as done against new moves.
PLN (Algolia) authored -
The compaction renames a `dN` declaration; it deliberately does not touch that orbit's knobs and buttons. So the moment 150 orbits moved, their controls were sitting on the column of an orbit that no longer existed there — d6 answering to column 7 in 84 files. The two tools are one job in two passes and only the second pass closes it. Found by accident, which is the part worth recording: ete_a_mauerpark reported "0 move" right after the compaction, then reported 4 the moment something else made me re-plan it. A conformance check run once, before the step that breaks conformance, proves nothing. 202 moves, 84 files, 0 clashes. Re-plan now reports 0 across all 703. Button conformance 93.3%, no CC gained inside the Ardour-learned or gF ranges, and the third full silent-eval sweep diffed per-file against the baseline: zero verdict changes. The seventeen-track set re-validated on its own afterwards — all 17 OK, pvlint 0 errors. Also in here, from reading ete_a_mauerpark's d9 to answer PLN's "[confirm d9 effects]" note: its attack was riding `^16`, which is A4 — d12's LEVEL knob, MIDI-learned to an Ardour track gain. One knob moved an Ardour fader AND d9's attack, the CC77-goes-to-silence class the driver header warns about. Moved to `^20`, idle in this track. Its `mask` on `^58` is d6's own gate and d6 IS declared here, so one button drives two orbits — left alone on purpose: d9 has no button slot in the grid at all, so that is gSel work, not a rename.
PLN (Algolia) authored -
Warm counts move with the plan, so the cards were understating coverage for the seven banks the new set just added.
PLN (Algolia) authored
-