HydraIssues

Portrait streaming: orientation contract implementation
open improvement Project: hydrabody Reporter: anonymous 13 May 2026 22:09

Description

Portrait streaming end-to-end implementation complete.

Comments (1)

admin 14 May 2026 15:19
## Full implementation description

### What was done

This tracks the full portrait streaming orientation work for cosmic-pretzel-98 (and all multi-orientation bodies). Root cause: landscape kiosk overlay showing behind a portrait UE app when a portrait stream started.

Root causes:
1. kioskoverlay did not respond to WM_DISPLAYCHANGE — overlay stayed landscape when VDD switched to portrait
2. handleStreamStarted did not receive orientation from prep-cmd explicitly
3. handleStreamEnded was not implemented — Undo hook silently 404'd, leaving VDD at last stream resolution
4. Hardcoded resolution strings scattered across multiple files
5. No explicit pause between writing dd_manual_resolution and Sunshine beginning capture

After v1.11.34 the architecture changed: dd_resolution_option switched from manual to auto. Sunshine now owns layer 2 directly (switches VDD to the Moonlight-requested resolution at RTSP negotiation). The root cause for auto mode not working was --no-game-optimization in Moonlight suppressing optimizeGameSettings=true, which Sunshine requires to apply auto mode. Fixed in hydraheadflatscreen v2.0.41.

### Changes shipped (v1.11.28 through v1.11.35)

cmd/kioskoverlay/main.go: Added WM_DISPLAYCHANGE handler — resizes overlay window to fill screen when VDD switches.

pkg/provider/experiences.go: Added Resolution field to liveExperience and experienceInstallInfo. Added streamResolution() as single source of truth for VDD resolution + UE args.

pkg/provider/sunshine_provider.go: Added desiredApp struct with orientation. Added buildOrientationHooks() — bakes orientation into prep-cmd JSON body at app registration. Added post-sync read-back verification.

pkg/provider/sunshine.go: Experience state loaded before head loop. Head apps resolve orientation from experience state cache. Uses streamResolution for UE launch args.

pkg/provider/httpserver.go: handleStreamStarted reads orientation from request, falls back to cache, logs mismatch, returns {"orientation":"..."} for layer 2 contract verification. handleStreamEnded resets VDD to landscape idle state.

pkg/provider/vdd_windows.go: writeVDDSettings now includes 60Hz mode variants. ensureVDDResolutions() tracks custom resolutions from experience library. Mutex on setSunshineConfig.

### Orientation contract (three layers)

Layer 1 — Stream encoding: Resolution Moonlight client requests from Sunshine (head: hydraheadflatscreen / hydraneckwebrtc)
Layer 2 — Display resolution: dd_resolution_option=auto in sunshine.conf, Sunshine switches VDD to Moonlight-requested resolution
Layer 3 — App window: UE launch args -ResX=1080 -ResY=1920 -windowed -ForceRes registered in Sunshine app cmd

### Verification (2026-05-14)

Sunshine log confirmed: Desktop resolution [1080x1920] on mercator-talks stream start.
hydrabody log: [stream] stream started: mercator-talks orientation=portrait (layer 2 via Sunshine auto)
Capture size: 1080x1920