# TASKS_DUMP — ParVagues / L'Armada board reconstruction

**Written 2026-08-16.** The Claude-Code Task API is not reachable from this session
(`TaskList`/`TaskCreate`/`TaskUpdate`/`TaskGet` all absent), so the live board — last seen at
112 tasks — could not be read or rewritten programmatically. This file rebuilds the tree from
durable sources instead, and is meant to be the thing you edit or re-import from.

**Provenance of every id below**

| source | trust |
|---|---|
| `armada/tasks/board-archive.md` | verbatim, ids exact (completed through 2026-07-29) |
| `armada/tasks/NNN-*.md` (001–025) + dated logs | verbatim, narrative achievement logs |
| memory store + this session's context | ids **recalled**, not verified — treat as labels |
| git log | authoritative for what actually shipped |

Ids in the #150+ range come from the lost board via session context. They are almost
certainly right but I cannot re-read them, so **treat a #17x number as a name, not a key**.

---

## EPIC A — OPAL-26 release  ⟵ *the only epic blocking a shipment*

North star: get "Sunset Forest" (opal-festival-2026, 2026-08-08) released. 14 tracks —
**#14 Desire is cut**, PLN confirmed 2026-08-16; #13 Vague de CRIME becomes the last official
track, #15 REVOLUTION stays the Encore.

### A1 · #177 — Re-cut v4 from ear-verified boundaries  ✅ **DONE — SIGNED OFF BY EAR 2026-08-16**

Ground truth: `armada/tide-table/judge_specs/opal26_boundaries_ear.json` — **do not overwrite
from a detector.** Nine boundaries verified by ear (#3 893.0 · #4 1118.4 · #5 1549.4 ·
#6 1808.1 · #7 2248.8 · #8 2726.4 (alt 2730.9) · #9 2954.4 already correct · #10 3269.0).

- [x] **#1 leading-silence measured** — 1.452 s of *literal digital silence* (all samples zero
      to sample 64038; first transient −28 dBFS). Objective, no ear needed.
- [x] **A1.1 #1 head trim recorded** — `start: 1.45` in the ear-boundary file, with the
      sample-exact measurement stored alongside for audit (`d3dbe59`). Kept as *data*, not
      baked into the rendered flac, because A1.4 regenerates the tracks from this file.
- [x] **A1.2 Settle #11, #12** — done in the boundary lab. #11 `takeover` 3557.33 (machine
      +0.13 s); #12 `mid`, then moved **1.66 s later** to 3681.76 when heard as a seam rather
      than as a cut. Two adjacent cuts, opposite verdicts on the same estimator — **there is no
      universal fraction**; offer candidates, never average.
- [x] **A1.3 Settle #13, #15** — ear-only as predicted, no candidate was ever possible.
      #13 3887.25 · #15 4547.57 · END of #13 at 4166.30 (he stops on the sound's decay,
      13.7 s before the nominal bar end).
- [x] **A1.4 DONE** — `apply_boundaries.py` → `segments_v4.json` → `render_release.py`
      (split · verify · continuous). 14 tracks × streaming+club, durations exact, 48 kHz/24-bit.
      Continuous mixes re-rendered drop-free.
- [x] **A1.5 Seam pass** — release check at `bounds.html?set=<gig>-release`; PLN's five fixes
      applied as `edits` (trims/fades), each verified by gain-ratio against the unfiltered master.
- [x] **A1.6 Close reverb** — A/B rendered (dry/subtle/more). PLN picked **subtle** from the
      isolated clip, then **revised to `more`** hearing it as the record's ending (`271e07c`).
      An effect judged on a clip ≠ the same effect judged as an ending — render the A/B
      *in place* next time. Ships as `reverb_tail_s: 5.0` + `reverb_preset: more` (650 ms top
      tap), ending on a decay with **zero** trailing silence.
- [x] **A1.7 PLN CONFIRM ✅** — 2026-08-16, on the rendered files: *"listened to 13... perfect
      end for crime. revolution starts good. listened to 99... all good perfect ending!"*
      Recorded as `release_signoff` in the ear-boundary file and in `performance_notes.md`.
      Also produced one late call: a **2 s rest** appended inside CRIME (`pad_end_s`, a third
      kind of edit — it ADDS time; a rest belongs to the outgoing track so it ships in the
      file AND in the mix). **The last gate is now CLEARED.**

**RENDERED, SIGNED OFF, READY TO UPLOAD** (2026-08-16): 14 tracks × streaming + club, durations
exact, 48 kHz/24-bit; continuous drop-free mixes `*_v4_{streaming,club}_nodrop.flac` at
**4361.35 s = 72.7 min**. Three gaps = the trims: 1.00 s (silence), 0.04 s (WAP burst),
410.17 s (Desire + CRIME/REV trims); plus one deliberate 2.00 s rest after #13.

⚠️ **The signoff is against `segments_v4.json` as rendered on 2026-08-16.** Any later re-render
invalidates it: re-run `render_release` + `build_release_joins` and get the ear again before
uploading.

### A2 · #183 — REVOLUTION mix fix  · blocked by A1

- [ ] Cut **d5 `the_revolution`**, *not* d10. PLN's first guess was wrong: measured
      band-limited (400 Hz–8 k, what a laptop reproduces) d5 leads d10 by **13.7 dB**;
      cutting d10 changes nothing he can hear. See memory `reference_small_speaker_and_rights`.
- [ ] Root-cause **d9 SUGAR CHOIR = digital silence** (−240 dB) while declared in the score.
      Two independent measurements agree. This is the finding that motivated D2's "shout until played".
- [ ] Measure the bass-"saturation" claim separately — it is a different question from loudness.

### A3 · #179 — SoundCloud uploader  ✅ **WORKING — 8 of 15 uploaded, all private**

**The 400 never needed solving.** `POST /tracks` was the wrong door: SoundCloud renders a
working upload form, and the transport's premise was always "be a real browser, don't
imitate one". `master sc form-album` (tidal-ears `66bacc5`, `src/tidal_ears/scform.py`)
fills that form. Superseded: the whole `POST /tracks` reconstruction effort, and the note
that "metadata PUT is free" — PUT also 400s, with a *different* error
("Failed to parse track update request") for flat, wrapped and array shapes alike. **The
api-v2 write path is a dead end for BOTH create and update. Do not go back to it.**

Two DOM facts that cost a session each, both now encoded in `scform.SEL`:
- the form is in a **nested same-origin iframe** `/n/upload?…v2_layout=true`, so the top
  document reports zero file inputs on a perfectly good session (`Browser.upload_frame()`);
- **sharing is a MUI Select**, portal-rendered only after its trigger is clicked. Sweeping
  radios/checkboxes/selects finds geo (worldwide/exclusiveRegions/blockedRegions) and
  licensing (all-rights-reserved/commons) and truthfully reports no privacy control.
  PLN supplied the markup: `li[role=option][data-value="Private"]`.

**STATE OF THE ACCOUNT (verified 2026-08-18 by API, not by memory):**

| up, private | missing |
|---|---|
| sunset-forest-opal-festival-2026-full-set (72:41) · wap · there-s-something-about-drums-3 · you-my-sunshine · take-five-drops · piment-bresilien · eh-ouais-je-funk · ghosts-in-the-toilets | ceci-n-est-pas-une-bombe · am-i-doing-it-right · perfect-3 · gimme-acid · mafia · vague-de-crime · revolution |

The run stopped when PLN closed the Chromium window by mistake. **It is idempotent by
permalink — just re-run**, the 8 will be skipped:

```
python3 armada/tide-table/build_release_plan.py judge_specs/opal26.json \
    --variant streaming --include-mix \
    --out /home/pln/Work/Sound/Prod/Opal26_master/release/opal26_plan.json
cd ~/Work/Sound/tidal-ears && .venv/bin/python -m tidal_ears.cli master sc form-album \
    --plan /home/pln/Work/Sound/Prod/Opal26_master/release/opal26_plan.json \
    --sharing private --state ../Prod/Opal26_master/release/sc_state.json --go
```

**TWO DEFECTS TO FIX FIRST:**
1. **`/take-five-drops` uploaded SHORT — 6:57 on SoundCloud vs 7:21 in the plan (440.7 s),
   ~24 s missing.** Every other track matches to the second. Delete it on SoundCloud and
   re-upload; do not assume a re-run fixes it (the permalink exists, so it will be
   SKIPPED). Verify with the duration check in `scratchpad/whatsup.py` pattern —
   `GET /users/{id}/tracks?access=playable,preview,blocked` and compare `duration`.
2. **`/ghosts-in-the-toilets` has one malformed tag** — `'"livecoding, festival, opal, …"'`
   as a single quoted chip, from the pre-fix run. The chip-input bug is fixed in
   `_fill_tags` for new uploads, but this track needs repair **via the track edit FORM**
   (api-v2 PUT is dead, see above).

**NOT YET DONE:** artwork (`--artwork` is wired but no cover chosen — the `output/*.jpg`
set is untracked GenAI of unknown provenance and was rejected by PLN), and no playlist/
album grouping on SoundCloud.

### A4 · Rights ledger  🟡 **29 of 60 banks answered**

`armada/tide-table/rights_audit.py` + `rights_ledger.json` (`a00066b`, `15ed166`).
Discriminator: `git ls-files` in the Dirt-Samples checkout — 217 upstream vs 509 local;
"lives in that folder" is NOT "is a stock bank". PLN cleared the five keystones
(jungle_breaks / h2o* / kick / risers / vec1*), which answered 10 banks.
**Next: `snare` (4 tracks), then 30 mostly-single-track banks.** Per-track release on
every platform waits on this; the continuous mix does not.

### A5 · Multi-platform adapters — YouTube ✅ BUILT · Bandcamp/RouteNote 🔴

**YouTube adapter shipped** (tidal-ears `30b64ea`): `master yt {render,probe,login,
upload}`, reading the SAME plan. **The Data API cannot publish** — any API project
created after 2020-07-28 has every `videos.insert` upload locked to private until
Google audits the project, so it is browser-Studio for upload and API for metadata
only. Quota is NOT the constraint (the old 1600-units/6-per-day figure became ~100
units in Dec 2025). `ytvideo.py` verified end to end on a real track. **Studio
SELECTORS ARE UNVERIFIED** — run `master yt probe` against a live session before
trusting `upload()`; needs `master yt login` first. Videos come from Slopmotion, not
a still — see EPIC G.


One manifest (`build_release_plan.py`, platform-neutral, refuses without an ear signoff),
many adapters, one state file `releases/<gig>.json` recording what landed where.
Researched 2026-08-16: **Bandcamp has NO upload API or CLI** (its OAuth API is for labels
/ merch-fulfilment, by application; every community tool is a scraper) → headful browser
reusing `scbrowser.Browser`. **RouteNote** likewise has no open API but ships a desktop
upload tool worth probing first. **YouTube** has a real API — and chapters come free from
`segments_v4.json`. PLN's priority: *"imo we need them all. youtube important, routenote
is top tier, bandcamp fine but prob clis exist, a4 pass indeed sounds important"*.
Rights rule, his words: *"cant push the single tracks when samples hit, but full mix
might well pass!"* — RouteNote/DSP is the one place a claim costs the account, not a track.

### A6 · www gig list  🔴 NOT STARTED

38 gigs published 2022–2026; 2026 has four (le-vortex 01-04, ete-surprise 03-07,
montreuil-algorave 05-22, opal-festival-2026 08-08). PLN believes some are missing —
reconcile against `backlog.md`'s month sections (`Juin Faites de la musique`, `Juillet
Douillet`, `AOUT Tout Doux`…). Also **opal-festival-2026.md has empty `audio`/`video`/
`archive`** — fill once the upload lands.

### A3-old · #179 original scope

Still **NOT BUILT**. PLN 2026-08-16: *"yes go build the uploader. im open to giving you SC
pass/user as in the .env if the cookie approach doesnt suffice. imo we need headful browser
automation."*

**Design settled by research** (see memory `reference_soundcloud_datadome`): SoundCloud is a
confirmed DataDome customer, and stealth/headless degrades to ~0 within 24 h. But that
literature is about impersonating many users; we automate PLN's own account with his own
music. So: **headful `launch_persistent_context` on `/usr/bin/chromium` with a real
user-data-dir holding the logged-in session** — be a real browser, don't defeat detection.
Browser for the FILE step only; metadata PUT is unwalled, so titles/artwork/tags/playlist go
through the plain API. `.env` credentials are the fallback only — a scripted login is the most
bot-shaped action in the flow.

- [ ] Build it. Only `/uploads/*` is DataDome-walled; **metadata PUT is free** — see
      `reference_soundcloud_write_path`, `reference_soundcloud_cli`.
- [ ] **Uploaded ≠ cleared.** Content-ID flags arrive by *email*, never through the API, so the
      tool must model those as separate states.
- [ ] Destructive calls must pass an allowlist predicate on a test-namespace permalink prefix.
      The 110 real tracks must be unreachable **by construction**.
- [ ] Delete playlist `2282681433` — sanctioned by PLN, **only once v4 is up**.

### A4 · Rights / Content ID

- [ ] **#4 "Am i Doing it Right" is flagged on SC** and it is a *true* positive — the score
      literally uses the `daft` sample bank. The durable asymmetry: the same audio **passed
      inside the 87-minute continuous mix** and flagged as a 7-minute standalone.
- [ ] Consequence to act on: static-check every track's `.tidal` sample banks **before**
      planning a per-track release. The full-set upload is the lower-risk path.

---

## EPIC B — Cut / boundary detection

### B1 · #178 — Crossfade-blind cut detection  ⚰️ **DEAD END, declared 2026-08-16**

Three attempts, all validated against the ear and all recorded in-code with their numbers:

| | approach | median err | coverage |
|---|---|---|---|
| `cut_lens.py` | midpoint reference, weighted mean, threshold | 24.0 s | — |
| `cut_lens2.py` | adjacent refs, median over orbits, CUSUM | 16.6 s | 8/14 |
| `cut_lens3.py` run 1 | span + self-calibrating fraction `u` | 3.7 s *(n=2)* | 8/14 |
| `cut_lens3.py` run 2 | medoid refs + effect-size gate | 0.4 s *(n=2)* | **4/14** ↓ |

**Do not tune this a fourth time.** The accuracy figures are not evidence — n=2, and those two
boundaries (#8, #10) already had ear answers; a metric computed on the cases that were already
easy measures nothing. The real result is the pattern: **#13 and #15 produced zero voting
orbits across all three gates and two estimators.** Those transitions have no orbit whose
timbre changes materially across the switch — the outgoing and incoming tracks share their
sounds — so a timbral lens is structurally blind there, exactly as an activity lens is blind
to a crossfade. Loosening further can only admit noise.

Two ways out, if ever revisited:
- [ ] **Change the feature** — harmonic/key change, or per-sample identity from the score's
      sample banks (which differ even when the spectrum does not).
- [ ] **Accept ear-only** for the blind cases. Cheap and honest: two timestamps, seconds each.

Standing rule: validate any new attempt against `Montreuil26_master/boundaries_ground_truth.json`
(14 user-confirmed) **first**. Note Montreuil has no per-orbit stems, so a stem-based lens
cannot use it — that gap is itself worth fixing before attempt four.

### B2 · Boundary lab  ✅ SHIPPED (`192c6e9`)

**→ http://192.168.1.3:8799/bounds.html** (LAN, phone-capable; smoke-tested against this
exact server, 11/11). `j`/`k` next-prev · space play/pause · **Enter takes the playhead** ·
drag to loop a span · candidate buttons audition from 4 s before. Export writes
`boundaries-<gig>.json` in **master** seconds — merge into `verified` in the ear file.

- [x] `build_bounds_set.py` renders a ±45 s clip per cut (not seeking the 817 MB master) and
      emits peaks + candidates. Re-encoded, never `-c copy` — FLAC stream-copy snaps to frame
      boundaries and every marker would inherit the offset.
- [x] #13/#15 show an explicit **ear-only notice** rather than being omitted; they are exactly
      the cuts that need a human.
- [ ] **PLN: make the four calls.** ~1 min each. Then A1.4.

### B3 · The set-judge  ✅ SHIPPED

`9c6a24c`, `20b9151`, `73e9d79` · log `armada/tasks/025-fifteen-intros-are-not-a-set.md` ·
memory `reference_set_judge`. Reusable per-gig via `judge_specs/<gig>.json`.

---

## EPIC C — The floor problem  ⟵ *the highest-value non-release thread*

Origin, PLN verbatim: *"I DIDNT KEEP THE KICK LONG ENOUGH. MAIN criticism / negfb I GOT:
'destructure. les gens etaient un peu perdu'."*

**Measured, and the data agrees with the floor: the kick was audible 59% of OPAL-26.**
13 of 15 tracks under 70%. Longest kickless stretch 164 s (#4). `f1798e7`.

### C1 · #180 — Pre-gig floor lens
- [ ] Static + stem analysis that flags a track's kickless stretches **before** the gig.
- [ ] **Count BARS, not seconds** — the set ran 89–166 BPM, so a seconds budget is wrong by 2×.

### C2 · #181 — HUD v2: debts + instruments  🎯 *the design is settled, see memory*
PLN: *"HUD needs evolve from 'just a lcxl repr' to proper parvagues success metrics/tooling/
state helper"* · *"things should shout at me until playd"* · *"2am-elevated-UX … like
macdonalds' screens help avoid cold burgers"*.

One mechanism — **DEBTS**, things that accrue until discharged:
- [ ] **floor debt** — bars since the kick last sounded. Must be a *budget* (≈16 bars calm →
      amber → red past 32) with an explicit "holding a breakdown" override. **A drop is
      supposed to remove the kick**; an alarm that fires on every intentional breakdown is
      ignored within one set, and then it is worse than nothing. This is the make-or-break.
- [ ] **unplayed debt** — an orbit declared in the buffer that has produced no events since
      the track loaded. Shouts until it plays. (Born from d9 SUGAR CHOIR, A2.)
- [ ] Two registers: **alarms** at the screen edge, colour **+ motion**, no numbers; **instruments**
      (kick % this track / this gig) in the topbar, never blinking.
- [ ] **Signal source**: the buffer parse structurally cannot answer any of this — it only knows
      what was *written*. Add an OSC target aimed at the HUD (`BootTidal.hs` already defines
      57120 / 1818 / 6013, so this is a one-line change avoiding contention). One stream answers
      all of it. Prior art `tidal_ears/osc_monitor.py`. **Verify the cost first** —
      `feedback_observer_perturbs`.

### C3 · #176 — `^41` standardisation across the corpus
- [ ] Verified from source: `midiOn ch pat = someCyclesBy (orDef 0 ch) pat`. So `^41` is a
      **fraction-of-cycles** control, *not* an on/off mute, and `orDef 0` means an untouched
      `^41` is safe (no mute-bomb). A mid value like 64 = the kick on ~half the cycles —
      literally structure that comes and goes, so **`^41`'s value is a first-class HUD signal**.

### C4 · HUD touched/untouched + scenes  · plan file exists, unstarted
Plan: `~/.claude/plans/zazzy-honking-waterfall.md` (touched-state tracker via `aseqdump`
read-only MIDI tap + perf-isolated scene backdrop). **The law: the HUD must never be able to
stall the editor or audio.**

---

## EPIC D — Rig & hardware

- [ ] **#154 — USB hub is a single point of failure** (drops interface + LCXL together).
- [ ] **LCXL v3 ordered** (2026-08-16). Retires: pot-pickup value jump (endless encoders send
      relative deltas — *the whole class disappears*), much of the LED-signalling cost (OLED
      renders values natively), and the USB SPOF (**DIN path is independent of USB**).
      🔍 **UNVERIFIED and it matters:** can third-party SysEx paint *arbitrary* text to the
      OLED? Documented behaviour is only "shows parameter values". C2's alarms are **derived**
      state, not control values — if the OLED is closed, the alarms stay on screen.
- [ ] CC numbering + relative-encoder mode will differ from v2. Change it in `lcxl_grid.py`
      and **regenerate** — never in a copy (`feedback_one_parser_per_concept`).

## EPIC G — Slopmotion release videos  🟡 *renderer fixed twice, one modal left*

The release videos come from **Slopmotion** (Kevin Granger / Shipow —
`../hydra-live-hexa`), NOT from a still image. PLN, rejecting the still:
*"i dont like these covers, wait where are tehse genai images from? these are not great
sources! we wanna use slopmotion for videos remember?"* The `output/*.jpg` set is
untracked, unprovenanced, and `6c3272c refactor: Remove covers from git` had deliberately
removed covers from this repo. **Rights: PLN has rights to ALL Slopmotion packs** — the
opposite of the sample-bank situation.

### G1 · Renderer fixes  🟡 two landed, one open

- ✅ **The playwright browser cache is empty now.** Returning `undefined` does not "let
  playwright pick" — it reaches for `chromium_headless_shell-1234`, never downloaded, and
  EVERY render died before a frame. Falls back to `/usr/bin/chromium` (`1fdfb85`).
- ✅ **Headless DOES reach the GPU** — `--use-gl=angle --use-angle=gl-egl`, ONE WORD from
  the `--use-gl=egl` that silently stays on SwiftShader (`32e7a00`). Measured: swiftshader
  3.5 fps → headful 11.4 → **headless+gl-egl 17.8 fps, 542 of 542 frames captured**.
  Headless is FASTER than headful (no compositor) and has no capture bottleneck.
  **Never open windows for a render** — PLN: *"stop opening chrome windows lol"*.
- 🔴 **A second modal blocks the scene: "START A SESSION".** The guard now refuses to
  capture (`8be26fb`) instead of shipping scrims. The photosensitivity modal IS dismissed
  (uncheck "Show this warning at startup" + OK; seeding localStorage does NOT work because
  `localStorageBridge.ts` re-hydrates from disk at boot and overwrites it).

### G2 · Adapt to the 50-commit `git pull`  🔴 THE NEXT THING TO DO

A pull on 2026-08-18 changed the app under us. All of this is read from source by a
subagent, NOT yet empirically confirmed (the dev server was down at the time):

- **`?studio=1` is DEAD** — it is stripped from the URL. Studio is now the DEFAULT and
  **`?live=1`** opts into the full-bleed performance shell (`app/appHelpers.ts:59-62`).
- **Studio FREEZES shader time** when `window.studioTransportActive === false`
  (`HydraCanvas.tsx:327-334`, `:459-470`) — this is the other half of "no motion", and
  probably the bigger half. **Set `window.studioTransportActive = true`.**
- The **"Minimal performance shell" button no longer exists**, so the click this renderer
  (and `scripts/capture_fx_previews.mjs`) makes is dead code.
- Seed `hexa-onboarding = {"completed":true,...}` and `hexa-onboarding-checklist =
  {"dismissed":true}` — absent from `.hydra-local/localStorage.json`, so a seed sticks.
  Fully deterministic alternative: POST them to `http://localhost:5173/api/local-storage`.
- **Dev server needs `npm install --no-save --no-package-lock`** — `npm ci` REFUSES
  (Kevin's `package.json` and lockfile are out of sync: `@emnapi/runtime` missing). Never
  plain `npm install` — it would rewrite a tracked file in someone else's repo.

### G3 · Bake-off, then the real render  🔴 blocked by G1+G2

`armada/tide-table/slop_bakeoff.py` (`c8e1aa6`) renders N presets of ONE fixed 30 s window
so looks are comparable. First run produced 19 clips that were all scrims — **delete
`visuals/slop/out/bake*.mp4` and re-render** once G1/G2 land. Window = **94 s** (the techno
opener, −10.1 dB, four-on-the-floor: the honest test for kick-synced triggers).
*Window-choice trap: scoring by spectral flux NORMALISED BY LEVEL picks near-silence — it
returned the 69:18 CRIME/REVOLUTION seam at −23 dB. Gate on level first.*

Then grade at `visuals/slop/bakeoff/bakeoff.html` (serve with
`python3 armada/serve.py --dir visuals/slop --port 8742`, root is `visuals/slop` because
the page reaches into `../out/`). Then the real thing:
`armada/tide-table/render_release_videos.py` — **2h25m of REALTIME capture** for the
landscape shape, hence its `--unit` systemd option. Resumable.

---

## EPIC H — Long-term ParVagues setup plan  ⟵ *PLN asked for this explicitly, 2026-08-18*

The rig work, split by what a gig needs BEFORE it versus what only matters AFTER. OPAL-26
(2026-08-08) is the reference gig: it exposed most of these.

### H1 · PRE-GIG — things that must be true before playing again

| # | thing | state |
|---|---|---|
| C1 | **Pre-gig floor lens** — kick was audible only **59%** of OPAL-26. Measure before, not after. | 🔴 |
| — | `tools/gig-up.sh` is THE gate; keep it cold-by-default, keep hard-vs-soft honest | ✅ exists |
| D | **LCXL v3** — ordered; endless encoders kill pot-pickup, OLED + RGB rings, DIN path | 🟡 arriving |
| — | **Orphan orbits** — 8 of 12 OPAL transitions left ghosts; `xfadeIn`'s outgoing pattern never stops | 🔴 |
| — | **Preload staleness** — generated + gitignored + never compared = the crackle that debuts at the venue | 🔴 |
| — | **SC supervision** — scsynth is sclang's CHILD; a green unit ≠ sound. Watchdog must outlive its supervisee | 🟡 |
| — | **Audio graph budget** — the J-5 dropout: unused nodes held open by a graph-wide no-suspend rule | 🟡 |
| E | **Open taste calls** — LED brightness, cut groups. Waiting on PLN's eyes/ears, not on work | ⏸ |

### H2 · POST-GIG — the release pipeline (where this session lived)

| # | thing | state |
|---|---|---|
| A1 | Ear-verified re-cut + sign-off | ✅ done 2026-08-16 |
| A3 | SoundCloud upload | 🟡 8/15 up, see A3 |
| A4 | Rights ledger — **30 of 60 banks**; `snare` gates 4 tracks | 🟡 |
| A5 | YouTube ✅ built · Bandcamp 🔴 · RouteNote 🔴 | 🟡 |
| A6 | www gig list + fill opal-festival-2026's empty `audio`/`video`/`archive` | 🔴 |
| G | Slopmotion release videos | 🟡 |

### H3 · The standing lesson from this session

Three of my confident conclusions were wrong the same way, and each cost hours:

1. *"The upload form does not render"* — it was in an iframe I never enumerated.
2. *"Headless cannot reach the GPU"* — I tested three flag combinations of five and stopped
   at the first that worked.
3. *"The uploader is public-only"* — a MUI Select's options do not exist in the DOM until
   the trigger is clicked, so "absent" and "unmounted" look identical to a sweep.

In all three the rig was fine and the **instrument** was blind, and in all three PLN's own
eyes beat the probe — one pasted `<li>` settled what four probes could not. This now joins
[[feedback_check_the_instrument_first]]: **before concluding a feature is missing, prove
the search could have found it.** An early success is not a search.

## EPIC E — Open taste calls  ⟵ *waiting on PLN's eyes/ears, not on work*
- [ ] **#11** LED brightness · **#70** cut groups. See memory `project_open_taste_calls`.

## EPIC F — Completed
Full tracker history in **`armada/tasks/board-archive.md`** (#1–#103, through 2026-07-29).
Narrative logs in **`armada/tasks/001-*.md` … `025-*.md`** + dated logs. Structured long-form
archive in **`armada/tasks/completed-archive.md`**.

---

## Recommended order  *(rewritten 2026-08-18 — the old one is done)*

The release is one re-run away from complete, so finish it before starting anything.

1. **A3 defect #1** — `/take-five-drops` is 24 s short. Delete it on SoundCloud, then
   re-run `form-album`. It will NOT self-heal: the permalink exists, so a re-run skips it.
2. **A3 re-run** — the other 6 missing tracks. Idempotent; the 8 already up are skipped.
   *(This is ~15 minutes of watching an upload. Do it first.)*
3. **A4 `snare`** — one answer unblocks 4 tracks. The single highest-leverage question on
   the whole board, and it is a question for PLN, not work.
4. **G2 → G1 → G3** — the slop renderer, in that order: adapt to the pull (`?live=1`,
   `studioTransportActive`, onboarding seeds), then the last modal, then re-bake the 19
   clips for PLN to grade. Everything downstream (YouTube, the release videos) waits here.
5. **A6** — the www gig list, and fill opal-festival-2026's `audio`/`video`/`archive` with
   what A3 produced.

**C2 (HUD v2: debts + instruments) is still the highest-value non-release thread**, and its
design is settled — it needs building, not deciding. **C1 (pre-gig floor lens) is the
highest-value thing before the NEXT gig**, since kick was audible only 59% of OPAL-26.

*Do not start EPIC B. B1 is a declared dead end.*

---

## AMENDMENT — 2026-08-21 reconciliation

### The live board WAS recoverable. It is in `.claude/RESUME.md`.

This file opens by saying the Task API was unreachable so the tree had to be rebuilt from
durable sources. That was true of the *tools*, not of the *data*: a near-limit checkpoint of
session `36236d51` wrote the full TodoWrite state to **`.claude/RESUME.md`** (2026-08-21
14:58) — **112 items: 22 done, 7 marked current, 83 open.** Read that file first; the epics
below are a curated 20% of it. (`.claude/` is untracked, and the checkpoint ref
`refs/claude/checkpoint-36236d51` expires in ~2 weeks — so the 112-item list is NOT durable.
If it matters, it must be promoted into this file.)

**Four of the 7 "current" items are stale-active** — git says they landed:
`Building the SoundCloud writer` → A3 (`66bacc5`); `Re-cutting OPAL-26 v4` → A1, ear-signed
2026-08-16; `Building the mix/master/split stack` → shipped; `Per-track acceptance test` →
`pv-at` exists. Genuinely in flight: **Slopmotion clips headlessly** (EPIC G), **gig
transitions**, **HUD REC warning**.

### EPIC I — Rig hardening & The Bridge  🆕 *9 commits the board never knew about*

Landed 2026-08-21 by a parallel session in this checkout, `2ef09af..c821bdf`:

| commit | what |
|---|---|
| `26a1e7b` | **one RIG UP button** — `gig-up --converge` and the gearbox both wrote governor/EPP with no arbiter; whichever ran last won, and ppd re-asserts on AC transitions, so a converge done on battery was undone by plugging in for the set. Converge now drives the gearbox, and deliberately does NOT touch trim (quiet-vs-cool is a room call). |
| `b893a06` | tray = the one face PLN actually looks at |
| `72937cc` | the LCXL panel could not see the daemon that lights the board |
| `7bda1cc` | gig-up: the gate that could not fail, and the two units nobody started |
| `4e0d63a` | two scripts were +x on disk and 644 in the index (`feedback_chmod_is_not_a_fix`) |
| `b503502` | absent is not broken, and a deep queue is just latency |
| `cb5a625` | **check-drift told PLN to delete his set the day before a gig** |
| `c821bdf` | stop naming a cause this check cannot see |
| `eb9221d` | **CosmicFest prep** — 24 track files + a new `live/midi/nova/techno/livecode_parade.tidal` |

This is H1 (PRE-GIG) executing. It closes H1's *"SC supervision"* and *"gig-up is THE gate"*
rows and adds the Bridge panel as the GUI half of converge.

### 🔴 CosmicFest — a gig thread with no canonical metadata

`eb9221d` is a prep pass across the whole set, and `cb5a625` says *"the day before a gig"*.
But **`content/lives/2026/` has no cosmicfest page** — only le-vortex, ete-surprise,
montreuil-algorave, opal-festival-2026. `content/lives/2025/cosmicfest.md` is the 2025
edition. Per `feedback_metadata_vs_mastering` I will not invent the date or the venue.
- [ ] **ASK PLN:** is there a CosmicFest 2026 (or another imminent gig)? A date turns every
      H1 row from "someday" into a deadline, and it decides whether the release or the rig
      comes first.

### Parked, not to be committed by anyone but PLN
`backlog.md` (+60 lines) and `armada/setlist_opal2026.txt` are modified by hand.
**`backlog.md` is the setlist SSOT — parse it, never rewrite it.**

### Merge candidates for PLN's call (flagged, not merged)
- **A3-old** and **A4 · Rights / Content ID** are superseded duplicates of A3 and A4 above.
  A3-old's *"Build it"* is done; its still-live residue is only the two safety rules
  (test-namespace allowlist, Content-ID-arrives-by-email) and the sanctioned deletion of
  playlist `2282681433` once v4 is up. Suggest folding both into A3/A4 and deleting.

---

## AMENDMENT — 2026-08-22, pre-gig close-out  *(appended; EPIC I above is the commit list)*

### ✅ CosmicFest is REAL and PLN plays it next — the open question above is half-answered

PLN, in his own words this session: *"do please do a commit `feat: cosmicfest prep`"* and
*"tomo ill setp and play"*. So the gig EXISTS and is imminent; that part needs no further
asking. What is still missing is only the **canonical metadata** — `content/lives/2026/`
has no cosmicfest page (2025's is a different edition). Per `feedback_metadata_vs_mastering`
the venue/date/lineup must come from PLN or the www content, never from a commit message.
- [ ] **A6-adjacent:** create `content/lives/2026/cosmicfest.md` + `tracks.json` from PLN's
      details AFTER the gig. Do not backfill it from `eb9221d`.

### gig-up: 4 of 5 blockers cleared, and the 5th is a DECISION not a defect

| gate | before | after |
|---|---|---|
| tools executable | FAIL | ✅ `4e0d63a` — two scripts +x on disk, 644 in the index |
| audio graph | FAIL | ✅ `b503502` — absent-UMC is now `warn`, not NO-GO |
| mute map | FAIL | ✅ `fix-mute-roles --apply`, converged to 0 |
| pvlint (setlist) | FAIL (4 err) | ✅ 0 errors — two crushbus slot collisions split |
| **surface grid intact** | FAIL | 🔴 **still red, on purpose** |

**Why the last one stays red.** 9 knobs off-column, **20 moves pending** — ghosts 10,
something_about_drums 7, perfect 2, the_revolution 1. PLN: *"migration i unfinishe dgrrr
lets not finish it before set for now."* Renumbering which physical knob does what, in four
tracks he plays imminently, is a muscle-memory change and not a safety fix; two of the moves
cannot resolve mechanically anyway (one `overflow -> gSel`, one `FIXME (no slot)`).
- [ ] **AFTER the gig:** `python3 tools/migrate-columns.py --plan` then apply, then
      `tools/check-drift.sh` must go green and `--plan` must report 0 moves.

### 🔎 RESUME: a renderer burns ~112% and it is NOT the highlighter

The open perf thread, with the guesswork already eliminated so nobody repeats it:

* **The highlighter is EXONERATED by its own instrument.** `tidalcycles:highlight-stats`
  over a 16919 s window: `RAF 5/s, work 5/s, frame avg 1.671 ms` = **0.8% of one core**.
  Not the burner, not close.
* **The marker leak is genuinely DEAD in the field:** `markers 293 live,
  creates/destroys 0/s`, `toggles 105/s` (the cheap reuse path doing its job), `errors 0`.
  Confirms `6e9f374` (2026-07-18) + `ba77c4f` (2026-08-03) over a 4.7-hour window. **Do not
  re-open the leak hunt.**
* **Already ruled out:** `editor-background` is in `disabledPackages`; sound-browser's three
  `rAF(processChunk)` loops all terminate on `currentIndex < total`; `aseqdump` flushes per
  event (proved on an isolated client — median 40 ms inter-arrival at a 25/s send rate);
  fork+exec per LED write costs 2.8 ms p50 and `--bench` shows the coalescer at p99 18 ms
  with zero overrun.
* **Caveat on the evidence:** the profile window was 4h42m while the hot renderer (pid
  799788, `--type=renderer`, 3 `Media` threads) was 3 minutes old — so the stats likely came
  from a DIFFERENT window than the burner. The 0.8% conclusion holds regardless.
* **Next step:** run `tidalcycles:highlight-stats` *in the hot window*, and get a per-thread
  breakdown inside pid `--type=renderer` (the 3 `Media` threads are unexplained with
  editor-background disabled).
- [ ] **NOT a gig blocker:** xrun delta over 25 s was **0 on every node** (ardour's 1200, SC's
      38, the sink's 11 are all historical). High CPU has not cost audio. Fix it for editor
      responsiveness, not for sound.

### Remaining known jank (editor only)

`jank: 245 frames >16ms, 91 >50ms | worst 179.82ms did 22 adds / 5 removes` — one visible
stall roughly every 3.1 minutes, ~6.5 ms per add, so stalls scale with burst size. The
highlight stylesheet is already proven to set no layout-affecting property
(`spec/highlight-class-contract-spec.cjs`), so the cost is elsewhere in the add path.
- [ ] Attribute the per-add cost in `#createPositionMarkers` / the add path (post-gig).

### ⚠️ PARKED — not mine to commit

* `~/Work/Tools/pulsar-tidalcycles` — **`3205e7f` is committed but UNPUSHED** (no SSH access
  to `github.com/PLNech/pulsar-tidalcycles` from that session). Branch
  `perf/event-highlighter`. PLN pushes it.
* Same repo: `lib/sound-browser.js` has **144 uncommitted insertions** (waveformCache, lazy
  AudioContext, currentAudio tracking) written by an earlier session. Loaded by the running
  Pulsar. Unvalidated — left alone deliberately.
* `Tidal`: `backlog.md` (+60 lines) and `armada/setlist_opal2026.txt` are PLN's own
  uncommitted edits, untouched all session (`feedback_...` — backlog is never mechanized).
* `Tidal`: `live/midi/nova/breaks/hello_fred.tidal` is a 1-line stub (`d1 $ `) — deliberately
  not committed.
