# Scene JSON, HUD overlays, OBS internals

**Read when:** editing `basic/scenes/Default_Stream_HUD.json`, building or modifying the HTML HUDs (`landing/`, `loading/`, `game/`, `desktop/`, `goodbye/`, `music-box/`, `music/`), debugging plugin behavior, or programmatically driving OBS.

## Scene collection JSON — structure

Top-level keys in `basic/scenes/Default_Stream_HUD.json`:

- `name` — collection name (matches filename stem).
- `sources` — **flat array of every source, scene, and group**. Scenes are entries here with `"id": "scene"`; their `settings.items[]` references child sources **by name string**, not uuid. Renaming a source means updating every scene that references it.
- `scene_order` — array of `{name}` defining sidebar order.
- `current_scene` / `current_program_scene` — strings matching scene names.
- `transitions`, `quick_transitions`, `current_transition`, `transition_duration` — transition setup.
- `groups` — group definitions.
- `modules` — per-source-plugin state.
- `version`, `migration_resolution`, `resolution`, `canvases` — collection schema + canvas sizing. Canvas: **2560×1336**.

Each source entry has: `name`, `uuid`, `id`, `versioned_id`, `settings` (plugin-specific), plus filters/transform/audio fields. Scene items inside `settings.items[]` reference sources via `source_uuid`. **Use uuids when adding new items programmatically; use names when reading.**

Source `id` values currently in use:

- `scene` — a scene
- `pipewire-screen-capture-source` — Wayland/PipeWire screen/window capture
- `v4l2_input` — V4L2 webcam (legacy path)
- `browser_source` — CEF browser (post-migration)
- `image_source` — static image
- `text_ft2_source` — FreeType2 text
- `ffmpeg_source` — media file (audio/video) via ffmpeg
- `pulse_output_capture` / `pulse_input_capture` — PulseAudio sink/source capture (used for `mpd_stream.monitor` and the mic)

When editing scene JSON: keep `uuid` values stable across edits, preserve 4-space indentation, and validate with `python3 -m json.tool` before re-launching OBS.

## Game / Desktop / Music Box HUDs — browser-resolution constraints

The Game (`HUD`), Desktop (`Desktop HUD`), and Music Box (`Music Box HUD`) browser sources all stretch (`bounds_type: 1`, bounds 2560×1336) to fill the canvas. **Their `width` / `height` settings must match the canvas (2560×1336)**, not 16:9 like the output resolution. If the browser viewport is 1920×1080, the canvas stretch is non-uniform (1.333× horizontal, 1.237× vertical), which distorts every CSS box relative to the camera's transform (camera is positioned in canvas coords directly, so a CSS-positioned frame around it looks visibly off — wider/squashed than the camera). Symptom: cyan "camera frame" doesn't align with the camera feed inside it. Fix: keep all three browser sources at `width: 2560, height: 1336` so 1 CSS px = 1 canvas px, then position any frame using canvas coords.

The Camera frame in both `game/index.html` and `desktop/index.html` auto-syncs via OBS WebSocket: `GetSceneItemTransform` on connect + `SceneItemTransformChanged` event listener update four CSS vars (`--cam-x/y/w/h`). Move/resize the Camera item in OBS and the frame follows live. Assumes Camera item alignment 5 (top-left) — OBS default; if you change alignment, the math in `setCameraTransform()` needs adjusting. The `?scene=` and `?camera=` URL params let either HUD target a different scene or camera source name.

**The Music Box widget** (`music-box/widget.html`) at 549×880 is the exception: it sits in canvas coords as a native-sized scene item (`bounds_type: 0`), NOT stretched. Authoring viewport must match scene-item dimensions; do not toggle `bounds_type` on without resizing the source's `width`/`height` to match the new bounds. The 549 width is deliberate — camera renders at 1920×1080 × 0.286 = 549×309, so the widget pairs visually as a same-width column directly under the camera.

## Working with scene JSON & overlays

- **Confirm OBS is using THIS dir, not the ghost** before assuming an edit will land. While OBS is running: `ls -la /proc/$(pgrep -x obs)/cwd` and check `lsof -p $(pgrep -x obs) | grep scenes`. Or compare mtimes after an OBS session — the file OBS rewrites is the live one.
- **Inspecting a scene collection:** `python3 -m json.tool basic/scenes/Default_Stream_HUD.json | less`. List scenes: `python3 -c "import json; d=json.load(open('basic/scenes/Default_Stream_HUD.json')); print([s['name'] for s in d['scene_order']])"`.
- **Diffing after an OBS session:** OBS rewrites the file even when nothing visibly changed. Expect noisy diffs; focus on `sources[]` and `scene_order` for meaningful changes.
- **Backing up before edits:** copy the active `.json` somewhere outside this dir — OBS's own `.bak` will be overwritten on the next save.
- **Refreshing a browser source after editing HTML:** right-click source in OBS → **Refresh cache**. No restart needed — CEF re-reads on demand.
- **Plugin development:** Flatpak OBS plugins live inside the runtime; you can't drop `.so` files into the host filesystem and have them load. Plugin extensions for the OBS flatpak come as `com.obsproject.Studio.Plugin.<name>` flatpak extensions (search Flathub). If a plugin only has source/AUR distribution, it won't work in Flatpak — that's the tradeoff of this distribution choice.
- **WebSocket control:** `plugin_config/obs-websocket/config.json` holds connection settings. Programmatic scene edits while OBS is running should go through obs-websocket — file edits get clobbered on save.
