Zum Inhalt

Windows v0.10.0: Kiosk, Settings-Parität, Performance, Multi-Monitor, Wallpaper

Datum: 2026-07-16 · Status: ratifiziert (Brainstorming mit Johannes; Block 1 explizit freigegeben, Block 2 per autonomem Mandat entschieden) Scope: native/windows/host/ + Settings-Seite (src/settings/) + gezielte Engine-Patches (kiosk-Param, renderScale, perfAdapt-Stufe, Scanline-Drift, Power-Bridge). macOS-Native-Code unberührt; Web-Demo (index.html) unberührt. Release-Schnitt: EIN Release v0.10.0 mit allen Blöcken (Wunsch Johannes, statt der ursprünglich in der Phase-2-Spec angedachten Zweiteilung).

Baut auf docs/specs/2026-07-15-windows-micro-host-design.md auf (§3/§4 dort = Phase 2; diese Spec ersetzt deren Wallpaper-MVP-Zuschnitt „nur primärer Monitor" durch konfigurierbare Monitor-Modi).

Kontext / Befunde (Exploration 2026-07-16)

  1. Menüleiste im .scr = die Live-Control-Bar der Engine (controller.ts buildControlBar()): controller.open() blendet sie bei jedem Start 30 s ein („orientation", controlBarAutoHideSec=30), kleine Mausbewegungen unter der 10-px-Exit-Schwelle re-triggern sie. Kein Query-Param kann sie heute abschalten. Unbedienbar, weil der Host-Watchdog bei >10 px Mausbewegung oder Taste den Prozess beendet.
  2. Settings-Lücke: macOS-Config-Fenster bietet ~30 Settings (5 Looks, 7 CRT-Slider, Szene/Farbe/Tempo, Stadt-Höhe, Fog, Wetter, Flug-Bank, Reactive World, Terminal-Layout, HUD/Radar/Boot/Tag-Nacht/Ton, Auto-Szenenwechsel). Windows: 3 Dropdowns + 9 Toggles. Engine-seitig ist fast alles vorhanden — es fehlt nur die Verdrahtung.
  3. Performance (Johannes: durchgehend zähe FPS): Engine rendert in voller Geräteauflösung (setPixelRatio(min(dpr,2)), kein renderScale im Web-Pfad), Bloom ≈13 Fullscreen-Draws + CRT-Pass 9 Taps/Pixel auf HalfFloat-Targets; perfAdapt im Screensaver-Entry hart deaktiviert; antialias:true mit Composer wirkungslos; Scanline-Drift mutiert backgroundPositionY pro Frame (Fullscreen-Re-Raster); Host setzt keine GPU-Präferenz (Hybrid-GPU).
  4. Multi-Monitor heute: ein Fenster + eine WebView2 pro Monitor, identische URL, unsynchronisiert (scene=random würfelt pro Monitor).
  5. /w existiert nicht; unbekannte Flags fallen still in RunSaver().
  6. Drift-Bug: app.rc VERSIONINFO steht auf 0.9.0.0 (v0.9.1 released) — bump-version.sh kennt die Datei nicht.

Ratifizierte Nutzer-Entscheidungen

  • Performance-Symptomatik: durchgehend zäh → echtes GPU-Problem, nicht (nur) die absichtlichen CRT-Artefakte.
  • Ein Release v0.10.0 mit allen fünf Blöcken.
  • Settings: volle macOS-Parität (Look-Zeile + Sektionen, alle Slider).
  • Multi-Monitor: zwei Modi für Saver und Wallpaper — Spanning (ein Bild über alle Monitore) und Je-Monitor (an mit Settings / aus / random). Je-Monitor-Overrides: Szene + Farb-Preset, Rest global. Ein Monitor ist Hochformat → echtes Monitor-Layout verwenden.
  • Spanning-Bau: ein Fenster, eine WebView2, eine Engine über die virtuelle Desktop-Bounding-Box (keine 3-Instanzen-Synchronisation).

Design

§1 Kiosk-Param (Engine + Host)

Neues Query-Param kiosk=on in src/screensaver/params.ts:

  • hud.controlBar = false + hudPreset = 'custom' (Guard gegen applyHudPreset-Clobbering, gleicher Mechanismus wie die bestehenden HUD-Overrides).
  • Unterdrückt das Cursor-Wiedereinblenden bei mousemove und setzt cursor: none aufs Overlay (deckt den Chromium-Cursor über dem WebView).
  • Überspringt den requestFullscreen-Versuch (im WebView2 nutzlos).

Host: /s und /w hängen konstant &kiosk=on an die URL an — kein Registry-Wert, sondern Betriebsmodus-Eigenschaft. macOS-App und Web-Dev-Loop unverändert (Bar dort gewollt). Test: tests/screensaver-params.test.ts.

§2 Settings-Vollausbau

Dialog-Gliederung (settings.html, Vorlage macOS-ConfigWindow): Look-Zeile (5 Bundles aus looks.ts: clean/heavy/broken/vapor/matrix; manueller Regler-Tweak ⇒ „Eigene") + Sektionen:

Sektion Inhalte (Engine-Setting)
Bild Szene (random+7), Farbe (13 Presets), Tempo (slow/norm/fast), Stadt-Höhe (cityAltitude), Sichtweite (fogMode), Wetter (weather, 5 Werte)
CRT-Effekte Slider: Glitch-Intensität (crtSim.intensity 0–1), Krümmung (fx.curvature.amount 0–0.25), Lochmaske (fx.aperture.strength 0–0.5), Bloom (fx.bloom.strength 0–3), Nachleuchten (fx.trails off/on+damp), NTSC (fx.ntsc.amount 0–1), Halation (fx.halation.amount 0–0.6); Toggles: Scanlines, CRT-Sim an/aus
Bewegung & Story Flug-Bank (bankStrength 0–2), Reactive World (narrativeReactiveWorld), Auto-Szenenwechsel (autoCycle.on) + Intervall (0.5–10 min, Clamp beidseitig), Story-Terminal (narrativeTerminal)
Terminal Layout-Select „aus / unten / Fenster": mappt auf die ZWEI bestehenden Felder — aus ⇒ terminal=off (Bool bleibt im gepinnten Präfix), sonst terminal=on + termlayout=strip\|window (⇒ terminalLayout bottom-strip/center-window)
Anzeige & Automatik HUD, Radar, Crosshair, Matrix-Rain, Boot an/aus + Geschwindigkeit (fast/normal/cinematic), Tag/Nacht (dayNightCycle.on), Audio
Monitore §4-UI
Leistung Render-Scale (50–100 %), Adaptive Qualität (perfAdapt)

Bewusste Skips (macOS-only, kein Web-Pendant): flatHud, Terminal-Größe/ Band-Höhe, Idle-Minuten (regelt Windows), Login-Autostart (ersetzt durch Tray-Autostart §5).

Persistenz/Bridge — additiv, Alt-Format bleibt Präfix:

  • Die 12 bestehenden Registry-Werte + Query-Reihenfolge bleiben byte-identisch als Präfix (Upgrade v0.9→v0.10 unsichtbar; fehlende neue Werte ⇒ Defaults).
  • Neue REG_SZ-Werte (PascalCase) in fester Anhänge-Reihenfolge, Query-Keys lowercase: look, altitude, fog, weather, bank, reactive, autocycle, cyclemin, termlayout, boot, bootspeed, daynight, crtintensity, curvature, aperture, bloomstrength, trailsamount, ntsc, halation, scale, perfadapt.
  • Numerik als Dezimal-String mit Punkt; Host validiert Format (^[0-9]+(\.[0-9]+)?$) + Range, params.ts parst + clampt. Bools bleiben on/off. Enums Whitelist-validiert wie bisher (all-or-nothing-Reject).
  • params.ts erhält die neuen Mappings; scene/preset/audio bleiben wie gehabt im Entry (src/screensaver/main.ts) verdrahtet.
  • Beide Fixture-Tests (options_test.cpp ↔ tests/settings-form.test.ts) werden auf das erweiterte Format gehoben und pinnen es wieder beidseitig (neuer Default-String, neuer Voll-Flip-String, Reject-Fälle für Range/Format).

Dialog-Fenster: ~720×640 logische Pixel, skaliert mit GetDpiForMonitor des Ziel-Monitors (schließt HiDPI-Punkt der v0.9-Abnahme-TaskNote). Abgelehnter Save schließt den Dialog nicht mehr still: Host antwortet der Seite (PostWebMessageAsString saveerror), Seite zeigt Fehlerzeile; Dialog bleibt offen.

§3 Performance-Paket

  1. renderScale (Engine, neu): Settings-Feld renderScale (0.25–1, Default 1) + Query-Param scale=. Wirkt via setPixelRatio(min(devicePixelRatio, 2) × renderScale); wird bei Resize re-appliziert. Golden-Harness unberührt (Default 1).
  2. perfAdapt reaktivieren + Downscale-Stufe: Entry setzt nicht mehr hart perfAdapt:false; neues Param perfadapt=on/off, Windows-Default on. Der FPS-Wächter erhält oberhalb der Effekt-Drosselung eine Stufe, die die effektive PixelRatio schrittweise senkt (0.85/0.7/0.55 relativ), mit Hysterese zurück.
  3. antialias: false im Renderer (Composer macht MSAA wirkungslos; Einzel-Commit, Golden-Check als Wächter).
  4. Scanline-Drift compositor-freundlich: doppelt hoher Gradient-Layer + CSS-transform: translateY-Animation statt Per-Frame- backgroundPositionY-Mutation.
  5. Host-Hygiene: WebView2-User-Data-Dir %TEMP%\KuroScreensaverWV2 → %LOCALAPPDATA%\KuroScreensaver\WebView2 (Shader-Cache überlebt Temp-Cleaner). Recherche-Punkt (kein Versprechen): GPU-Präferenz via ICoreWebView2EnvironmentOptions/Chromium-Flag für Hybrid-GPU-Laptops.

Flankierend: CRT-Intensitäts-Slider (§2) zähmt die absichtlichen Glitch-Artefakte separat.

§4 Multi-Monitor (Saver)

  • MonitorMode (REG_SZ): per (Default, verhaltenskompatibel) | span.
  • Spanning: EIN Fenster über die virtuelle Desktop-Bounding-Box (SM_XVIRTUALSCREEN/SM_YVIRTUALSCREEN/SM_CXVIRTUALSCREEN/ SM_CYVIRTUALSCREEN), eine WebView2, ein Canvas; jeder Monitor zeigt seinen Ausschnitt (Hochformat inklusive). Ein Input-Watch-Timer. Bekannte, akzeptierte Quirks (in Doku): Lücken der Anordnung werden mitgerendert, ein gemeinsamer Schärfegrad bei Misch-DPI, HUD/Terminal an den Rändern des Gesamtbilds. Empfehlung im Dialog: Render-Scale ≤66 % bei 3 Monitoren.
  • Je-Monitor: Fenster pro Monitor wie heute, aber pro Monitor Registry-Subkey HKCU\Software\KuroScreensaver\Monitors\<StableId> mit Mode = on | off | random | scene, Scene (Slug, bei scene), Preset (Slug oder leer ⇒ global). off ⇒ schwarzes Deck-Fenster OHNE WebView2 (spart die Chromium-Instanz, Input-Watch läuft trotzdem). random ⇒ scene=random für dieses Fenster. Unbekannte/neue Monitore ⇒ on mit globalen Settings.
  • StableId: aus QueryDisplayConfig + DISPLAYCONFIG_TARGET_DEVICE_NAME.monitorDevicePath (EDID-basiert, überlebt Docking/Umstecken), sanitized auf [A-Za-z0-9_-], Fallback GetMonitorInfo.szDevice. Kollisions-Fallback: numerischer Suffix.
  • Dialog-UI („Monitore"): Modus-Radio (Ein Bild über alle / Je Monitor)
  • Karte pro erkanntem Monitor (Name, Auflösung, „Hochformat"-Badge, „Hauptmonitor"-Badge) mit Mode-Select + Szene-/Preset-Selects. Host liefert die Monitor-Liste als monitors=<URL-encoded JSON>-Query-Param an settings.html (id, name, w, h, portrait, primary — NICHT Teil des Save-Formats; im Browser-Dev-Loop liefert ein Fake die Liste).
  • Save-Format: indexierte Keys im bestehenden save:-Query: m0id, m0mode, m0scene, m0preset, m1id, … (Cap: 8 Monitore). Host validiert (Id-Charset, Mode-Enum, Slugs) all-or-nothing und schreibt die Subkeys transaktional.

§5 Wallpaper /w + Tray + PowerPolicy

  • Dispatch: expliziter /w-Zweig in main.cpp (unbekannte Flags fallen weiterhin in den Saver — .scr-Konvention).
  • WorkerW-Einhängung: beide Strategien mit Feature-Detection — klassisch (SendMessageTimeout(Progman, 0x052C, …) → WorkerW hinter dem Desktop-SHELLDLL_DefView suchen) und 24H2 (WorkerW direkt unter Progman).
  • Monitor-Modi wie §4, aber eigene Werte: WallpaperMonitorMode (per | span, Default per) und pro Monitor-Subkey WMode/WScene/ WPreset (Default: Hauptmonitor on, alle anderen off — Effizienz; an der Docking-Station umstellbar).
  • Wallpaper-Fenster: Kind des WorkerW, kiosk=on, audio erzwungen off, eigener WallpaperScale (Default 0.66, macOS-Parität).
  • Tray (Shell_NotifyIcon): Wallpaper an/aus · Einstellungen (öffnet das /c-Fenster in-process) · Autostart (HKCU-Run-Key KuroWallpaper = "<pfad>\KuroScreensaver.scr" /w) · Beenden.
  • PowerPolicy: render_policy.{h,cpp} als pure Entscheidungstabelle (Port von Core/RenderPolicy.swift; Zustände hidden/frozen/animating, acFps 30 / batteryFps 10, gleiche Prioritäten). Signalsammlung (power_signals.cpp): Session-Lock (WTSRegisterSessionNotification), Vollbild-/Präsentationsmodus (SHQueryUserNotificationState), Monitor-Occlusion (Foreground-Fenster-Bounds vs. Monitor-Rect, Poll im bestehenden Timer), Batterie/Stromsparen (GetSystemPowerStatus + Power-Broadcast), Display-off (PBT_POWERSETTINGCHANGE, Console-Display-State). Kein Thermal-Signal (kein sauberes Win32-API) — dokumentierter Unterschied zu macOS.
  • Durchsetzung zweistufig: (1) Host→Page PostWebMessageAsString power:<state>:<fps> → neuer Engine-Hook im Screensaver-Entry pausiert den rAF-Loop bzw. capped die Framerate (frozen/hidden ⇒ pause, animating ⇒ Cap). (2) Bei hidden zusätzlich CoreWebView2.TrySuspend(), Resume() beim Zurückkehren. Der Vollbild-Saver (/s) ist strukturell ausgenommen (wie macOS).

§6 Build, CI, Tests, Migration

  • Tests: options_test.cpp erweitert (neues Default-/Flip-Fixture, Range-/Format-Rejects, Monitor-Save-Parsing, Subkey-Roundtrip); render_policy-Tabelle bekommt eigene Testfälle im selben Test-Target (Parität zu tests/main.swift-Fällen). tests/settings-form.test.ts spiegelt die neuen Fixtures. Engine-Patches: Typecheck + bestehende Param-Tests + neue kiosk-/scale-Fälle.
  • bump-version.sh schreibt zusätzlich native/windows/host/app.rc (VERSIONINFO) — behebt den 0.9.0.0-Drift dauerhaft.
  • Packaging/CI unverändert: package-windows.sh bleibt die Single Source, die CI ruft; Installer zeigt weiter auf die Mini-exe.
  • Doku: README (inkl. der 3 Linter-Pflichtsektionen Features/Usage/ No build step), docs/WINDOWS-INSTALL.md (Wallpaper-Kapitel, Monitor-Modi, Performance-Tipps), neue TaskNote „v0.10.0 on-device-Abnahme Windows" (übernimmt offene v0.9-Punkte: Upgrade-Pfad, Win10-MessageBox, SmartScreen).
  • Release: bump-version.sh 0.10.0 → Tag → CI; macOS-dmg-Anhang nur bei Bedarf (macOS-App unverändert; Engine-Web-Änderungen betreffen die Metal-App nicht). Release-Publikation erst nach explizitem User-Go.

Risiken (benannt, akzeptiert)

  • WorkerW/24H2: beide Strategien implementiert, Rest-Risiko on-device.
  • Spanning-Framebuffer: 3 Monitore ≈ >10 MPixel bei Scale 1 — Gegenmittel Render-Scale + perfAdapt-Downscale; Empfehlung im Dialog dokumentiert.
  • Ein großer Release-Schnitt: bewusste Nutzer-Entscheidung; die on-device-TaskNote deckt alle Blöcke einzeln ab, damit Fehler isolierbar bleiben.
  • Kein lokaler MSVC-Build: Iteration über CI-Artefakte (unverändert).
  • Misch-DPI im Spanning: ein gemeinsamer devicePixelRatio; minimal weicheres Bild auf dem schärfsten Display — dokumentierter Quirk.