Now I have enough information to provide a comprehensive answer. Let me compile my findings:

## FINDINGS: Sauna Codebase Architecture Review

### 1. **HOW sauna acquires and presents to the PHYSICAL Bigscreen Beyond display**

**Answer: NVIDIA DirectMode via NVAPI D3D12**

- **File Evidence:**
  - `src/present/nvapi_d3d12.h:1-34` — Header describes Windows backend: "D3D12 + NVAPI DirectMode"
  - `src/present/nvapi_d3d12.cpp:1-33` — Implementation uses NVAPI to acquire exclusive DirectMode display
  - `src/app/spatial_light.cpp:61` — Main app includes `nvapi_d3d12.h` as the presenter

- **Mechanism:**
  - **NVAPI DirectMode Acquisition** (`nvapi_d3d12.cpp:330-347`): Calls `NvAPI_D3D_AcquireDirectModeDisplay()` with vendor ID `0x2709` ("BIG" in EDID), creating an exclusive D3D12 surface chain
  - **Mode Selection by Value** (`nvapi_d3d12.cpp:240-287`): Mode chosen by exact resolution/refresh/format match (not by index—the mode list reorders between runs per S2 spike findings)
  - **5088×2544 native resolution** at 75/90 Hz, X8R8G8B8 format (NV format 22)
  - **DSC (Display Stream Compression) forced** to v1.1 / 4 slices / 8 bpp (per device JSON)
  - **Double-buffered scanout surfaces** created via `NvAPI_D3D_DirectModeCreateSurface()` with shared NT handles opened into D3D12 resources (`nvapi_d3d12.cpp:348-358`)
  - **Pacing by present waitable** — the driver owns the vsync-locked flip (`nvapi_d3d12.cpp:408-411`), app draws each frame in callback, GPU queue presents via `NvAPI_D3D_DirectModePresent12()`

- **Not a monitor/DXGI output enumeration**: This is **exclusive-mode DirectMode**, not extended desktop (which is fallback-only per ADR-0001)

---

### 2. **"requestHmdOwnership", "hmdReleasedNow", and SteamVR ownership handoff state machine**

**Answer: Four-state ownership model with release/reclaim**

**State Machine (` src/app/spatial_light.cpp:852-935`):**
```
enum class Own { kOwned, kReleased, kVrActive, kAwaitDon };
```

| State | Meaning | Trigger | Next |
|-------|---------|---------|------|
| **kOwned** | Sauna holds DirectMode acquisition, presenting normally | vrserver.exe appears | → kReleased |
| **kReleased** | Acquisition released (scanout destroyed, display released); waiting for vrcompositor to take it | vrcompositor.exe appears | → kVrActive |
| | | vrserver disappears (aborted) + worn/no-prox | → kOwned (reclaim) |
| | | vrserver disappears + not worn + doze-capable | → kOwned (reclaim into doze) |
| | | vrserver disappears + not worn + no doze | → kAwaitDon |
| **kVrActive** | SteamVR's vrcompositor owns the video | vrcompositor.exe exits + worn | → kOwned (reclaim) |
| | | vrcompositor.exe exits + not worn + doze-capable | → kOwned (reclaim into doze) |
| | | vrcompositor.exe exits + not worn + no doze | → kAwaitDon |
| **kAwaitDon** | Display released, waiting for headset don (prox event) to reclaim | prox don OR tray "Wake" click | → kOwned (reclaim) |
| | | vrserver/vrcompositor reappear | → kReleased |

**Release Mechanism** (`nvapi_d3d12.cpp:417-447`):
- Drain GPU fence, destroy D3D12 resources (RTVs, descriptor heap)
- Call `NvAPI_D3D_DirectModeDestroySurface()` for both scanout surfaces
- Call `NvAPI_D3D_ReleaseDirectModeDisplay()` — relinquishes the exclusive acquisition entirely
- **Zero GPU submissions** while released — present loop parks

**Reclaim Mechanism** (`nvapi_d3d12.cpp:330-414`):
- `NvAPI_D3D_AcquireDirectModeDisplay()` with retry backoff (10 attempts, 1s sleep)
- `NvAPI_D3D_DirectModeCreateSurface()` × 2 (double-buffered)
- `NvAPI_D3D_DirectModeSetDisplayMode()`, then `POWER_ON` control
- Re-fetch the present waitable (`GetPresentWaitableObject`)

**Key Classes & Methods** (`src/present/nvapi_d3d12.h:60-92`):
- `requestHmdOwnership(bool own)` — **thread-safe atomic flag** (`hmdOwned_`) that the run loop polls at frame boundaries
  - `true` → try to acquire if released
  - `false` → release the entire acquisition and park
- `hmdReleasedNow() const` — cheap state peek (does not reset watermarks)
- `releaseDisplay() / acquireDisplay()` — actual acquire/release plumbing

**D3D12 device persistence**: The device, command queue, and fence **survive** across release/reclaim cycles (line 99-100 of header), so render resources uploaded once stay resident.

**Amendment (ADR-0002, 2026-06-12)**: On SteamVR exit while not worn, if doze is capable, sauna now **reclaims into doze** (`reclaimIntoDoze` at line 835-848) instead of awaiting a don — re-acquires, brings up video born-dozed, leaves it dozed (warm). Instant ~95 ms wake on next don vs. cold 3–5 s re-acquire.

---

### 3. **How sauna DETECTS SteamVR taking over / exiting**

**Answer: Process polling via Windows toolhelp snapshots**

**Detection Code** (`src/app/spatial_light.cpp:732-936`):
- **SteamVR start trigger:** `vrserver.exe` OR `vrmonitor.exe` appears (checked every 2 s when owned, 0.5 s when released)
- **SteamVR active trigger:** `vrcompositor.exe` appears and is running
- **SteamVR exit trigger:** `vrcompositor.exe` exits (watches exit specifically, not just absence, because vrserver lingers)

**Implementation** (`spatial_light.cpp:733-752`):
```cpp
auto procRunning = [](const wchar_t* name) {
  HANDLE snap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
  // iterate Process32FirstW/Process32NextW to find exe by name
  return found;
};
auto vrServerUp = [&] { return procRunning(L"vrserver.exe") ||
                               procRunning(L"vrmonitor.exe"); };
auto vrCompositorUp = [&] { return procRunning(L"vrcompositor.exe"); };
```

**Detection is not event-based**: No OpenVR API, no driver hooks, no IPC. Pure process enumeration every 500–2000 ms (adaptive: fast when released, slow when owned). This **keeps the SteamVR-free goal honest** — zero SteamVR SDK dependency.

**Why vrcompositor.exe, not vrserver.exe, for "done" signal:**
- vrcompositor.exe owns the actual video link — its exit is the precise moment SteamVR releases the display
- vrserver.exe (SteamVR background service) lingers after compositor exits, would delay reclaim by seconds

---

### 4. **Existing virtual-display, IDD, staging/intermediate-surface, or "pipe another app's output" code**

**Answer: NONE — Sauna is a direct-capture, in-process compositor**

**Evidence:**
- **No IDD (Indirect Display Driver)**: Zero references to indirect display drivers, `IDDCX`, or Windows Display Driver Model extensions
- **No virtual display enumeration**: No code enumerating or creating virtual DXGI outputs
- **No inter-process capture/sharing**: All capture is in-process; duplication sources are owned by spatial_light.exe itself
- **No KeyedMutex or shared textures between processes**: NVAPI DirectMode surfaces are shared *within* sauna (D3D12 render process ↔ D3D12 present backend), but not shared with external runtimes
- **Doze single-buffer VRAM trick** (`nvapi_d3d12.cpp:455-482`, `freeScanoutBuffer`): During doze, sauna frees the second scanout surface (~100 MB) to save VRAM *for SteamVR*—but this is internal state optimization, not inter-process sharing

**Why not IDD?** ADR-0001 explicitly rejected building a platform ("the render-surface SDK"), and chose a standalone direct-mode compositor. An IDD would couple sauna to the OS display infrastructure and require Windows display driver certification.

---

### 5. **What sauna captures/composites as normal content**

**Answer: Desktop monitor output via DXGI Desktop Duplication**

**Capture Abstraction** (`src/capture/duplication_source.h:23-72`):
- **DuplicationSource** — one per monitor, uses `IDXGIOutputDuplication` on a private D3D11 device
- **Frame source trait** (`src/capture/frame_source.h`) — interface for capture / streaming-bridge / test-pattern (v1 implements duplication only)
- **Multiple screens** (`spatial_light.cpp`): One `DuplicationSource` per DXGI output; arranged curved or flat per `monitor_layout.cpp`

**Resilience** (`duplication_source.h:9-12`):
- `ACCESS_LOST` (display mode change, UAC, fullscreen exclusive) → reacquire loop with backoff; never wedges

**GPU Pipeline** (`duplication_source.cpp:47-83`):
- Copy duplicated frame into intermediate (format-convert if needed — HDR scRGB ↔ BGRA8, 10-bit SDR)
- Composite cursor shape (alpha quad)
- GenerateMips chain (4K desktop on 2 m quad is heavily minified)
- Publish to shared D3D12 textures the render device opened

**Pause/Resume** (`duplication_source.h:31`, `spatial_light.cpp:777-781`):
- `stop()` releases all GPU resources (frees VRAM); keeps object alive
- `resume()` rebuilds GPU side after stop() for doze VRAM recovery
- Paused during SteamVR session (line 777-781: `if (presenter.hmdReleasedNow())` stop the sources to free GPU ring for SteamVR's compositor)

**No test-pattern fallback in v1**: `--no-capture` uses a built-in grid for M2 testing, but there's no "pipe another app's output" or virtual-desktop-style proxy.

---

### 6. **Does sauna talk to SteamVR/OpenVR as a driver or client?**

**Answer: NO — Sauna is SteamVR-independent**

**Evidence:**
- **Zero OpenVR SDK imports**: No `openvr.h`, no vrclient, no SteamVR API calls
- **No driver manifest**: No `.vrdrivermanifest`, no `IVRDriver`, no driver registration
- **No driver code**: No `src/driver/` or `driver_*.cpp` files
- **Process polling only** — the sole SteamVR integration is **watching for vrserver/vrcompositor processes** to trigger release/reclaim (ADR-0002, lines 719-732)

**Why this design?** ADR-0001:
- v1 must be **SteamVR-free** and must reach low-power targets (Steam Deck, ARM) where SteamVR is a poor fit
- Sauna implements its own IMU fusion (Mahony AHRS), distortion correction (POLY3 warp), and compositor
- SteamVR as a *coexisting* runtime is supported (automatic yield/reclaim) but *not* a platform dependency

**MCU/Tundra interaction** (not SteamVR):
- `TundraImu` — reads Tundra IMU directly via USB HID
- `McuProx` — reads proximity sensor from MCU over HID control interface (35BD:0101)
- These are raw hardware interfaces, completely independent of SteamVR

---

### 7. **Existing docs: CONTEXT.md, ADRs, and architecture documentation**

**Present:**

**CONTEXT.md** (`C:\Users\decid\Documents\projects\sauna\CONTEXT.md`) — **Comprehensive domain language reference**:
- Glossary fixing terminology (Spatial display, 3DOF/6DOF, Virtual screen, Curved/Flat arrangement, Streaming bridge)
- Hardware terms (Beyond, Tundra, MCU, Prox, VXR7200, Linkbox)
- Display modes (Monitor mode, Extended vs Direct, Persistence)
- Optics/correction (Distortion correction, Calibration, Yaw drift)
- Runtime terms (Overlay/in-image cursor, Compositor, SteamVR-free, **SteamVR coexistence**, Photon latency)
- **Asleep depths**: Doze (shallow, ~95 ms wake) vs Parked (deep, ~4–6 s wake)

**ADRs in `docs/adr/`:**

1. **0001-standalone-direct-mode-compositor.md** — v1 is own compositor, not SteamVR driver
   - Windows: D3D12 + NVAPI DirectMode
   - Linux: Vulkan + DRM lease (future)
   - Feasibility gate PASSED 2026-06-10 (spike S2)

2. **0002-steamvr-coexistence-yield-and-reclaim.md** — Process polling, four-state ownership, prox-gated reclaim
   - Release on vrserver/vrmonitor; reclaim on vrcompositor exit
   - Amendment 2026-06-12: reclaim-into-doze when not worn (warm, instant wake)

3. **0003-prox-gated-sleep-wake-policy.md** — Proximity sensor drives wake; motion-wake failsafe (not fully read, but referenced)

4. **0004-curved-virtual-screen-arrangement.md** — Multi-monitor layouts (curved on cylinder, flat coplanar)

5. **0005-doze-warm-shallow-sleep.md** — **Status: Proposed** (2026-06-12, pre-implementation)
   - Firmware owns sleep choreography via new HID `display-sleep` / `display-wake` commands
   - Host present-idle while firmware sweeps panels dark and fan off
   - **Validation on hardware** (firmware 0.4.0): ~94 ms to display-on, 45–65× win over cold ~4–6 s parked baseline
   - Default sleep depth; parked is fallback / SteamVR release / deep 30-min escalation
   - Universal rule: **worn → active, not-worn → born dozing**

**No `docs/adr/0005` implementation in spatial_light.cpp YET:**
- The code at `spatial_light.cpp:757-810` references doze but with note "M5/ADR-0005": the **present loop plumbing exists** (`requestDoze()`, `dozingNow()`, doze idle loop), but the MCU firmware handshake (HID command pair, version check) may still be pending.
- The SteamVR reclaim-into-doze amendment IS implemented (line 835-848: `reclaimIntoDoze()`).

---

### Summary Table

| Question | Answer | Key Files |
|----------|--------|-----------|
| **1. Display acquisition** | NVAPI DirectMode, D3D12, 5088×2544, double-buffered scanout, forced DSC 1.1 | `nvapi_d3d12.h/cpp`, `spatial_light.cpp:61` |
| **2. Ownership & handoff** | Four-state (kOwned, kReleased, kVrActive, kAwaitDon); release destroys surfaces + acquisition; reclaim retries; device persists | `spatial_light.cpp:852-936`, `nvapi_d3d12.h:67,91`, `nvapi_d3d12.cpp:330-447` |
| **3. SteamVR detection** | Process polling: vrserver.exe/vrmonitor.exe start → release; vrcompositor.exe exit → reclaim; 500–2000 ms poll | `spatial_light.cpp:732-752, 746-752` |
| **4. Virtual display / IDD** | **NONE** — direct-capture in-process compositor only | N/A |
| **5. Capture source** | DXGI Desktop Duplication (per-monitor DuplicationSource), GPU mip chain, shared textures to render device | `duplication_source.h/cpp`, `spatial_light.cpp:56` |
| **6. SteamVR/OpenVR API** | **NONE** — zero SDK, no driver manifest, process polling only | (no SteamVR includes in codebase) |
| **7. Docs** | CONTEXT.md (glossary), ADR 0001–0005 (design decisions), ADR-0005 doze validated on hardware 2026-06-12 | `CONTEXT.md`, `docs/adr/*.md` |

Prompt:
Explore the sauna codebase at C:\Users\decid\Documents\projects\sauna to answer these specific questions. Be thorough — check multiple files/naming conventions. Return concise findings with file:line refs.

1. HOW does sauna acquire and present to the PHYSICAL Bigscreen Beyond display? Is it a Windows monitor (DXGI/DirectMode), a direct-mode HMD via a vendor/OpenVR API, a Vulkan/D3D swapchain to a specific adapter+output, or something else? Find the presenter/swapchain/output-acquisition code. Look in src/app/spatial_light.cpp and any src/*/presenter*, *display*, *output*, *swapchain*, *vrlink*, *directmode* files.

2. What exactly is "requestHmdOwnership", "hmdReleasedNow", and the SteamVR ownership handoff state machine? How does sauna RELEASE the physical display to SteamVR and RECLAIM it? What Windows/GPU mechanism actually transfers the display (does sauna release a DXGI output / direct-mode acquire, stop presenting, etc.)? Summarize the Own::k* states and transitions.

3. How does sauna DETECT SteamVR taking over / exiting? (process polling vrserver/vrcompositor? OpenVR events? a driver hook?) Find the detection code.

4. Is there ANY existing virtual-display, indirect-display-driver (Windows IDD), staging/intermediate-surface, or "pipe another app's output" code already in sauna? Search for IDD, "indirect", "virtual display", desktop duplication, shared texture / shared handle, NT handle, KeyedMutex, capture of another process.

5. What does sauna capture/composite as its normal content (desktop duplication? specific windows? a capture ring)? Find the capture source abstraction (the "screens"/"src" with stop()/resume() seen in spatial_light.cpp).

6. Does sauna talk to SteamVR/OpenVR at all as a driver or client? Any openvr.h / vrclient / driver_*.cpp / .vrdrivermanifest / IVRDriver in the repo?

7. Existing docs: is there a CONTEXT.md or CONTEXT-MAP.md at repo root, and a docs/adr/ directory? List ADR filenames + titles if present. Also note any ADR-0005 (doze) or architecture docs describing the display/ownership model.

Return a structured summary per question with file:line evidence. Do not modify anything.


/grill-with-docs experimental concept proposal: can sauna create a virtual display driver that steamvr targets, such that sauna can keep ownership of the physical video stream/display interface at all…