# Phase 03.2: Integrate into Official Beyond Driver Package - Research

**Researched:** 2026-03-22
**Domain:** SteamVR driver packaging, manifest configuration, deployment to Steam-managed directories
**Confidence:** HIGH

## Summary

This phase repackages the existing `beyond_proximity` sidecar driver into the official `bigscreenbeyond` driver package that ships with the Bigscreen Beyond via Steam. The core change is modifying the `bigscreenbeyond` manifest from `resourceOnly: true` to `resourceOnly: false` with `alwaysActivate: true`, renaming the DLL output to `driver_bigscreenbeyond.dll`, and deploying it to `bin/win64/` within the Steam-installed Beyond driver directory.

The most important finding from this research is that **SteamVR uses the manifest `name` field for DLL filename lookup, not the folder name**. This is proven by the BeyondEyetracking driver on this system: its folder is `ETDriver`, its manifest name is `BeyondEyetracking`, and SteamVR loads `driver_BeyondEyetracking.dll` from `ETDriver/bin/win64/`. The same pattern means `bigscreenbeyond` with a folder named `Bigscreen Beyond Driver` will work -- SteamVR will look for `bin/win64/driver_bigscreenbeyond.dll` at the registered path.

**Primary recommendation:** Change CMake to output `driver_bigscreenbeyond.dll` into `build/driver/bigscreenbeyond/bin/win64/`, create a new manifest at `driver/bigscreenbeyond/driver.vrdrivermanifest`, write a PowerShell deploy script that copies DLL + manifest to the Steam path, and unregister the old `beyond_proximity` sidecar.

<user_constraints>
## User Constraints (from CONTEXT.md)

### Locked Decisions
- Modify the existing `bigscreenbeyond` driver manifest -- change `resourceOnly: false`, `alwaysActivate: true`, `hmd_presence: ["35BD.0101"]`
- DLL output renamed to `driver_bigscreenbeyond.dll` (matches manifest `name` field convention: `driver_{name}.dll`)
- CMake project name stays `beyond_proximity` internally -- only the output artifact name changes
- Existing `resources/` directory (icons, `driver.vrresources`) left untouched -- our DLL just adds to the package
- CMake build output mirrors bigscreenbeyond layout locally: `build/driver/bigscreenbeyond/bin/win64/driver_bigscreenbeyond.dll`
- Deploy script (PowerShell) copies DLL + manifest to the Steam-installed Beyond driver directory
- Deploy target on this system: `C:\Program Files (x86)\Steam\steamapps\common\Bigscreen Beyond Driver`
- Deploy scope: DLL + manifest only -- don't touch resources, BeyondHID.exe, eyetracking, or anything else
- `beyond_prox_ctl.exe` stays in build output as dev tool only, not deployed to production path
- One-time manual `vrpathreg removedriver` for the standalone `beyond_proximity` sidecar -- done by agent during execution, not in a reusable script
- Remove `driver/beyond_proximity/` directory from repo (git history preserves old files)
- New driver package assets live in `driver/bigscreenbeyond/` (manifest only -- resources stay in the installed package, not in the repo)
- Verification script confirms `beyond_proximity` is NOT registered in SteamVR driver paths
- Verification script confirms `bigscreenbeyond` driver loads from the Steam install path
- Existing functionality checks: HMD property write (proximity on/off), HID access, icons still display

### Claude's Discretion
- Deploy script implementation details (elevation mechanism, error handling)
- CMake variable naming for the new output paths
- Whether `driver/bigscreenbeyond/` in the repo contains just the manifest or also a stub resources/ directory
- Verification script structure (extend existing vs new script)

### Deferred Ideas (OUT OF SCOPE)
None -- discussion stayed within phase scope
</user_constraints>

## Architecture Patterns

### Current State (Before This Phase)
```
PROJECT ROOT
├── driver/
│   └── beyond_proximity/
│       ├── driver.vrdrivermanifest    # name: "beyond_proximity", alwaysActivate: true
│       └── resources/
│           └── .gitkeep
├── build/driver/beyond_proximity/
│   ├── driver.vrdrivermanifest        # Copied by CMake POST_BUILD
│   ├── resources/
│   └── bin/win64/
│       ├── driver_beyond_proximity.dll
│       └── beyond_prox_ctl.exe

STEAM INSTALL (C:\Program Files (x86)\Steam\steamapps\common\Bigscreen Beyond Driver\)
├── driver.vrdrivermanifest    # name: "bigscreenbeyond", resourceOnly: true
├── resources/
│   ├── driver.vrresources     # Uses {bigscreenbeyond} path prefix
│   └── icons/                 # All headset status icons
├── bin/
│   ├── BeyondHID.exe          # Companion tool
│   ├── hidapi.dll, libusb-1.0.dll
│   ├── eyetracking/ETDriver/  # Sub-driver (separate registration)
│   └── ...
```

### Target State (After This Phase)
```
PROJECT ROOT
├── driver/
│   └── bigscreenbeyond/
│       └── driver.vrdrivermanifest    # name: "bigscreenbeyond", resourceOnly: false,
│                                      # alwaysActivate: true, hmd_presence: ["35BD.0101"]
├── build/driver/bigscreenbeyond/
│   ├── driver.vrdrivermanifest        # Copied by CMake POST_BUILD
│   └── bin/win64/
│       ├── driver_bigscreenbeyond.dll
│       └── beyond_prox_ctl.exe

STEAM INSTALL (unchanged except for 2 additions)
├── driver.vrdrivermanifest    # MODIFIED: resourceOnly: false, alwaysActivate: true, hmd_presence
├── bin/
│   ├── win64/                 # NEW DIRECTORY
│   │   └── driver_bigscreenbeyond.dll  # NEW FILE
│   ├── BeyondHID.exe          # Untouched
│   └── ...                    # Everything else untouched
├── resources/                 # Untouched
```

### Pattern 1: SteamVR Driver Loading (Name vs Folder)
**What:** SteamVR uses the manifest `name` field for DLL filename, but the vrpathreg-registered path for locating `bin/<platform>/`.
**When to use:** Understanding why folder name mismatch is acceptable.
**Evidence from this system (HIGH confidence):**
- BeyondEyetracking driver: folder=`ETDriver`, manifest name=`BeyondEyetracking`, loads from `ETDriver/bin/win64/driver_BeyondEyetracking.dll`
- vrserver.txt log line: `Loaded server driver BeyondEyetracking (IServerTrackedDeviceProvider_004) from C:\...\ETDriver\bin\win64\driver_BeyondEyetracking.dll`
- bigscreenbeyond driver: folder=`Bigscreen Beyond Driver`, manifest name=`bigscreenbeyond`, currently `resourceOnly: true` so no DLL loaded
- Therefore: placing `driver_bigscreenbeyond.dll` in `Bigscreen Beyond Driver/bin/win64/` will work

### Pattern 2: Resource-Only to Active Driver Conversion
**What:** Changing `resourceOnly: false` tells SteamVR to look for and load a DLL from `bin/<platform>/`.
**When to use:** Converting the bigscreenbeyond package from resource-only to active.
**Key fields to modify:**
```json
{
    "alwaysActivate": true,
    "name": "bigscreenbeyond",
    "directory": "",
    "resourceOnly": false,
    "hmd_presence": ["35BD.0101"]
}
```
**What changes from original:**
- `alwaysActivate`: `false` -> `true` (load even when lighthouse is the HMD driver)
- `resourceOnly`: `true` -> `false` (look for DLL in bin/)
- `hmd_presence`: `[]` -> `["35BD.0101"]` (detect Beyond 2 USB presence)

### Pattern 3: Deploy to Program Files (Elevation Required)
**What:** The Steam install path is under `C:\Program Files (x86)\` which requires admin privileges to write.
**Recommendation:** Deploy script should use `Start-Process -Verb RunAs` to elevate, or simply require the script be run from an elevated terminal. The simpler approach (require elevated terminal) is preferred since this is a dev tool, not end-user software.
**Implementation:**
```powershell
# At script start, check for elevation
$isAdmin = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
if (-not $isAdmin) {
    Write-Error "This script must be run as Administrator. Right-click PowerShell and select 'Run as Administrator'."
    exit 1
}
```

### Anti-Patterns to Avoid
- **Copying resources to the repo:** The `resources/` directory with icons is managed by Steam/Bigscreen. The repo should NOT contain a copy. The deploy script only touches DLL + manifest.
- **Registering bigscreenbeyond with vrpathreg:** It is already registered by Steam. Adding it again would create a duplicate entry. Only the old `beyond_proximity` sidecar needs removal.
- **Touching any files besides DLL and manifest in the Steam directory:** BeyondHID.exe, firmware files, eyetracking -- all belong to the existing package and must not be modified.

## Don't Hand-Roll

| Problem | Don't Build | Use Instead | Why |
|---------|-------------|-------------|-----|
| Admin privilege check | Custom UAC elevation logic | `[Security.Principal.WindowsPrincipal]` check at script start | Standard Windows pattern; self-elevating scripts have complications |
| SteamVR driver path discovery | Hardcoded paths | `vrpathreg show` output parsing | Path can vary between systems |
| File backup before overwrite | Complex backup rotation | Single `Copy-Item` with `-Force` and pre-deploy manifest backup | Manifest is 6 lines of JSON; DLL is always rebuilt |

## Common Pitfalls

### Pitfall 1: Steam Update Overwrites Modified Manifest
**What goes wrong:** Steam updates the Bigscreen Beyond Driver package and reverts `driver.vrdrivermanifest` to `resourceOnly: true`, silently disabling the proximity driver.
**Why it happens:** Steam manages the `Bigscreen Beyond Driver` directory and replaces files during updates.
**How to avoid:** This is expected behavior and acceptable for development. The deploy script is designed to be re-run after Steam updates. For production distribution, the user (who is on the Bigscreen dev team) will modify the official package itself.
**Warning signs:** After a Steam/Beyond driver update, proximity stops working. Re-run deploy script.

### Pitfall 2: Forgetting to Create bin/win64/ Subdirectory
**What goes wrong:** The production `Bigscreen Beyond Driver` directory has a flat `bin/` directory with no `win64/` subdirectory. SteamVR expects `bin/win64/` for 64-bit Windows driver DLLs.
**Why it happens:** The original package is `resourceOnly: true` so no DLL directory was needed.
**How to avoid:** Deploy script must create `bin/win64/` if it does not exist before copying the DLL.
**Warning signs:** `VRInitError_Init_FileNotFound` in vrserver.txt.

### Pitfall 3: Old Sidecar Still Registered
**What goes wrong:** Both `beyond_proximity` and `bigscreenbeyond` load, causing duplicate HMD property writes or log confusion.
**Why it happens:** The `beyond_proximity` driver is registered via vrpathreg from the build directory. If not removed, both drivers initialize.
**How to avoid:** Use `vrpathreg removedriver` for the old `beyond_proximity` path before testing the integrated driver. Verification script must confirm it is not registered.
**Warning signs:** Two `"Loaded server driver"` lines in vrserver.txt for proximity-related drivers.

### Pitfall 4: CMake Build Directory Confusion
**What goes wrong:** After changing CMake output paths from `beyond_proximity` to `bigscreenbeyond`, old build artifacts in `build/driver/beyond_proximity/` persist and cause confusion.
**Why it happens:** CMake does not clean old output directories when variables change.
**How to avoid:** Clean the build directory or at minimum delete the old `build/driver/beyond_proximity/` directory after the CMake changes.
**Warning signs:** Both `build/driver/beyond_proximity/` and `build/driver/bigscreenbeyond/` exist.

### Pitfall 5: SetBoolProperty Error Code 5 on First Call
**What goes wrong:** The first `SetBoolProperty` call returns error code 5 (`TrackedProp_NotYetAvailable`) when called during Init() before the HMD is fully online.
**Why it happens:** The lighthouse HMD device may not have its property container fully initialized when sidecar drivers initialize.
**How to avoid:** This is already handled -- the driver logs it and the property eventually takes effect. Not a new concern for this phase, but the new verification script should account for error code 5 as acceptable on startup.
**Warning signs:** Error code 5 in the log for the Init-time call (this is normal and already observed in Phase 3.1).

## Code Examples

### CMake Changes (Key Lines)
```cmake
# Lines 7-10: Change DRIVER_NAME and TARGET_NAME
# Driver name must match manifest "name" field with driver_ prefix
# Manifest name: "bigscreenbeyond" -> DLL: "driver_bigscreenbeyond.dll"
set(DRIVER_NAME "driver_bigscreenbeyond")
set(TARGET_NAME "bigscreenbeyond")
```
Source: Direct modification of existing `CMakeLists.txt` lines 8-10.

The rest of CMake (output directories, post-build copy) already uses `${TARGET_NAME}` and `${DRIVER_NAME}` variables, so changing these two values propagates everywhere:
- `CMAKE_RUNTIME_OUTPUT_DIRECTORY` becomes `build/driver/bigscreenbeyond/bin/win64/`
- Post-build copy reads from `driver/bigscreenbeyond/` (source assets)
- DLL is named `driver_bigscreenbeyond.dll`

### New Manifest (driver/bigscreenbeyond/driver.vrdrivermanifest)
```json
{
    "alwaysActivate": true,
    "name": "bigscreenbeyond",
    "directory": "",
    "resourceOnly": false,
    "hmd_presence": ["35BD.0101"]
}
```
Source: Derived from existing `code_samples/Bigscreen Beyond Driver/driver.vrdrivermanifest` with modifications per CONTEXT.md decisions.

### Deploy Script Pattern
```powershell
# deploy_driver.ps1 - Deploy proximity driver to Steam-installed Beyond driver directory
param(
    [string]$TargetPath = "C:\Program Files (x86)\Steam\steamapps\common\Bigscreen Beyond Driver"
)

# Require elevation
$isAdmin = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole(
    [Security.Principal.WindowsBuiltInRole]::Administrator)
if (-not $isAdmin) {
    Write-Error "Run as Administrator."
    exit 1
}

# Source paths (build output)
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
$projectRoot = Split-Path -Parent $scriptDir
$buildDriver = Join-Path $projectRoot "build\driver\bigscreenbeyond"
$srcDll = Join-Path $buildDriver "bin\win64\driver_bigscreenbeyond.dll"
$srcManifest = Join-Path $buildDriver "driver.vrdrivermanifest"

# Target paths
$targetBinWin64 = Join-Path $TargetPath "bin\win64"
$targetManifest = Join-Path $TargetPath "driver.vrdrivermanifest"

# Create bin/win64 if needed
if (-not (Test-Path $targetBinWin64)) {
    New-Item -ItemType Directory -Path $targetBinWin64 -Force | Out-Null
}

# Copy DLL and manifest
Copy-Item $srcDll (Join-Path $targetBinWin64 "driver_bigscreenbeyond.dll") -Force
Copy-Item $srcManifest $targetManifest -Force
```

### vrpathreg Removal of Old Sidecar
```powershell
# One-time manual removal during execution:
$vrpathreg = "C:\Program Files (x86)\Steam\steamapps\common\SteamVR\bin\win64\vrpathreg.exe"
& $vrpathreg removedriver "C:\Users\decid\Documents\Projects\bey-closer-t1\build\driver\beyond_proximity"
```

## State of the Art

| Old Approach | Current Approach | When Changed | Impact |
|--------------|------------------|--------------|--------|
| Standalone sidecar driver (`beyond_proximity`) registered via `vrpathreg adddriver` | Integrated into `bigscreenbeyond` package at Steam install path | This phase (03.2) | Eliminates separate registration step; driver loads from official package |
| `resourceOnly: true` bigscreenbeyond manifest | `resourceOnly: false` with `alwaysActivate: true` | This phase (03.2) | Enables DLL loading from the official Beyond driver package |

## Validation Architecture

### Test Framework
| Property | Value |
|----------|-------|
| Framework | PowerShell verification scripts (established pattern) |
| Config file | None -- scripts are standalone |
| Quick run command | `powershell -ExecutionPolicy Bypass -File scripts/verify_integration.ps1` |
| Full suite command | `powershell -ExecutionPolicy Bypass -File scripts/verify_integration.ps1` (single script covers all) |

### Phase Requirements -> Test Map

Since no formal requirement IDs are mapped to this inserted phase, tests map to the 7 success criteria:

| SC | Behavior | Test Type | Automated Command | File Exists? |
|----|----------|-----------|-------------------|-------------|
| SC-1 | Manifest: resourceOnly=false, alwaysActivate=true | unit (file check) | `powershell -c "(Get-Content ...manifest | ConvertFrom-Json).resourceOnly -eq $false"` | Wave 0 |
| SC-2 | DLL at bin/win64/ in build output | unit (file check) | `Test-Path build\driver\bigscreenbeyond\bin\win64\driver_bigscreenbeyond.dll` | Wave 0 |
| SC-3 | Driver loads from official Beyond directory | integration (log check) | Parse vrserver.txt for `Loaded server driver bigscreenbeyond` | Wave 0 |
| SC-4 | HMD property write still works | integration (pipe+log) | `beyond_prox_ctl.exe proximity on/off` + log check | Reuse from verify_proximity.ps1 |
| SC-5 | HID access still works | integration (status check) | `beyond_prox_ctl.exe status` checking `hid=open` | Reuse from verify_proximity.ps1 |
| SC-6 | Icons still load | manual-only | Visual check in SteamVR dashboard | N/A |
| SC-7 | Sidecar not registered | unit (vrpathreg) | `vrpathreg show` parsed for absence of `beyond_proximity` | Wave 0 |

### Sampling Rate
- **Per task commit:** Build and check DLL exists at new path
- **Per wave merge:** Full verification script with SteamVR running
- **Phase gate:** All 7 success criteria validated (6 automated, 1 manual)

### Wave 0 Gaps
- [ ] `scripts/verify_integration.ps1` -- new verification script for Phase 03.2 (or extend verify_proximity.ps1)
- [ ] `scripts/deploy_driver.ps1` -- deploy script for copying to Steam install path

## Key Technical Details

### DLL Naming Convention
Source: `extern/openvr/docs/Driver_API_Documentation.md` line 172, 211, 667 (HIGH confidence)

SteamVR requires:
- DLL at: `<registered_path>/bin/<platform><arch>/driver_<name>.dll`
- `<name>` must match the `name` field in `driver.vrdrivermanifest`
- `<platform><arch>` is `win64` for 64-bit Windows

For our case: `driver_bigscreenbeyond.dll` in `bin/win64/` under the registered path.

### Existing Beyond Driver Directory Contents
Verified on this system at `C:\Program Files (x86)\Steam\steamapps\common\Bigscreen Beyond Driver`:
- `driver.vrdrivermanifest` -- currently `resourceOnly: true`
- `resources/driver.vrresources` -- uses `{bigscreenbeyond}` path prefix for icons
- `resources/icons/` -- headset status PNGs and GIFs
- `bin/BeyondHID.exe` -- companion tool (NOT a driver DLL)
- `bin/eyetracking/` -- ETDriver sub-driver and ETClient
- `bin/win64/` -- **DOES NOT EXIST** (must be created by deploy script)

### vrpathreg Registration State
Current state from `vrpathreg show`:
- `bigscreenbeyond : C:\Program Files (x86)\Steam\steamapps\common\Bigscreen Beyond Driver` -- already registered by Steam
- `BeyondEyetracking : ...\bin\eyetracking\ETDriver` -- sub-driver, separately registered
- `beyond_proximity : C:\Users\decid\...\build\driver\beyond_proximity` -- OLD SIDECAR, must be removed

**Key insight:** No `vrpathreg adddriver` needed for bigscreenbeyond. It is already registered. We only need to: (1) place the DLL, (2) update the manifest, (3) remove the old sidecar registration.

### CMake Variable Propagation
The current `CMakeLists.txt` uses `DRIVER_NAME` and `TARGET_NAME` throughout. Changing these two variables at lines 8-10 automatically updates:
- Line 9: `set(DRIVER_NAME "driver_bigscreenbeyond")` -- DLL output filename
- Line 10: `set(TARGET_NAME "bigscreenbeyond")` -- directory name in output structure
- Lines 25-31: Output directory becomes `build/driver/bigscreenbeyond/bin/win64/`
- Lines 55-59: Post-build copies from `driver/bigscreenbeyond/` source directory
- Lines 38+: Library target name for compile

### Verification Scripts Requiring Updates
Three existing scripts hardcode `beyond_proximity` paths:
1. `scripts/verify_driver.ps1` -- checks `build\driver\beyond_proximity\...`
2. `scripts/verify_hid.ps1` -- checks `build\driver\beyond_proximity\...`
3. `scripts/verify_proximity.ps1` -- checks `build\driver\beyond_proximity\...`

These will need path updates to point to `bigscreenbeyond` build output. Additionally, a new verification script is needed for integration-specific checks (driver loading from Steam path, sidecar removal).

### Discretion Recommendations

**Deploy script elevation:** Require elevated terminal (simple `IsInRole` check at script start). Self-elevation via `Start-Process -Verb RunAs` is more complex and harder to debug.

**CMake variable naming:** Keep `DRIVER_NAME` and `TARGET_NAME` -- the existing names are clear. Just change the values.

**Repo directory contents:** `driver/bigscreenbeyond/` should contain ONLY the manifest file. No stub `resources/` directory needed -- the build output does not need resources (they live in the Steam install directory, not in the build output). The post-build copy just copies the manifest.

**Verification script structure:** Create a NEW `verify_integration.ps1` for Phase 03.2 specific checks. Also update the three existing scripts to use new paths. This keeps each phase's verification cleanly separated.

## Open Questions

1. **beyond_prox_ctl.exe output location**
   - What we know: Currently outputs to `build/driver/beyond_proximity/bin/win64/`. After CMake changes, it will output to `build/driver/bigscreenbeyond/bin/win64/`.
   - What's unclear: The CONTEXT.md says it stays as dev tool only. The location change is automatic from CMake variable propagation.
   - Recommendation: No action needed -- location change is fine, it is just a dev tool.

2. **Steam update frequency for Beyond driver**
   - What we know: Steam auto-updates the package, which will overwrite our modified manifest and remove the DLL.
   - What's unclear: How often updates happen.
   - Recommendation: Accept this as expected during development. The deploy script is designed to be re-run. For production, the user modifies the official package source.

## Sources

### Primary (HIGH confidence)
- `extern/openvr/docs/Driver_API_Documentation.md` -- Driver folder structure, manifest fields, DLL naming conventions (vendored in repo)
- Live system inspection: `vrpathreg show` output, `vrserver.txt` log, filesystem directory listings
- BeyondEyetracking driver precedent on this system -- proves folder name != manifest name works

### Secondary (MEDIUM confidence)
- `extern/openvr/samples/drivers/drivers/tutorial/README.md` -- CMake output conventions for driver packages
- Existing project verification scripts and CMakeLists.txt -- established patterns

## Metadata

**Confidence breakdown:**
- Standard stack: HIGH -- all changes are to build config and manifests, using patterns already proven in this project
- Architecture: HIGH -- the BeyondEyetracking precedent on this system proves the exact integration pattern
- Pitfalls: HIGH -- identified from direct system inspection and existing project history

**Research date:** 2026-03-22
**Valid until:** 2026-04-22 (stable -- OpenVR driver conventions change very rarely)
