Skip to content

fix(director): air talk instead of blocking on an unready music pick [spec 04] - #31

Merged
wine-fall merged 1 commit into
mainfrom
zachg-0716--music-no-block-dead-air
Jul 17, 2026
Merged

fix(director): air talk instead of blocking on an unready music pick [spec 04]#31
wine-fall merged 1 commit into
mainfrom
zachg-0716--music-no-block-dead-air

Conversation

@wine-fall

Copy link
Copy Markdown
Owner

Problem

A real listening run (.dev/dev.log) opened with ~63s of dead air before the
first song. A talk beat was already synthesized and buffered, but when music was
due the branch entered _play_music_segment_take_pick and blocked ~56s
awaiting a not-yet-resolved prefetched pick
instead of airing the warm buffered
talk. The dev-log seam shows it plainly:

music.pick  found=True prefetched=True elapsed_s=56.52

prefetched=True with a 56s elapsed means the prefetch was in flight but not
done
, and the music branch awaited it — blocking the whole loop.

Root cause: the music branch awaited an in-flight prefetch, blocking the loop
even when talk was ready.

Fix

Guard Director._play_music_segment: when the prefetched pick is still
resolving
(in flight, not .done()), return to talk and re-attempt music at
the next boundary. The pick keeps resolving in the background — the slot is not
cleared, so no duplicate prefetch fires — and the depth-2 talk look-ahead
(spec 04 §3.3) covers the search adaptively, however long it takes, with no
dead air and no hardcoded "talk for N minutes" duration.

The cold path (nothing prefetched — the first-ever segment) and the resolved-pick
path are byte-for-byte unchanged; only an in-flight pick now defers instead
of blocking.

Tests

  • New regression test_music_does_not_block_on_an_unresolved_pick_airs_talk: a
    fake TrackSource whose pick never resolves now yields continuous talk and
    the run completes under max_segments. Pre-fix the music branch awaited the
    pick and the run hung (the test times out on old code).
  • Full unit suite: 250 passed. Every existing music-first test is preserved
    (they exercise the untouched cold path).

Spec

Aligned specs/spec04/04-no-dead-air.md §3.1 Consume bullet (no longer "finish
what's left" — a resolving pick now defers to talk) and added acceptance
criterion #4 (no-block / dead-air).

Implements specs/spec04/04-no-dead-air.md

Peer review

Peer review (codex gpt-5.5): 1 finding, 0 applied, 1 dismissed. The finding — a
resolved pick held across several talk boundaries under erratic
RandomCadence/BrainCadence grows stale and blocks a fresher prefetch — is a
spec-accepted trade-off (§3.1 "Staleness (accepted)" + §6 open question on
mood-staleness tolerance) and the intended single-slot "one pick ahead" design
(re-prefetching every talk would waste the expensive yt-dlp resolves slice 1
exists to avoid). Not a regression: the change adds at most one deferral boundary
to an already-accepted staleness window, and music airs whenever cadence next
chooses it.

AI coding brief

Original request: handoff scratch/handoff-spec04-followups.md Task 1 — a
real listening run (.dev/dev.log) showed ~63s dead air before the first song
because the music branch blocked ~56s awaiting a not-yet-ready prefetched pick
instead of airing the already-buffered warm talk. Why: cold-start
responsiveness / no-dead-air (spec 04).

Manual interventions: none — the fix followed the handoff's diagnosis
directly.

Retro: the handoff pre-diagnosed the root cause with the exact dev.log line,
which made this a one-guard fix. Precise seam-level evidence (the
prefetched=True elapsed_s=56.52 line) is what turned a vague "dead air" symptom
into a targeted change — capture the deterministic seam in the bug report and the
fix writes itself.

🤖 Generated with Claude Code

…[spec 04]

The music branch awaited an in-flight-but-not-done prefetched pick, blocking
the loop ~56s on a slow long-playlist resolve even with a warm talk beat
already buffered (observed live: `music.pick prefetched=True elapsed_s=56.52`).

Guard _play_music_segment: when the prefetched pick is still resolving, return
to talk and re-attempt music at the next boundary. The pick keeps resolving in
the background (slot not cleared, so no duplicate prefetch) and the depth-2 talk
look-ahead covers the search adaptively — however long it takes — with no dead
air and no hardcoded talk-for-N duration. The cold (nothing-prefetched) and
resolved-pick paths are unchanged.

Regression test: a fake TrackSource whose pick never resolves yields continuous
talk and completes under max_segments (pre-fix the run hung). Aligns spec04 §3.1
Consume + acceptance criterion #4.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@wine-fall
wine-fall merged commit 461f172 into main Jul 17, 2026
4 checks passed
@wine-fall
wine-fall deleted the zachg-0716--music-no-block-dead-air branch July 17, 2026 03:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant