# L'Armada — completed-task archive (structured)

Long-term learning + blog/video source material. Each entry = a finished board task
captured in full (description · done · learnings · deps), appended by the `/pre-compact`
ritual before the task is cleared off the active board. Richer than the terse tracker
snapshot in `board-archive.md`. Newest first.

---

## #85 — Pack-level sample grouping (the grouping above the folder)
- **Description:** The Dirt-Samples FOLDER is a loose convention, not the true grouping —
  `rample*` is one bank sliced A0..S57, `808bd/cy/hc/mc` is one drum machine per voice,
  `*_commodore` is one chip sliced by role. PLN wanted the PACK granularity above the
  folder, so we can browse/aggregate at three grains (pack / folder / sample).
- **Done:** `sample_packs.py` + `sample_packs.json` (commit 96bc7bc), captain's log 016.
  A curated REGISTRY (types: drum_machine / bank / chip / source / genre) + an auto-grouper
  (alpha-prefix-before-digit + underscore head/tail tokens, ≥2 folders) that PROPOSES
  packs the registry missed — it caught `trance_*` and `voices_*`, folded in. Reads the
  already-grounded `sample_families.json` (no audio → fast, CPU-light). 22 packs over
  83/150 grounded folders; rolls per-sample family counts up to each pack.
- **Learnings:** Each grain answers a different question — folder = "what did the pack
  author call this slice?", pack = "where did it come from?", sample = "what does it
  actually sound like?". The signal isn't agreement, it's the MISMATCH: 15 of 22 packs
  span multiple audio families BY DESIGN (rample → 8 families, jungle → 11, one Commodore
  chip → bass/lead/pad/fx/synth). A pack defined by ORIGIN is *expected* to scatter across
  the timbre wheel; that scatter is a story (for Unwrapped viz / vibe-search scoping), not
  an error. A 1-folder "pack" is just a folder within the grounded subset — require ≥2
  (may gain siblings when #84 grounds all 718). Same discipline as the folder: a hint to
  compare against, never a law.
- **Deps:** Builds on the grounded `sample_families.json` (#69 resolver); feeds the
  Unwrapped viz (#64 lineage) and vibe-search. Re-run after #84.

## #65 — Fix serve.py Range broken-pipe crash on audio seek
- **Description:** `armada/serve.py copyfile()` threw `ConnectionResetError`/`BrokenPipeError`
  (Errno 104) when a browser aborts/seeks a Range request mid-stream (e.g. `proxy_take89.mp3`
  in the triangle drawer) — the 206 responses worked but the unhandled exception spammed the
  console.
- **Done:** Wrapped both copy branches of `copyfile()` and guaranteed the file handle closes
  in a `finally`; swallow `BrokenPipeError`/`ConnectionResetError`. Verified: a mid-stream
  abort (`curl --max-time` cut) leaves NO traceback and the server keeps serving (206
  follow-up OK). Commit **d70c3ee**.
- **Learnings:** A client disconnect on `<audio>` scrubbing is *normal*, not a failure —
  swallow it, don't log it. This was the keystone QOL fix: it de-frictions every audition
  task (#35 ear-verify, #43/#44 frag-audition, #20 best-take), so cheap-but-high-leverage.
- **Deps:** Blocked nothing formally; unblocks the ear-gating workflow ergonomically.

## #64 — "Information is Beautiful" dataviz of the generated corpus (EPIC)
- **Description:** Phase-2 infodesign of the tide-table corpus (after data-scientist
  exploration) — explorable insights served to a music aficionado.
- **Done:** `corpus.html` narrative + the **Unwrapped** explorable (vibe-search via CLAP,
  dominant-colour density field, zoom/pan) + landing `index.html` + honest style cloud +
  serve.py `/vibe` warm endpoint. Shipped across the prior session's commits; vibe-search,
  field-tuning, label-completion (1490/1501 samples), and zoom all landed.
- **Learnings:** Dots are the stars — the field stays subtle; **don't average hues** (→ mud),
  use the per-cell DOMINANT family colour + purity-fade + desaturate + lift. Cache the field
  offscreen, rebuild on colour/axis/filter change, never on hover. Per-one-shot CLAP
  genre/mood is unreliable (0.3s hits) but CLAP **embeddings are gold for relative
  similarity** (vibe-search / find-similar). Seed free-text search with the user's OWN words.
  DarkReader can fight a dark theme — suspect the inverter before re-tuning to a phantom.
- **Deps:** Spawned #63 (cross-app SPA landing — the tide-table index shipped; tying in the
  React surfaces remains). Set the foundation the emotion-engine work (#89–#92) built on.
