From 8752fc0c9835a6d76c710acaea0154ba31e6f0ee Mon Sep 17 00:00:00 2001 From: daniel-c-harvey Date: Tue, 23 Jun 2026 05:36:25 -0400 Subject: [PATCH] docs: resolve Phase 18 OQ7 seek-index granularity to 0.5s buckets --- PLAN.md | 12 ++--- .../phase-18-opus-low-data-streaming.md | 53 ++++++++++--------- 2 files changed, 34 insertions(+), 31 deletions(-) diff --git a/PLAN.md b/PLAN.md index 7bd65b1..8220acc 100644 --- a/PLAN.md +++ b/PLAN.md @@ -473,8 +473,8 @@ Settings menu** (not a bare app-bar control); OQ2 default → **Opus by default, network-awareness); OQ3 remembered → **persisted via the dark-mode seam** (cookie → prerender-read → `PersistentComponentState` → client cookie service); OQ4 → **always-on Opus + Backfill-Opus**; OQ5 → **Ogg Opus**; OQ6 transcode model → **background job after the file is available, with a visible -Post-Processing phase on the CMS upload meter.** One new tuning OQ (OQ7: seek-index granularity — recommend -~1–2 s buckets) is non-blocking. +Post-Processing phase on the CMS upload meter.** OQ7 (seek-index granularity) → **0.5 s (half-second) +buckets** (~115 KB index for a 1-hour mix). **Architectural spine — a derived artifact set + a delivery param + one new decoder + a precomputed accurate seek index; leaf implementations only, zero changes to existing format code (the strong OCP @@ -496,8 +496,8 @@ path on browsers that can't decode it. byte-offset seek and rough page interpolation are inadequate for VBR Opus — there is no linear time↔byte relationship. The fix is an **accurate transfer function built at transcode time** (the one moment the whole encoded stream is walked): a precomputed **seek index** mapping Ogg-page `granulepos` (48 kHz sample -counts → time) → exact byte offset (recommend ~1–2 s buckets snapped to page starts; ~58 KB for a 1-hour -mix). The decode **setup header** (`OpusHead`/`OpusTags`, needed to decode any mid-stream slice) is made +counts → time) → exact byte offset (**0.5 s buckets** snapped to page starts — OQ7; ~7,200 entries × +16 bytes ≈ ~115 KB for a 1-hour mix). The decode **setup header** (`OpusHead`/`OpusTags`, needed to decode any mid-stream slice) is made available too. Recommended concrete design: **one sidecar artifact per track = `[setup header][seek index]`, built at transcode, stored beside the Opus bytes, fetched once on track load**, parsed into `OpusSeekData`. Client seek flow: `calculateByteOffset(t)` binary-searches the index for the exact page @@ -554,8 +554,8 @@ bytes to serve, decode, or seek against until those artifacts exist. **Dependency shape:** `18.1 → 18.2 → {18.3 ∥ 18.4} → 18.5`; `18.6 ∥` (needs 18.3 for the live toggle); 18.1 is the only cold-start wave. **Phase-level: 18 precedes Phase 21** (windowed refill consumes the Phase -18 seek-index resolver). **OQ1–OQ6 RESOLVED (above); OQ7 (seek-index granularity, recommend ~1–2 s buckets) -is a non-blocking tuning steer.** None block 18.1. +18 seek-index resolver). **OQ1–OQ7 RESOLVED (above); OQ7 (seek-index granularity) = 0.5 s buckets.** None +block 18.1. --- diff --git a/product-notes/phase-18-opus-low-data-streaming.md b/product-notes/phase-18-opus-low-data-streaming.md index a54ec7b..43d7e1a 100644 --- a/product-notes/phase-18-opus-low-data-streaming.md +++ b/product-notes/phase-18-opus-low-data-streaming.md @@ -3,8 +3,9 @@ Product spec. Status: **design / framing — open questions RESOLVED (Daniel, 2026-06-23); implementation-ready.** Author: product-designer. Date: 2026-06-23. **No code has been written by this doc.** -> **Resolution pass (Daniel, 2026-06-23).** OQ1–OQ6 are resolved (see §6 — each marked RESOLVED, kept -> visible per file convention). Two resolutions reshaped the spec materially: (a) the listener quality +> **Resolution pass (Daniel, 2026-06-23).** OQ1–OQ7 are resolved (see §6 — each marked RESOLVED, kept +> visible per file convention; OQ7 — seek-index granularity — set to **0.5 s buckets**). Two resolutions +> reshaped the spec materially: (a) the listener quality > selection lives inside a **new public-site Settings menu surface** (not a bare app-bar control) — §4 + > §4a; and (b) Daniel rejected the "approximate page-interpolation" seek hand-wave outright — **VBR-safe > *accurate* seeking is now a first-class part of the architecture** (a precomputed seek-index artifact + @@ -329,15 +330,17 @@ estimated. fixed-width records is the natural shape (e.g. a `uint64 granulepos` + `uint64 byteOffset` per entry); the exact encoding is staff-engineer's, but it should be a **compact binary blob**, fetched once and parsed into a typed array client-side. -- **Granularity vs. size (the one real tuning knob).** One entry per Ogg page is the most precise but - largest; an Ogg page is typically a few KB of audio (~tens of ms to a few hundred ms), so a 1-hour mix - could be tens of thousands of pages. Recommend a **coarser bucket: one index entry per ~1–2 seconds of - audio** (snap each bucket boundary to the *nearest enclosing page start*, so every indexed offset is - still an exact page boundary). At ~1 s granularity a 1-hour mix is ~3,600 entries × 16 bytes ≈ **~58 KB** - — a trivial one-time fetch, and 1 s seek resolution is more than fine (the decoder re-syncs to the exact - page within the bucket anyway — see the client flow). **Per-page precision is the fallback if 1 s buckets - ever prove too coarse**, at a larger index. The number is staff-engineer's call; the *shape* (precomputed - exact granule→byte, bucketed, snapped to page starts) is fixed. +- **Granularity vs. size — RESOLVED: 0.5 s (half-second) buckets (Daniel, 2026-06-23).** One entry per + Ogg page is the most precise but largest; an Ogg page is typically a few KB of audio (~tens of ms to a + few hundred ms), so a 1-hour mix could be tens of thousands of pages. The chosen bucket is **one index + entry per 0.5 seconds of audio** (snap each bucket boundary to the *nearest enclosing page start*, so + every indexed offset is still an exact page boundary). At 0.5 s granularity a 1-hour mix is + ~7,200 entries × 16 bytes ≈ **~115 KB** — still a trivial one-time fetch, and 0.5 s seek resolution is + finer than required (the decoder re-syncs to the exact page within the bucket anyway — see the client + flow — so the in-bucket trim is *sub-half-second*, tighter than the earlier ~1–2 s recommendation). + **Per-page precision remains the fallback if 0.5 s buckets ever prove too coarse**, at a larger index. + The bucket size is now fixed; the *shape* (precomputed exact granule→byte, bucketed, snapped to page + starts) is unchanged. - **Sidecar, not embedded (recommended).** Store the index as a **third derived artifact** alongside the Opus bytes and the waveform datum — the same "derived artifacts get their own vault" pattern this phase already uses (S2 / `track-opus`; the `track-waveforms` precedent). Keep it a separate vault resource @@ -395,8 +398,8 @@ With the sidecar (`OpusSeekData` = setup header + granule→byte index) fetched page start, the stream is immediately Ogg-sync-aligned. 4. **Fine re-sync within the bucket.** The granule of the first decoded page tells the decoder the *exact* time it landed at (≤ the bucket granularity ahead of `t`); the scheduler trims/positions to land - playback at `t` precisely. With ~1 s buckets the trim is sub-second; with per-page granularity it is - near-zero. **Either way the listener lands at the correct time, not approximately** (AC9). + playback at `t` precisely. With 0.5 s buckets the trim is sub-half-second; with per-page granularity it + is near-zero. **Either way the listener lands at the correct time, not approximately** (AC9). #### D. Composition with Phase 21 windowed refill @@ -581,9 +584,9 @@ the "one source, multiple views" / design-for-adaptability discipline applied to ## 6. Open questions — RESOLVED (Daniel, 2026-06-23) -All six original open questions are resolved. Kept visible per file convention, each with the decision and -the section that now carries it. One new open question (OQ7) is raised by the seek-model design; it is a -narrow tuning/scoping call, not a blocker. +All seven open questions are resolved. Kept visible per file convention, each with the decision and +the section that now carries it. OQ7 (raised by the seek-model design) is a narrow tuning call, now set to +0.5 s buckets. - **OQ1 — Selection UX — RESOLVED: global, via a Settings *menu* (not a bare app-bar control).** Daniel: *"Global is perfect, but we need a menu system for settings, don't just slap the quality control directly @@ -612,15 +615,15 @@ narrow tuning/scoping call, not a blocker. track is lossless-only until its Opus finishes — accepted, and now made visible rather than implicit. `[RESOLVED — §3.1a]` -**New open question raised by the seek-model design (§3.4a) — narrow, non-blocking:** +**New open question raised by the seek-model design (§3.4a) — RESOLVED:** -- **OQ7 — Seek-index granularity (tuning, leans implementation).** The seek index trades precision against - size: per-Ogg-page (most precise, largest) vs. coarser time buckets snapped to page starts. Recommend - **~1–2 s buckets** (~58 KB for a 1-hour mix at 1 s; the decoder fine-re-syncs within the bucket so seek - *accuracy* is unaffected — only the in-bucket trim distance changes). This is largely staff-engineer's - call at implementation; flagged because the *number* is a deliberate choice and Daniel may have a feel - for acceptable index size vs. in-bucket trim. Does **not** block — the shape (precomputed exact - granule→byte, page-snapped) is fixed regardless of the bucket size. `[Daniel steer — not a blocker]` +- **OQ7 — Seek-index granularity — RESOLVED: 0.5 s (half-second) buckets (Daniel, 2026-06-23).** The seek + index trades precision against size: per-Ogg-page (most precise, largest) vs. coarser time buckets snapped + to page starts. Daniel set the bucket at **0.5 s** (finer than the ~1–2 s the spec had recommended): + ~7,200 entries × 16 bytes ≈ **~115 KB** for a 1-hour mix — still a trivial one-time fetch. The decoder + fine-re-syncs within the bucket so seek *accuracy* is unaffected; at 0.5 s the in-bucket trim is + sub-half-second, tighter than before. The shape (precomputed exact granule→byte, page-snapped) is + unchanged. `[RESOLVED — §3.4a A]` --- @@ -652,7 +655,7 @@ narrow tuning/scoping call, not a blocker. the client fetches and parses it once on track load (into `OpusSeekData`) before issuing any seek. - **AC9 (the seek-accuracy criterion) — an Opus seek lands at the *correct* time, not approximately.** Seeking to time `t` in an Opus stream resolves via the precomputed index and lands playback at `t` - (within the fine-resync tolerance — sub-second at the recommended bucket granularity), **measurably + (within the fine-resync tolerance — sub-half-second at the chosen 0.5 s bucket granularity), **measurably accurate**, not a `byteRate`/interpolation estimate. Verifiable: seek to a known marker (e.g. a downbeat at a known timestamp) and confirm playback resumes there, not seconds off. This holds **without** the full PCM decoded in memory (composes with Phase 21).