HydraIssues

Portrait streaming: orientation contract implementation + remaining robustness improvements
open improvement Project: hydrabody Reporter: anonymous 13 May 2026 22:08

Description

What was done

This tracks the full portrait streaming orientation work for cosmic-pretzel-98 (and all multi-orientation bodies). The root cause was a visible artifact: a landscape kiosk overlay showing behind a portrait Unreal Engine app when a portrait stream started.

Root cause breakdown:

  1. kioskoverlay did not respond to WM_DISPLAYCHANGE -- overlay stayed landscape when VDD switched to portrait
  2. handleStreamStarted did not receive orientation from the prep-cmd; it had to resolve it from cache with no explicit contract
  3. handleStreamEnded was not implemented -- the Undo hook silently 404'd, leaving VDD at the last stream's resolution
  4. Hardcoded resolution strings scattered across multiple files
  5. No explicit pause between writing dd_manual_resolution and Sunshine beginning capture
  6. --no-game-optimization flag in Moonlight suppressed optimizeGameSettings=true, preventing Sunshine auto mode from switching VDD resolution

Architecture shift (root fix)

Changed from dd_resolution_option=manual (hydrabody writes dd_manual_resolution per-session in prep-cmd) to dd_resolution_option=auto (Sunshine switches VDD to the client-requested resolution at RTSP negotiation time). This required removing --no-game-optimization from hydraheadflatscreen (v2.0.41).

Three-layer orientation contract

Layer Controls Owner
1 -- Stream encoding Resolution Moonlight client requests from Sunshine Head (hydraheadflatscreen / hydraneckwebrtc)
2 -- Display resolution dd_resolution_option=auto in sunshine.conf, Sunshine switches VDD Sunshine (triggered by optimizeGameSettings=true)
3 -- App window UE launch args registered in Sunshine app cmd sunshine.go:tickSunshineApps

Changes shipped (hydrabody v1.11.32 - v1.11.34)

  • kioskoverlay: WM_DISPLAYCHANGE handler resizes overlay window on resolution change
  • experiences.go: Resolution field on liveExperience + experienceInstallInfo; streamResolution helper as single source of truth
  • sunshine_provider.go: desiredApp struct; buildOrientationHooks baking orientation into prep-cmd JSON body; post-sync verification
  • sunshine.go: Experience state loaded before head loop; orientation resolved by StreamAppID
  • httpserver.go: Orientation field in streamEventRequest; stream-started returns orientation for caller verification; layer 2 now owned by Sunshine auto
  • vdd_windows.go: dd_resolution_option=auto in sunshine.conf template; 60Hz portrait mode variants in vdd_settings.xml
  • Mutex on setSunshineConfig (defensive, race-safe)
  • Custom resolution tracking from experience library (ensureVDDResolutions)

Changes shipped (hydraheadflatscreen v2.0.41)

  • Removed --no-game-optimization from Moonlight launch args. This flag suppressed the optimizeGameSettings signal that Moonlight sends to Sunshine; without it Sunshine refuses to switch the display resolution regardless of dd_resolution_option.

Remaining improvements

1. Mutex on setSunshineConfig (already done in v1.11.32)

setSunshineConfig does read-merge-write on sunshine.conf. Mutex added to prevent race on concurrent prep-cmd calls.

2. Custom resolutions in vdd_settings.xml (already done in v1.11.32)

ensureVDDResolutions() added: reads experience state, extracts custom Resolution values, hashes against stored fingerprint, rewrites vdd_settings.xml if changed. Logs warning that VDD reinstall is needed for new resolutions to take effect.

Verification

Verified end-to-end on cosmic-pretzel-98 (bxl1-test district):

  • VDD switches to 1080x1920 when portrait stream starts (Win32_VideoController confirmed)
  • Mercator56-Win64-Shipping runs with -ResX=1080 -ResY=1920 -windowed -ForceRes
  • prep-cmd bakes orientation=portrait in stream-started hook
  • hydraneckwebrtc relay session confirmed portrait stream delivery