# Beyond 2 SteamVR Proximity Driver

## What This Is

A SteamVR driver for the Bigscreen Beyond 2 VR headset that provides automatic standby/wake via proximity sensing and live software IPD adjustment with SteamVR slider integration. Deployed as a nested driver within the official Bigscreen Beyond Driver package.

## Core Value

SteamVR and OpenVR applications can detect when the Beyond 2 is on or off the user's head, enabling automatic standby/wake and correct in-game presence detection.

## Requirements

### Validated

- ✓ Driver loads as sidecar alongside lighthouse driver without regression — v1.0
- ✓ HID access to Beyond 2 proprietary device from within vrserver.exe — v1.0
- ✓ HMD proximity triggers SteamVR standby/wake + app-visible state — v1.0 property toggle, v2.0 /proximity input component (Phase 11.1)
- ✓ Firmware-equivalent proximity algorithm with calibration, moving average, hysteresis — v1.0
- ✓ Algorithm-driven standby/wake with manual override CLI — v1.0
- ✓ VRSettings-based runtime configuration — v1.0
- ✓ Graceful degradation and crash safety — v1.0
- ✓ Single-click installer for tester distribution — v1.0
- ✓ All 31 v1 requirements satisfied — v1.0

- ✓ Live IPD change via named pipe command (`ipd <mm>`) with 48-75mm validation — v2.0
- ✓ SetDisplayEyeToHead from sidecar for cross-driver rendering control — v2.0
- ✓ SteamVR built-in IPD slider UI appears and works with Beyond 2 HMD — v2.0
- ✓ IPD changes persist to headset flash via lighthouse_console on shutdown — v2.0
- ✓ /proximity input component for app-visible presence (VRChat, ET enrollment) — v2.0

- ✓ USB-first LED hardware communication via Adalight over Win32 overlapped serial to WLED/ESP32-C3 (MagWLED-1) — v3.0 (Phase 14)
- ✓ Per-LED RGB addressability (10× WS2812B) with global max brightness ceiling (default 50/255, LHWD-02 safety cap) — v3.0 (Phase 14)
- ✓ Named pipe/CLI extension for backglow control (`backglow fill|set|bri|off|on`) with degraded-mode hotplug re-init — v3.0 (Phase 14)

- ✓ ESP32-C3 COM port auto-detect via SetupAPI VID/PID scan (`0x303A`/`0x1001`, lowest-COM tiebreak, hotplug-arrival rescan) — v3.0 (Phase 15, LHWD-04)
- ✓ WiFi/DDP fallback transport (WledDdpTransport over UDP/4048 with `/json/info` HTTP probe + segment-clear POST + 20ms double-send first-frame mitigation) — v3.0 (Phase 15, TRNS-02)
- ✓ User-selectable transport via `backglow_transport` VRSettings (`usb|ddp|auto` with startup-only AUTO fallback, no live failover per D-04) — v3.0 (Phase 15, TRNS-03)
- ✓ Multi-line `backglow status` diagnostics (transport / conn / port|host / bri / ceiling / leds / err with stable reason tokens; 1024-byte pipe buffer) — v3.0 (Phase 15, DIAG-01)

- ✓ VRChat OSC → LED bridge daemon (beyond_backglow_ctl.exe) spawned by driver via Windows Job Object kill-on-close, with watchdog 1s/2s/4s respawn backoff cap 3, dedicated PIPE_ACCESS_INBOUND+PIPE_NOWAIT daemon pipe avoiding FlushFileBuffers server_main hang, graceful stdin-EOF shutdown — v3.0 (Phase 16, VRCH-01)
- ✓ 31-parameter OSC decoder (`/avatar/parameters/Backglow{R|G|B}0..9` + `BackglowBri`) with lock-free ParamMap, 90Hz pipe writer with frame+Bri dedup, 3s silence-fade 500ms ramp, 4s startup animation, loopback-only bind — v3.0 (Phase 16, VRCH-02)
- ✓ D-19 UDP 127.0.0.1:9001 fallback path (D-18 OSCQuery+mDNS primary deferred post-ship due to Windows system mDNS port 5353 conflict — documented in 16-HUMAN-UAT.md as non-blocking gap) — v3.0 (Phase 16, VRCH-01f)

### Active

## Current Milestone: v3.0 Backglow Driver

**Goal:** Extend bey-closer to drive facial-interface LEDs from VR applications, digitally emulating transscleral illumination ("backglow") on prototype hardware.

**Target features:**
- VRChat world-controlled backglow (control interface TBD via research)
- USB-first LED hardware communication (WLED/ESP32-C3 prototype), WiFi/DDP fallback
- Core/prototype architecture separation (core protocol independent of prototype hardware)
- Named pipe/CLI extension for backglow control and diagnostics
- Per-LED RGB addressability (10x WS2812B) with global max brightness ceiling
- Avatar-based control as secondary priority to world-based control

### Out of Scope

- Firmware changes to add person_detected to HID report — PC-side replication sufficient
- Modifications to SteamVR source code — no access
- Eye tracking integration — separate concern
- Fan/thermal control via driver — existing tools handle this
- Changes to lighthouse config stored in headset flash (except IPD persistence via lighthouse_console — shipped v2.0)
- Auto-update mechanism — relies on Steam's existing update mechanism

## Context

**Shipped v1.0** (2026-03-23) with 1,147 LOC C++ proximity driver. **Shipped v2.0** (2026-04-05) with live IPD adjustment (+1,208 LOC, 2,230 LOC total across 15 source files). Tech stack: C++17, OpenVR SDK v2.5.1, HIDAPI 0.14.0, MSVC 2022, CMake, Inno Setup 6.

**Architecture:**
- Nested driver within official Bigscreen Beyond Driver package (`bin/BeyondProximity/`)
- `DeviceProvider` orchestrates HID reader thread, proximity algorithm, and RunFrame state machine
- `HidDevice` owns background reader thread with TLV+CRC8 calibration parser and USB reconnect
- `ProximityAlgorithm` computes person_detected with calibration subtraction, moving average, and hysteresis
- Named pipe server (`\\.\pipe\beyond_proximity_ctl`) for debug commands and manual override
- `beyond_prox_ctl.exe` CLI for pipe commands (proximity on/off/auto, status)

**Key technical decisions:**
- HMD property write (Prop_ContainsProximitySensor_Bool) for standby/wake — /proximity on GenericTracker only affects tracker
- No device registration (no TrackedDeviceAdded) — driver is invisible in SteamVR device list
- HIDAPI shared-mode access — no contention with BeyondHID.exe companion app
- In-driver HID access — no IPC bridge needed

## Key Decisions

| Decision | Rationale | Outcome |
|----------|-----------|---------|
| Sidecar driver (not override) | Phase 1 validated coexistence — zero HMD regression | ✓ Good |
| HMD property write for standby/wake | /proximity on GenericTracker only affects tracker sleep | ✓ Good — discovered in Phase 3 spike |
| Remove virtual tracker (Phase 3.1) | No device needed — HMD property write is sufficient | ✓ Good |
| Nested driver in Beyond package (Phase 3.2) | Follows existing BeyondEyetracking pattern | ✓ Good |
| PC-side proximity computation | All needed data available via HID; avoids firmware dependency | ✓ Good |
| In-driver HID access | Phase 2 proved shared-mode works from vrserver.exe | ✓ Good |
| Inno Setup installer | Single .exe for tester distribution with full deploy logic | ✓ Good |
| SetDisplayEyeToHead needed for IPD | Phase 10 spike: property-based IPD (SetFloatProperty) doesn't affect rendering — lighthouse/ET driver's SetDisplayEyeToHead overrides it | Key finding — Phase 11 must use SetDisplayEyeToHead |
| SetDisplayEyeToHead works from sidecar | Phase 10.1 spike: sidecar can call SetDisplayEyeToHead(0, left, right) for HMD it doesn't own — visual eye separation changes confirmed | GO for Phase 11 direct approach |
| EyeToHead rotation from lhr-* config by serial | Phase 10.1: lhr-* config files are fine but must match the connected HMD's lighthouse serial (readable from user flash, same source as proximity calibration). Spike used lighthouse_console.exe but production should match by serial. Cache rotation after first read. | Key implementation detail for Phase 11 |
| Slider UI not triggerable from sidecar | Phase 10 spike: IpdUIRange properties accepted (err=0) but slider doesn't appear — alternative UI approach needed | Phase 12 descoped/revised |
| Separate process per lighthouse_console command | HMDUtility pattern: spawn-serial-command-exit per operation with stdout-blocking sync, not single persistent session with file-polling | ✓ Good — reliable, proven pattern |
| ENABLE_IPD_PERSIST compile flag | All persistence code behind #ifdef so Beyond Utility can take over without code churn | ✓ Good — clean separation |
| LH serial from SteamVR property system | Phase 11: HID user flash tag 0x09 not present on all HMDs. Prop_SerialNumber_String on HMD container (set by lighthouse driver) is the reliable source. Falls back to HID flash. | ✓ Good — no lighthouse_console needed |
| CreateBooleanComponent("/proximity") from sidecar | Phase 11.1 spike: sidecar can create its own /proximity component on HMD container (deferred until HMD ready). UpdateBooleanComponent propagates to app-side ActivityLevel + events. Cross-driver Update is blocked (WrongType) but creating a new component with the same name works. | ✓ Good — replaces property toggling, fixes VRChat AFK + ET enrollment |

## Constraints

- **Language**: C++ — standard for SteamVR driver DLLs
- **Platform**: Windows (primary SteamVR platform for Beyond 2)
- **API**: OpenVR driver API (openvr_driver.h)
- **Compatibility**: Must not break existing tracking, display, or audio
- **Distribution**: Via Bigscreen Beyond driver package pipeline

## Completed Milestones

### v2.0 Live IPD Change (shipped 2026-04-05)

Live software IPD adjustment via named pipe and SteamVR dashboard slider, with persistence to headset flash. Key findings: SetDisplayEyeToHead from sidecar works for cross-driver rendering; /proximity input component fixes VRChat/ET app compatibility. 6 phases, 12 plans, +1,208 LOC.

## Evolution

This document evolves at phase transitions and milestone boundaries.

**After each phase transition** (via `/gsd:transition`):
1. Requirements invalidated? → Move to Out of Scope with reason
2. Requirements validated? → Move to Validated with phase reference
3. New requirements emerged? → Add to Active
4. Decisions to log? → Add to Key Decisions
5. "What This Is" still accurate? → Update if drifted

**After each milestone** (via `/gsd:complete-milestone`):
1. Full review of all sections
2. Core Value check — still the right priority?
3. Audit Out of Scope — reasons still valid?
4. Update Context with current state

---
*Last updated: 2026-04-19 after Phase 16 complete (VRChat OSC bridge daemon + Job Object lifecycle + 90Hz pipe + startup anim + D-19 fallback; D-18 OSCQuery+mDNS primary deferred post-ship)*
