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.
### 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