# 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  🟡 **BUILT; one call unresolved**

**Done** (tidal-ears `d8f275d` `e5f0693` `b72449e` `8c4df8e` `1a64c35`): `scbrowser.py`
= headful persistent Chromium; only `POST /uploads/track-upload-policy` goes through the
browser, bytes go to S3 from Python, metadata over the plain API. Proven durable:
`master sc browser-check --repeat 14 --every 100` → **14/14 policies over 21.7 min**,
latency dropping to 0.1–0.2 s. The 790 MB mix reached S3 with a 200.

**BLOCKED ON ONE THING:** `POST /tracks` → 400 `{"errors":[]}`. Dead hypotheses, do NOT
retry: fingerprinting (same from both transports), the envelope (`{"track":{…}}` is the
only shape that parses), the web session (retested after a real login, still 400s).

**EXACT NEXT STEP:** the consent dialog is covering the upload form (`session_ok()` is
True but `input[type=file]` count is 0). Dismiss it in the persistent profile, then run
the capture script at `scratchpad/probe_ui.py` — it attaches a 1 s probe to the real form
and logs every non-GET api-v2 request. Copy the finalize call's shape verbatim into
`upsert_track`. Then re-run:

```
python3 armada/tide-table/build_release_plan.py judge_specs/opal26.json \
    --variant streaming --include-mix --out /tmp/opal26_plan.json
cd ~/Work/Sound/tidal-ears && .venv/bin/python -m tidal_ears.cli master sc album \
    --plan /tmp/opal26_plan.json --sharing private --transport browser
```
Idempotent by permalink — a half-finished run is safe to simply re-run. **Nothing is on
the account today.**

### 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 — the agreed architecture

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 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

1. **A1.1** — the 1.452 s head trim. Independent, measured, done in minutes.
2. **B2** — the audition-and-mark webview, now simpler than specced.
3. **A1.2 + A1.3** in one sitting at that webview → **A1.4** → unblocks **A2** and **A3**.

C2 is the highest-value thread once the release ships, and its design is already settled —
it needs building, not deciding.
