From ae148d5c4a56139bfbee0eb0c63059c36c9d3af6 Mon Sep 17 00:00:00 2001 From: OneTooMany Date: Thu, 1 Oct 2026 12:47:45 +0200 Subject: [PATCH 1/6] quest: plan fallback renditions Co-Authored-By: Claude Opus 5.5 --- quest/m1/README.md | 1 + quest/m1/js-ranked.md | 3 +- quest/m1/rendition-fallback.md | 50 ++++++++++++++++++++++++++++++++++ 3 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 quest/m1/rendition-fallback.md diff --git a/quest/m1/README.md b/quest/m1/README.md index da1236cff0..58765f9327 100644 --- a/quest/m1/README.md +++ b/quest/m1/README.md @@ -106,6 +106,7 @@ QUIC studies there on that rule. - [CMAF surround Opus](/quest/m1/cmaf-opus-surround.md) - fMP4 import and export carry an Opus channel mapping table - [A self-hosted NVIDIA runner is registered](/quest/m1/gpu-runner.md) - the maintainer registers the host that runs the NVIDIA tests - [GPU CI](/quest/m1/gpu-ci.md) - NVIDIA tests run nightly on a self-hosted GPU runner, and `just rs nvidia` runs them locally instead of skipping +- [Fallback renditions](/quest/m1/rendition-fallback.md) - a video rendition marked `fallback` is only picked, by `` and `Video::ranked`, when nothing else decodes - [JS rendition ranking](/quest/m1/js-ranked.md) - `@moq/hang` ranks video renditions like Rust, and `@moq/watch`'s fallback uses it - [Audio rendition pick](/quest/m1/audio-ranked.md) - single-track FLV/RTMP and WHEP serve the best audio rendition, not the first by name - [FLV catalog stream](/quest/m1/flv-catalog-stream.md) - on dev, `flv::Export` takes a catalog stream like fmp4, replacing `with_select` diff --git a/quest/m1/js-ranked.md b/quest/m1/js-ranked.md index c7f2131e89..bf5f03cc7e 100644 --- a/quest/m1/js-ranked.md +++ b/quest/m1/js-ranked.md @@ -9,6 +9,7 @@ native egresses can't drift apart. ## Plan -Mirror the Rust name and semantics. `bestRendition` in `js/watch` already +Mirror the Rust name and semantics, including fallback renditions sorting +last once [Fallback renditions](/quest/m1/rendition-fallback.md) lands. `bestRendition` in `js/watch` already matches today; replace it rather than keep two copies. Check whether the `byDimensions`/`byBitrate` filters can reuse the same ordering. diff --git a/quest/m1/rendition-fallback.md b/quest/m1/rendition-fallback.md new file mode 100644 index 0000000000..d7a9c2dca8 --- /dev/null +++ b/quest/m1/rendition-fallback.md @@ -0,0 +1,50 @@ +# [S] Fallback renditions + +## Goal + +A hang video rendition marked `fallback: true` is only chosen when no +non-fallback rendition can be decoded. `` and +`hang::catalog::Video::ranked` both honor it, so a viewer, RTMP play, WHEP, +FLV export, and moq-transcode's source pick only subscribe to a transcode +(such as H.265 republished as H.264) when they must. + +## Plan + +Decided in a planning interview on 2026-10-01: + +- A boolean `fallback`, not a numeric `cost` or `preference`. Routing cost + picks between paths to the same bytes. Renditions are different pictures, + and an honest "produced on demand" cost would mark moq-transcode's rungs, + so a strict rule would stop capable viewers from ever stepping down. The + field names the role (a compatibility fallback), so moq-transcode's rungs + stay `false` and keep adapting. DASH's `@selectionPriority` across + per-codec Adaptation Sets is the precedent for the strict rule. Two tiers + cover the use case; multi-level ranking waits for a consumer. +- Video only. Optional on the wire, absent means `false`, omitted when + `false` (like `stalled`). Additive, so older players ignore it. +- Selection order in `js/watch/src/video/source.ts`: decode support, then + drop fallbacks when any supported non-fallback remains, then `stalled`, + then the existing target and bitrate pick within what is left. Fallback is + about decodability only: a stalled source does not move capable viewers + onto a transcode, which is derived from it and likely stalled too. +- A manual `target.name` still wins, as it does for `stalled`. The quality + picker keeps listing fallbacks. +- `Video::ranked` sorts every fallback after every non-fallback, then by + picture and bitrate as today. Its callers take the first rendition they + support, so they need no change. Update the + [JS rendition ranking](/quest/m1/js-ranked.md) Plan if it is still open. +- Update `rs/hang` `VideoConfig`, `js/hang` `VideoConfigSchema`, + `drafts/draft-lcurley-moq-hang.md` (next to `stalled`, with a + source-plus-fallback example), and `doc/concept/hang.md`. No new docs page. +- Tests: a supported source wins over an unstalled fallback with a higher + bitrate or a bitrate budget that fits only the fallback; an unsupported + source selects the fallback; `ranked` orders a larger fallback after a + smaller source. +- Out of scope: moq-ffi, libmoq, and the bindings until a native player needs + the field. moq-transcode producing same-size codec fallbacks; the consumer + publishes its own. + +## Related + +- [JS rendition ranking](/quest/m1/js-ranked.md) - mirrors `Video::ranked` in `@moq/hang`, which now sorts fallbacks last +- [Audio rendition pick](/quest/m1/audio-ranked.md) - audio ranking, where `fallback` could join later From 2a782b092e2559ba86eaad302144060644da3ca8 Mon Sep 17 00:00:00 2001 From: OneTooMany Date: Thu, 1 Oct 2026 12:59:23 +0200 Subject: [PATCH 2/6] quest: fold review findings into fallback renditions Co-Authored-By: Claude Opus 5.5 --- quest/m1/README.md | 2 +- quest/m1/js-ranked.md | 4 ++-- quest/m1/rendition-fallback.md | 33 +++++++++++++++++++++------------ 3 files changed, 24 insertions(+), 15 deletions(-) diff --git a/quest/m1/README.md b/quest/m1/README.md index 58765f9327..e514c8c578 100644 --- a/quest/m1/README.md +++ b/quest/m1/README.md @@ -106,7 +106,7 @@ QUIC studies there on that rule. - [CMAF surround Opus](/quest/m1/cmaf-opus-surround.md) - fMP4 import and export carry an Opus channel mapping table - [A self-hosted NVIDIA runner is registered](/quest/m1/gpu-runner.md) - the maintainer registers the host that runs the NVIDIA tests - [GPU CI](/quest/m1/gpu-ci.md) - NVIDIA tests run nightly on a self-hosted GPU runner, and `just rs nvidia` runs them locally instead of skipping -- [Fallback renditions](/quest/m1/rendition-fallback.md) - a video rendition marked `fallback` is only picked, by `` and `Video::ranked`, when nothing else decodes +- [Fallback renditions](/quest/m1/rendition-fallback.md) - a video rendition marked `fallback` is only picked automatically, by ``, `Video::ranked`, and WHEP, when nothing else decodes - [JS rendition ranking](/quest/m1/js-ranked.md) - `@moq/hang` ranks video renditions like Rust, and `@moq/watch`'s fallback uses it - [Audio rendition pick](/quest/m1/audio-ranked.md) - single-track FLV/RTMP and WHEP serve the best audio rendition, not the first by name - [FLV catalog stream](/quest/m1/flv-catalog-stream.md) - on dev, `flv::Export` takes a catalog stream like fmp4, replacing `with_select` diff --git a/quest/m1/js-ranked.md b/quest/m1/js-ranked.md index bf5f03cc7e..2ef27b956b 100644 --- a/quest/m1/js-ranked.md +++ b/quest/m1/js-ranked.md @@ -4,12 +4,12 @@ `@moq/hang` ranks video renditions the same way as Rust's `hang::catalog::Video::ranked` (largest picture, then highest bitrate, ties in -name order), and `@moq/watch`'s fallback pick uses it, so the browser and the +name order), and `@moq/watch`'s no-target pick uses it, so the browser and the native egresses can't drift apart. ## Plan Mirror the Rust name and semantics, including fallback renditions sorting last once [Fallback renditions](/quest/m1/rendition-fallback.md) lands. `bestRendition` in `js/watch` already -matches today; replace it rather than keep two copies. Check whether the +matches except exact ties, which keep catalog order instead of name order; replace it rather than keep two copies. Check whether the `byDimensions`/`byBitrate` filters can reuse the same ordering. diff --git a/quest/m1/rendition-fallback.md b/quest/m1/rendition-fallback.md index d7a9c2dca8..29a6be0d29 100644 --- a/quest/m1/rendition-fallback.md +++ b/quest/m1/rendition-fallback.md @@ -1,12 +1,14 @@ -# [S] Fallback renditions +# [M] Fallback renditions ## Goal -A hang video rendition marked `fallback: true` is only chosen when no -non-fallback rendition can be decoded. `` and -`hang::catalog::Video::ranked` both honor it, so a viewer, RTMP play, WHEP, -FLV export, and moq-transcode's source pick only subscribe to a transcode -(such as H.265 republished as H.264) when they must. +A hang video rendition marked `fallback: true` is only selected +automatically when no non-fallback rendition can be decoded. ``, +`hang::catalog::Video::ranked`, and WHEP's codec choice honor it, so a viewer +and every single-rendition egress (single-track RTMP play, WHEP, FLV export, +moq-transcode's source pick) only subscribe to a transcode (such as H.265 +republished as H.264) when they must. A multitrack RTMP client still receives +every rendition, fallbacks included. ## Plan @@ -30,16 +32,23 @@ Decided in a planning interview on 2026-10-01: - A manual `target.name` still wins, as it does for `stalled`. The quality picker keeps listing fallbacks. - `Video::ranked` sorts every fallback after every non-fallback, then by - picture and bitrate as today. Its callers take the first rendition they - support, so they need no change. Update the + picture and bitrate as today. RTMP play, FLV export, and moq-transcode take + the first rendition they support, so they need no change. Update the [JS rendition ranking](/quest/m1/js-ranked.md) Plan if it is still open. +- WHEP needs its own step: `Session::handle_media` (`rs/moq-rtc`) takes the + peer's first negotiated payload type, then `pick_video` filters `ranked()` + to that codec, so a peer offering the fallback's codec first would get the + fallback. Choose across every negotiated video codec so a supported + non-fallback wins. - Update `rs/hang` `VideoConfig`, `js/hang` `VideoConfigSchema`, `drafts/draft-lcurley-moq-hang.md` (next to `stalled`, with a source-plus-fallback example), and `doc/concept/hang.md`. No new docs page. -- Tests: a supported source wins over an unstalled fallback with a higher - bitrate or a bitrate budget that fits only the fallback; an unsupported - source selects the fallback; `ranked` orders a larger fallback after a - smaller source. +- Tests: a supported source wins over a fallback with a higher bitrate, over + a bitrate budget that fits only the fallback, and while the source is + stalled and the fallback is not; an unsupported source selects the + fallback; a manual `target.name` selects a fallback; `ranked` orders a + larger fallback after a smaller source; a WHEP peer offering the fallback's + codec first still gets the source. - Out of scope: moq-ffi, libmoq, and the bindings until a native player needs the field. moq-transcode producing same-size codec fallbacks; the consumer publishes its own. From 8c06a096cf992a4ffa941e6dfca48e04cecf08e4 Mon Sep 17 00:00:00 2001 From: OneTooMany Date: Thu, 1 Oct 2026 13:27:48 +0200 Subject: [PATCH 3/6] quest: say fallback ranking lands with this quest Co-Authored-By: Claude Opus 5.5 --- quest/m1/rendition-fallback.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quest/m1/rendition-fallback.md b/quest/m1/rendition-fallback.md index 29a6be0d29..e24ae2cf6e 100644 --- a/quest/m1/rendition-fallback.md +++ b/quest/m1/rendition-fallback.md @@ -55,5 +55,5 @@ Decided in a planning interview on 2026-10-01: ## Related -- [JS rendition ranking](/quest/m1/js-ranked.md) - mirrors `Video::ranked` in `@moq/hang`, which now sorts fallbacks last +- [JS rendition ranking](/quest/m1/js-ranked.md) - mirrors `Video::ranked` in `@moq/hang`, which sorts fallbacks last once this quest lands - [Audio rendition pick](/quest/m1/audio-ranked.md) - audio ranking, where `fallback` could join later From ea159cb499deee7670c712c220b8cf8af39eefc7 Mon Sep 17 00:00:00 2001 From: OneTooMany Date: Thu, 1 Oct 2026 13:54:21 +0200 Subject: [PATCH 4/6] quest: credit the fallback renditions request Co-Authored-By: Claude Opus 5.5 --- quest/m1/rendition-fallback.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/quest/m1/rendition-fallback.md b/quest/m1/rendition-fallback.md index e24ae2cf6e..22435f37c7 100644 --- a/quest/m1/rendition-fallback.md +++ b/quest/m1/rendition-fallback.md @@ -12,6 +12,10 @@ every rendition, fallbacks included. ## Plan +Requested by an external consumer (OneTooMany), who publishes an H.265 source +with its own H.264 transcode beside it, so a viewer that can decode H.265 +should never pull the transcode. + Decided in a planning interview on 2026-10-01: - A boolean `fallback`, not a numeric `cost` or `preference`. Routing cost From 520e3210642d80383723c40a80228bbc8973d36a Mon Sep 17 00:00:00 2001 From: Luke Curley Date: Thu, 1 Oct 2026 14:42:12 -0700 Subject: [PATCH 5/6] quest: generalize fallback renditions into a preference tier Co-Authored-By: Claude Opus 5.5 --- quest/m1/README.md | 2 +- quest/m1/js-ranked.md | 8 ++-- quest/m1/rendition-fallback.md | 63 --------------------------- quest/m1/rendition-preference.md | 73 ++++++++++++++++++++++++++++++++ 4 files changed, 79 insertions(+), 67 deletions(-) delete mode 100644 quest/m1/rendition-fallback.md create mode 100644 quest/m1/rendition-preference.md diff --git a/quest/m1/README.md b/quest/m1/README.md index 6324e039ef..77807f2164 100644 --- a/quest/m1/README.md +++ b/quest/m1/README.md @@ -109,7 +109,7 @@ QUIC studies there on that rule. - [CMAF surround Opus](/quest/m1/cmaf-opus-surround.md) - fMP4 import and export carry an Opus channel mapping table - [A self-hosted NVIDIA runner is registered](/quest/m1/gpu-runner.md) - the maintainer registers the host that runs the NVIDIA tests - [GPU CI](/quest/m1/gpu-ci.md) - NVIDIA tests run nightly on a self-hosted GPU runner, and `just rs nvidia` runs them locally instead of skipping -- [Fallback renditions](/quest/m1/rendition-fallback.md) - a video rendition marked `fallback` is only picked automatically, by ``, `Video::ranked`, and WHEP, when nothing else decodes +- [Rendition preference](/quest/m1/rendition-preference.md) - automatic selection by ``, `Video::ranked`, and WHEP keeps the highest `preference` that decodes, so a compatibility transcode is only picked when nothing preferred decodes - [JS rendition ranking](/quest/m1/js-ranked.md) - `@moq/hang` ranks video renditions like Rust, and `@moq/watch`'s fallback uses it - [Audio rendition pick](/quest/m1/audio-ranked.md) - single-track FLV/RTMP and WHEP serve the best audio rendition, not the first by name - [FLV catalog stream](/quest/m1/flv-catalog-stream.md) - on dev, `flv::Export` takes a catalog stream like fmp4, replacing `with_select` diff --git a/quest/m1/js-ranked.md b/quest/m1/js-ranked.md index 2ef27b956b..2611d75706 100644 --- a/quest/m1/js-ranked.md +++ b/quest/m1/js-ranked.md @@ -9,7 +9,9 @@ native egresses can't drift apart. ## Plan -Mirror the Rust name and semantics, including fallback renditions sorting -last once [Fallback renditions](/quest/m1/rendition-fallback.md) lands. `bestRendition` in `js/watch` already -matches except exact ties, which keep catalog order instead of name order; replace it rather than keep two copies. Check whether the +Mirror the Rust name and semantics, including sorting by `preference` first +once [Rendition preference](/quest/m1/rendition-preference.md) lands. +`bestRendition` in `js/watch` already matches except exact ties, which keep +catalog order instead of name order; replace it rather than keep two copies. +Check whether the `byDimensions`/`byBitrate` filters can reuse the same ordering. diff --git a/quest/m1/rendition-fallback.md b/quest/m1/rendition-fallback.md deleted file mode 100644 index 22435f37c7..0000000000 --- a/quest/m1/rendition-fallback.md +++ /dev/null @@ -1,63 +0,0 @@ -# [M] Fallback renditions - -## Goal - -A hang video rendition marked `fallback: true` is only selected -automatically when no non-fallback rendition can be decoded. ``, -`hang::catalog::Video::ranked`, and WHEP's codec choice honor it, so a viewer -and every single-rendition egress (single-track RTMP play, WHEP, FLV export, -moq-transcode's source pick) only subscribe to a transcode (such as H.265 -republished as H.264) when they must. A multitrack RTMP client still receives -every rendition, fallbacks included. - -## Plan - -Requested by an external consumer (OneTooMany), who publishes an H.265 source -with its own H.264 transcode beside it, so a viewer that can decode H.265 -should never pull the transcode. - -Decided in a planning interview on 2026-10-01: - -- A boolean `fallback`, not a numeric `cost` or `preference`. Routing cost - picks between paths to the same bytes. Renditions are different pictures, - and an honest "produced on demand" cost would mark moq-transcode's rungs, - so a strict rule would stop capable viewers from ever stepping down. The - field names the role (a compatibility fallback), so moq-transcode's rungs - stay `false` and keep adapting. DASH's `@selectionPriority` across - per-codec Adaptation Sets is the precedent for the strict rule. Two tiers - cover the use case; multi-level ranking waits for a consumer. -- Video only. Optional on the wire, absent means `false`, omitted when - `false` (like `stalled`). Additive, so older players ignore it. -- Selection order in `js/watch/src/video/source.ts`: decode support, then - drop fallbacks when any supported non-fallback remains, then `stalled`, - then the existing target and bitrate pick within what is left. Fallback is - about decodability only: a stalled source does not move capable viewers - onto a transcode, which is derived from it and likely stalled too. -- A manual `target.name` still wins, as it does for `stalled`. The quality - picker keeps listing fallbacks. -- `Video::ranked` sorts every fallback after every non-fallback, then by - picture and bitrate as today. RTMP play, FLV export, and moq-transcode take - the first rendition they support, so they need no change. Update the - [JS rendition ranking](/quest/m1/js-ranked.md) Plan if it is still open. -- WHEP needs its own step: `Session::handle_media` (`rs/moq-rtc`) takes the - peer's first negotiated payload type, then `pick_video` filters `ranked()` - to that codec, so a peer offering the fallback's codec first would get the - fallback. Choose across every negotiated video codec so a supported - non-fallback wins. -- Update `rs/hang` `VideoConfig`, `js/hang` `VideoConfigSchema`, - `drafts/draft-lcurley-moq-hang.md` (next to `stalled`, with a - source-plus-fallback example), and `doc/concept/hang.md`. No new docs page. -- Tests: a supported source wins over a fallback with a higher bitrate, over - a bitrate budget that fits only the fallback, and while the source is - stalled and the fallback is not; an unsupported source selects the - fallback; a manual `target.name` selects a fallback; `ranked` orders a - larger fallback after a smaller source; a WHEP peer offering the fallback's - codec first still gets the source. -- Out of scope: moq-ffi, libmoq, and the bindings until a native player needs - the field. moq-transcode producing same-size codec fallbacks; the consumer - publishes its own. - -## Related - -- [JS rendition ranking](/quest/m1/js-ranked.md) - mirrors `Video::ranked` in `@moq/hang`, which sorts fallbacks last once this quest lands -- [Audio rendition pick](/quest/m1/audio-ranked.md) - audio ranking, where `fallback` could join later diff --git a/quest/m1/rendition-preference.md b/quest/m1/rendition-preference.md new file mode 100644 index 0000000000..a05b153a2e --- /dev/null +++ b/quest/m1/rendition-preference.md @@ -0,0 +1,73 @@ +# [M] Rendition preference + +## Goal + +A hang video rendition carries an optional `preference` (a signed integer, +absent means 0, higher wins). Automatic selection keeps only the highest +preference among the renditions it can decode, then picks by target and +bitrate inside that tier as today. ``, +`hang::catalog::Video::ranked`, and WHEP's codec choice honor it, so a viewer +and every single-rendition egress (single-track RTMP play, WHEP, FLV export, +moq-transcode's source pick) only subscribe to a compatibility transcode +(such as H.265 republished as H.264, marked `-1`) when nothing preferred +decodes. A multitrack RTMP client still receives every rendition. + +## Plan + +Requested by an external consumer (OneTooMany), who publishes an H.265 source +with its own H.264 transcode beside it, so a viewer that can decode H.265 +should never pull the transcode. + +Decided in planning interviews on 2026-10-01: + +- A strict numeric tier, not `fallback: bool`. Two tiers cover the request, + but a ladder per codec (AV1 > H.265 > H.264) needs three, and today's + area-then-bitrate sort would pick H.264 over a same-size AV1 rung because + its bitrate is higher. The tier costs the same to implement as the bool. +- Not a total order (DASH `@qualityRanking`, HLS `SCORE`): the publisher + would have to rank every rung, and ABR still needs size and bitrate to + step down a ladder. +- Not a routing-style `cost`: an honest "produced on demand" cost would mark + moq-transcode's rungs, so the strict rule would stop capable viewers from + stepping down. The publisher sets `preference` explicitly; moq-transcode's + rungs keep the default and keep adapting. +- Named `preference`, higher wins, after DASH's `@selectionPriority` (strict + across per-codec Adaptation Sets, higher preferred). Not `priority`, which + already means track send priority. Signed, so a new preferred ladder is `1` + without renumbering existing renditions, and a fallback is `-1`. +- Video only. Optional on the wire, omitted when 0 (like `stalled`). + Additive, so older players ignore it. +- Selection order in `js/watch/src/video/source.ts`: decode support, then + keep the highest preference among supported renditions, then `stalled`, + then the existing target and bitrate pick within what is left. Preference + is about decodability only: a stalled source does not move capable viewers + onto a lower tier, which is usually derived from it and likely stalled too. +- A manual `target.name` still wins, as it does for `stalled`. The quality + picker keeps listing every tier. +- `Video::ranked` sorts by preference (highest first), then by picture and + bitrate as today. RTMP play, FLV export, and moq-transcode take the first + rendition they support, so they need no change. Update the + [JS rendition ranking](/quest/m1/js-ranked.md) Plan if it is still open. +- WHEP needs its own step: `Session::handle_media` (`rs/moq-rtc`) takes the + peer's first negotiated payload type, then `pick_video` filters `ranked()` + to that codec, so a peer offering the fallback's codec first would get the + fallback. Choose across every negotiated video codec so the highest + supported preference wins. +- Update `rs/hang` `VideoConfig`, `js/hang` `VideoConfigSchema`, + `drafts/draft-lcurley-moq-hang.md` (next to `stalled`, with a + source-plus-fallback example), and `doc/concept/hang.md`. No new docs page. +- Tests: a supported source wins over a lower-preference rendition with a + higher bitrate, over a bitrate budget that fits only the lower one, and + while the source is stalled and the lower one is not; an unsupported + source selects the lower preference; three tiers resolve to the highest + supported one; a manual `target.name` selects a lower preference; `ranked` + orders a larger lower-preference rendition after a smaller source; a WHEP + peer offering the fallback's codec first still gets the source. +- Out of scope: moq-ffi, libmoq, and the bindings until a native player needs + the field. moq-transcode producing same-size codec fallbacks; the consumer + publishes its own. + +## Related + +- [JS rendition ranking](/quest/m1/js-ranked.md) - mirrors `Video::ranked` in `@moq/hang`, which sorts by preference first once this quest lands +- [Audio rendition pick](/quest/m1/audio-ranked.md) - audio ranking, where `preference` could join later From dd1257357519bb40cf8cc7b69baca8733f79ee25 Mon Sep 17 00:00:00 2001 From: Luke Curley Date: Thu, 1 Oct 2026 15:18:50 -0700 Subject: [PATCH 6/6] quest: transcode rungs inherit the source preference Co-Authored-By: Claude Opus 5.5 --- quest/m1/rendition-preference.md | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/quest/m1/rendition-preference.md b/quest/m1/rendition-preference.md index a05b153a2e..d591afe19f 100644 --- a/quest/m1/rendition-preference.md +++ b/quest/m1/rendition-preference.md @@ -29,8 +29,11 @@ Decided in planning interviews on 2026-10-01: step down a ladder. - Not a routing-style `cost`: an honest "produced on demand" cost would mark moq-transcode's rungs, so the strict rule would stop capable viewers from - stepping down. The publisher sets `preference` explicitly; moq-transcode's - rungs keep the default and keep adapting. + stepping down. The publisher sets `preference` explicitly. +- moq-transcode's rungs inherit their source's `preference`, so a ladder + adapts within one tier even when the source is not at 0. `rung_entry` + copies it like `optimize_for_latency`, and the per-snapshot refresh beside + `inherit_stalled` keeps it current when the source catalog changes. - Named `preference`, higher wins, after DASH's `@selectionPriority` (strict across per-codec Adaptation Sets, higher preferred). Not `priority`, which already means track send priority. Signed, so a new preferred ladder is `1` @@ -62,7 +65,9 @@ Decided in planning interviews on 2026-10-01: source selects the lower preference; three tiers resolve to the highest supported one; a manual `target.name` selects a lower preference; `ranked` orders a larger lower-preference rendition after a smaller source; a WHEP - peer offering the fallback's codec first still gets the source. + peer offering the fallback's codec first still gets the source; a + moq-transcode source at preference 1 under a budget that fits only a rung + still selects the rung. - Out of scope: moq-ffi, libmoq, and the bindings until a native player needs the field. moq-transcode producing same-size codec fallbacks; the consumer publishes its own.