# Session 0021.0 — Transcript

> App: human-experience-filter-art
> Start: 2026-06-26T08-12 (PST)
> End: 2026-06-26T08-20 (PST)
> Type: executing-plans (operator-driven hotfix/revision of session 0020's audio feature)
> Posture: yolo
> Claude-Session: e0cc053a-41fa-416d-a4f5-493f14984e75
> Status: FINALIZED

## Launch prompt

> Show video unchecked doesn't turn off the video. Make "show video" just "Video"
> and have a toggle like "Dev Mode" has. I can't hear any audio when "soundtrack
> (follows altitude)" is selected. Let's just have Audio as off/on like Video for
> now and get rid of white noise.

## Pre-state

- Session 0020 had just merged the audio feature to `main` (Visual×Audio split,
  per-altitude soundtracks, pink white-noise, A/B crossfade). The operator tested it
  live and reported three issues.

## Session arc

1. **Diagnosis (the key work).** Both reported bugs were reproduced *only* in the
   operator's real browser — neither appeared in headless Chromium **or** WebKit
   (installed `playwright install webkit` to test Safari's engine). Root causes:
   - **Video-off doesn't blank:** `#vid` (`<video>`) and `#paint` (WebGL `<canvas>`)
     are GPU-composited layers; the `#black` cover had *auto* z-index, so on a real
     GPU (Safari/Chrome) the video layer can composite *above* it despite DOM order.
     Headless software-raster hides this.
   - **No soundtrack audio:** real Safari/iOS unlocks an `<audio>` element only when
     `play()` is called *synchronously inside* the user gesture; the 0020 gesture
     played in an async continuation (after `await fetch`), which Chrome allows but
     Safari blocks. Headless WebKit relaxes autoplay, so it passed.
   This is the session's main lesson: **Playwright headless relaxes both GPU
   compositing and autoplay**, so the 0020 E2E couldn't have caught either — real
   device / by-eye review is required.
2. **Fixes (branch `session-0021`):**
   - **Video** + **Audio** are now Dev-Mode-style on/off toggles (`.dev-switch`),
     wired on `change`. Audio on → soundtrack; **white-noise deferred** (removed from
     the live control; `AUDIO_SOURCES={off,soundtrack}`; the pure pink-noise
     machinery + tests stay for later).
   - Video-off: `.black` gets `z-index:50` + `translateZ(0)` (own layer) **and** the
     video layers are set `opacity:0` on video-off (belt-and-suspenders blanking).
   - Safari audio: the tap-to-start gesture now primes both A/B `<audio>` elements
     **synchronously in-gesture** (set src, vol 0, `play().then(pause)`), unlocking
     the later async crossfades.
   - Server: `player/audio.py` resolver + `controls.py`/`state.py` + `/api/alteration`
     drop white-noise; `build_audio_media.py` stops emitting the noise bed.
3. **Verify + ship.** Updated unit/contract/E2E tests; rewrote the 3 E2E for the new
   toggles (click the visible `.dev-switch-track`; wait for the opacity transition).
   **288 passed, 2 skipped.** Re-verified in **both Chromium and WebKit**: Audio toggle
   plays `cosmos/pillars.loop.mp3`, video-off sets vid+paint opacity 0 with `#black`
   shown, zero JS errors; screenshotted the new toggle UI. Merged `session-0021` →
   `main` and pushed. (Also untracked a stray `docs/.threads/*.json` editor artifact
   swept in by `git add -A`, and gitignored `.threads/`.)

## Deferred decisions

_No low-confidence calls. All three changes were explicit operator requests; the
root-cause fixes (GPU z-index/layer-hide; Safari in-gesture unlock) are the standard
remedies for these well-known browser behaviors._

## Cut state

- `main` @ `fddb6d6` (merge of session-0021), clean, pushed.
- **288 tests + 3 Playwright E2E pass** (2 skipped: opt-in media integration).
- White-noise is deferred, not deleted: `tools/pipeline/audio_ops.white_noise_args` +
  `audio_run.generate_white_noise` + their tests remain; only the live control + the
  build-script emission were removed.
- **Unvalidated by this session:** real-device audio (Safari autoplay) and real-GPU
  video-off — headless can't exercise either; needs the operator's by-ear/by-eye pass.

## Operator plate

- Video + Audio are simple on/off toggles (Dev-Mode style). Audio on = the current
  altitude's soundtrack. The two real-browser bugs should now be fixed — but please
  confirm on a real device (especially iPad/Safari).
- Deferred: white-noise (easy to re-add as a 3rd Audio position), the per-altitude
  music layer, the networked-control-surface build, Pi renderer.

## Next /goal

```
/goal by-ear/by-eye review the audio + Video/Audio toggles on a REAL device (esp. iPad/Safari — headless can't validate Safari autoplay or GPU compositing), then source/compose the deferred per-altitude music layer per docs/audio-candidate-pool.md (or re-enable white-noise as a 3rd Audio position if wanted)
```
