Hold 0111's ladder as values: which source, which streams, and what the handover carries (#111) - #294
Merged
Conversation
…he handover carries (#111) 0111 fixes that the play call carries the client's own capability description and the answer is read against it, that a source sits on the highest of four rungs the description admits and is judged only on the streams that will be played, that a tie goes to the order the server listed, how the audio and subtitle streams are chosen, what the handover carries and nothing else, and that nothing playable is request-refused rather than an empty answer. The record had landed and nothing in the tree held any of it, which is the state 0111 was written against: the first playback code answers five records at once, and the shortest correct-looking code takes the first source listed. src/playback/handover.rs is the rule. What the server offered arrives as values, each source has its streams chosen first and is judged on those and its container, and the highest rung wins with fewer converted streams winning on the conversion rung and the server's order breaking a tie. The start is 0058's answer applied. A source played as it stands names itself and carries no address, because the server supplies one only for the rungs below and 0010's table has no row for the path the core would build. Every lower rung is built from the address the server supplied, so a rung without one cannot be written. Nothing playable is told apart as the server's refusal or the core's, and both become one kind through a further entrance to 0037's mapping point in src/failure/mod.rs. Eight guards were watched failing, each reddening the case named for it: taking the first source listed, judging every stream rather than the ones to be played, treating a picture of unstated size as covered, ignoring the ceiling, giving a tie to the later source, formatting the address the server put the token into, reading the file's default flag before the server's index, and ignoring the server's error code. The ninth is the address guard, and its one-line removal does not compile. Nothing here sends a byte. #111's four test conditions are each a run against the fake server and every one of them is untouched; the issue stays open on them and on the address for a source played as it stands. Signed-off-by: Nils Lehnen <30603423+iderex@users.noreply.github.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.
The issue this belongs to
#111. The last section says which of its conditions this reaches, which is one of the five as written, and what it reaches instead of the other four.
What changed
src/playback/handover.rsis 0111's ladder as values.docs/decisions/0111-which-source-is-played-and-the-handover.mdhad landed on 2026-08-16 and nothing in the tree held any of it: no rung, no stream rule, no handover type, and no refusal for nothing playable. That is exactly the state the record was written against, in its own words: the first playback code answers five records at once, and the shortest correct-looking code takes the first source the server listed.What the server offered arrives as values, in the shape both supported lines answer with: per source its container, its streams with kind, codec, language, default flag and picture size, the three route flags, the address the server supplies for a route below the top one, its rate, and its default stream indices. The fields were read off
MediaSourceInfo.csandMediaStream.csat the two commits 0010 names, and the diff between the two commits moves none of the fields this reads:Handover::chosenis the rule. Each source has its streams chosen first, by 0111's stream rule, and is judged on those and its container; it lands on the highest rung the description admits or on none; the highest rung across sources wins, with fewer converted streams winning on the conversion rung and the server's own order breaking a tie. The start is 0058's answer applied:Resume::Atis where playback starts and the two answers that offer no resume start from the beginning. Nothing playable is told apart as the server's refusal or the core's, andNothingPlayable::as_failurehands both toFailure::nothing_playable, which is the one further entrance this change adds to 0037's mapping point insrc/failure/mod.rs. The module cannot name a variant ofFailurein an expression, exactly as before.The other files register the module where every landing registers one:
src/playback/mod.rs, the crate's thread assertions insrc/lib.rs, and the same assertions asked from outside intests/thread_statements.rs.src/playback/is not an area on.github/coverage/pinned-surface, so that register is untouched.The means is the one the tree already carries, which is the check
## Choosing the meansasks for: values in the language 0011 measured and chose, judged by the suite that already exists, with no dependency, no runtime and no parser added. What the answer body looks like on the wire is not parsed here and is not decided here.What this decided that 0111 left to whoever built it
A picture whose size the server did not state is not covered by the description. 0036's second part is "at what sizes", a size nobody stated cannot be inside it, and 0036 names the direction this fails in as the cheap one: a conversion the device did not need, rather than a stream that fails inside a decoder. The alternative reads as generous and ships the failure 0111 spends its argument on.
Every rung below the top one is built from the address the server supplied, and a source with none clears no lower rung. Both supported lines supply an address only where the source is not played as it stands, read at the commits 0010 names, and the address is what the player opens. The one-line removal of that guard does not compile, which is written in the guard section below rather than claimed as a run that went red. A source the server marks as playable as it stands, that the description does not cover, and that carries no address, is therefore nothing playable from the core: that is the mismatch 0111 names, arriving here rather than inside a decoder.
Two things can mark a default stream and both are read, the server's own index first. The source's
DefaultAudioStreamIndexis the server's statement about this source and the flag on a stream is the file's about itself; where they disagree the server's is the one that reflects what it will serve. Reading the flag first reddens the language case.A source describing no stream clears no rung. It is judged by nothing, and the alternative admits a file on the strength of its container alone.
Names are compared without regard to letter case. A container or a codec is the same platform fact spelled by two parties, and a client writing
MKVwould otherwise have every file converted with nothing saying why.A source whose rate the server did not state is not above any ceiling, because the core cannot say that it is, and 0111 has the core invent no ceiling of its own.
What failure it prevents
The first source the server listed being taken, because it compiles and it works on every machine where every file is already playable. 0111 names that as the decision that gets taken by accident, and says it ships a stream the first narrower device cannot decode, with the failure arriving from inside the platform's decoder where 0004 has no kind and the core no visibility. The case named for it went red with the ladder's comparison deleted.
A source judged on a track nobody will hear. A file whose second audio track is undecodable would be converted for a stream the person was never going to play; the ladder judges only the streams chosen for it, and the case named for it went red with every stream judged.
A token in a formatted line. Both supported lines write the session's token into the address they supply for a conversion, so
ConversionAddressformats by hand and the case that reads the output went red with the derived formatting restored.Evidence
All at
eae60866cf1ec45029e2119928534e7561aaac98, with a clean working tree:The invariants leg was run at the same commit, alone, because the machine this was written on was under load from other sessions and the run took 263 seconds where it had taken a few earlier in the day:
Nothing under
src/reaches the network, before or after this change:What a guard here refuses, and the proof it bites
Each violation below was applied to
src/playback/handover.rson the working tree at this commit's content, the module's cases were run, and the file was restored and compared byte for byte against the kept copy before the next one. Every run iscargo test --locked --lib playback::handover, and the green run without any violation is the first line under Evidence.false, so the first source listed is keptthe_first_source_listed_is_not_taken_when_a_later_one_sits_higher,fewer_converted_streams_wins_between_two_sources_on_the_conversion_runga_stream_nobody_will_play_is_not_judged,a_subtitle_is_chosen_by_the_same_rule_and_counts_when_judgedis_some_andtois_none_or)a_picture_larger_than_the_largest_or_of_unstated_size_is_not_played_as_it_standsa_source_above_the_ceiling_is_not_a_candidate_and_one_of_unstated_rate_is<to<=)a_tie_on_one_rung_goes_to_the_source_the_server_listed_first,a_source_above_the_ceiling_is_not_a_candidate_and_one_of_unstated_rate_isthe_conversion_address_is_written_out_nowherelanguages_are_read_in_order_then_the_default_then_the_first_listeda_server_that_offers_nothing_is_its_refusal_and_its_code_rides_the_kindEach of those eight runs reported exactly the cases named and no other, and the summary line of each was
test result: FAILEDwith 19 or 20 passed.The ninth guard is that a rung below the top one is built from the server's address, so a source without one cannot land there. Its one-line removal, replacing
let address = self.conversion_address.as_ref()?;with a constructed address, does not compile:That is a proof by construction rather than a run that went red, and I am saying so rather than counting it among the eight. The case
a_route_below_the_top_needs_the_address_the_server_supplies_for_itasserts the behaviour from outside.What this does not cover
None of #111's four test conditions. A test producing a handover from a play call against the fake server, a test covering a source the device cannot decode and proving the conversion path is the one requested, a test covering a server that offers nothing playable, and the handover crossing nothing 0112 draws are each a run with a request in it, and nothing here sends a byte. The fifth condition, the source-selection rule in
docs/decisions/with its reason, was met by the record on 2026-08-16 and is untouched. The issue stays open on the four.The address for a source played as it stands. 0111 says the handover carries an address the platform's own player opens. For every rung but the top one the server supplies it in its answer and
WhatThePlayerOpens::TheServersConversioncarries it as it arrived. For a source played as it stands the server supplies none, the path is one the core would build, and 0010's table has no row for it:0010 is the authority for which paths the core may name and says of itself that a path outside its table arrives by a superseding record, so
WhatThePlayerOpens::TheSourceAsItStandsnames the source and carries no address, and the row is a rule question I have not answered. It is written on #111 rather than decided here.Nothing parses an answer body.
WhatTheServerOffered::answeredtakes values; the reading that turns bytes into them sits at 0037's mapping point for a body nothing in this tree reads, and 0037 already names that half as owed.Nothing emits an event.
NothingPlayablesays which of the two refusals it was so that an event under 0100 can carry the difference, and nothing here holds a diagnostics facility at a point where a play call is answered.Nothing measures anything. The interval 0008 opens on this call is
play.core, its span is declared insrc/measurement/and opened by nothing outside that module's own cases, and #65 is where a number comes from.One language list serves both kinds of stream, which is 0111's sentence as written; a caller wanting one list for audio and another for subtitles is a reversal for the record and not a field added here.
The change is 1474 added lines in five files, of which the module is 1391 and its cases are about half of that. One property holds across every changed byte: values and their cases, with no request, no store and no clock. I am stating the size rather than leaving it to be found.
Who has read it
Nobody other than me has read this change. There was no second reader available for it, and the evidence above stands in place of one.