Commit 67b14a31 by PLN (Algolia)

docs(board): the release is one re-run from done, plus a pre/post-gig setup plan

Session of 2026-08-17/18 written back to the board for a cold reader, since the
Task API is still unreachable from this session and the scratchpad — which held
the release plan and every log — was cleaned mid-session. (The plan regenerates
from canonical sources, which is exactly why it is a generated artifact; it now
lands in Prod/Opal26_master/release/ rather than /tmp.)

A3 rewritten: the SoundCloud uploader WORKS. `POST /tracks` never needed solving
— it was the wrong door. Recorded the account's verified state (8 of 15 up, all
private, including the 72:41 mix), the two DOM facts that cost a session each,
and two defects a re-run will NOT fix by itself: take-five-drops uploaded 24 s
short, and ghosts-in-the-toilets carries one malformed tag from before the
chip-input fix. Also recorded that api-v2 is a dead end for BOTH create and
update, so nobody re-opens it.

A5: the YouTube adapter is built, with the finding that decides its architecture
— the Data API cannot publish at all from an unaudited project.

New EPIC G (Slopmotion release videos) carries the renderer's two landed fixes
with their measurements, the one modal still blocking, and the 50-commit pull
that inverted the app's mode semantics under us.

New EPIC H is what PLN asked for: the long-term setup plan split into PRE-GIG
(what must be true before playing again — the 59%-audible kick, orphan orbits,
preload staleness, SC supervision, the LCXL v3) and POST-GIG (the release
pipeline). Plus H3, the standing lesson: three confident conclusions this session
were wrong the same way — a blind instrument reported absence — and PLN's own
eyes beat the probes every time.
parent 8be26fb8
...@@ -78,32 +78,56 @@ uploading. ...@@ -78,32 +78,56 @@ uploading.
Two independent measurements agree. This is the finding that motivated D2's "shout until played". 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. - [ ] Measure the bass-"saturation" claim separately — it is a different question from loudness.
### A3 · #179 — SoundCloud uploader 🟡 **BUILT; one call unresolved** ### A3 · #179 — SoundCloud uploader ✅ **WORKING — 8 of 15 uploaded, all private**
**Done** (tidal-ears `d8f275d` `e5f0693` `b72449e` `8c4df8e` `1a64c35`): `scbrowser.py` **The 400 never needed solving.** `POST /tracks` was the wrong door: SoundCloud renders a
= headful persistent Chromium; only `POST /uploads/track-upload-policy` goes through the working upload form, and the transport's premise was always "be a real browser, don't
browser, bytes go to S3 from Python, metadata over the plain API. Proven durable: imitate one". `master sc form-album` (tidal-ears `66bacc5`, `src/tidal_ears/scform.py`)
`master sc browser-check --repeat 14 --every 100`**14/14 policies over 21.7 min**, fills that form. Superseded: the whole `POST /tracks` reconstruction effort, and the note
latency dropping to 0.1–0.2 s. The 790 MB mix reached S3 with a 200. 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
**BLOCKED ON ONE THING:** `POST /tracks` → 400 `{"errors":[]}`. Dead hypotheses, do NOT api-v2 write path is a dead end for BOTH create and update. Do not go back to it.**
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). 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 |
**EXACT NEXT STEP:** the consent dialog is covering the upload form (`session_ok()` is The run stopped when PLN closed the Chromium window by mistake. **It is idempotent by
True but `input[type=file]` count is 0). Dismiss it in the persistent profile, then run permalink — just re-run**, the 8 will be skipped:
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 \ python3 armada/tide-table/build_release_plan.py judge_specs/opal26.json \
--variant streaming --include-mix --out /tmp/opal26_plan.json --variant streaming --include-mix \
cd ~/Work/Sound/tidal-ears && .venv/bin/python -m tidal_ears.cli master sc album \ --out /home/pln/Work/Sound/Prod/Opal26_master/release/opal26_plan.json
--plan /tmp/opal26_plan.json --sharing private --transport browser 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
``` ```
Idempotent by permalink — a half-finished run is safe to simply re-run. **Nothing is on
the account today.** **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** ### A4 · Rights ledger 🟡 **29 of 60 banks answered**
...@@ -114,7 +138,18 @@ Discriminator: `git ls-files` in the Dirt-Samples checkout — 217 upstream vs 5 ...@@ -114,7 +138,18 @@ Discriminator: `git ls-files` in the Dirt-Samples checkout — 217 upstream vs 5
**Next: `snare` (4 tracks), then 30 mostly-single-track banks.** Per-track release on **Next: `snare` (4 tracks), then 30 mostly-single-track banks.** Per-track release on
every platform waits on this; the continuous mix does not. every platform waits on this; the continuous mix does not.
### A5 · Multi-platform adapters — the agreed architecture ### 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), 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. many adapters, one state file `releases/<gig>.json` recording what landed where.
...@@ -276,6 +311,111 @@ stall the editor or audio.** ...@@ -276,6 +311,111 @@ stall the editor or audio.**
- [ ] CC numbering + relative-encoder mode will differ from v2. Change it in `lcxl_grid.py` - [ ] 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`). 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* ## 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`. - [ ] **#11** LED brightness · **#70** cut groups. See memory `project_open_taste_calls`.
...@@ -286,11 +426,24 @@ archive in **`armada/tasks/completed-archive.md`**. ...@@ -286,11 +426,24 @@ archive in **`armada/tasks/completed-archive.md`**.
--- ---
## Recommended order ## 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.
1. **A1.1** — the 1.452 s head trim. Independent, measured, done in minutes. **C2 (HUD v2: debts + instruments) is still the highest-value non-release thread**, and its
2. **B2** — the audition-and-mark webview, now simpler than specced. design is settled — it needs building, not deciding. **C1 (pre-gig floor lens) is the
3. **A1.2 + A1.3** in one sitting at that webview → **A1.4** → unblocks **A2** and **A3**. highest-value thing before the NEXT gig**, since kick was audible only 59% of OPAL-26.
C2 is the highest-value thread once the release ships, and its design is already settled — *Do not start EPIC B. B1 is a declared dead end.*
it needs building, not deciding.
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