Repository navigation
Hand a cued clip's warm decoder to Program (T1.11 / #449 step 2) - #492
Merged
Merged
Conversation
A clip taken from Preview to Program cold-started: a placeholder slab on air for the ticks before its first frame, and a take record reading `rebuilt` with `missingSources=[media:<id>]`. It cold-started because a clip changes identity TWICE on go-live. The `preview:` namespace that keeps a paused poster from replacing Program's rolling copy collapses, AND MediaGoLiveLedger advances the generation baked into the playback key. So the arriving request matched no entry in OwnedMediaFrameSource, a fresh decoder opened, and resolveLayers painted colorFromParticipantId over PROGRAM. adoptCuedDecoders now RE-KEYS the cue's entry onto the live request instead of letting it retire. Entry is a shared_ptr whose worker holds its own reference, so the hand-over is a map re-key: the decoder and its held poster never notice. This is not an exception to the go-live contract, it is the contract. The cue poster sits paused at frame 0 (MediaVideoPresentation::hold shows the first prepared frame and never advances), so resuming it is exactly "roll from 0, audio on". The decision is pure (modules/MediaCueHandoff.h, the CaptureReaderStallPolicy / TakeRecordPolicy shape). Every condition is required: same asset id AND same path, same loop flag, the retiring sourceId is exactly "preview:" + the arriving one, the arriving generation is exactly +1, and the cue NEVER ROLLED. That last one is load-bearing: a decoder that has played is at an arbitrary position, and adopting it would put a clip on air mid-roll while the take record still read `cut`. Two candidates for one arrival is refused loudly and cold-starts — never guess which cue is the predecessor. Three things the tests caught that reasoning had not: - It must run on the REQUEST path (selectVideo / pollMediaAudioFrames), never in manage(). The request set changes on the take tick, but manage() is a separate thread on a 2 ms wait, so an adoption deferred to it lands a tick late — at 60 Hz, exactly one frame of the placeholder this exists to remove. Adoption starts no thread and does no I/O, which is what makes it safe on the caller's path where creating a worker would not be. - The queued frames must be DROPPED and current_ kept (MediaVideoPresentation::dropQueued). They were scheduled against the cue's paused epoch and can never come due on the go-live clock, so keeping them froze the clip on its poster forever. The held poster is what covers the refill, and is what removes the flash. - The OWNER names the source, not the decoder. Every decoder stamps participantId from the layer it was handed, so selectVideo re-stamping it is normally a no-op — but an adopted poster was decoded under the `preview:` id and the compositor looks a media layer up by the LIVE source id. Without the re-stamp the hand-off delivered a frame nothing could match and Program painted the placeholder anyway. The take record now reads missingSources=[] for this case because the cold start stopped happening. The judge was not touched. Tests (903 pass, 0 fail; the threaded ones stable over three repeats): MediaCueHandoffTest.cpp covers every refusal; OwnedMediaFrameSource.ACuedClipHandsItsWarmDecoderToProgram and ProgramPixelContinuity.ACuedClipTakenToProgramNeverShowsThePlaceholder both fail against main (verified by reverting the wiring and rebuilding); AnAdoptedCueRollsInsteadOfFreezingOnItsPoster failed against the intermediate build that adopted without dropQueued; ACueThatAlreadyRolledIsNeverHandedOver and AnAdoptedCueTurnsItsAudioOn are contract guards that pass either way. Still cold-starts, honestly: a clip cut to Program that was never cued in Preview has no warm decoder to adopt. That is step 1 of #449, not done here. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014yuH7EMvWCWdkhvevrtMyJ
iamfatness
pushed a commit
that referenced
this pull request
Sep 12, 2026
beta-2026-09-12-aac2d98 is cut and published, so the "live check with the batch beta" that T1.14-T1.17 each name is now possible. It is still OWED: none of those fixes, nor T1.11 step 2, has been seen in a real meeting, and none may be ranked verified until it has. Build provenance recorded next to the claim so a later reader can tell what the beta actually proves. T1.11 stays at order 1 and drops from "S then M" to "S": #492 shipped step 2 (the warm cue decoder is handed to Program), so a clip cued in Preview no longer flashes. Step 1 remains for a clip taken without a cue, which has no warm decoder to adopt. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014yuH7EMvWCWdkhvevrtMyJ
iamfatness
added a commit
that referenced
this pull request
Sep 12, 2026
* docs(backlog): reconcile with main and the issue tracker Three things the file and the tracker disagreed on, all verified against main @ eefee0c and `gh issue`/`gh pr` state. No re-ranking. - T1.17 (#479) was still the next open Tier 1 item. It is closed by #490, which is on main. Moved to Done; the open order renumbers 1-3, so the top of the list is now T1.11 (#449), T1.12 (#475), T1.13 (#473). - T3.8 (#482, ISO crops/pads a guest on a mid-recording frame-size change) was filed 2026-09-11 with no tier row, against this file's own rule that a new defect gets an issue AND a row. Placed in Tier 3 as a proposal, labelled as such — the tier is not an owner ruling. - The "shipped in beta-2026-09-11-404602b" line covered the whole Done table, but T1.14-T1.17 (#487, #488, #489, #490) merged after that beta was cut. Each one's Done row asks for a "live check with the batch beta", and no such beta exists yet. Said so explicitly. Also noted why Tier 3 has no T3.6 (#465 was promoted to T1.15). Separately, #432 (T1.5) was closed on GitHub: #462 said "Fixes #432" and merged as 3a3bedf but never auto-closed it, so the issue contradicted this file's Done row. #457 (T1.7) has the same unclosed-by-#462 shape but is left open deliberately — its own PR says the real check is a long in-meeting close, which has not run. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014yuH7EMvWCWdkhvevrtMyJ * docs(backlog): the batch beta exists; T1.11 step 2 shipped beta-2026-09-12-aac2d98 is cut and published, so the "live check with the batch beta" that T1.14-T1.17 each name is now possible. It is still OWED: none of those fixes, nor T1.11 step 2, has been seen in a real meeting, and none may be ranked verified until it has. Build provenance recorded next to the claim so a later reader can tell what the beta actually proves. T1.11 stays at order 1 and drops from "S then M" to "S": #492 shipped step 2 (the warm cue decoder is handed to Program), so a clip cued in Preview no longer flashes. Step 1 remains for a clip taken without a cue, which has no warm decoder to adopt. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014yuH7EMvWCWdkhvevrtMyJ --------- Co-authored-by: Claude <noreply@anthropic.com>
iamfatness
added a commit
that referenced
this pull request
Sep 12, 2026
…acOS CI (#498) I broke main with #492. Three path literals were written with SINGLE backslashes -- "C:\media\clip.mp4" -- so \m, \c and \o are invalid escape sequences. MSVC accepts them with a warning and the tests passed locally; GCC and Clang reject them outright: native/tests/MediaCueHandoffTest.cpp:56:30: error: '\o' not followed by '{' That failed native-stub, native-stub-tsan, native-stub-macos, native-metal-macos and mac-show-drill on every commit and PR since aac2d98. I reported "903 tests pass" and it was true, but only on the one platform I built. I did not check CI after merging, which is the actual mistake. A Windows-only local build cannot catch this class, so the fix ships with the check that can: scripts/qa/check-string-escapes.py walks the native tree for escape sequences that are invalid in standard C++. It found a SECOND instance I had missed by grep (ProgramPixelContinuityTest.cpp:190), which would have kept CI red after a fix that looked complete. 903 native tests still pass on Windows with the corrected literals; the paths are test data compared as strings, so the assertions are unchanged. Claude-Session: https://claude.ai/code/session_014yuH7EMvWCWdkhvevrtMyJ Co-authored-by: Claude <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #449 (step 2).
docs/BACKLOG.mdT1.11.A clip taken from Preview to Program cold-started: a placeholder slab on air for the ticks before its first frame, and a take record reading
rebuiltwithmissingSources=[media:<id>].Why it cold-started
A clip changes identity twice on go-live:
sourceIdpreview:media:<id>media:<id>mediaPlaybackKeymedia:<id>:live:<n>media:<id>:live:<n+1>Both are deliberate — the
preview:prefix keeps a paused poster from replacing Program's rolling copy, and the generation bump is what guarantees "roll from 0, audio on". ButOwnedMediaFrameSource::requests()keys a decoder onsourceId|path|assetId|playbackKey|loop, so the arriving request matched nothing, a fresh decoder opened, andresolveLayerspaintedcolorFromParticipantIdover PROGRAM.The fix
adoptCuedDecodersre-keys the cue's entry onto the live request instead of letting it retire.Entryis ashared_ptrwhose worker holds its own reference, so the hand-over is a map re-key — the decoder and its held poster never notice.This is not an exception to the go-live contract, it is the contract. The cue poster sits paused at frame 0 (
MediaVideoPresentation::holdshows the first prepared frame and never advances), so resuming it is exactly "roll from 0, audio on".The decision is pure (
modules/MediaCueHandoff.h, theCaptureReaderStallPolicy/TakeRecordPolicyshape). Every condition is required: same asset id and same path, same loop flag, the retiringsourceIdis exactly"preview:" + arriving.sourceId, the arriving generation is exactly +1, and — the load-bearing one — the cue never rolled. A decoder that has played is at an arbitrary position, and adopting it would put a clip on air mid-roll while the take record still readcut. Two candidates for one arrival is refused loudly and cold-starts: never guess which cue is the predecessor.Three things the tests caught that reasoning had not
selectVideo/pollMediaAudioFrames), never inmanage(). The request set changes on the take tick, butmanage()is a separate thread on a 2 ms wait, so an adoption deferred to it lands a tick late — at 60 Hz, exactly one frame of the placeholder this exists to remove. Adoption starts no thread and does no I/O, which is what makes it safe on the caller's path where creating a worker would not be.current_kept (MediaVideoPresentation::dropQueued). They were scheduled against the cue's paused epoch and can never come due on the go-live clock, so keeping them froze the clip on its poster forever. The held poster is what covers the refill.participantIdfrom the layer it was handed, soselectVideore-stamping it is normally a no-op — but an adopted poster was decoded under thepreview:id, and the compositor looks a media layer up by the LIVE source id. Without the re-stamp the hand-off delivered a frame nothing could match and Program painted the placeholder anyway.The take record now reads
missingSources=[]for this case because the cold start stopped happening. CLAUDE.md says that record is honest and the judge must not be taught to excuse a cold start — the judge was not touched.Verification
903 native tests pass, 0 fail (892 on
mainbefore this). The threaded tests were run three times each to check for flakiness; stable.Red-green, verified by reverting the production wiring and rebuilding:
OwnedMediaFrameSource.ACuedClipHandsItsWarmDecoderToProgramProgramPixelContinuity.ACuedClipTakenToProgramNeverShowsThePlaceholderOwnedMediaFrameSourceAnAdoptedCueRollsInsteadOfFreezingOnItsPosterdropQueued, which is where I watched it failACueThatAlreadyRolledIsNeverHandedOvereverPlayed, which the unit test cannotAnAdoptedCueTurnsItsAudioOnMediaCueHandoff.*(6)A sensitivity check on the audio test found two dead lines in the adoption path (the creation loop re-arms
wantsAudiounconditionally in the samemanage()pass); they were removed rather than kept as decoration.Not verified: no live run. The proof is the pixel test over the real owner plus the take record, not a real meeting.
What this does not fix
A clip cut to Program that was never cued in Preview has no warm decoder to adopt and still cold-starts. That is step 1 of #449 — hold the outgoing picture until the first real frame — and is next.
No shell code changed.
🤖 Generated with Claude Code
https://claude.ai/code/session_014yuH7EMvWCWdkhvevrtMyJ