Commit 7a2b2886 by PLN (Algolia)

Pre-compact cleanup: CHANGELOG Sprint 7 + archive entry + memory

Sprint 7 covers the morning: mi-UGens s.sync, the preload pointed at what gets
played, three stacked bugs in its freshness check, 198 AppleDouble deletions,
s.latency 0.3->0.2, and the wireplumber start-limit outage.

Archive entry carries nine learnings, the load-bearing ones being that pgrep
costs 53-59ms a call here while the real trick is the cheap prefilter (the regex
on every comm is WORSE than the pgrep it replaced); that case-in-a-variable does
no alternation; that start-limit-hit is this box's signature trap and retrying is
the one action that cannot work; and that a valid measurement can be silent,
because mute sits downstream of all DSP while a null sink lies.

Memory: verify-the-leg-that-breaks gains the three 2026-09-07 instances plus two
rules (ask why two numbers disagree; a tool owns the commands it prints).
rig-state records the reset-failed lesson and the BT-by-name detail.
parent 9edfd743
...@@ -4,6 +4,69 @@ Sprint entries, newest first. Player-facing: what changed about *playing*, not ...@@ -4,6 +4,69 @@ Sprint entries, newest first. Player-facing: what changed about *playing*, not
about the code. Task IDs reference the L'Armada board; `n/a` where the work was about the code. Task IDs reference the L'Armada board; `n/a` where the work was
unplanned (which, on a gig night, is most of it). unplanned (which, on a gig night, is most of it).
## Sprint 7 — 2026-09-07 (morning) · the rig stops lying about itself
Five separate things were reporting green while doing nothing. None of them were
found by reading code; every one came from making the rig say out loud what it
was actually doing, then listening to the part that did not match.
### Fixed
- **Every orbit's MiVerb / MiClouds / MiRipples send was a dead node** (n/a).
84 `SynthDef global_mi_verb2 not found` lines per boot — 3 defs × 14 orbits ×
2 — so `# verbwet`, `# cloudswet`, `# ripplesreson` did nothing, on any orbit,
all session, silently. mi-UGens was installed perfectly and the SynthDefs were
written correctly; `.add` is asynchronous and the next statement asked the
server for those names. One `s.sync`. 84 → 0.
- **The preload warms what gets PLAYED** (n/a) — 60 banks → 124, 16 tracks → 39,
computed from the canonical gig records rather than a hand-kept file. 51 banks
were one first-play from a disk read.
- **The preload freshness check is trustable** (n/a) — three stacked bugs: a
headline that sent you to regenerate when regenerating could not help; a
comparison of bank NAMES that never looked at counts (so a new pack dropped
into an existing bank was invisible under a green check); and a case-blind
regex that truncated `rampleA0` to `rample` and collapsed the whole `vocal*`
family, which is why the checker said 111 while SuperDirt loaded 124.
- **198 AppleDouble resource forks deleted** (n/a), on PLN's say-so, every one
confirmed by `file` magic rather than by name. They failed to load AND
occupied sample indices in four `rhadamanthe_*` banks.
- **`s.latency` 0.3 → 0.2** (n/a). The comment said "increase this if you get
late messages" and the rig had 136 — but they came in two bursts with values
decaying 8.1 s → 0.65 s, which is a backlog draining, and both sat against
restarts. The reason to pad it was a misdiagnosis of a bug now fixed.
- **All audio came back** (n/a). Reported as dead media keys and a silent
Bluetooth headset; actually `wireplumber` in `failed (start-limit-hit)` after
five hand restarts in two minutes, leaving `Dummy Output` as the only sink.
The headset itself was merely muted at 20% on an already-correct `a2dp-sink`
profile.
### Added
- **`tools/gen_setlist.py`** (n/a) — computes what was played in the last N
months from `lives/*/tracks.json` plus the ear-derived `judge_specs`, so the
preload's input cannot go stale. Ear lists carry no gig date, so it includes
them and SAYS SO rather than dating them by guesswork.
- **`rig-doctor` gained three checks that ask about function** (n/a):
SuperDirt boot errors (scoped to the current boot, names the missing
SynthDefs), the mi-UGens plugin/class inventory, and the audio session
(wireplumber up AND a real `alsa_output`/`bluez_output` in the graph, never
`auto_null`). The last one caught a live regression the first time it ran.
47 → 48 checks, 0 fail.
### Doctrine
- **`start-limit-hit` is this box's signature trap, and the instinct is the one
thing that cannot work.** Three units in one session — `parvagues-sc` twice,
`wireplumber` once — left permanently unstartable, each after someone
restarted them again. "X will not start" means `reset-failed` FIRST.
- **A test must not grep for a word its own good news contains**, and an error
is not a negative: the mute guard read a wireplumber `Translate ID error` as
"unmuted" and aborted a six-minute sweep naming a cause that never happened.
- **Read the exit code of the command, not the pipe.** `check-preload.sh` was
briefly accused of exiting 0 while reporting STALE; the 0 was `tail`'s. Same
family as `grep -c` printing 0 and exiting 1.
- **Advice is part of the tool.** `latency-lens --print-config` handed over
`systemctl --user restart pipewire pipewire-pulse wireplumber` with no
warning, which repeated is exactly the trap above. A tool that prints a
dangerous command owns the consequence.
## Sprint 6 — 2026-09-07 · the crackle that was waiting at the venue ## Sprint 6 — 2026-09-07 · the crackle that was waiting at the venue
Went looking for latency to shave. Found that the thing making the rig miss its Went looking for latency to shave. Found that the thing making the rig miss its
...@@ -28,7 +91,7 @@ generated on this laptop. ...@@ -28,7 +91,7 @@ generated on this laptop.
### Found, not yet fixed ### Found, not yet fixed
- **The plan is now fresh and pointed at the wrong set** (n/a). Re-running the - **The plan is now fresh and pointed at the wrong set** (n/a). Re-running the
identical load after the fix still logged 29 lazy reads — and every one was identical load after the fix still logged 41 lazy reads — and every one was
the `rose` bank, because `rose_rouge` is not in `setlist_opal2026.txt`. Zero the `rose` bank, because `rose_rouge` is not in `setlist_opal2026.txt`. Zero
of the 60 preloaded banks lazy-loaded, so the mechanism is provably working: of the 60 preloaded banks lazy-loaded, so the mechanism is provably working:
it is warming the 16-track OPAL set while the track this box actually plays it is warming the 16-track OPAL set while the track this box actually plays
......
...@@ -7,6 +7,69 @@ snapshot in `board-archive.md`. Newest first. ...@@ -7,6 +7,69 @@ snapshot in `board-archive.md`. Newest first.
--- ---
## n/a — the rig stops lying about itself
**Description.** "Fix parvagues-protect burning 10.7% of a core, then do a pass
on perf and tooling efficiency/footprint." Which turned into: find out why the
box misses its deadlines, and fix everything that was reporting green while
doing nothing.
**Done.**
- `parvagues-protect` 10.96% → 1.42% of a core, 6 forks/tick → **0**. All of the
219 ms was discovery, none protection: three `pgrep -x` at 59 ms each.
- `sc-watchdog` no longer restarts SuperDirt on every clean start. The unit goes
`active` when sclang execs; scsynth appears ~8 s later; the loop acted at 6 s.
Each clean start spent two of systemd's three allowed starts.
- `preload.scd` generated, then pointed at what actually gets played: 60 → 124
banks, 16 → 39 tracks, computed from the gig records by `gen_setlist.py`.
- Three stacked bugs in the preload freshness check; three numbers now agree.
- 84 `SynthDef not found` errors per boot → 0, with one `s.sync`.
- 198 AppleDouble resource forks deleted, confirmed by magic not by name.
- `s.latency` 0.3 → 0.2; `late` is now a counted metric that rejects a quantum.
- All audio restored from `wireplumber: failed (start-limit-hit)`.
- rig-doctor 45 → 48 checks, 0 fail, and three of them now ask about function.
**Learnings.**
1. **`pgrep` is not free.** 53-59 ms per call on this box, because it reads
`cmdline` for every process. One pure-bash pass over `/proc/*/comm` is 21-28
ms and forks nothing. But the win came from the *prefilter*: a `[sSaA]*`
character class rejects 606 of 641 processes before bash's regex engine,
which is the slowest thing in reach — testing the regex on every comm costs
149 ms, **worse than the pgrep it replaced.** Measure the replacement too.
2. **`case "$x" in $pattern)` does not do alternation.** `|` is recognised when
`case` is parsed, so `s*|S*|a*|A*` arriving from a variable is one pattern of
literal pipes and matches nothing. Every part was individually correct and
the scan came back empty. A bracket class survives expansion.
3. **`start-limit-hit` is the trap, and retrying is the one thing that cannot
work.** Three units, one session. `reset-failed` first, always.
4. **The tool's advice is part of the tool.** Printing
`systemctl --user restart pipewire pipewire-pulse wireplumber` without the
start-limit warning is handing over a loaded gun.
5. **A generated artifact stays fresh only via the one path nobody took.**
`preload.scd` was absent because `gig-up.sh` — which regenerates it, and
whose checker exits non-zero correctly — had never been run on this box.
6. **Freshness checks compare what they were told to compare.** Bank NAMES
matched while COUNTS had drifted, and the checker printed `ok` while
SuperDirt's own boot printed `expected 77 files, got 72`.
7. **Ask why two numbers differ instead of assuming one rounds the other.**
111 vs 124 banks was a case-blind regex silently inventing two bank names
that do not exist, noticed only on the third sighting.
8. **A valid measurement can be silent.** Mute sits downstream of all DSP, so a
muted sink still clocks the graph at the quantum — but a null sink is driven
by a software timer and would answer far too optimistically. The constraint
("friend sleeping") forced a better test design, not a worse one.
9. **Test the detector in BOTH directions.** Doing so caught a `WATCH` list
naming the process `scsynth` instead of the node `SuperCollider`, a mute
guard reading an error as a negative, and `re` never having been imported in
`rig-doctor.py`.
**Deps.** The quantum question is still open and now unblocked: last night's
`W/Q 1.30` was disk stalls, the cache is warm, and `latency-lens.py` implements
the documented method. It needs a muted sink (or explicit consent to be audible)
and, before any venue claim, a re-run with the UMC202HD on USB.
---
## n/a — the box plays alone, and the guard that said so was lying ## n/a — the box plays alone, and the guard that said so was lying
**Description.** Answer "are we ready to take off with only this laptop?" — **Description.** Answer "are we ready to take off with only this laptop?" —
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment