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
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).
## 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
Went looking for latency to shave. Found that the thing making the rig miss its
......@@ -28,7 +91,7 @@ generated on this laptop.
### Found, not yet fixed
- **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
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
......
......@@ -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
**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