Skip to content

feat(play-setup): make High FPS tuning binding via one composed launch blob - #246

Merged
papi-ux merged 1 commit into
masterfrom
feat/high-fps-binding
Aug 20, 2026
Merged

feat(play-setup): make High FPS tuning binding via one composed launch blob#246
papi-ux merged 1 commit into
masterfrom
feat/high-fps-binding

Conversation

@papi-ux

@papi-ux papi-ux commented Aug 20, 2026

Copy link
Copy Markdown
Owner

Summary

  • Tuning = High FPS is binding now. It used to be only a request string on the /optimize query, which the host was free to decline — a game holding a Recovery profile launched at 30 FPS while the row still read "High FPS". It now pins the stream to the Settings frame rate through a composed launch blob that sets safe_target_fps_relaxed, the release field resolveAutoSafeTargetFps already honors. Overriding a recovery hold is an explicit, visible act: the Tuning caption states the pin and what the host would have run ("Pins 120 FPS · overrides the recovery hold (host would run 30)"), the row carries the override accent, and the launch button reads "Launch 120 FPS · your pick" instead of promising a recovery launch it will not perform. auto/quality/stability keep the host in control. (Design note: an FPS row was considered and rejected — Play Setup's act column is deliberately fixed at four rows per the NovaPlaySetupRow budget; papi picked the binding-tuning shape.)
  • New NovaLaunchStreamOverride.compose — one composed launch blob. Replaces the display planner's buildLaunchOptimizationOverride, which rebuilt the blob from scratch and silently dropped the stability block, so picking a resolution discarded the recovery clamp as a side effect. Composition deep-copies the host blob: a resolution pick pins width×height and leaves the fps clamp standing (intended behavior change — the accidental recovery bypass is gone); only the fps pin releases the clamp, explicitly.
  • Every launch path composes the same way. Game.kt's self-queried optimization fallback applies the pin for Continue-Playing launches; ShortcutTrampoline honors the per-game tuning preference instead of a hardcoded "auto" before composing. The attemptLaunch wait-guard is read from the raw blob now, since a pick or pin makes the composed blob non-null while the desktop-Steam answer is still on the wire.
  • The Resolution row shows width×height only; the trailing rate was the host's plan, not that row's decision. Full target_mode stays on the option cards.
  • Two new StreamSyncManagerTest cases pin the resolver ordering the design rests on: a paired override is still clamped by a confirmed recovery, and the relaxed flag on the same blob is what releases it.
  • Known follow-up (next PR): asked-vs-granted reconciliation in the summary/facts and outcome-aware Tuning copy when the host declines a non-binding preference; the review interstitial still reads the raw blob.

Exact candidate

Verification

  • :app:testNonRoot_gameDebugUnitTest — 1382 tests, 0 failures, 0 errors, 0 skipped (XML-counted; includes the new NovaLaunchStreamOverrideTest and the three new resolver pins)
  • :app:lintNonRoot_gameDebug -PlintFailOnError=true — 0 new errors (baseline noise only)
  • :app:assembleNonRoot_gameDebugAndroidTest — compiles (public symbol removed from NovaDisplayResolutionPlanner; CI never builds this variant)
  • Source guards re-pinned in the same commit, not deleted (NovaLaunchSourceGuardTest: composer symbol + raw-blob wait-guard)
  • Device QA on the RP6 (arc's end-to-end pass: recovery game + High FPS launches pinned with warning caption; resolution-only pick stays clamped; Continue-Playing launch honors the pin)

…h blob

The Tuning row's High FPS option used to be only a request string on the
/optimize query, which the host was free to decline, and often did: a game
holding a Recovery profile launched at 30 FPS while the row still read
High FPS. It now pins the stream to the Settings frame rate. The pin rides
in a composed launch blob that sets safe_target_fps_relaxed, the release
field the resolver already honors, so overriding a recovery hold is an
explicit, visible act: the Tuning caption states the pin and exactly what
the host would have run, the row carries the override accent, and the
launch button says the pinned rate instead of promising a recovery launch
it will not perform.

The new NovaLaunchStreamOverride.compose replaces the display planner's
buildLaunchOptimizationOverride, which rebuilt the blob from scratch and
silently dropped the stability block, so picking a resolution discarded
the recovery clamp as a side effect. Composition deep-copies the host
blob instead: a resolution pick pins width and height and leaves the fps
clamp standing, and only the fps pin releases it. The resolution row now
shows width x height alone, since the trailing rate was the host's plan
rather than that row's decision.

Paths that bypass Play Setup compose the same way: Game.kt's self-queried
optimization fallback applies the pin for Continue Playing launches, and
ShortcutTrampoline now honors the per-game tuning preference instead of a
hardcoded "auto" before composing. The launch-wait guard in attemptLaunch
is read from the raw blob now, because a pick or pin makes the composed
blob non-null while the desktop-Steam answer is still on the wire.

Two new StreamSyncManagerTest cases pin the resolver ordering the design
rests on: a paired override is still clamped by a confirmed recovery, and
the relaxed flag on the same blob is what releases it.
@papi-ux
papi-ux merged commit 9ebba7e into master Aug 20, 2026
6 checks passed
@papi-ux
papi-ux deleted the feat/high-fps-binding branch August 20, 2026 05:08
@papi-ux

papi-ux commented Aug 20, 2026

Copy link
Copy Markdown
Owner Author

Device QA completed on the RP6 (com.papi.nova.benchmark updated in place to 1.3.7-benchmark built from 0c9af32d, cert dbb684b4 verified before install -r, pairing preserved). Covers this PR plus #245 and #247.

Settings (#245): frame rate 120 survived a Balanced to Quality to Balanced preset round trip; presets changed only resolution, bitrate, and codec. Panel offers 30/60/90/120 as expected on the 120Hz display.

Binding pin (this PR), end to end with Control Ultimate Edition in a live recovery-labeled state:

  • Tuning = High FPS: caption reads "Pins 120 FPS · overrides the recovery hold (host would run 30)", row carries the override accent, button reads "Launch 120 FPS · your pick".
  • Launch log: Launch resolved stream mode 1920x1080x120.0 with displayMode=1920x1080x120. Host journal: Display mode for client [RetroidPocket6] overriden to [1920x1080x120], session_optimization: requested=1920x1080x120 selected=1920x1080x120, session_pacing: policy=client_fps_limit target_fps=120. Command Center in stream showed 1920x1080@120 · HEVC and "Manual stream overrides are active".
  • The session delivered 120.8/120 FPS (graded D on network, which is the honest split: the pin holds the rate, the host keeps reporting the network verdict).
  • Pin persisted across the session and an app round trip.

Ask vs grant (#247): on Auto, the detail status and Play Setup plan read "Recovery profile / 30 FPS · you asked 120 · Limited by: Network"; with the pin, "120 FPS pinned (host offered Recovery profile / 30 FPS)".

Resolution pick on Auto: picked the Sharp card (2880x1620); row showed "2880x1620 · Chosen here · applies at launch" with width x height only. Launch resolved 2880x1620x60, the host's own advertised rate for that card, and the host accepted it verbatim (requested=2880x1620x60 selected=2880x1620x60). One nuance worth recording: the live blob here is source=ai_cached with a Recovery label, which does not set the confirmed-recovery signals (history_safe / apply_recovery / stability_first), so the paired pick pins the card's rate rather than being min-clamped to 30. That matches the resolver's design and the pre-change behavior on this blob shape; the hardened keep-the-clamp path applies to confirmed-recovery blobs and is pinned by the two new StreamSyncManagerTest cases.

Both sessions ended via End Session with clean teardown (no labwc orphans, no zombies). Device left at the library with Tuning = High FPS restored on Control.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant