# Phase 1: Skeleton Driver and Coexistence - Research

**Researched:** 2026-03-21
**Domain:** SteamVR / OpenVR driver development (C++, CMake, MSVC)
**Confidence:** HIGH

## Summary

Phase 1 requires building a minimal SteamVR driver DLL that loads alongside the built-in lighthouse driver and coexists without affecting existing Beyond 2 HMD functionality (tracking, display, audio). The OpenVR Driver API is well-documented and stable. Valve provides official sample drivers (including a "barebones" skeleton) in the OpenVR SDK repository. The driver must export `HmdDriverFactory`, implement `IServerTrackedDeviceProvider`, and optionally register a tracked device via `VRServerDriverHost()->TrackedDeviceAdded()`.

The key technical decisions for this phase are: (1) whether to bundle the DLL into the existing `Bigscreen Beyond Driver` package (preferred but requires converting `resourceOnly: true` to `false`) or use a standalone driver package, (2) whether to register a tracked device in Phase 1 or defer to Phase 3, and (3) how to minimize device visibility in SteamVR's dashboard. Research found that there is no official "invisible device" API, but `TrackedDeviceClass_GenericTracker` with `Prop_NeverTracked_Bool = true` is the standard pattern for non-tracked auxiliary devices.

**Primary recommendation:** Build a standalone driver package named `beyond_proximity` for Phase 1 development. Use `alwaysActivate: true` in the manifest. Register a `GenericTracker` device with `Prop_NeverTracked_Bool = true` to validate the full driver lifecycle. Defer bundling into the existing Bigscreen package to a later distribution phase -- modifying a Steam-distributed package during active development creates unnecessary iteration friction.

<user_constraints>

## User Constraints (from CONTEXT.md)

### Locked Decisions
- **Driver naming & packaging**: Primary path is bundling into existing `Bigscreen Beyond Driver` package (Steam auto-installs). Fallback: standalone `beyond_proximity` in `steamvr/drivers/beyond_proximity/`. Research must investigate whether `resourceOnly: true` package can be converted to active driver with DLL.
- **Device identity**: Manufacturer "Bigscreen", Model "Beyond Proximity Sensor". Serial number at Claude's discretion.
- **Device visibility**: Strongly prefer invisible in SteamVR device list. Research must investigate feasibility. If not possible, use `TrackedDeviceClass_GenericTracker` as fallback.
- **Verification approach**: Scripted verification (vrserver log parsing, OpenVR API queries), then manual UAT.
- **Source code layout**: `src/driver/` for SteamVR driver code, `src/hid/` for HID (Phase 2+). OpenVR SDK vendored in `extern/openvr/`. CMake outputs complete driver package structure.

### Claude's Discretion
- Whether to register a tracked device in Phase 1 (TrackedDeviceAdded) or defer to Phase 3
- MSVC/C++ standard and Visual Studio version selection
- Serial number format

### Deferred Ideas (OUT OF SCOPE)
None -- discussion stayed within phase scope

</user_constraints>

<phase_requirements>

## Phase Requirements

| ID | Description | Research Support |
|----|-------------|-----------------|
| DRIV-01 | Driver DLL exports `HmdDriverFactory` and implements `IServerTrackedDeviceProvider` (Init, Cleanup, RunFrame) | OpenVR SDK sample drivers provide exact patterns; HmdDriverFactory signature and IServerTrackedDeviceProvider interface fully documented |
| DRIV-02 | Driver includes `driver.vrdrivermanifest` with correct name, directory, and auto-activation via `hmd_presence` VID/PID | Manifest format documented; `alwaysActivate: true` is the correct mechanism for sidecar drivers (not `hmd_presence` for non-HMD providers) |
| DRIV-03 | Driver registers a tracked device via `TrackedDeviceAdded` with appropriate device class | `VRServerDriverHost()->TrackedDeviceAdded(serial, class, driver)` signature confirmed; GenericTracker class recommended |
| DRIV-04 | Driver uses `alwaysActivate: true` to load as sidecar alongside built-in driver | Manifest field confirmed; `activateMultipleDrivers` in steamvr.vrsettings may also be required |
| FEAS-01 | Sidecar driver loads alongside built-in generic driver without breaking HMD functionality | `alwaysActivate: true` is the standard mechanism; multiple community and Valve examples confirm sidecar coexistence works |

</phase_requirements>

## Standard Stack

### Core
| Library | Version | Purpose | Why Standard |
|---------|---------|---------|--------------|
| OpenVR SDK | v2.5.1 (latest tagged release, Mar 2025) | SteamVR driver API headers and libs | Official Valve SDK; only supported way to write SteamVR drivers |
| MSVC (Visual Studio 2022) | v17.x | C++ compiler | Required for Windows SteamVR drivers; all Valve samples target MSVC |
| CMake | 3.20+ | Build system | Used by official OpenVR samples; cross-platform, standard in C++ ecosystem |

### Supporting
| Library | Version | Purpose | When to Use |
|---------|---------|---------|-------------|
| OpenVR driverlog utility | (bundled in SDK samples) | Printf-style logging wrapper for `IVRDriverLog` | All driver logging; wraps `VRDriverLog()->Log()` into `DriverLog()` printf-style |
| OpenVR vrmath utility | (bundled in SDK samples) | Quaternion/matrix helpers for `DriverPose_t` | Returning poses from `GetPose()` |

### Alternatives Considered
| Instead of | Could Use | Tradeoff |
|------------|-----------|----------|
| Vendored OpenVR SDK in `extern/` | vcpkg or git submodule | Vendoring is simpler for a small project; no external dependency manager needed |
| CMake | Visual Studio .sln directly | CMake matches the official samples and produces the required output directory structure automatically |

**Installation:**
The OpenVR SDK is vendored, not installed via package manager. Download from GitHub release and place in `extern/openvr/`.

```bash
# Clone or download OpenVR SDK v2.5.1 into extern/
mkdir -p extern
cd extern
git clone --depth 1 --branch v2.5.1 https://github.com/ValveSoftware/openvr.git
```

**Version verification:** OpenVR SDK v2.5.1 is the latest tagged release on GitHub as of research date. The SDK version is relatively stable -- the driver API (`IServerTrackedDeviceProvider`, `ITrackedDeviceServerDriver`) has not changed substantially since v1.0.6. Interface version strings like `IServerTrackedDeviceProvider_Version` = `"IServerTrackedDeviceProvider_004"` remain current.

## Architecture Patterns

### Recommended Project Structure
```
bey-closer-t1/
├── CMakeLists.txt                      # Root CMake (project-level)
├── extern/
│   └── openvr/                         # Vendored OpenVR SDK (headers, libs)
│       ├── headers/
│       │   └── openvr_driver.h
│       └── lib/
│           └── win64/
│               └── openvr_api.lib
├── src/
│   └── driver/                         # SteamVR driver code
│       ├── CMakeLists.txt              # Driver-specific CMake
│       ├── hmd_driver_factory.cpp      # HmdDriverFactory export
│       ├── device_provider.h           # IServerTrackedDeviceProvider
│       ├── device_provider.cpp
│       ├── proximity_device.h          # ITrackedDeviceServerDriver
│       ├── proximity_device.cpp
│       └── driverlog.h / .cpp          # Logging utility (from SDK samples)
├── driver/                             # Output: complete driver package
│   └── beyond_proximity/
│       ├── driver.vrdrivermanifest
│       ├── bin/
│       │   └── win64/
│       │       └── driver_beyond_proximity.dll
│       └── resources/
│           └── (empty or minimal for Phase 1)
└── .planning/
```

### Pattern 1: HmdDriverFactory Entry Point
**What:** Single exported function that SteamVR calls to get driver interface implementations
**When to use:** Every driver must implement this
**Example:**
```cpp
// Source: OpenVR SDK Driver API Documentation + Driver Factory Function wiki
#include <openvr_driver.h>
#include "device_provider.h"

// Global singleton instances
static DeviceProvider g_deviceProvider;

HMD_DLL_EXPORT void* HmdDriverFactory(
    const char* pInterfaceName, int* pReturnCode)
{
    if (0 == strcmp(IServerTrackedDeviceProvider_Version, pInterfaceName))
    {
        return &g_deviceProvider;
    }

    if (pReturnCode)
        *pReturnCode = VRInitError_Init_InterfaceNotFound;

    return nullptr;
}
```

### Pattern 2: IServerTrackedDeviceProvider Implementation
**What:** Core provider that manages driver lifecycle and device registration
**When to use:** Every driver implements this as its server-side provider
**Example:**
```cpp
// Source: OpenVR SDK simplehmd sample device_provider.cpp
class DeviceProvider : public vr::IServerTrackedDeviceProvider
{
public:
    vr::EVRInitError Init(vr::IVRDriverContext* pDriverContext) override
    {
        VR_INIT_SERVER_DRIVER_CONTEXT(pDriverContext);
        InitDriverLog(vr::VRDriverLog());

        DriverLog("Beyond Proximity driver initializing\n");

        // Create and register our device
        m_pDevice = std::make_unique<ProximityDevice>();
        vr::VRServerDriverHost()->TrackedDeviceAdded(
            m_pDevice->GetSerialNumber(),
            vr::TrackedDeviceClass_GenericTracker,
            m_pDevice.get());

        return vr::VRInitError_None;
    }

    void Cleanup() override
    {
        CleanupDriverLog();
        m_pDevice.reset();
    }

    const char* const* GetInterfaceVersions() override
    {
        return vr::k_InterfaceVersions;
    }

    void RunFrame() override
    {
        // Phase 1: nothing to poll yet
    }

    bool ShouldBlockStandbyMode() override { return false; }
    void EnterStandby() override {}
    void LeaveStandby() override {}

private:
    std::unique_ptr<ProximityDevice> m_pDevice;
};
```

### Pattern 3: ITrackedDeviceServerDriver Implementation (Minimal)
**What:** Per-device driver that SteamVR activates after TrackedDeviceAdded
**When to use:** For each device the driver exposes to SteamVR
**Example:**
```cpp
// Source: OpenVR SDK simplehmd sample + Driver API docs
class ProximityDevice : public vr::ITrackedDeviceServerDriver
{
public:
    ProximityDevice()
        : m_unObjectId(vr::k_unTrackedDeviceIndexInvalid)
    {}

    const char* GetSerialNumber() const { return "BSB_PROX_0001"; }

    // Called by SteamVR after TrackedDeviceAdded
    vr::EVRInitError Activate(uint32_t unObjectId) override
    {
        m_unObjectId = unObjectId;

        vr::PropertyContainerHandle_t props =
            vr::VRProperties()->TrackedDeviceToPropertyContainer(unObjectId);

        // Identity properties
        vr::VRProperties()->SetStringProperty(props,
            vr::Prop_ManufacturerName_String, "Bigscreen");
        vr::VRProperties()->SetStringProperty(props,
            vr::Prop_ModelNumber_String, "Beyond Proximity Sensor");
        vr::VRProperties()->SetStringProperty(props,
            vr::Prop_SerialNumber_String, GetSerialNumber());

        // Mark as non-tracked device (no valid pose expected)
        vr::VRProperties()->SetBoolProperty(props,
            vr::Prop_NeverTracked_Bool, true);

        DriverLog("Beyond Proximity device activated with ID %u\n", unObjectId);
        return vr::VRInitError_None;
    }

    void Deactivate() override
    {
        m_unObjectId = vr::k_unTrackedDeviceIndexInvalid;
    }

    void EnterStandby() override {}

    void* GetComponent(const char*) override { return nullptr; }

    void DebugRequest(const char*, char* buf, uint32_t sz) override
    {
        if (sz >= 1) buf[0] = '\0';
    }

    vr::DriverPose_t GetPose() override
    {
        vr::DriverPose_t pose = {};
        pose.poseIsValid = false;
        pose.deviceIsConnected = true;
        pose.result = vr::TrackingResult_Running_OK;
        pose.qWorldFromDriverRotation.w = 1.0;
        pose.qDriverFromHeadRotation.w = 1.0;
        pose.qRotation.w = 1.0;
        return pose;
    }

private:
    uint32_t m_unObjectId;
};
```

### Pattern 4: Driver Manifest for Sidecar
**What:** JSON manifest that tells SteamVR how to load the driver
**When to use:** Every driver package root
**Example:**
```json
{
    "alwaysActivate": true,
    "name": "beyond_proximity",
    "directory": "",
    "resourceOnly": false,
    "hmd_presence": []
}
```

### Anti-Patterns to Avoid
- **Registering a second HMD device:** Never use `TrackedDeviceClass_HMD` -- SteamVR only supports one HMD; a second HMD registration will conflict with the lighthouse driver's HMD.
- **Using `hmd_presence` for a sidecar driver:** The sidecar is not an HMD provider. Use `alwaysActivate: true` instead, which activates the driver regardless of which HMD is present.
- **Blocking in Init():** `IServerTrackedDeviceProvider::Init()` must return promptly. Do not block on hardware detection or long operations. If hardware is absent, return `VRInitError_None` and handle gracefully.
- **Calling VR interfaces before Init():** The driver must not call any runtime methods until after `Init()` is called and `VR_INIT_SERVER_DRIVER_CONTEXT` has set up the context.
- **Hardcoding paths:** Use the OpenVR settings and driver context APIs. Driver paths are relative to the manifest location.

## Don't Hand-Roll

| Problem | Don't Build | Use Instead | Why |
|---------|-------------|-------------|-----|
| Driver logging | Custom file logging | `driverlog.h` utility from OpenVR SDK samples | Writes to vrserver log file where SteamVR collects all driver output; consistent with ecosystem |
| Quaternion/matrix math | Custom math | `vrmath.h` utility from OpenVR SDK samples | Correct quaternion initialization for `DriverPose_t` is error-prone |
| Driver package output layout | Manual copy scripts | CMake `set_target_properties(RUNTIME_OUTPUT_DIRECTORY)` + `add_custom_command(copy_directory)` | Official samples use this pattern; produces the exact directory structure SteamVR expects |
| DLL export macros | Custom `__declspec(dllexport)` | `HMD_DLL_EXPORT` macro from `openvr_driver.h` | Already defined correctly for Windows and Linux in the SDK header |

**Key insight:** The OpenVR SDK samples directory (`samples/drivers/drivers/`) contains utility code (`driverlog`, `vrmath`) and build patterns that should be directly reused, not reimplemented.

## Common Pitfalls

### Pitfall 1: DLL Name Mismatch
**What goes wrong:** SteamVR fails to load the driver with a cryptic error
**Why it happens:** The DLL must be named `driver_<name>.dll` where `<name>` matches the `name` field in `driver.vrdrivermanifest`. If the manifest says `"name": "beyond_proximity"`, the DLL must be `driver_beyond_proximity.dll`.
**How to avoid:** CMake target should be named `driver_beyond_proximity` and output to `bin/win64/`
**Warning signs:** "Unable to load driver" in vrserver log; HmdDriverFactory never called

### Pitfall 2: Incorrect Driver Package Directory Structure
**What goes wrong:** SteamVR finds the manifest but cannot locate the DLL
**Why it happens:** SteamVR expects: `<driver_root>/driver.vrdrivermanifest` and `<driver_root>/bin/win64/driver_<name>.dll`. Missing any part of this hierarchy causes silent failure.
**How to avoid:** CMake build should output directly to the correct structure; verify with `dir` before testing
**Warning signs:** Driver appears in vrpathreg list but Init is never called

### Pitfall 3: activateMultipleDrivers Not Set
**What goes wrong:** The sidecar driver's TrackedDeviceAdded call silently fails or the device never activates
**Why it happens:** By default, SteamVR only activates devices from the primary HMD driver. For a sidecar driver to register devices, `activateMultipleDrivers` must be `true` in `steamvr.vrsettings`. Note: `alwaysActivate: true` in the manifest causes the driver DLL to *load* and `Init()` to be called, but device registration via `TrackedDeviceAdded` may still be blocked without `activateMultipleDrivers`.
**How to avoid:** Document this as a setup requirement. The driver's Init() can check and warn in the log if the setting is missing. Alternatively, the driver can set this programmatically via `VRSettings()`.
**Warning signs:** Init() succeeds but "TrackedDeviceAdded returning false" in the log

### Pitfall 4: Serial Number Collisions
**What goes wrong:** TrackedDeviceAdded fails silently
**Why it happens:** Serial numbers must be unique across all drivers and persistent across sessions. If a serial number duplicates one from another driver, registration fails.
**How to avoid:** Use a distinctive prefix like `BSB_PROX_` that cannot collide with lighthouse device serials
**Warning signs:** TrackedDeviceAdded returns false; no Activate() callback

### Pitfall 5: Invalid Pose Quaternions
**What goes wrong:** SteamVR logs pose errors or the device shows as "tracking error"
**Why it happens:** `DriverPose_t` has three quaternion fields (`qRotation`, `qWorldFromDriverRotation`, `qDriverFromHeadRotation`) that must all have `.w = 1.0` for identity. Default zero-initialization leaves `.w = 0` which is an invalid quaternion.
**How to avoid:** Always explicitly set `.w = 1.0` on all three quaternions, even for non-tracked devices
**Warning signs:** "Invalid pose" warnings in vrserver log

### Pitfall 6: GenericTracker Triggers "Manage Trackers" Dialog
**What goes wrong:** SteamVR prompts the user to configure the tracker role when a new `GenericTracker` device appears
**Why it happens:** SteamVR has a tracker management UI that activates for devices of class `TrackedDeviceClass_GenericTracker`
**How to avoid:** Set `Prop_ControllerRoleHint_Int32` to `TrackedControllerRole_OptOut` to suppress role assignment. Combined with `Prop_NeverTracked_Bool = true`, this minimizes UI intrusion.
**Warning signs:** User sees "New tracker detected" dialog on first launch

## Code Examples

### Complete Driver Manifest (beyond_proximity)
```json
{
    "alwaysActivate": true,
    "name": "beyond_proximity",
    "directory": "",
    "resourceOnly": false,
    "hmd_presence": []
}
```
Source: [OpenVR DriverManifest wiki](https://github.com/ValveSoftware/openvr/wiki/DriverManifest)

### Minimal CMakeLists.txt
```cmake
cmake_minimum_required(VERSION 3.20)
project(beyond_proximity LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# Driver name must match manifest "name" field
set(DRIVER_NAME "driver_beyond_proximity")
set(TARGET_NAME "beyond_proximity")

# OpenVR SDK paths (vendored)
set(OPENVR_ROOT "${CMAKE_SOURCE_DIR}/extern/openvr")
set(OPENVR_INCLUDE_DIR "${OPENVR_ROOT}/headers")

# Platform-specific lib path
if(CMAKE_SIZEOF_VOID_P EQUAL 8)
    set(OPENVR_LIB "${OPENVR_ROOT}/lib/win64/openvr_api.lib")
    set(ARCH_TARGET "win64")
else()
    message(FATAL_ERROR "Only 64-bit builds are supported")
endif()

# Output directly to driver package structure
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY
    "${CMAKE_BINARY_DIR}/driver/${TARGET_NAME}/bin/${ARCH_TARGET}")

# Driver shared library
add_library(${DRIVER_NAME} SHARED
    src/driver/hmd_driver_factory.cpp
    src/driver/device_provider.h
    src/driver/device_provider.cpp
    src/driver/proximity_device.h
    src/driver/proximity_device.cpp
    src/driver/driverlog.h
    src/driver/driverlog.cpp
)

target_include_directories(${DRIVER_NAME} PRIVATE ${OPENVR_INCLUDE_DIR})
target_link_libraries(${DRIVER_NAME} PRIVATE ${OPENVR_LIB})

# Copy driver assets (manifest, resources) to output
add_custom_command(TARGET ${DRIVER_NAME} POST_BUILD
    COMMAND ${CMAKE_COMMAND} -E copy_directory
        "${CMAKE_SOURCE_DIR}/driver/${TARGET_NAME}"
        "${CMAKE_BINARY_DIR}/driver/${TARGET_NAME}"
)
```
Source: adapted from [OpenVR SDK simplehmd CMakeLists.txt](https://github.com/ValveSoftware/openvr/blob/master/samples/drivers/drivers/simplehmd/CMakeLists.txt)

### Driver Registration Command
```bash
# Register driver with SteamVR (run once after build)
"C:/Program Files (x86)/Steam/steamapps/common/SteamVR/bin/win64/vrpathreg.exe" adddriver "C:/path/to/build/driver/beyond_proximity"

# Verify registration
"C:/Program Files (x86)/Steam/steamapps/common/SteamVR/bin/win64/vrpathreg.exe" show
```

### Verification: Parse vrserver Log
```bash
# After starting SteamVR with driver installed, check:
grep -i "beyond_proximity" "$LOCALAPPDATA/Steam/logs/vrserver.txt"
# Expected: driver loading messages, Init success, TrackedDeviceAdded
# Not expected: errors, warnings about missing DLL, interface not found

grep -i "HmdDriverFactory" "$LOCALAPPDATA/Steam/logs/vrserver.txt" | grep -i "beyond"
# Expected: factory function called for our driver
```

## Investigation Results: Bundling vs. Standalone

### Can the Existing Bigscreen Package Be Converted?

**Current state:** The `Bigscreen Beyond Driver` package has `resourceOnly: true`, `alwaysActivate: false`, and empty `hmd_presence: []`. It provides status icons for the HMD via `driver.vrresources`.

**Conversion requirements:** Changing `resourceOnly` to `false` and adding a DLL to `bin/win64/` would convert it to an active driver. Setting `alwaysActivate: true` would cause it to load alongside the lighthouse driver.

**Risk assessment (MEDIUM confidence):**
- The `name` field is `bigscreenbeyond`, so the DLL would need to be named `driver_bigscreenbeyond.dll`
- The existing `bin/` directory already contains `BeyondHID.exe`, `hidapi.dll`, and `libusb-1.0.dll` -- these are companion app files, NOT driver DLLs. SteamVR specifically looks for `driver_<name>.dll` by convention.
- Adding `driver_bigscreenbeyond.dll` to `bin/win64/` alongside existing files should work because SteamVR only loads the specific `driver_<name>.dll` pattern.
- **HOWEVER:** the existing `bin/` directory is flat (no `win64/` subdirectory). SteamVR expects `bin/win64/` for 64-bit Windows. The existing package layout may not match this requirement.
- Changing a Steam-distributed package's manifest during development is impractical: every test cycle would require modifying the installed package, and Steam updates would overwrite changes.

**Recommendation:** Use standalone `beyond_proximity` package for Phase 1 development. Bundling investigation is a distribution concern (v2 requirement DIST-01) that should be deferred. The standalone approach provides fast iteration with `vrpathreg adddriver/removedriver`.

### Can the Device Be Hidden from SteamVR Dashboard?

**Finding (HIGH confidence):** There is NO official API to make a registered device invisible in SteamVR's device list. Once `TrackedDeviceAdded` is called, the device will appear somewhere in SteamVR's UI.

**Mitigation options (ordered by intrusiveness):**
1. **Don't register a device in Phase 1** -- Driver loads, Init succeeds, RunFrame runs, but no TrackedDeviceAdded call. This validates driver loading without any UI impact. Device registration deferred to Phase 3 when `/proximity` component is actually needed.
2. **Register as GenericTracker with Prop_NeverTracked_Bool = true** -- Device appears in device list but shows as disconnected/non-tracked. Combined with `TrackedControllerRole_OptOut`, it suppresses the "Manage Trackers" dialog. Least intrusive registered-device option.
3. **Register as GenericTracker with no special properties** -- Device appears as a tracker in the device list. Most visible but functionally harmless.

**Discretion recommendation:** Option 1 (no device registration) is valid for Phase 1 since the success criteria only require proving that the driver *can* register a device. However, Option 2 provides a more complete feasibility validation by exercising the full `TrackedDeviceAdded -> Activate` lifecycle. Since the roadmap's success criteria explicitly state "Driver registers a tracked device via TrackedDeviceAdded", **use Option 2 to satisfy the criteria**.

## State of the Art

| Old Approach | Current Approach | When Changed | Impact |
|--------------|------------------|--------------|--------|
| `IClientTrackedDeviceProvider` (client-side) | Removed; server-side only via `IServerTrackedDeviceProvider` | OpenVR v1.0.6 | Drivers only need server provider |
| `GetTrackedDeviceDriverCount` / `GetTrackedDeviceDriver` | `TrackedDeviceAdded` via `VRServerDriverHost()` | Pre-v1.0 | Devices are push-registered, not pull-enumerated |
| `activateMultipleDrivers` in vrsettings | `alwaysActivate: true` in manifest | Gradual | Manifest-level activation is preferred for sidecar drivers |

**Deprecated/outdated:**
- `IClientTrackedDeviceProvider`: Removed in v1.0.6. Do not implement.
- `IVRWatchdogProvider`: Optional. Only needed if the driver must monitor hardware presence for SteamVR startup. Not needed for Phase 1 (HMD already detected by lighthouse driver).

## Open Questions

1. **activateMultipleDrivers interaction with alwaysActivate**
   - What we know: `alwaysActivate: true` causes Init() to be called. `activateMultipleDrivers: true` in steamvr.vrsettings allows non-primary drivers to register devices.
   - What's unclear: Whether `alwaysActivate: true` alone is sufficient for device registration, or whether `activateMultipleDrivers` is also required. Documentation is ambiguous.
   - Recommendation: Set both. If `alwaysActivate: true` is sufficient alone, `activateMultipleDrivers` being also true causes no harm. Test by checking TrackedDeviceAdded return value.

2. **GenericTracker visibility in SteamVR 2.x**
   - What we know: In older SteamVR, GenericTracker devices appear in "Manage Trackers". Prop_NeverTracked_Bool + TrackedControllerRole_OptOut reduces visibility.
   - What's unclear: Whether current SteamVR (2025-era) handles this differently or has new device-hiding capabilities.
   - Recommendation: Test with Prop_NeverTracked_Bool first. If the UI impact is unacceptable, consider deferring device registration to Phase 3 (the device exists only when /proximity is actively needed).

3. **Existing bin/ directory structure compatibility**
   - What we know: The existing Bigscreen Beyond Driver package has `bin/` containing flat files (BeyondHID.exe, etc.), not the `bin/win64/` subdirectory SteamVR expects for driver DLLs.
   - What's unclear: Whether SteamVR would correctly find `driver_bigscreenbeyond.dll` if placed in `bin/win64/` alongside the flat `bin/` contents.
   - Recommendation: Defer to v2 distribution phase. This does not block Phase 1.

## Validation Architecture

### Test Framework
| Property | Value |
|----------|-------|
| Framework | Manual verification + scripted log parsing (no unit test framework for Phase 1) |
| Config file | None -- greenfield project, no test infrastructure exists |
| Quick run command | `powershell -File scripts/verify_driver.ps1` (to be created) |
| Full suite command | Same as quick run for Phase 1 |

### Phase Requirements to Test Map
| Req ID | Behavior | Test Type | Automated Command | File Exists? |
|--------|----------|-----------|-------------------|-------------|
| DRIV-01 | HmdDriverFactory exported, IServerTrackedDeviceProvider implemented | smoke | `dumpbin /exports <dll> \| findstr HmdDriverFactory` | Wave 0 |
| DRIV-02 | Valid manifest in correct directory structure | smoke | `python -c "import json; json.load(open('driver.vrdrivermanifest'))"` | Wave 0 |
| DRIV-03 | TrackedDeviceAdded called, device activated | integration | `findstr /i "TrackedDeviceAdded" %LOCALAPPDATA%\Steam\logs\vrserver.txt` | Wave 0 |
| DRIV-04 | alwaysActivate causes driver to load alongside lighthouse | integration | `findstr /i "beyond_proximity" %LOCALAPPDATA%\Steam\logs\vrserver.txt` | Wave 0 |
| FEAS-01 | HMD tracking/display/audio unaffected by sidecar | manual-only | Manual UAT: wear HMD, verify tracking, display, audio | N/A |

### Sampling Rate
- **Per task commit:** Build succeeds (`cmake --build . --config Release`) + DLL exports check
- **Per wave merge:** Full verification script (log parsing + manual check)
- **Phase gate:** All scripted checks pass + manual UAT confirms HMD unaffected

### Wave 0 Gaps
- [ ] `scripts/verify_driver.ps1` -- scripted verification: build check, DLL export check, vrserver log parsing
- [ ] Driver package structure validation script (manifest present, DLL in correct path)
- [ ] No unit test framework needed for Phase 1 (driver is a DLL loaded by vrserver; integration testing is the appropriate level)

## Sources

### Primary (HIGH confidence)
- [OpenVR Driver API Documentation](https://github.com/ValveSoftware/openvr/blob/master/docs/Driver_API_Documentation.md) - IServerTrackedDeviceProvider, ITrackedDeviceServerDriver, TrackedDeviceAdded, device lifecycle
- [OpenVR DriverManifest wiki](https://github.com/ValveSoftware/openvr/wiki/DriverManifest) - Manifest fields: name, resourceOnly, alwaysActivate, hmd_presence, other_presence
- [OpenVR Driver Factory Function wiki](https://github.com/ValveSoftware/openvr/wiki/Driver-Factory-Function) - HmdDriverFactory signature and implementation
- [OpenVR IVRDriverInput Overview](https://github.com/ValveSoftware/openvr/wiki/IVRDriverInput-Overview) - CreateBooleanComponent, UpdateBooleanComponent
- [OpenVR SDK simplehmd sample](https://github.com/ValveSoftware/openvr/blob/master/samples/drivers/drivers/simplehmd/) - Reference implementation of device_provider.cpp, hmd_device_driver.cpp, CMakeLists.txt
- [OpenVR SDK sample drivers README](https://github.com/ValveSoftware/openvr/blob/master/samples/drivers/drivers/README.md) - Build process, driver registration, debugging guidance
- [OpenVR openvr_driver.h header](https://github.com/ValveSoftware/openvr/blob/master/headers/openvr_driver.h) - ETrackedDeviceClass enum (Invalid=0, HMD=1, Controller=2, GenericTracker=3, TrackingReference=4, DisplayRedirect=5)
- Existing `Bigscreen Beyond Driver` package (local reference: `code_samples/Bigscreen Beyond Driver/`) - manifest structure, resource layout, bin/ contents

### Secondary (MEDIUM confidence)
- [HoboVR Labs OpenVR Introduction](https://www.hobovrlabs.org/docs//openvr/introduction/) - Community documentation of manifest fields and driver structure (cross-verified with official docs)
- [Simple-OpenVR-Driver-Tutorial](https://github.com/terminal29/Simple-OpenVR-Driver-Tutorial) - Community reference for C++17 driver with CMake build (cross-verified with official samples)
- [Pimax-EyeTracker-SteamVR](https://github.com/mbucchia/Pimax-EyeTracker-SteamVR) - Demonstrates sidecar/shim approach alongside existing driver
- [OpenVR Issue #822](https://github.com/ValveSoftware/openvr/issues/822) - Discussion of device classes for non-tracked devices, Prop_NeverTracked_Bool usage

### Tertiary (LOW confidence)
- activateMultipleDrivers interaction details -- based on community discussions, not verified with official Valve documentation. Needs empirical testing.
- GenericTracker visibility behavior with Prop_NeverTracked_Bool in current SteamVR -- behavior may vary between SteamVR versions. Needs testing.

## Metadata

**Confidence breakdown:**
- Standard stack: HIGH -- OpenVR SDK is the only option; versions and APIs verified against official repository
- Architecture: HIGH -- patterns directly from Valve's official sample drivers
- Pitfalls: HIGH -- documented in official issues and confirmed across multiple community projects
- Device hiding investigation: MEDIUM -- no official "invisible device" API confirmed; mitigation strategies based on community patterns

**Research date:** 2026-03-21
**Valid until:** 2026-06-21 (90 days -- OpenVR driver API is very stable; changes are rare)
