# Project Research Summary

**Project:** bey-closer v3.0 - Backglow LED Control
**Domain:** SteamVR sidecar driver extension - WS2812B LED control via WLED/ESP32-C3, VRChat OSC integration
**Researched:** 2026-04-05
**Confidence:** HIGH

## Executive Summary

This is a SteamVR driver extension that adds ambient facial LED illumination to the Beyond 2 headset. The hardware prototype is an ESP32-C3 running stock WLED firmware (MagWLED-1) controlling 10x WS2812B LEDs, connected via USB serial. The established pattern for projects of this type uses a two-process architecture: LED I/O lives inside the driver, while VRChat OSC translation lives in a separate companion process that sends named pipe commands to the driver. This cleanly isolates VR-session-critical I/O from application-level concerns and mirrors bey-closer existing design philosophy.

The recommended approach is USB-first with the Adalight binary protocol for per-LED color streaming, JSON API over serial for infrequent control commands (brightness, power), and a DDP/UDP path as a WiFi fallback. No new external libraries are needed beyond oscpp (header-only, dropped into extern/). The control surface extends the existing named pipe with led prefixed commands. VRChat integration is indirect: worlds drive avatar parameters via contact senders, VRChat emits those parameter changes as OSC on port 9001, and a standalone beyond_backglow_ctl.exe daemon bridges OSC to pipe commands.

The top risk is safety: 10x WS2812B at full white draw 3W of heat directly against the face and can exceed USB 2.0 current limits. A software brightness ceiling must be the first thing implemented and enforced in the driver layer. The second major risk is threading: serial I/O must never touch the RunFrame thread. Both risks have well-documented mitigations from the existing HidDevice pattern in the codebase.

## Key Findings

### Recommended Stack

The existing C++17/MSVC/CMake/OpenVR stack is unchanged. New additions are minimal by design. Win32 serial API handles COM port I/O with no library needed (30 lines of setup for Adalight). Winsock2 handles both DDP/UDP and OSC transport already available in the Windows SDK. The only new compile-time dependency is oscpp, a header-only C++11 library for OSC packet construction and parsing that carries no networking baggage.

**Core technologies:**
- Win32 serial API: USB serial to WLED ESP32-C3 - zero new dependencies, Windows-only project, about 30 lines for full Adalight support
- Winsock2 ws2_32.lib: UDP transport for DDP (WiFi fallback) and OSC listener - already in Windows SDK, may already be linked
- oscpp header-only: OSC packet encode/decode - header-only C++11, ISC license, no networking layer, fits project minimal-dependency philosophy
- Adalight protocol: binary per-LED streaming over serial - 36 bytes per 10-LED frame at 115200 baud = 0.31ms, well within RunFrame budget if async
- WLED JSON API over serial: non-realtime control (brightness, power, state query) - same serial port, WLED auto-detects by first byte

### Expected Features

The MVP draws a clear line: hardware communication and safety features first, VRChat integration second.

**Must have (table stakes):**
- USB serial communication to WLED - foundation; everything else depends on this
- Brightness safety ceiling (driver-side clamp, non-negotiable) - LEDs are millimeters from the eyes; thermal and ocular safety risk at full brightness
- Static color via named pipe (led fill, led set) - basic control and testing
- Per-LED RGB addressability - 10 independent LEDs for directional effects
- Global brightness control - ceiling enforcement
- Auto-off on driver Cleanup - LEDs must not remain lit when VR session ends
- WiFi/DDP fallback - for wireless VR scenarios; same 40-byte UDP packet

**Should have (competitive):**
- VRChat avatar OSC bridge via beyond_backglow_ctl.exe daemon - the product differentiator; world/avatar-reactive facial glow
- Parameter-to-LED mapping config - how avatar float params map to LED colors and zones
- Reference avatar prefab with contact receivers - enables world creators to target the system
- Real-time color streaming at target framerate - smooth transitions at 30fps
- Preset/effect system - named configurations for demos

**Defer (v2+):**
- World creator SDK and documentation
- Multi-headset sync
- Screen color sampling (ambilight mode) - high complexity, not the product vision
- Alternative hardware backends

### Architecture Approach

The architecture extends the existing driver DLL with a src/led/ module containing an ILedTransport abstract interface, a WledSerialTransport prototype implementation, and a LedController orchestrator that owns a dedicated writer thread, enforces brightness ceiling, rate-limits output, and double-buffers LED state. VRChat OSC handling lives in a separate beyond_backglow_ctl.exe process (not in vrserver.exe) to isolate UDP socket concerns from the VR session. The named pipe remains the universal control surface.

**Major components:**
1. ILedTransport interface + WledSerialTransport - hardware abstraction; Adalight for color streaming, JSON for control commands
2. LedController - writer thread, rate limiter (30fps), brightness ceiling, double-buffered state; lives inside driver DLL
3. Named pipe extension - led set/fill/bri/on/off/status commands added to existing named pipe
4. beyond_backglow_ctl.exe daemon - standalone process; OSC UDP listener on port 9001, parameter mapper, named pipe client
5. beyond_prox_ctl.exe (modified) - CLI gains led command validation; no new executable needed for pipe-based control

### Critical Pitfalls

1. **Blocking serial writes in RunFrame** - Serial WriteFile can block 1-50ms on buffer-full; stalls ALL SteamVR drivers sharing vrserver thread. Use dedicated writer thread with atomic command queue; open COM port with FILE_FLAG_OVERLAPPED; set WriteTotalTimeoutConstant to 50ms max. Never call serial I/O from RunFrame.

2. **Thermal and power safety (brightness ceiling)** - 10x WS2812B at full white = 600mA/3W against face; exceeds USB 2.0 500mA limit. Implement driver-side brightness clamp as the first thing built. Default ceiling at 50/255 keeps total draw under 200mA. This is non-negotiable.

3. **WLED JSON API unreliability for realtime** - ESP32-C3 USB CDC buffer is small; JSON payloads for 10 LEDs can exceed it and be silently dropped. Use Adalight (binary, 36 bytes/frame) for per-LED color streaming. Reserve JSON for infrequent control commands with 20ms inter-command delay.

4. **VRChat pipeline has no direct world-to-external path** - Udon has no OSC, HTTP, or socket API. The only viable path is avatar parameters to VRChat OSC output to bridge process. Use unsynced avatar parameters to avoid the 256-bit sync budget constraint.

5. **ESP32-C3 USB disconnect handling** - Device disconnect fails pending serial ops with non-obvious error codes. Must model serial lifecycle after existing HidDevice reconnect pattern: background thread, state machine, periodic re-enumeration, never propagate errors to RunFrame.

## Implications for Roadmap

Based on research, the dependency graph is unambiguous: hardware I/O must be solid before any VRChat integration is layered on top. The interface boundary must be defined before any WLED-specific code is written, or refactoring cost is HIGH.

### Phase 1: USB Serial Foundation

**Rationale:** Everything else depends on hardware I/O working correctly and safely. Brightness ceiling and threading model must be correct from the first commit - retrofitting either is medium-to-high cost. This is the highest-risk phase technically.
**Delivers:** CLI-controllable LEDs with safety ceiling; end-to-end hardware demo from CLI command to physical LEDs.
**Addresses:** USB serial communication, brightness safety ceiling, static color control, per-LED addressability, auto-off on exit, global brightness.
**Avoids:** Blocking serial in RunFrame (dedicated writer thread from day one), WLED JSON for realtime (Adalight binary protocol), no brightness ceiling (first feature implemented), WLED coupling (ILedTransport interface before any implementation).
**Build order within phase:** ILedTransport interface, WledSerialTransport (Adalight + JSON), LedController (writer thread + ceiling), named pipe led commands, CLI extension.

### Phase 2: WiFi/DDP Fallback

**Rationale:** DDP over UDP is structurally simpler than OSC integration and validates the ILedTransport abstraction with a second concrete implementation. Delivers value for wireless VR scenarios before the more complex VRChat work begins.
**Delivers:** WledDdpTransport implementation; automatic USB-to-WiFi failover; led status reports active transport.
**Uses:** Winsock2 UDP (no new dependency); DDP 40-byte packet format (10-byte header + 30-byte RGB payload).
**Implements:** Transport selection logic in LedController; COM port auto-detection via VID/PID enumeration.
**Avoids:** WiFi-first communication (USB remains primary; DDP is explicit fallback only).

### Phase 3: VRChat OSC Bridge

**Rationale:** Built on top of proven hardware I/O layer. Architecturally isolated in a separate process, so driver stability is not at risk. This is the product differentiator - the feature that makes backglow world-interactive.
**Delivers:** beyond_backglow_ctl.exe; VRChat avatar parameter to OSC to LED color pipeline; reference avatar parameter layout documentation.
**Uses:** oscpp header-only for OSC packet parsing; Winsock2 UDP listener on port 9001; existing named pipe client pattern.
**Implements:** Parameter-to-LED mapping (BackglowR/G/B/Bri float params to led fill commands); configurable mapping via settings file.
**Avoids:** OSC listener in driver DLL (firewall prompts, port conflicts, crash risk); synced avatar parameters (use unsynced to avoid 256-bit sync budget).

### Phase 4: VRChat Avatar Prefab and World Creator Enablement

**Rationale:** The bridge is useless without an avatar that has the right parameters. A reference Unity prefab with contact receivers pre-wired to backglow parameters is what enables the VRChat ecosystem to target the system.
**Delivers:** Unity avatar prefab with VRCContactReceiver components mapped to BackglowR/G/B/Bri; setup guide for world creators; end-to-end world-to-LED demo.
**Avoids:** Synced parameter sync budget overflow (use unsynced params); complex per-LED world scripts (3-4 float params covers all use cases).

### Phase 5: Polish and UX

**Rationale:** User-facing quality features that do not affect core functionality.
**Delivers:** Preset/effect system (named configurations); real-time smooth color streaming at 30fps; graceful degradation when ESP32 absent; connection status reporting; installer bundling of backglow daemon.

### Phase Ordering Rationale

- Phase 1 must be first: brightness ceiling and threading model are foundational safety/stability decisions. Getting them right up front costs one phase; retrofitting them costs multiple.
- Phase 2 before Phase 3: validates the transport abstraction with a second concrete implementation before VRChat complexity is added.
- Phase 3 before Phase 4: no point building the avatar prefab until the bridge exists to test against.
- Phase 5 last: polish that does not gate any functionality.

### Research Flags

Phases likely needing /gsd:research-phase deeper dive during planning:
- **Phase 3 (VRChat OSC Bridge):** Avatar parameter layout design, contact receiver UX, parameter-to-LED mapping config format, and interaction between multiple avatar param updates in a single VRChat frame need detailed design work.
- **Phase 4 (Avatar Prefab):** Unity avatar prefab structure, VRCContactReceiver configuration, animator parameter wiring - requires Unity-specific knowledge beyond what was researched here.

Phases with standard patterns (skip research-phase):
- **Phase 1 (USB Serial Foundation):** Win32 serial API, Adalight protocol, and threading patterns are thoroughly documented. The existing HidDevice pattern in the codebase is the direct model.
- **Phase 2 (WiFi/DDP Fallback):** DDP protocol spec is authoritative and simple. Winsock2 UDP is standard.
- **Phase 5 (Polish):** Straightforward UX improvements on validated foundations.

## Confidence Assessment

| Area | Confidence | Notes |
|------|------------|-------|
| Stack | HIGH | All protocols verified against official docs and WLED source. Win32 APIs are stable. oscpp well-understood. No speculation. |
| Features | MEDIUM | Core LED features are HIGH confidence. VRChat avatar OSC path is HIGH confidence (proven by multiple shipping projects). World-to-avatar contact chain is MEDIUM - well-documented pattern but specific avatar param layout needs design validation. |
| Architecture | HIGH | Existing codebase patterns (HidDevice threading, named pipe routing, compile-time feature flags) provide direct models for every new component. ILedTransport interface boundary and process separation rationale are solid. |
| Pitfalls | HIGH | Critical pitfalls verified against official WLED source, ESP-IDF docs, VRChat official docs, and existing codebase. WS2812B power measurements from hardware teardown data (PJRC). |

**Overall confidence:** HIGH

### Gaps to Address

- **Avatar parameter layout design:** Exactly which parameters (count, names, types, ranges) to expose on the avatar prefab has not been finalized. Research identified constraints (unsynced, float 0.0-1.0, up to 8192 total) but the specific layout (uniform color vs. per-zone vs. per-LED) is a design decision. Flag for Phase 3 planning.
- **MagWLED-1 GPIO3 serial conflict:** Research noted that if WLED allocates GPIO3 for LED data output, serial RX is disabled on ESP32-C3. The MagWLED-1 product page does not publish pin assignments. Validate with hardware before Phase 1 implementation.
- **ESP32-C3 USB CDC baud rate framing at startup:** Architecture and pitfalls research consistently note USB CDC ignores software baud rate. Framing behavior at startup needs a quick validation test in Phase 1 spike.
- **WLED seg.i dimness bug (issue 2549):** Per-LED color via JSON seg.i has known dimness issues. Since Adalight is recommended for streaming, this may be moot - verify during Phase 1 spike.

## Sources

### Primary (HIGH confidence)
- https://kno.wled.ge/interfaces/serial/ - Adalight/TPM2 protocol, baud rate commands, JSON API reliability
- https://github.com/Aircoookie/WLED/blob/main/wled00/wled_serial.cpp - Exact protocol parser, Adalight state machine
- http://www.3waylabs.com/ddp/ - Authoritative DDP header format spec
- https://kno.wled.ge/interfaces/ddp/ - WLED DDP implementation details
- https://docs.espressif.com/projects/esp-idf/en/stable/esp32c3/get-started/establish-serial-connection.html - USB CDC/ACM, Windows auto-detection, VID/PID
- https://docs.espressif.com/projects/esp-idf/en/stable/esp32c3/api-guides/usb-serial-jtag-console.html - Buffer behavior, sleep disconnect behavior
- https://docs.vrchat.com/docs/osc-avatar-parameters - Ports 9000/9001, parameter types, bidirectional flow
- https://docs.vrchat.com/docs/osc-overview - Port config, enable requirements
- https://creators.vrchat.com/common-components/contacts/ - Contact sender/receiver mechanics
- https://magwled.com/pages/about-magwled-1 - ESP32-C3, USB-C PD, board dimensions
- https://www.pjrc.com/how-much-current-do-ws2812-neopixel-leds-really-use/ - 60mA/LED at full white, measured
- https://support.microsoft.com/en-us/topic/howto-specify-serial-ports-larger-than-com9-db9078a5-b7b6-bf00-240f-f749ebfd913e - COMx prefix requirement
- https://github.com/kaoskorobase/oscpp - Header-only C++11, ISC license, feature set

### Secondary (MEDIUM confidence)
- https://github.com/danielfvm/Patstrap - Avatar OSC to ESP32 hardware reference confirming the pattern
- https://github.com/kikookraft/HapticPatPat - Avatar OSC to ESP32 hardware reference
- https://wiki.openshock.org/guides/shockosc/avatar-setup-vrc - Avatar OSC hardware bridge, another shipping example
- https://github.com/Aircoookie/WLED/issues/2557 - JSON command drop reports at speed
- https://creators.vrchat.com/worlds/udon/networking/network-details/ - 256-bit sync limit, unsynced parameter capacity
- https://feedback.vrchat.com/udon/p/osc-for-worlds-udon - Confirms world OSC not available as of 2026-04

### Tertiary (LOW confidence, needs validation)
- https://github.com/Aircoookie/WLED/issues/2549 - Known limitation for seg.i JSON control; moot if Adalight used for streaming
- https://blog.arduino.cc/2023/07/10/add-peripheral-lighting-to-improve-vr-immersion/ - Prior art for face-mounted WS2812B in VR

---
*Research completed: 2026-04-05*
*Ready for roadmap: yes*