# Sauna — v1 Plan

Output of the design interview closed 2026-06-09. Language: `CONTEXT.md`. Facts and risk
register: `reference/BASELINE.md`. Architecture decision: `docs/adr/0001`.

## v1 in one sentence

A standalone Windows+NVIDIA app that reads the Tundra IMU for 3DOF pose, captures the desktop's
monitors, composites them as head-tracked virtual screens, warps per-eye using the per-unit
calibration, and presents to the Beyond in Vulkan direct mode — no SteamVR, no lighthouses, no
firmware changes.

## Locked decisions

| # | Decision | Notes |
|---|---|---|
| 1 | v1 experience = virtual screen(s) via our own compositor, direct mode on stock EDID | ADR-0001 (proposed; gated on spike S2) |
| 2 | Windows+NVIDIA first, present-backend seam | *(amended by S2)* Windows backend = D3D12+NVAPI DirectMode; `VK_EXT_acquire_drm_display` = Steam Deck rung later; Vulkan render core still viable via shared-handle interop |
| 3 | Per-unit calibration via native Watchman HID config read | port from vendored libsurvive; JSON file-import kept as dev scaffold |
| 4 | Own minimal AHRS (Mahony-style) for 3DOF | config IMU mount frame + biases; recenter UX owns yaw drift; magnetometer = bonus if S1 finds one |
| 5 | Content = multi-monitor desktop capture | *(API amended by M3)* DXGI Desktop Duplication, not Windows.Graphics.Capture (per-monitor GPU textures, no picker/border baggage — see docs/m3-product-shape.md); layout mirrors OS monitor arrangement; PipeWire backend later |
| 6 | Frame-source abstraction from day one | capture / remote-stream / test-pattern; only capture implemented in v1 — keeps the streaming-bridge stretch additive |

UX defaults: proximity-sensor display sleep when doffed + idle timeout (burn-in safety is ours
once SteamVR is out of the loop); conservative persistence + brightness defaults; 75 Hz default
with 90 Hz opt-in flag; recenter on a configurable global hotkey; settings = plain desktop window
(visible in-headset via capture).

## Architecture (components)

- **Device layer** — Tundra HID `28DE:2300` MI_00 (IMU stream + config read); MCU HID `35BD:0101`
  (display power, per-eye panel-on, persistence, brightness, proximity, EDID/refresh).
- **Tracking** — Watchman packet parse → bias/scale correction → AHRS → head pose; recenter.
- **Frame sources** — trait per decision 6; v1 backend: Windows.Graphics.Capture per monitor.
- **Compositor** — scene of virtual screens (OS layout); per-eye render from `intrinsics`,
  `eye_to_head` canting, IPD; distortion pass (per-channel poly3 + `grow_for_undistort`);
  `display_color_mult`.
- **Present** — per-platform backend behind one interface: Windows = D3D12 + NVAPI DirectMode
  (proven, S2); Linux = Vulkan + DRM lease (unproven rung). Fallback: extended-mode SBS fullscreen.
- **App shell** — tray + settings window, hotkeys, safety policy.

## Spike ladder (run before architecture code; in this order)

| Spike | Question | Pass criterion |
|---|---|---|
| **S1** ✅ PASS 2026-06-10 | Does the Tundra stream IMU with Steam never run? | Streams unconditionally — 993.8 Hz unique samples, no wake needed, SteamVR never ran since boot. 4096 LSB/g re-confirmed; sliding 3-sample window (dedupe by timecode+seq); no mag on MI_00; gyro scale still unverified (→ M2). See `spikes/s1-imu-no-steam/FINDINGS.md`. |
| **S2** ✅ PASS 2026-06-10 | Can our own process acquire + continuously present in direct mode? | PASS via **NVAPI/D3D12** (75.07 fps native 5088×2544, zero errors; 10-min soak logged). Vulkan winrt rung DEAD on consumer Windows — specialized displays edition-gated. Windows backend = D3D12+NVAPI DM (NDA lib, referenced not vendored); Linux rung unchanged (VK DRM). See `spikes/s2-vulkan-direct/FINDINGS.md`. |
| **S3** ✅ PASS 2026-06-09 | Which direction do the production POLY3 coeffs run? | PASS — `DISTORT_POLY3` is the **direct** polynomial `d = 1+k1r²+k2r⁴+k3r⁶` (not Monado's reciprocal), output rescale `0.5/(1+grow)`, **no r² clamp** (`undistort_r2_cutoff` unused by `ComputeDistortion`). Evaluator matches ground truth to 0.0002 px max (65×65×3ch×2 eyes, unit LHR-599F3B91). Bonus: driver folds canting into asymmetric `GetProjectionRaw` (eye_to_head reported identity; `parallel_render_cameras: true`), IPD translation = user setting not config default. Eyes-in PASS on enlyzeam: pre-warped grid rectilinear through lens both eyes, CA stable, v-orientation + eye assignment confirmed. See `spikes/s3-distortion-truth/FINDINGS.md`. **Spike ladder complete 3/3.** |

## Build order after spikes

1. **M1 first light** ✅ 2026-06-10 — S1+S2 glued: head-locked test pattern in-headset from a
   cold boot. Eyeball PASS on enlyzeam (pattern + IMU needle); cold boot confirmed; bonus
   hardening: Watchman disambiguation (MCU hub pairing), MCU prox doff/don, free-run throttle
   (verified live on cable pull), hot-unplug auto-recovery (present + both HID readers).
2. **M2 tracked + warped** — AHRS pose + distortion/canting/IPD pipeline: one virtual screen
   fixed in space, fusion-correct binocular view.
3. **M3 product shape** — multi-monitor capture + OS layout, recenter, prox/idle safety,
   settings UI, native calibration read replacing dev import.
4. **M4 hardening** — pacing (capture Hz vs panel Hz), color correctness, install story.

## Out of scope for v1

SteamVR driver mode; Linux/Steam Deck port (first post-v1 target); streaming bridge (seam only);
6DOF; linkbox-less (hardware adapter, separate track); IddCx virtual display driver.
