Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions docs/core-protocol.md
Original file line number Diff line number Diff line change
Expand Up @@ -562,6 +562,19 @@ attempts; only a presented first frame resets this budget. Ending the session ca
recovery. A native stop stalled for 30 seconds reports an error without launching
another transport over the still-owned resources.

For the embedded Qt client, `session.create` also accepts an optional numeric `maxEntitledFps`:
the highest frame rate the signed-in membership entitles at the requested resolution, or `0`
or absent when that is not confirmed. Qt derives it from the normalized `entitledResolutions`
entries it already displays; the core never invents it. The requested frame rate is bounded by
the documented resolution ceiling (1920x1080 and 1920x1200 top out at 360 FPS, every other
resolution at 240 FPS) and, above 240 FPS, by both a hardware decoder for the selected codec
confirmed in `runtimeCapabilities` and an entitlement limit that covers the requested rate.
An absent or software-only capability probe and an absent or lower entitlement limit both bound
the request to the unconditional 240 FPS ceiling, so the saved preference cannot request the
conditional tier without affirmative evidence. This is a necessary condition, not a throughput
qualification: the client does not measure decoder throughput, and CloudMatch remains the
authority on the finalized profile.

For the embedded Qt client, `session.create` and `streamer.prepare` accept an optional
`runtimeCapabilities` object copied from the in-process streamer's protocol-7 `hello` response.
The core filters its available `videoBackends` by the persisted `nativeVideoBackend` preference
Expand Down Expand Up @@ -1161,6 +1174,14 @@ game or keyboard values with the existing respective defaults before create,
immediate resume, or claim requests. The interface locale never enters these
query parameters.

`settings.choices.get({runtimeCapabilities})` also returns `frameRates` for the documented
frame-rate tiers, using the same `value`, `disabled`, and nullable `reason` descriptor shape.
The conditional top tier reports a reason for each unconfirmed condition: a resolution outside
full HD, an unreported capability probe, or a reported probe without a hardware decoder for the
selected codec. Missing or unreported capabilities never imply support, and only rates at or
below 240 FPS are unconditional. The entitlement limit stays a Qt-side decision because the
core holds no subscription data.

`settings.choices.get({runtimeCapabilities})` returns `colorQualities` for the current
persisted settings and embedded streamer capability snapshot. Each of the four
descriptors contains `value`, `disabled`, and nullable `reason`. The operation
Expand Down
70 changes: 69 additions & 1 deletion native/opennow-core/src/cloudmatch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1306,7 +1306,7 @@ fn monitor_display_data(hdr: bool, settings: &Value) -> Value {

fn build_create_body(app_id: &str, params: &Value, settings: &Value, device_id: &str) -> Value {
let (width, height) = parse_resolution(&setting_string(settings, "resolution", "1920x1080"));
let fps = setting_i64(settings, "fps", 60).clamp(30, 240);
let fps = crate::frame_rate::request_frame_rate(settings, params, width, height);
let bitrate = setting_i64(settings, "maxBitrateMbps", 75).clamp(1, 200) * 1000;
let codec = codec_wire(&setting_string(settings, "codec", "auto"));
let hdr = setting_bool(settings, "enableHdr", false)
Expand Down Expand Up @@ -4190,6 +4190,74 @@ mod tests {
assert_eq!(features["vsync"], false);
}

#[test]
fn session_create_requests_the_documented_frame_rate_ceiling() {
let hardware = json!({"protocolVersion":7, "videoBackends":[{"backend":"vaapi",
"available":true, "codecs":[{"codec":"h265", "available":true,
"colorQualities":["8bit_420"]}]}]});
let software = json!({"protocolVersion":7, "videoBackends":[{"backend":"software",
"available":true, "codecs":[{"codec":"h265", "available":true,
"colorQualities":["8bit_420"]}]}]});
let request = |resolution: &str, fps: i64, capabilities: &Value, entitled: i64| {
let body = build_create_body(
"12345",
&json!({"title":"Portal 2", "runtimeCapabilities":capabilities,
"maxEntitledFps":entitled}),
&json!({"resolution":resolution, "fps":fps, "codec":"h265"}),
"device-id",
);
body["sessionRequestData"]["clientRequestMonitorSettings"][0]["framesPerSecond"].clone()
};
assert_eq!(request("1920x1080", 360, &hardware, 360), json!(360));
assert_eq!(request("1920x1200", 360, &hardware, 360), json!(360));
for resolution in [
"2560x1440",
"2560x1600",
"3440x1440",
"3840x2160",
"3840x1080",
] {
assert_eq!(
request(resolution, 360, &hardware, 360),
json!(240),
"{resolution} must not request the full-HD-only tier"
);
assert_eq!(request(resolution, 240, &hardware, 360), json!(240));
}
assert_eq!(request("1920x1080", 999, &hardware, 360), json!(360));
assert_eq!(request("1920x1080", 1, &hardware, 360), json!(30));
assert_eq!(
request("1920x1080", 360, &software, 360),
json!(240),
"a software-only decode path cannot request the top tier"
);
assert_eq!(
request("1920x1080", 360, &json!({}), 360),
json!(240),
"an unreported capability probe is not affirmative support"
);
assert_eq!(
request("1920x1080", 360, &hardware, 0),
json!(240),
"unconfirmed entitlement cannot request the top tier"
);
assert_eq!(
request("1920x1080", 360, &hardware, 240),
json!(240),
"a 240 FPS entitlement cannot request the top tier"
);
assert_eq!(
request("1920x1080", 360, &hardware, 120),
json!(120),
"a lower entitlement bounds the request"
);
assert_eq!(
request("1920x1080", 240, &software, 0),
json!(240),
"base rates stay unaffected by the capability verdict"
);
}

#[test]
fn manual_av1_uses_native_nvst_even_with_a_legacy_transport_value() {
let body = build_create_body(
Expand Down
Loading
Loading