Physarum Universal App — macOS Playback (Increment 3a) Design Spec¶
Status: Draft · 2026-06-02 · author: Johannes (brainstormed with Claude) Builds on:
2026-06-02-native-ambiance-app-design.md(the native-pivot North Star). This spec scopes the first concrete app target: a SwiftUI Multiplatform app whose macOS build delivers Playback + Preset-Browser + curated Live-Sliders, on top of the already-complete nativePhysarumEngine.
1. Kontext & Ziel¶
Die native PhysarumEngine (Swift/Metal) ist feature-complete + getestet: alle 25
Presets, voller Post-FX-Stack inkl. Temporal-Feedback, 19 XCTests grün. Was fehlt, ist
eine App darüber. Diese Spec definiert das Universal-App-Gerüst (ein SwiftUI-
Multiplatform-Codebase für macOS + iOS + iPadOS + tvOS) und baut als ersten Schritt die
macOS-Playback-UI aus.
Warum macOS zuerst (statt tvOS-Hero): läuft sofort ohne Simulator/Gerät (sofort
iterierbar, remote-tauglich), validiert das komplette Multiplatform-Gerüst end-to-end
(Xcode-Projekt, App-Target, Engine-Einbindung, SwiftUI-Lifecycle), bevor die tvOS-
Spezifika (Focus-Engine, Top-Shelf) dazukommen. Der bestehende PhysarumViewer (AppKit)
ist faktisch schon ein macOS-Playback-Prototyp. tvOS folgt auf bewährtem Gerüst.
2. Festgezurrte Entscheidungen (aus dem Brainstorming 2026-06-02)¶
| Achse | Entscheidung |
|---|---|
| Erster Target | macOS-Playback. Das Universal-Gerüst (alle 4 Plattformen) wird angelegt, nur die macOS-UI v1 ausgebaut. |
| Funktionsumfang v1 | Playback (Fenster + Fullscreen) + Preset-Browser (25 mit Thumbnails) + Auto-Cycle + kuratierte Live-Slider. |
| Target-Organisation | Ein Multiplatform-App-Target (Physarum), das das PhysarumEngine-SwiftPM-Package einbindet. SwiftUI Multiplatform, ein Codebase. |
| Projekt-Generierung | XcodeGen — project.yml (versioniert) generiert die .xcodeproj (gitignored). Setup ist Code, kein GUI-Klicken. |
| macOS-DMG-Strategie | Native löst die WKWebView-DMG ab — bei Reife (Cutover erst mit dem Authoring-Editor). Bis dahin reift die native App parallel; die WKWebView-DMG bleibt offiziell. |
| Browser-Web-App | Codeberg Pages + embeddable Player bleiben unangetastet (freier Universal-Zugang, unabhängig von der DMG). |
| Live-Slider | Kuratierter Satz respawn-freier Parameter (Post-FX + sim-seitig). agentCount/spawnPattern bleiben Respawn-Only → kein Slider in v1. |
3. Architektur — Repo-Struktur & Gerüst¶
apple/
PhysarumEngine/ # bestehendes SwiftPM-Package (Engine, plattform-agnostisch)
Package.swift # platforms erweitern: .macOS(.v14) + .iOS(.v17) + .tvOS(.v17)
PhysarumApp/ # NEU — die Universal-App (XcodeGen)
project.yml # XcodeGen-Manifest → generiert Physarum.xcodeproj (gitignored)
Sources/
App/ # @main SwiftUI App + Scene
Player/ # MetalView (NS/UIViewRepresentable um MTKView) + RenderCoordinator
Library/ # Preset-Browser (LazyVGrid der 25 + Thumbnails)
Controls/ # Live-Slider-Panel
Model/ # AppState (@Observable), PresetCatalog, LiveParams
Resources/ # 25 Preset-JSONs, Thumbnails, Assets.xcassets, Info.plist, Entitlements
Kernpunkte:
- Ein Multiplatform-App-Target Physarum; Destinations macOS (aktiv), iOS/iPadOS/tvOS
im Manifest vorbereitet, aber UI noch nicht ausgebaut.
- Engine bleibt unangetastet als lokale SwiftPM-Dependency. swift test läuft
unabhängig weiter. Nur Package.swift bekommt die zusätzlichen Plattformen.
- XcodeGen: project.yml ist Single-Source; .xcodeproj generiert + gitignored.
- Bestehende Dev-Tools bleiben: PhysarumViewer/EngineCheck/PresetGallery als
macOS-only swift run-Tools. Die WKWebView-DMG (macos/) bleibt separat.
- Preset-Assets: Das Export-Skript (scripts/export-native-presets.ts) bekommt das
App-Resources/-Verzeichnis als drittes Ziel — Single-Source bleibt apple/presets/,
kein Drift. Thumbnails werden ebenfalls ins Bundle kopiert.
4. Komponenten & Datenfluss¶
| Komponente | Rolle |
|---|---|
PhysarumApp (@main) |
App-Entry, hält AppState |
AppState (@Observable) |
selectedPreset, liveParams (Overrides), isFullscreen, autoCycle, PresetCatalog |
PresetCatalog |
lädt die 25 JSONs aus dem Bundle → [Preset] (sortiert) |
LiveParams |
die respawn-freien Overrides + Mapping auf PresetParams |
PlayerView |
MetalView (NS/UIViewRepresentable um MTKView) + RenderCoordinator (MTKViewDelegate) |
RenderCoordinator |
hält SimEngine + Renderer; treibt den 60fps-Loop; reagiert auf Preset-/Live-Param-Wechsel |
LibraryView |
LazyVGrid der Presets mit Thumbnails → setzt selectedPreset |
ControlsView |
Slider-Panel, gebunden an AppState.liveParams |
macOS-Layout v1: NavigationSplitView — Library (Sidebar) · Player (Detail) ·
Controls (Inspector). Fullscreen → reines Playback.
Datenfluss:
Browser-Klick → AppState.selectedPreset → RenderCoordinator: neue SimEngine(preset) + resetFeedback()
Slider → AppState.liveParams → RenderCoordinator pusht Live-Params (kein Respawn)
Auto-Cycle-Timer → nächster Preset (wie Browser-Klick)
RenderLoop (draw): sim.step() → renderer.render(sim, livePreset, into: drawable)
5. Engine-Erweiterungen (im PhysarumEngine-Package)¶
Package.swiftplatforms:.macOS(.v14)→+ .iOS(.v17) + .tvOS(.v17). Die transpilierten MSL-Shader + der Metal-Code sind cross-platform; die macOS-only Executables werden nur viaswift runauf macOS gebaut (von der App nicht referenziert).SimEngine.updateLiveParams(_:): übernimmt die respawn-freien Felder (decayRate, fadeStrength, trailSaturation, diffusionFreq, chaosStrength/-Interval, rhythmAmplitude, exposure, headIntensity, simSpeed, per-species turn/move/sensor/deposit, matrix) ins gespeicherte Preset, sodasswriteParams()sie ab dem nächsten Frame nutzt. Nicht übernommen: agentCount, spawnPattern, populationShare (brauchen Re-Seed).- Post-FX-Live-Params (exposure/bloom/feedback/vignette/saturation/contrast/kaleidoscope/
blendMode/backgroundColor) brauchen keine Engine-Änderung: die App reicht den
live-editierten Preset jeden Frame an
renderer.render(...), das ihn bereits liest.
6. Kuratierte Live-Slider (v1)¶
Gruppiert, hoher visueller Impact, alle respawn-frei:
- Motion: simSpeed (1–8), decayRate (Trail-Persistenz)
- Glow: bloomIntensity, feedbackAmount (Echo/Trails)
- Color/Tone: exposure, saturation, contrast, vignette
Ein „Reset auf Preset-Defaults"-Button stellt die Originalwerte wieder her. (Exakte Range/
Step-Werte aus dem Web-paramConfig/Schema im Plan übernehmen.)
7. Plattform-Strategie¶
- Geteilt (≈90%): Engine-Package,
AppState,PresetCatalog,LiveParams,RenderCoordinator-Logik, der Großteil der Views. - Plattform-spezifisch via
#if os(): der dünne MetalView-Wrapper (NSViewRepresentable↔UIViewRepresentable) und Navigation/Input (macOS: SplitView + Maus/Tastatur · tvOS: Focus-Engine + Siri-Remote · iOS: Touch).MTKViewläuft überall. project.ymldeklariert alle Destinations; nur macOS-UI wird v1 ausgebaut. tvOS/iOS-UI sind je ein eigenes Increment.
8. Testing & Verifikation¶
- Engine-XCTests laufen unverändert weiter (19 grün), inkl. neuer Tests für
updateLiveParams(respawn-freie Felder werden übernommen, agentCount/spawnPattern nicht). - App-Logik (unit-testbar, eigenes Test-Target oder via die Engine-Suite, soweit ohne
UI):
PresetCataloglädt alle 25 JSONs;LiveParams→PresetParams-Mapping ist korrekt. - SwiftUI-Views: keine Unit-Tests; Verifikation über
xcodebuild(macOS-Target baut) - visuell (laufende App screenshotten / Johannes sieht sie auf dem Mac).
- Verifikations-Gate:
xcodegen generate→xcodebuild -scheme Physarum -destination 'platform=macOS' buildgrün; App startet, rendert ein Preset, Browser + Slider reagieren.
9. Distribution-Roadmap¶
- Jetzt: native App ist ein lokaler Dev-Build (
xcodebuild/Xcode-Run). Keine neue DMG. - Bei Editor-Reife (späteres Increment): native App wird die macOS-DMG (signiert +
notarisiert über die bestehende
build-macos-app.sh-Kette, angepasst auf das App-Target). Die WKWebView-DMG wird dann zurückgezogen. - Offener Roadmap-Punkt: Audio-Reaktivität + Capture/Export sind Web-only — vor dem Cutover entscheiden: nativ portieren oder bewusst fallenlassen (Spec hatte Audio v1-out).
10. Scope v1 (in) / Out-of-scope¶
In: macOS-App · Playback (Fenster + Fullscreen) · Preset-Browser (25 + Thumbnails) · Auto-Cycle · kuratierte Live-Slider · „Reset auf Defaults" · die zwei Engine-Erweiterungen.
Out-of-scope (spätere Increments): tvOS/iOS/iPadOS-UI · voller Parameter-Editor · Custom-Preset-Speichern/-Verwalten · Audio · Capture/Export · Hue Entertainment · CloudKit-Sync · neue notarisierte DMG.
11. Offene Risiken / noch zu verifizieren¶
- XcodeGen-Verfügbarkeit:
brew install xcodegen(autonom installierbar). Falls unerwünscht, Fallback Tuist/Xcode-GUI — aber XcodeGen ist gewählt. project.ymlMultiplatform-Single-Target-Syntax: ein Target mit mehrerensupportedDestinations(XcodeGen unterstützt das); im Plan verifizieren.- Live-Param-Update Thread-Safety:
updateLiveParamswird vom Main/Draw-Thread gerufen,writeParamsläuft im Draw-Loop — beide auf derselben SimEngine; im Plan sicherstellen, dass der Push vor dem nächstenstep()sichtbar ist (einfachster Weg: beide auf dem Draw-Thread). - Asset-Pfad im Bundle:
Bundle.main(App) vsBundle.module(Package) — die App lädt Presets/Thumbnails ausBundle.main; im Plan korrekt verdrahten.