# Phase 5: Proximity Algorithm - Context

**Gathered:** 2026-03-22
**Status:** Ready for planning

<domain>
## Phase Boundary

Driver computes `person_detected` boolean using the same algorithm as the Beyond 2 firmware: calibration offset subtraction, sample validation (100-16383 range), 16-sample moving average, and hysteresis-based detection. This phase builds the algorithm from raw HID data to a computed boolean — no SteamVR integration (Phase 6 wires algorithm output to standby/wake).

</domain>

<decisions>
## Implementation Decisions

### Processing location
- Algorithm runs inside the HidDevice reader thread, processing each HID sample immediately as it arrives
- New `ProximityAlgorithm` class in separate file (`src/hid/proximity_algorithm.h/.cpp`) — clean separation from HID I/O, independently testable
- HidDevice owns a ProximityAlgorithm instance, feeds it samples in the reader thread
- HidDevice exposes `GetPersonDetected()` as atomic bool — same pattern as `GetProxDistance()`
- All SteamVR API calls stay on the main thread (RunFrame); reader thread only computes

### Bootstrap behavior
- Moving average buffer initialized to all zeros, always divides by 16 — matches firmware exactly
- `person_detected` defaults to false on driver startup (headset assumed off-head)
- On USB reconnect: reset moving average buffer to zeros and `person_detected` to false (same as fresh startup)
- Calibration is re-read on reconnect (Phase 4 decision), so stale samples are never mixed with new calibration

### Diagnostic exposure
- Pipe `status` command extended with full algorithm state: `averaged_prox=X detected=true/false eff_thresh=Y samples=N`
  - `averaged_prox`: current 16-sample moving average value
  - `detected`: person_detected boolean
  - `eff_thresh`: effective threshold (threshold + user_trim, clamped >= 0)
  - `samples`: total samples processed since last reset
- No new pipe commands — existing `proximity on/off` continues to work for manual override
- Algorithm logs state transitions to SteamVR driver log: "Proximity: person detected (avg=X, thresh=Y)" / "Proximity: person removed (avg=X, thresh=Y)"

### Claude's Discretion
- ProximityAlgorithm class interface design (method signatures, internal data structures)
- Circular buffer implementation details (array + index vs std::array)
- Atomic bool implementation for person_detected (std::atomic<bool> vs std::atomic<int>)
- Exact log message formatting
- Whether averaged_prox is exposed as atomic or via mutex with other algorithm state

</decisions>

<deferred>
## Deferred Ideas

### DeviceProvider RunFrame wiring (deferred from locked decisions)
Originally planned: "DeviceProvider reads `GetPersonDetected()` in RunFrame, compares to `m_bProximity`, calls `SetHmdProximity()` on change."

**Deferred to Phase 6.** Research (05-RESEARCH.md, Pitfall 5) revealed that the existing `SetHmdProximity()` toggles `Prop_ContainsProximitySensor_Bool`, which tells SteamVR whether the HMD *has* a proximity sensor — not whether a person is detected. Setting it to `false` would deregister the sensor entirely, which is incorrect. The correct SteamVR API for communicating person-detected state changes needs to be resolved in Phase 6 before wiring RunFrame. Phase 5 computes `person_detected` correctly and exposes it via `GetPersonDetected()` and the status command for validation; Phase 6 will wire it to the correct SteamVR integration.

</deferred>

<canonical_refs>
## Canonical References

**Downstream agents MUST read these before planning or implementing.**

### Firmware proximity algorithm (the reference implementation)
- `code_samples/beyond_firmware/src/Devices/prox_control.c` lines 91-156 — `prox_update()`: the exact algorithm to replicate. Calibration subtraction, sample validation, circular buffer moving average, hysteresis comparison with trimmed threshold
- `code_samples/beyond_firmware/src/Devices/prox_control.c` lines 22-77 — `prox_init()`: initialization of calibration params from signature tags, buffer to zeros, person_detected=false
- `code_samples/beyond_firmware/src/Devices/prox_control.h` — MIN_PROX_VALUE (100), MAX_PROX_VALUE (16383), Proximity_T struct definition

### Existing driver code (integration points)
- `src/hid/hid_device.h` — HidDevice class with reader thread, GetProxDistance(), GetCalibration(). Algorithm will be owned by this class.
- `src/hid/hid_device.cpp` — ReaderThreadFunc is where algorithm processing will be called per sample
- `src/hid/user_signature.h` — CalibrationData struct with programmed_cal, proximity_threshold, proximity_hysteresis, user_trim
- `src/driver/device_provider.h` — DeviceProvider with m_bProximity, SetHmdProximity(), RunFrame(), HandlePipeCommand()
- `src/driver/device_provider.cpp` — RunFrame and HandlePipeCommand implementations to extend

### Project requirements
- `.planning/REQUIREMENTS.md` — PROX-01 through PROX-06 define Phase 5 requirements

</canonical_refs>

<code_context>
## Existing Code Insights

### Reusable Assets
- `HidDevice` class: Already has reader thread, atomic `m_lastProxDistance`, mutex-protected `CalibrationData`. Algorithm fits naturally as a member processed per sample in `ReaderThreadFunc`.
- `CalibrationData` struct (`user_signature.h`): Already contains programmed_cal, proximity_threshold, proximity_hysteresis, user_trim — all inputs to the algorithm.
- `DeviceProvider::SetHmdProximity()`: Already writes to HMD property container with state-change guard. RunFrame just needs to read person_detected and compare.
- `HandlePipeCommand("status")`: Already outputs proximity/HID/calibration values. Extend with algorithm state fields.

### Established Patterns
- Lock-free atomics for frequently-read values (prox_distance pattern) — use same for person_detected
- Mutex for complex state that's read infrequently (calibration pattern) — use for algorithm diagnostic snapshot if needed
- DriverLog for all logging from driver context
- Reader thread is self-contained (handles connect/disconnect/reconnect internally)

### Integration Points
- `HidDevice::ReaderThreadFunc()` — After extracting prox_distance from report, feed sample to ProximityAlgorithm
- `HidDevice` — Add GetPersonDetected() and GetAlgorithmState() (for diagnostics) public methods
- `DeviceProvider::HandlePipeCommand("status")` — Append algorithm state to response string

</code_context>

<specifics>
## Specific Ideas

- Algorithm must be a faithful port of `prox_control.c` `prox_update()` — not a reinterpretation. Same subtraction order, same validation bounds, same hysteresis direction (>= for detect, <= for release).
- 500ms report rate means 8 seconds to fill the 16-sample buffer. This is acceptable — headset on/off detection doesn't need sub-second response.
- Reset algorithm state on USB reconnect to match fresh-boot firmware behavior and avoid stale-sample artifacts.

</specifics>

---

*Phase: 05-proximity-algorithm*
*Context gathered: 2026-03-22*
