# Phase 11.1: Proximity Sensor App Compatibility Spike - Context

**Gathered:** 2026-03-26
**Status:** Ready for planning (restructured as spike)

<domain>
## Phase Boundary

Determine whether the sidecar driver can discover and update the lighthouse driver's `/user/head/proximity` input component handle, enabling proper proximity signaling to VR apps. This is a feasibility spike — if GO, a follow-up phase implements the production fix.

</domain>

<decisions>
## Implementation Decisions

### Architecture understanding (confirmed)
- **D-01:** Beyond 2 does NOT communicate proximity sensor status to the onboard Tundra SiP. The SiP handles the USB device that SteamVR associates with the lighthouse driver. Therefore proximity state never reaches the lighthouse driver.
- **D-02:** The lighthouse driver DOES create `/user/head/proximity` input component on the HMD as part of its standard HMD input profile — but never updates it (stuck at initial state).
- **D-03:** Our sidecar reads proximity from HID and needs to update the lighthouse driver's existing `/proximity` component.

### Failed approaches (pre-spike findings)
- **D-04:** `CreateBooleanComponent` on HMD container from sidecar returns `VRInputError_InvalidParam` (err=4). Cross-device input component creation is not allowed by the OpenVR API.
- **D-05:** `TrackedDeviceButtonPressed/Unpressed` does not exist in `IVRServerDriverHost_006` (removed when Input 2.0 replaced the button API).
- **D-06:** `VendorSpecificEvent` only allows events in range 10000-19999 (cannot send `VREvent_ButtonPress`).
- **D-07:** `Prop_ContainsProximitySensor_Bool` toggling works for SteamVR standby/wake but is a capability flag, not state — causes issues in other apps (ET enrollment goes black, VRChat ignores it for AFK).

### Spike investigation targets
- **D-08:** Handle probing — `VRInputComponentHandle_t` is a `uint64_t`. Binary analysis of `driver_lighthouse.dll` confirms HMD creates exactly 2 input components: `/input/system/click` then `/proximity`. Probe range is tiny (1-20). Driver's `probe_proximity` command (already implemented) calls `UpdateBooleanComponent(handle, value, 0)` for handles 1-20 and reports error codes.
- **D-09:** Test companion app — Build a small OpenVR client app that connects to SteamVR and monitors `GetTrackedDeviceActivityLevel(0)` and `VRControllerState_t::ulButtonPressed` (bit 31 = `k_EButton_ProximitySensor`). This automates handle identification: driver probes handles, companion app detects which handle actually changes the proximity state. The driver-side `IVRDriverInput` API is write-only (no GetBooleanComponent), so application-side monitoring is the only way to observe proximity state changes programmatically.
- **D-10:** Coordinated spike flow — Driver runs `probe_proximity` (tries handles 1-20), companion app logs which handle triggers a proximity state change. Then `test_handle <N> <0|1>` confirms the identified handle works bidirectionally.

### Verification targets (if handle discovery works)
- **D-12:** SteamVR standby/wake must not regress
- **D-13:** VRChat must detect headset on/off and trigger AFK mode
- **D-14:** Beyond ET enrollment must not go black when proximity triggers

### Claude's Discretion
- Companion app implementation details (polling interval, output format, logging)
- Spike code structure for coordinated testing
- How to build the companion app (same CMake project or standalone)

</decisions>

<canonical_refs>
## Canonical References

**Downstream agents MUST read these before planning or implementing.**

### OpenVR driver API
- `extern/openvr/headers/openvr_driver.h` — `IVRDriverInput` (line ~3705): `CreateBooleanComponent`, `UpdateBooleanComponent`, `EVRInputError` enum (line ~1419); `IVRServerDriverHost_006` (line ~3766); `VRInputComponentHandle_t` (uint64_t typedef)
- `extern/openvr/headers/openvr_capi.h` — `k_pchPathProximity = "/proximity"` (line 412)

### OpenVR client API (for companion app)
- `extern/openvr/headers/openvr.h` — `IVRSystem::GetTrackedDeviceActivityLevel()`, `IVRSystem::GetControllerState()`, `VRControllerState_t::ulButtonPressed`, `k_EButton_ProximitySensor = 31`

### Monado (reverse-engineered OpenVR/SteamVR)
- https://gitlab.freedesktop.org/monado/monado — Open-source OpenXR runtime with reverse-engineered SteamVR internals. Key areas to research: input component handle assignment, IVRDriverInput implementation, how SteamVR maps component handles to devices

### Existing driver code
- `src/driver/device_provider.cpp` — `SetHmdProximity()` (line 355): current v1.0 property-toggling implementation
- `src/driver/device_provider.h` — Class declaration

### Pre-spike test code (worktree, NOT merged)
- `.claude/worktrees/agent-ac451618/src/driver/device_provider.cpp` — CreateBooleanComponent attempt that confirmed err=4. Reference for what was tried and failed.

### SteamVR paths
- `C:\Program Files (x86)\Steam\config\` — SteamVR config directory (from openvrpaths.vrpath)
- `C:\Program Files (x86)\Steam\logs\vrserver.txt` — Driver log showing "Invalid input type click for controller (/user/head/proximity)" confirming lighthouse driver creates the component

</canonical_refs>

<code_context>
## Existing Code Insights

### Reusable Assets
- `DeviceProvider::SetHmdProximity()` (device_provider.cpp:355) — Will be modified if spike succeeds to call UpdateBooleanComponent with discovered handle
- HMD container access: `VRProperties()->TrackedDeviceToPropertyContainer(k_unTrackedDeviceIndex_Hmd)` — works for properties, NOT for input components
- Named pipe command handler — add spike test commands (e.g. `probe_proximity`, `test_handle <N>`)

### Established Patterns
- Pipe command format: `"command arg"` in, `"OK key=value"` / `"ERR message"` out
- Spike commands pattern from Phase 10/10.1: temporary commands for testing, removed in production phase

### Integration Points
- `DeviceProvider::HandlePipeCommand()` — Add spike probe commands
- `DeviceProvider::Init()` — Run handle probing at startup or on first pipe command

</code_context>

<specifics>
## Specific Ideas

- The Monado project (https://gitlab.freedesktop.org/monado/monado) has reverse-engineered virtually the entirety of SteamVR/OpenVR and is a key resource for understanding handle assignment internals
- Handle probing should try `UpdateBooleanComponent` with a range of handles and observe which succeed — if one triggers a standby/wake behavior change, that's the proximity component
- Component handles may be structured (device_index encoded in upper bits) or sequential — Monado source should reveal which
- The driver log message "Invalid input type click for controller (/user/head/proximity)" confirms the lighthouse driver creates this component — the handle exists, we just need to find it

</specifics>

<deferred>
## Deferred Ideas

- If spike fails (handle not discoverable or UpdateBooleanComponent rejects cross-driver handles): consider registering a minimal virtual device approach
- Production implementation of proximity fix — separate phase after spike confirms viability

</deferred>

---

*Phase: 11.1-proximity-sensor-app-compatibility-fix*
*Context gathered: 2026-03-26 (restructured as spike after initial approach failed)*
