# Phase 16: VRChat OSC Bridge - Discussion Log

> **Audit trail only.** Do not use as input to planning, research, or execution agents.
> Decisions are captured in 16-CONTEXT.md — this log preserves the alternatives considered.

**Date:** 2026-04-19
**Phase:** 16-vrchat-osc-bridge
**Areas discussed:** Daemon lifecycle, Avatar OSC param surface, Param → command mapping, OSC + config robustness

---

## Area Selection

User selected all 4 gray areas for discussion (multi-select).

| Option | Description | Selected |
|--------|-------------|----------|
| Daemon lifecycle | Process launch/stop/watchdog, VRSettings toggle, CreateProcess, Job Object, crash recovery | ✓ |
| Avatar OSC param surface | Which avatar params consumed, per-LED vs uniform, naming, off-trigger | ✓ |
| Param → command mapping | Rate limit, epsilon, brightness routing, startup state | ✓ |
| OSC + config robustness | Port config, bind scope, collision behavior, mapping source | ✓ |

---

## Daemon Lifecycle

### VRSettings toggle behavior

| Option | Description | Selected |
|--------|-------------|----------|
| Init-only | Read at driver init; SteamVR restart required to change | |
| Poll via RunFrame | Poll periodically (~1 Hz); toggle on → spawn, off → kill | |
| IVRSettings change callback | Subscribe to change notifications if API supports | |
| **No toggle at all** (free text) | | ✓ |

**User's choice:** "No toggle in vrsettings. If this driver is loaded (there's a toggle for this for all SteamVR drivers), then backglow attempts to function."
**Notes:** Refines VRCH-01 requirement wording. Refines roadmap success criterion #1. Locked as D-01.

### Launch timing

| Option | Description | Selected |
|--------|-------------|----------|
| At driver init if toggle on (Recommended) | DeviceProvider::Init spawns daemon if backglow enabled | ✓ |
| Lazy / on first OSC demand | Requires VRChat-detection mechanism; complex | |
| Always on when backglow enabled | Idles holding UDP socket when VRChat not running | |

**User's choice:** At driver init. Reinterpreted per D-01: no toggle, so spawn happens on `InitBackglow` success path unconditionally (unless backglow is in degraded-disabled state).

### Process kill guarantee

| Option | Description | Selected |
|--------|-------------|----------|
| Job Object + KILL_ON_JOB_CLOSE (Recommended) | Kernel guarantees daemon death even on vrserver crash | ✓ |
| TerminateProcess in Cleanup only | Orphans daemon if vrserver crashes | |
| Graceful exit pipe + timeout-kill | Mirrors HMDUtility pattern; no orphan protection | |

**User's choice:** Job Object + KILL_ON_JOB_CLOSE. Locked as D-03. D-06 layers a graceful-exit handshake on top before the Job Object closes.

### Crash recovery policy

| Option | Description | Selected |
|--------|-------------|----------|
| Respawn with backoff cap (Recommended) | Exponential 1s/2s/4s, capped at 3 attempts | ✓ |
| One-shot, log + give up | User restarts SteamVR to recover | |
| Immediate unlimited respawn | Risks tight crash loop | |

**User's choice:** Respawn with backoff cap. Locked as D-04. Uptime ≥ 60 s resets counter.

---

## Avatar OSC Parameter Surface

### Parameter set

| Option | Description | Selected |
|--------|-------------|----------|
| BackglowR/G/B floats (Recommended) | 3 floats, uniform fill | |
| BackglowR/G/B + BackglowBri | Adds analog brightness | |
| BackglowR/G/B + BackglowEnable bool | Adds on/off toggle | |
| Full surface: R/G/B + Bri + Enable | All four | |
| **R/G/B + Bri, exploring single-param RGB packing** (free text) | | ✓ |

**User's choice:** "R/G/B + Bri. This way, the avatar menu can expose a user-facing control for Backglow Brightness (analog). Setting it to 0 is functionally equivalent to 'off'. Actually, is there any way to encode all of (RGB) into a single param? Like being able to pass up to the decimal equivalent of #FFFFFF. This would cut per-LED params from 30 to 10."
**Notes:** Triggered a deeper dive into VRChat synced-param precision (8 bits per float) — see budget sub-discussion below.

### Per-LED vs uniform

| Option | Description | Selected |
|--------|-------------|----------|
| Uniform fill only (Recommended) | 3-param surface; simplest | |
| Uniform + per-LED hook deferred | Phase 17 adds per-LED | |
| Per-LED from day 1 | 30-param surface | ✓ |

**User's choice:** "Per-LED from day 1. The whole point is being able to emulate how light reaches the face at different angles and intensities."
**Notes:** Locked as D-07, D-11. Rejects "LEDs >30 fps imperceptible" reasoning implicit in uniform-only baseline.

### Parameter naming

| Option | Description | Selected |
|--------|-------------|----------|
| BackglowR, BackglowG, BackglowB (Recommended) | PascalCase, matches research example | ✓ (PascalCase) |
| backglow_r, backglow_g, backglow_b | snake_case | |
| Bey / Beyond prefix | Brand-prefixed | |

**User's choice:** "PascalCase ok. Hopefully we can store (RGB) in a single param, so it's then Backglow0...Backglow9."
**Notes:** PascalCase locked; single-packed-param approach ruled out after budget discussion; per-LED naming finalized as `Backglow{R|G|B}{0..9}` in D-08.

### Off trigger

| Option | Description | Selected |
|--------|-------------|----------|
| RGB=0,0,0 → backglow off (Recommended) | Zero-epsilon fill check | |
| Explicit BackglowEnable bool | Requires separate param | |
| Timeout after silence | Auto-off on VRChat close | |
| **BackglowBri=0 triggers off** (free text) | | ✓ |

**User's choice:** "BackglowBri = 0. Even if one LED is 0,0,0, another LED might need to be on. Assume that the backglow driver will be constantly controlling the LEDs over the course of an entire VR session. They don't just 'time out', they are an ever-present representation of the light in the virtual environment."
**Notes:** Locked as D-10. Overrides the RGB=0 heuristic because per-LED model allows one dark LED while others remain active. Later revised by D-15 (3 s silence fade) because of second concern about LEDs being "distracting if on but not representative of environment" — see mapping area.

### Budget sub-discussion (encoding)

Triggered by user's question about packing RGB into a single avatar param.

Facts presented:
- VRChat synced float params are 8-bit quantized → 24-bit RGB cannot fit in one synced float.
- Default avatar expression param budget = 256 bits (total, across all synced params).
- Full face tracking + AudioLink + eye tracking + base menus already consume ~200-250 bits on typical avatars.

| Option | Description | Selected |
|--------|-------------|----------|
| 30 synced floats (R0..9, G0..9, B0..9) | 240 bits + 8 for Bri = 248 bits | |
| 10 synced ints as RGB332 packed | 80 bits, 256 colors/LED, lossy | |
| 10 ints + 10 floats (hue/value split) | 160 bits, no saturation | |
| Uniform fill only; defer per-LED to Phase 17 | Sidesteps the whole problem | |
| **Unsynced params, full precision** (recommended after user asked about shared budget) | 0 synced bits; full IEEE 754 | ✓ |
| Packed ints (animator bit-math) | 80 bits; complex avatar-side math | |
| Research mode: spawn advisor agent | Defer decision | |

**User's choice:** "Oh well yeah, that's a no-brainer then. We have no reason to sync backglow over the network to other players. It's all about the singular local user's experience. So let's go unsynced params, full precision."
**Notes:** Locked as D-09. The single most consequential engineering decision in Phase 16. Must be documented prominently for Phase 17 avatar prefab.

---

## Param → Command Mapping

### Silence fade implementation

| Option | Description | Selected |
|--------|-------------|----------|
| Daemon-side linear bri ramp (Recommended) | Daemon detects 3 s silence, ramps bri to 0 over ~500 ms | ✓ |
| Driver-side | Adds LedController state machine | |
| Instant off, no fade | Potentially jarring | |

**User's choice:** Daemon-side linear bri ramp. Locked as D-15.

### Rate limit

| Option | Description | Selected |
|--------|-------------|----------|
| Coalesce to 30 Hz, send latest (Recommended) | Matches Phase 14 LedController tick | |
| Coalesce to 60 Hz | Double rate; may exceed LedController tick | |
| No throttle | Pipe thrash | |
| **Coalesce to 90 Hz / ~11 ms, retune LedController** (free text) | | ✓ |

**User's choice:** "The ceiling should be 90 fps. Coalesce to 10 Hz/11 ms intervals. Update the LedController if necessary."
**Notes:** Interpreted "10 Hz/11 ms" as "~90 Hz at 11 ms per tick" given the 90 fps ceiling. Locked as D-12 and D-26. Overrides Phase 14 D-11.

### Epsilon / dedup

| Option | Description | Selected |
|--------|-------------|----------|
| Skip writes where all channels change <1/255 (Recommended) | 8-bit uint dedup | ✓ |
| Stricter: 2/255 minimum delta | Drops flicker; less responsive | |
| No epsilon | Simplest | |

**User's choice:** Skip writes where all channels change <1/255. Locked as D-14 (byte-identical frame skip).

### Startup initial state

| Option | Description | Selected |
|--------|-------------|----------|
| Send backglow off + bri 0 (Recommended) | Clean-slate; no visible cue | |
| Leave untouched until first OSC | Driver's own default | |
| Startup idle animation | Visible cue, extra complexity | |
| **Brief startup animation: ramp white over 1s to 50%, fade to off over 3s** (free text) | | ✓ |

**User's choice:** "Brief startup animation. Ramp white over 1s to 50% brightness, then fade to off over 3s."
**Notes:** Locked as D-16. Doubles as a connectivity smoke indicator.

---

## OSC + Config Robustness

### Port configurability

| Option | Description | Selected |
|--------|-------------|----------|
| Fixed 9001 (Recommended) | VRChat default, zero-config | ✓ (initially) |
| VRSettings knob | Per-install override | |
| CLI flag --port | Daemon argv surface | |

**User's choice:** Fixed 9001.
**Notes:** Superseded by OSCQuery decision (D-18) — dynamic port via mDNS advertise; 9001 becomes the fallback (D-19).

### Bind scope

| Option | Description | Selected |
|--------|-------------|----------|
| Loopback only 127.0.0.1 (Recommended) | No firewall, no remote surface | ✓ |
| All interfaces 0.0.0.0 | LAN OSC; firewall prompt | |

**User's choice:** Loopback only. Locked as D-20.

### Port-busy behavior

| Option | Description | Selected |
|--------|-------------|----------|
| Log ERR, daemon exits (Recommended) | Driver respawn-cap catches | |
| Retry with backoff, wait forever | Masks misconfig | |
| Fall back to 9002/9003 | Silently breaks VRChat | |
| **Research multi-client coexistence** (free text) | | ✓ |

**User's choice:** "Need to research this. There must be a way to have more than one OSC client interacting with VRChat simultaneously."
**Notes:** Triggered OSCQuery research — see next question.

### OSC transport strategy (follow-up)

| Option | Description | Selected |
|--------|-------------|----------|
| OSCQuery + mDNS advertise (Recommended) | Sanctioned VRChat multi-client path | ✓ |
| Fixed 9001 + fail-if-busy for Phase 16, OSCQuery in Phase 17 | Ship solo-tool-only first | |
| Fixed 9001 now, advise user startup sequence | Fragile UX | |

**User's choice:** OSCQuery + mDNS advertise. Locked as D-18. Fixed 9001 downgraded to fallback (D-19).

### Mapping source

| Option | Description | Selected |
|--------|-------------|----------|
| Hardcoded in daemon for Phase 16 (Recommended) | No config file; 1:1 mapping | ✓ |
| JSON config next to exe | Flexible, over-engineered | |
| Driver passes mapping via CLI/env | Tight coupling | |

**User's choice:** Hardcoded in daemon. Locked as D-17.

---

## Claude's Discretion

- mDNS library choice (Windows DNS-SD vs `mdns.h` single-header vs Bonjour SDK)
- OSCQuery HTTP port allocation (dynamic vs probe)
- Daemon log sink (stderr vs rolling file)
- Watchdog thread ownership (dedicated vs RunFrame reuse)
- Graceful-exit signaling mechanism (stdin close / WM_CLOSE / Event handle / ctl pipe)
- Startup-animation easing curve (linear vs ease-in-out)
- Silence-fade timer implementation (wall-clock vs QPC)
- `oscpp` receive buffer size (planner-tuned)
- Pipe-client reconnect cadence on driver pipe-server drops
- OSCQuery JSON payload detail beyond VRChat minimum

## Deferred Ideas (see 16-CONTEXT.md § Deferred Ideas for full list)

- Unity avatar prefab + VRCContactReceiver wiring → Phase 17 (VRCH-03)
- Reference VRChat world with colored light zones → Phase 17 (VRCH-04)
- Runtime daemon kill-switch pipe command → post v3.0
- JSON-configurable param→LED mapping → post v3.0
- `backglow status` daemon-side metrics extension → post v3.0
- Multi-headset sync (ECOS-02) → out of current milestone
- Ambilight / screen sampling (ADVN-01) → out of current milestone
- Effect preset library (ADVN-02) → out of current milestone
