Repository navigation
Conversation
Quest-Claim: e97694e335dd9933
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… fetches only the sound it plays Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…omposition waits for timed catalogs A broadcast source clears what a stream showed once it ends or another replaces it, so the composition drops an offline guest instead of remembering its offset. A layer keeps its strip and fetches its sound only while a mix routes it, so a sound change no longer reopens it, and a still never reads as stalled. The quest is rewritten to moving the composition into its broadcast's hang catalog, which requires the new pin-timed-catalog quest. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Design change: after the round-3 design checkpoint, the maintainer chose (2026-11-12, /quest-complete) that a broadcast source going offline clears its picture, in the studio, for every broadcast source, and the composition drops the guest until they return (rejected: holding the frozen picture and qualifying the README; merging with the findings open). On b2c6598:
Also settled: the composition keeps #146's shape (child entries carry sound, priority, toggle; placements in (Written by Opus 5.5 with low effort) |
…eps its landing A layer's offset is a watch, so playback finds its moments again when the composition moves it, even with its queue full. A broadcast source keeps where its stream last landed while a replacement video track has shown nothing, so a video-only guest stays in the composition. The README's example offset is a realistic one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Each audio scene that reaches a published source gets a second mix, which Listen and guests' return feeds read, with that source in at its gain, mute, pan, and delay; the program, recordings, and published scenes still encode the mix without it. This settles and deletes quest/m2/compose/hear.md. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… the pin moves Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Advances the quest
quest/m2/compose/layout.md, rewritten here to what remains: moving the composition into its broadcast's hang catalog once moq's timed catalogs are pinned.Problem
The provisional
live.kixel.studio.layerstrack named tracks of the program's own broadcast, so a guest published independently had nowhere to go: a guest's broadcast keeps its own timeline, and hang'sinclude(moq-dev/moq#7943) requires an included broadcast to share the listing catalog's clock. The maintainer settled on 2026-11-12 that the studio publishes a composition, a catalog of catalogs: its own broadcast, with the program and every independently published source, local or guest, as a broadcast of its own, in a versioned format the studio prototypes.Approach
moq_scene::composition(replaceslayout):Compositionv2 withcanvas,start,program(a relative broadcast ref and its instance),sources(each child's relative broadcast ref, instance, signed microsecondoffset,sound,priority,toggle), andpictures.Publisherwrites it as a JSON snapshot tracklive.kixel.studio.compositionon a broadcast at<--broadcast>/composition, whose hang catalog'smedia.clockis the program's.publish.rs): each becomes a broadcast at<--broadcast>/source/<name>with its own hang catalog on the program's clock, replacing the renamed tracks on the program's broadcast. A local published source's name must be one path segment.Source::independent()). The studio publishes nothing more of it; the composition names it where the studio reads it, relative to the composition (../guests/alice), with the offset where the studio's playout put its timestamps on the program's clock (Feed::offset, from the newest shown picture or the playhead). A new composition follows a drift pastDRIFT(10 ms), and a new instance starts afresh.moq-studio --watch: now takes the relay URL plus--broadcast, since the composition's children are siblings of the program and must resolve under the connection's root. Each child plays as aSource::Layeron its own broadcast, its timestamps shifted by the offset onto the program's clock; a new offset updates in place without fetching again. A child whose instance another stream replaced shows nothing until a composition names it, and a composition naming another program instance waits for it (relay-minted instances are not compared).README.md: "Published independently", "Composition" (was "Layer layout"), and "Watching" rewritten.server.md,hang-layers.md, composeREADME.md,m0/README.md);controls.mdupdated to the per-sourcepriorityandtogglethis carries.Decisions
Settled by the maintainer on 2026-11-12 (/quest-complete):
followclears what a stream showed once it ends or another replaces it,Feed::landslost its offset memory, a layer keeps its strip and fetches sound only while a mix routes it (so a sound change no longer reopens it), and a still is never stalled.sound,priority, andtoggle, and placements live inpicturesnaming a child, so one broadcast can show in several places (rejected: one placement per child, as moq's spec quest said). moq'squest/m3/layers/spec.mdnow matches (moq-dev/moq#8592).live.kixel.studio.compositionas an interim track). The composition belongs in the composition broadcast's hang catalog, applied at each update's timestamp (moq's catalog-at-timestamp, moq's composition plan). The code is not restructured onto the catalog here; the quest is rewritten to that remaining work, requiring a new pin quest.Settled by the maintainer on 2026-11-12 (/quest-complete), second round:
quest/m2/compose/hear.mdas its Plan suggested (deleted here): Listen and guests' return feeds hear published sources, while the encoded program, published scenes, and recordings still leave them out (rejected: keep it a follow-up and accept the gap; don't offer publishing guests yet). Done:Sources::listenedis a second mix for each audio scene reaching a published source, read by the monitor, each return feed (and its mix-minus), and the audio scenes' meters;Sources::mixstays the encoded one.Choices made without asking (for review)
The quest's "Recommended, not decided" items, and other judgment calls:
pictures: a child entry carriessound,priority, andtoggle; placement, crop, filters, visibility, and keyframes stay per picture, because one source can be shown by several items (clones, the top layer). Socontrols.md's toggle/priority are per subscription, as it needs. (Recommended item, adjusted.)DRIFT= 10 ms, so the playout clock's jitter does not publish a group every frame, while staying well inside the ~45 ms sound-before-picture detectability threshold (ITU-R BT.1359).<--broadcast>/composition, local sources at<--broadcast>/source/<name>. A published scene namedcompositionis refused, since it would publish at the composition's path.~) instances are left out, since another relay mints its own.quest/m0/pin-sync.mdhas the studio do once that quest lands; until then the studio holds a guest's picture through such a stall and a viewer does not.Impact
Breaking, on the wire and for users:
live.kixel.studio.layerstrack and the program broadcast's renamed per-source tracks are gone. Viewers read<--broadcast>/compositionand each listed broadcast instead. Migration: a player of layout v1 must read the composition (v2); there is no other player but--watch.--watch https://relay/anon/studiobecomes--watch https://relay/anon --broadcast studio.a/bfails validation and must rename it.<--broadcast>as before.Public API and wire impact
moq_scene::layout→moq_scene::composition(Composition,Program,Child,Version,Publisher,TRACK,PATH,SOURCES,DRIFT).Source::Layernow{ clock, path, instance, offset }(was a track of the program's broadcast), always with a strip, fetching its sound only while a mix routes it;Feed::offsetbecomesFeed::landsreturningLanding { instance, offset },Nonewhile the source shows nothing of a stream; aSource::Broadcastwithout video shows an image still; newSource::independent()andSource::plays_alike()(an offset-only layer change keeps its feed and picture).Sources::listened(scene): the mix people in the studio hear, published sources included;Sources::mixis the encoded one.publish::Publish::forwardtakes the origin and base path to publish each source's broadcast.<b>/compositionwith tracklive.kixel.studio.composition(JSON snapshot, v2); new broadcasts<b>/source/<name>; the layers track and renamed tracks removed.Alternatives
media.clockwall times) would ignore the studio's playout delay, so viewers would show guests earlier than the studio did; the offset is where the studio actually played it.--watch(follow(path, Some(instance))) never matches relay-minted instances, so the watcher follows the path and skips a non-matching unminted instance instead.Validation
just cipasses (fmt, clippy with every feature, 312 tests).just render-swlines for moq-scene compositor and moq-studio engine/watch on lavapipe pass (55 + 8), including the new end-to-endwatch::tests::plays_a_guest_where_the_studio_did, added torender-sw: a relay, a guest broadcast on its own clock started 1.5 s before the program's, and the studio publishing it independently. Read off the relay, the composition lists../guests/aliceonce, and its offset is checked against a model:offset - (guest wall clock - program wall clock)must equal the studio's measured playout delay for alice, within one frame plusDRIFT. A watcher built from that composition playslayer/alicethrough the relay toLive.moq-relay(moq'slocalhost.toml), a secondmoq-studiopublishing a green program atguests/alice, the studio (blue background,aliceand a local redbadgepublished independently) atstudio, andmoq-studio --watch http://localhost:14443/ --broadcast studio, all on moq-lite-07-wip. The watcher's window showed the blue program with the green guest at (40,40) 480x270 and the red badge at (1000,560) 200x100 scaled to the window, matching the studio's Program panel, and opened two H.264 decoders (the program and the guest; the badge is a still).a_replacement_track_keeps_its_delayalso checks a video-only broadcast keeps its landing while a replacement video track has shown nothing. The offset-change wake (offsets.changed()infollow_video/follow_audio) has no deterministic test: a paused-clock test cannot fill the decode queue reliably, since decoding runs off the runtime.just ci(315 tests) and the lavapipe compositor (55) and engine/watch (7) lines pass on da69368.source::tests::a_published_source_is_heard_but_not_encoded: a published source leaves the encoded mix, stays in the listened one, leaves that on restart, and the second mix goes once nothing is published.source::tests::a_broadcast_shows_a_still(and never stalls),a_layer_fetches_only_the_sound_it_plays(the audio track's subscriber demand comes and goes as the layer enters and leaves the audio scene, with no reopen), anda_broadcast_lands_on_a_timeline(offline, the feed clears its picture and lands nowhere).compositor::tests::a_layer_moved_on_its_clock_keeps_its_picture(lavapipe, inrender-sw): an offset-only change keeps the layer's feed and its picture on the next render; it fails with the previous equality check.Follow-ups
quest/m2/compose/pin-timed-catalog.md[S]: pin moq once hang's timed catalogs land;quest/m2/compose/layout.mdrequires it. No open decisions.Waiting
quest/m2/compose/pin-timed-catalog.md), so the composition can move into its broadcast's catalog (maintainer's 2026-11-12 decision 3). Check: that quest's PR has merged on moq'smain, deleting its file, and themoqgitlink on studiomainincludes it (rs/hang/src/catalog/consumer.rsreturns each catalog with its timestamp).Caused by
None: new work replacing a provisional format.
Models
Size: L
Implementer: Claude Code, claude-opus-5-5, unknown effort
Implementer: Claude Code, claude-opus-5-5, low effort (iterate, M)
🤖 Generated with Claude Code
(Written by Opus 5.5)
Review
Rewritten by
quest reviewafter each round.Head: 1a0107c, named in full once no reviewer reports a finding
Base: origin/main at 5b6c2c8
Reviewer: Codex, gpt-6.1-sol, xhigh effort
Session:
01a1d1a0-0006-76d1-994e-8b9f8af2a769, round 3Score: 10/10,
Code review found no new blockers in the incremental audio routing changes; builds and tests were not run, as instructed.Findings: none
Reviewer: opencode, opencode-go/deepseek-v4.1-flash, high effort
Session:
ses_e2e5ff872ffebc3XwH7XgtIRU7, round 3Score: 8/10,
The earlier monitoring gap is fixed: the monitor and guest return feeds now read a second mix that routes published sources while the program, recordings, and published scenes keep the encoded mix, but the mixer's scene meters and waveforms still read the encoded mix, so they disagree with what Listen plays.Findings, open until the next round settles them:
rs/moq-studio/src/ui/levels.rs:80:Audio-scene meters still read the encoded mix while Listen plays the listened mixThe last round's findings, at 6c47d61:
rs/moq-scene/src/source.rs:447:Publishing a guest makes the studio stop hearing that guest;Sources now keeps a second listened mix for each audio scene reaching a published source (source.rs Sources::listened and the sync routing), the monitor picks it at engine.rs:1314, the return feed uses it at feed.rs:103, while the program, recording, and published-scene audio still use Sources::mix, and a_published_source_is_heard_but_not_encoded covers routing, leaving on restart, and dropping the second mix when nothing is published.Round 3's one low finding (opencode,
rs/moq-studio/src/ui/levels.rs:80, audio-scene meters read the encoded mix while Listen plays the listened one) is fixed on da69368: the meters readSources::listened, and the README's mixer paragraph says so.