# Phase 10.1: SetDisplayEyeToHead Spike - Findings

**Date:** 2026-03-24
**SteamVR version:** 2.15.6
**HMD:** Beyond 2
**Driver:** BeyondProximity sidecar (no tracked devices)

## SPIKE-01: SetDisplayEyeToHead from Sidecar Changes Rendering

**Result:** PASS

**Evidence:**
- Command: `beyond_prox_ctl.exe eyetohead_set 63`
- Response: `OK ipd=63.0mm left_yaw=2.00 right_yaw=-2.00 left_pitch=-5.05 right_pitch=-5.75 left_roll=0.00 right_roll=0.00`
- SetDisplayEyeToHead IS called successfully from the sidecar driver for device 0 (HMD owned by lighthouse driver)
- Visual eye separation changes confirmed across IPD values (48mm, 55mm, 63mm, 75mm)
- Extreme low (48mm) vs extreme high (75mm) produces dramatically different eye separation
- Each intermediate value (55mm, 63mm) produces visible shift proportional to the IPD change

**Human verification (visual):**
- Eye separation changes visually in HMD: **YES** -- confirmed across multiple IPD values
- 48mm vs 75mm produces dramatic visible difference
- Intermediate values produce proportional shifts

**Conclusion:** SetDisplayEyeToHead called from the sidecar driver successfully changes the rendered eye separation for device 0 (HMD owned by lighthouse). The sidecar CAN call SetDisplayEyeToHead for a device it does not own, and the compositor applies the new matrices immediately.

## SPIKE-02: Euler Angle Decomposition Validation

**Result:** PASS

**Evidence:**
- Euler decomposition reports: `left_yaw=2.00 right_yaw=-2.00 left_pitch=-5.05 right_pitch=-5.75 left_roll=0.00 right_roll=0.00`
- Yaw values show opposite signs (left positive, right negative) confirming correct eye order detection
- Pitch values show per-eye lens cant geometry from lighthouse config
- Roll values are zero as expected for the Beyond 2's lens geometry
- Decomposition uses intrinsic XYZ convention matching the VAP alignment code (fixed after bug in extrinsic xyz)

**Note on yaw magnitude:** The observed yaw values (~2 degrees) differ from the initially expected ~6.17 degrees noted in the plan. The ~6.17 degree estimate was based on a rough calculation; the actual lighthouse config rotation matrices encode the real lens cant geometry which produces ~2 degree yaw and ~5 degree pitch. These are the correct values as read from the headset's own config.

**Conclusion:** Euler angle decomposition correctly extracts per-eye rotation from the lighthouse config matrices. The intrinsic XYZ convention matches the VAP alignment code convention. Eye order is validated by yaw sign (positive = left, negative = right).

## SPIKE-03: Repeated Calls (Live-Change Capability)

**Result:** PASS

**Evidence:**
- First call after driver startup causes ~20 second tracking disruption (lighthouse_console.exe USB connection to read config)
- Subsequent calls are instant due to cached rotation matrices
- Multiple rapid calls with different IPD values (48, 55, 63, 75) each produce immediate visible changes
- This confirms live-change capability, not one-shot

**Caching behavior:**
- First `eyetohead_set` call spawns `lighthouse_console.exe downloadconfig` to read HMD rotation matrices
- This USB connection causes ~20 seconds of tracking loss
- Rotation matrices are cached in memory after first read
- All subsequent calls reuse cached rotation, producing instant visual changes with no tracking disruption

**Conclusion:** Repeated SetDisplayEyeToHead calls from the sidecar produce repeated visual changes, confirming live-change capability. The caching strategy eliminates the tracking disruption after the first call.

## Bugs Found and Fixed During Testing

Five bugs were discovered and fixed during the deployment and testing process:

| # | Bug | Fix | Commit |
|---|-----|-----|--------|
| 1 | CLI allowlist missing `eyetohead_set` command | Added to allowlist | `01e37d8` |
| 2 | JSON parser could not handle pretty-printed multi-line format from lighthouse_console | Rewrote parser for multi-line JSON | `bb6b623` |
| 3 | Euler decomposition used wrong convention (extrinsic xyz instead of intrinsic XYZ) | Corrected to intrinsic XYZ matching VAP alignment code | `60db6cc` |
| 4 | Config reading used stale lhr-* directory scanning instead of live HMD config | Changed to lighthouse_console.exe downloadconfig | `8e5cf67` |
| 5 | Each eyetohead_set call spawned lighthouse_console causing ~20s tracking loss | Cache rotation matrices after first read | `32008b1` |

## Go/No-Go Decision

**Decision:** GO

**Rationale:**
- SPIKE-01 (SetDisplayEyeToHead from sidecar): **PASS** -- visual eye separation changes confirmed, sidecar can call SetDisplayEyeToHead for device 0 (HMD owned by lighthouse)
- SPIKE-02 (Euler angle validation): **PASS** -- decomposition produces correct per-eye rotation values, intrinsic XYZ convention validated
- SPIKE-03 (live-change capability): **PASS** -- repeated calls produce repeated visual changes, caching eliminates tracking disruption after first call

**Critical finding:** The sidecar driver CAN successfully call SetDisplayEyeToHead for device 0 (HMD it does not own). The compositor immediately applies the new eye-to-head matrices, producing visible changes in rendered eye separation. This is the core mechanism for Phase 11's IPD implementation.

**Impact on roadmap:**
- Phase 11 (Core IPD Pipe Command): **GO** -- use SetDisplayEyeToHead with rotation from lighthouse config + IPD-based translation. The eyetohead_set command from this spike is the prototype.
- Phase 12 (SteamVR IPD Slider UI): Slider trigger mechanism still unresolved from Phase 10 (FEAS-03 FAIL). Alternative UI approach needed.
- Phase 13 (Slider Persistence): **GO** -- persistence via lighthouse_console.exe confirmed viable (already used for config reading).

**Key implementation notes for Phase 11:**
1. Reading from `lhr-*` config files is fine — but must match the correct one to the connected HMD. The lighthouse serial needed to select the right `lhr-<serial>/config.json` should be readable from the same user flash where the driver already reads proximity calibration values. The spike's lighthouse_console.exe approach works but is heavyweight and causes tracking disruption; matching by serial from HID flash is the production approach.
2. Rotation matrices must be cached to avoid redundant config reads on every IPD change
3. Intrinsic XYZ Euler convention must be used (matches VAP alignment code)
4. SetDisplayEyeToHead(0, left, right) works from sidecar for lighthouse-owned HMD
5. SetFloatProperty(Prop_UserIpdMeters_Float) should still be called for metadata/GUI consistency
