# Phase 12: SteamVR IPD Slider UI - Context

**Gathered:** 2026-03-28
**Status:** Ready for planning

<domain>
## Phase Boundary

Users can adjust IPD using SteamVR's built-in dashboard slider, and the driver correctly responds to slider-driven changes. The slider appears for Beyond 2 only (not Beyond 1). This phase builds on the Phase 11 SetDisplayEyeToHead pipeline — it adds a UI surface and auto-apply logic, not a new rendering mechanism.

</domain>

<decisions>
## Implementation Decisions

### Slider activation strategy
- **D-01:** Spike native slider first (Track B) — try setting `Prop_IpdUIRangeMinMeters_Float`, `Prop_IpdUIRangeMaxMeters_Float`, and `Prop_DriverDisplaysIPDChanges_Bool=false` on HMD container at **runtime** (not Init — Init-time writes to HMD container fail per Phase 11.1 findings)
- **D-02:** Also explore component-based approaches on HMD container (analogous to Phase 11.1 proximity handle probing) — there may be an input component or property that triggers SteamVR to show the native IPD slider
- **D-03:** If Track B fails, fall back to **SteamVR Settings Tab** (Track A) using `settingsschema.vrsettings` with `"control": "slider"` — proven by vrlink and Shiftall drivers. Create `driver/BeyondProximity/resources/settings/settingsschema.vrsettings` with IPD slider (min=48, max=75, step=0.5, decimals=1). Driver polls `VRSettings()->GetFloat("driver_BeyondProximity", "ipd_mm")` in RunFrame for changes.
- **D-04:** If using settings tab, also need `resources/localization/localization.json` for UI labels

### Slider-to-rendering pipeline
- **D-05:** Auto-apply — when slider changes IPD, driver automatically calls SetDisplayEyeToHead (reuse existing Phase 11 pipeline at device_provider.cpp:750)
- **D-06:** React to ALL `VREvent_IpdChanged` events, not just slider-originated ones. Any source that changes IPD triggers rendering update.
- **D-07:** Loop guard needed — when our code calls SetDisplayEyeToHead + SetFloatProperty, it may fire VREvent_IpdChanged. Simple float comparison guard: if new IPD equals `m_fCurrentIpd`, skip re-application.
- **D-08:** Named pipe `ipd <mm>` command stays fully operational alongside slider. The official Beyond Utility app may leverage the pipe interface in the future.

### Beyond 1 exclusion (HARD-04)
- **D-09:** If **HMD serial** starts with `BS1`, skip ALL slider setup — no IpdUIRange properties, no settings tab slider, no auto-apply from VREvent_IpdChanged
- **D-10:** IPD pipe command also disabled for Beyond 1
- **D-11:** HMD serial is tag `0x08` in user flash (NOT the tracking/lighthouse serial which is tag `0x09`). Currently only tag `0x09` is parsed — must add `0x08` (HMD_Serial) to `SigTag` enum and `CalibrationData` struct, then expose via `HidDevice`
- **D-12:** Serial check at runtime (HMD container not available at Init). HMD serial comes from same user flash read as proximity calibration data

### Claude's Discretion
- Exact spike test methodology for Track B (which properties/components to try, in what order)
- Settings tab slider formatting details (step size, decimal places, label text)
- How to structure the settings change polling (timer interval, event-based vs polling)

</decisions>

<specifics>
## Specific Ideas

- The Shiftall driver at `C:\Program Files (x86)\Steam\steamapps\common\Shiftall Controller Drivers\resources\settings\settingsschema.vrsettings` shows a basic toggle control example
- The vrlink driver at `C:\Program Files (x86)\Steam\steamapps\common\SteamVR\drivers\vrlink\resources\settings\settingsschema.vrsettings` shows a slider control with min/max/decimals/step — this is the reference implementation for Track A
- SteamVR's own settings schema at `C:\Program Files (x86)\Steam\steamapps\common\SteamVR\resources\settingsschema.vrsettings` shows additional control types (radio, select, toggle, label) and features (requires, advanced_only, off_subsection)
- Phase 11.1 proximity approach: creating components on HMD container via handle probing worked at runtime — similar technique may reveal IPD-related component handles

</specifics>

<canonical_refs>
## Canonical References

### Phase 10 feasibility findings
- `.planning/phases/10-feasibility-spike/10-FINDINGS.md` — FEAS-03 slider test results (properties accepted err=0 but slider didn't appear), alternative approaches table, DriverDisplaysIPDChanges analysis

### Phase 11 IPD implementation
- `.planning/phases/11-core-ipd-pipe-command/11-CONTEXT.md` — SetDisplayEyeToHead approach decisions, lighthouse config loading, eye rotation matrix preservation
- `src/driver/device_provider.cpp:710-763` — HandleIpdCommand implementation (SetDisplayEyeToHead + property set), the pipeline to reuse
- `src/driver/device_provider.cpp:155-171` — Current VREvent_IpdChanged handler (log + m_fCurrentIpd update only, needs extension)

### Phase 11.1 HMD container operations
- `.planning/phases/11.1-proximity-sensor-app-compatibility-fix/11.1-CONTEXT.md` — Key constraint: Init-time writes to HMD container fail, must use runtime; component creation on HMD container via handle probing works

### Driver structure
- `driver/BeyondProximity/driver.vrdrivermanifest` — Current driver manifest (no resources directory yet)
- `CMakeLists.txt:58-63` — Build copies entire `driver/BeyondProximity/` to build output (new resources/ dir auto-included)

### SteamVR settings schema reference implementations
- `C:\Program Files (x86)\Steam\steamapps\common\SteamVR\drivers\vrlink\resources\settings\settingsschema.vrsettings` — Slider control with min/max/decimals/step, conditional subsections
- `C:\Program Files (x86)\Steam\steamapps\common\Shiftall Controller Drivers\resources\settings\settingsschema.vrsettings` — Simple toggle with localization
- `C:\Program Files (x86)\Steam\steamapps\common\Shiftall Controller Drivers\resources\localization\localization.json` — Localization file format

### HID user flash (HMD serial for Beyond 1 detection)
- `src/hid/user_signature.h` — SigTag enum and CalibrationData struct. Currently parses tag 0x09 (TrackingSerial). Must add tag 0x08 (HMD_Serial) for BS1/BS2 detection
- `src/hid/user_signature.cpp` — TLV+CRC8 parser. Tag 0x08 parsing follows same pattern as 0x09
- `code_samples/proximity_sensor_access/config_editor.py:19-29` — Reference SigTag enum showing all flash tags: Serial=0x01, HMD_Serial=0x08, Tracking_Serial=0x09

### OpenVR API
- `extern/openvr/headers/openvr_driver.h:2337-2355` — IVRSettings interface (GetFloat, SetFloat, etc.)
- `extern/openvr/headers/openvr_driver.h` — Prop_IpdUIRangeMinMeters_Float, Prop_IpdUIRangeMaxMeters_Float, Prop_DriverDisplaysIPDChanges_Bool property enums

</canonical_refs>

<code_context>
## Existing Code Insights

### Reusable Assets
- `HandleIpdCommand` (device_provider.cpp:698-764): Full IPD pipeline — validates range, loads lh config, builds eye matrices, calls SetDisplayEyeToHead, sets property, updates m_fCurrentIpd. Can be refactored into shared helper for slider auto-apply.
- `LoadLighthouseConfig` / `LoadLighthouseConfigFromPath`: Config loading already implemented, caches rotation matrices in `m_cachedLeftRot` / `m_cachedRightRot`
- `m_sTrackingSerial` (device_provider.h:42): Tracking/lighthouse serial from HID user flash — NOT the HMD serial. HMD serial (tag 0x08) must be added to parse for BS1/BS2 distinction

### Established Patterns
- Settings read pattern: `VRSettings()->GetInt32(kSettingsSection, "key", &err)` used in Init() for report_rate_ms, log_verbosity, moving_avg_length — same pattern extends to float reads
- VREvent polling: Already in RunFrame (line 159-171) — extend to handle IpdChanged with auto-apply
- Pipe command pattern: HandleIpdCommand uses snprintf response pattern — consistent with all other pipe commands
- Driver section name: `kSettingsSection = "driver_BeyondProximity"` — settings tab values must use this section

### Integration Points
- RunFrame(): Add settings polling (Track A) or IpdChanged auto-apply logic
- Init(): Cannot set HMD container properties here (Phase 11.1 constraint)
- driver/BeyondProximity/resources/: New directory for settingsschema.vrsettings and localization (Track A)
- CMakeLists.txt: No changes needed — build already copies entire driver directory

</code_context>

<deferred>
## Deferred Ideas

- IPD persistence across restarts — Phase 13
- Explore `/input/system/click` handle probing on HMD (related todo exists, not part of slider work)

</deferred>

---

*Phase: 12-steamvr-ipd-slider-ui*
*Context gathered: 2026-03-28*
