---
status: proposed (2026-06-12; grilled design, pre-implementation)
---

# Doze: a warm shallow sleep for sub-second wake

The headset takes 3–5 s from "software feeds video" to "panels emit" on a cold DirectMode
bringup (measured, docs/photon-latency-bench.md). Today every sleep is **parked** — host
`POWER_OFF`, video pipeline torn down — so resume-from-sleep, the case people hit constantly
(brief doff, glance away), pays that full cold cost. The goal is sub-1s resume, ideally far
less.

The bench showed the firmware already has a ~50 ms emission gate (`OLED_set_brightness(0)`,
`video_displays_off_prox`). The 3–5 s only exists because `POWER_OFF` kills the DP video
signal → the VXR7200 de-locks → firmware tears the panel stack down → wake must re-detect
video, re-settle DSC, and run the firmware's fixed bringup delays (`PANEL_INIT_TO_STARTUP_DELAY`
= 500 ms, fan ramp, etc.). So the lever is not "make bringup faster" — it is **don't tear the
pipeline down on sleep.**

We add a second, shallower sleep depth: **doze**. The video pipeline stays warm
(`video_enabled` true, VXR locked, host presenting black); emission is swept off, the panels
are driven to true display-off, the fan idles, and the LED breathes — so the headset looks and
sounds asleep while waking in ~340 ms. **Parked** stays as the deep/fallback depth (minimum
footprint, 3–5 s wake). See **doze** / **parked** / **asleep** in CONTEXT.md.

## Decision

- **Firmware owns the sleep choreography** behind a new HID command pair, `display-sleep` /
  `display-wake` (control interface 35BD:0101). One command each way; the firmware runs the
  whole transition atomically. The host never orchestrates the individual actuators.
  - **Sleep:** sweep brightness `oled_current_brightness` → the **brightness floor** over
    ~300 ms (target-seeking ramp) → `OLED_display_on_off(false)` for true black at the floor →
    fan → `FS_Idle` → LED → breathing → set the **doze-latch**. `video_enabled` stays true
    throughout. The floor is **register 0** — the lowest possible brightness register value (the
    same value `video_displays_off_prox` already writes); the visible fade ends there before the
    cut to true black. (Register is the raw 0–1023 OLED value, distinct from the HMDUtility UI
    scale where the min is -20.)
  - **Wake:** `OLED_display_on_off(true)` (+~40 ms, two `OLED_POST_INIT_DELAY`s) → sweep
    floor → `oled_current_brightness` over ~300 ms → fan → `FS_Video` → LED → static → clear
    latch.
- **The doze-latch is authoritative over the firmware prox gate** while set: emission stays
  off regardless of prox, and the autonomous `video_displays_on_prox` gate is suppressed. This
  is required for doze-while-worn (tray "Sleep now" mid-use), which a pure prox gate gets wrong.
- **The doze-latch is subordinate to `video_enabled`: losing video clears it.** Any host
  disappearance (force-close, crash, SteamVR release) stops scanout → VXR de-locks →
  `video_enabled` false → the firmware's video-loss teardown also clears the latch. The
  headset degrades to its normal dark "no video" state (fan off, LED breathing), not stuck
  dozed; the next runtime brings video up and lights normally. This rule is non-negotiable.
- **Sweeps are target-seeking ramps** (the existing `task_fan` pattern): a mid-sweep reversal
  just chases the new target from the current value, so an interrupting don wakes smoothly
  from wherever the sweep was. `OLED_display_on_off(false)` is gated on actually reaching the
  floor; wake re-enables the display only if it had been turned off.
- **Host stays the single wake authority.** A don out of doze is the host sending
  `display-wake` through the existing ADR-0003 grace/override logic — not the firmware lighting
  up autonomously on its own prox read. Doze inherits the full ADR-0003 wake matrix (prox-dead
  → motion-wake failsafe, sleep-while-worn grace, tray "Wake up" always wins).
- **Doze is the default sleep depth.** All three sleep entries (idle timeout, tray "Sleep now",
  launch-not-worn) → doze. Universal bring-up rule: whenever sauna brings up or reclaims the
  video pipeline it checks prox — **worn → active** (capturing, streaming); **not-worn → born
  dozing** (set the latch over HID *before* starting video so the firmware bringup comes up
  straight into doze-dark, no emission/fan/LED flash). Applies to launch and SteamVR-exit
  (ADR-0002). **Parked** is reached only via the optional doze→park escalation (default off,
  30 min), SteamVR release, or the capability fallback.
- **Capability-gated.** Doze is used only if the firmware advertises it (version check over
  `HID_CODE_FOR_SW_VER` at host startup). Otherwise the host falls back to **parked**. The
  doze firmware build bumps the version. Mixed fleets are safe and rollout order is irrelevant
  (old-host + new-firmware never sets the latch; new-host + old-firmware never sends the
  command).
- **Scope:** MCU firmware (ATSAMG55) only. Doze never touches the VXR7200 — it deliberately
  preserves the VXR's locked video. Both BS1 and BS2/BS2E (doze never cuts panel *power*, so
  the BS1-only 1.8 V-rail shutdown difference does not apply).

## Considered options

- **Host orchestrates the primitives (brightness stream + `'f'`/`'F'` fan + LED command)** —
  rejected. The fan overtemp net only exists in `FS_Idle`; a host-forced fan-0 goes through
  `FS_Debug`/`FS_Video` with no thermal protection, which is unsafe with panels driven. Only
  the firmware can reach Idle-but-video-on. Host orchestration is also chatty and races on a
  multi-actuator transition.
- **Firmware prox gate wakes doze autonomously (host sends nothing on don)** — rejected. ~50 ms
  and zero host involvement, but it breaks doze-while-worn (prox says worn → gate keeps panels
  lit) and bypasses the ADR-0003 grace, re-introducing the flapping ADR-0003 fought.
- **Doze replaces parked entirely** — rejected. Parked is still needed for SteamVR release and
  as the capability fallback, and an explicit deep depth is wanted for the rare long-absence
  case (optional escalation).
- **Reduce refresh/resolution during doze to save power** — rejected. Any change to the
  negotiated DP mode (1 Hz, lower res, lower link rate) makes the VXR re-detect/re-lock — that
  *is* the parked teardown. Doze must hold the mode pixel-for-pixel. The liteness lever is
  host-side (strip the present loop to a black present; spike whether scanout free-runs without
  continuous present).
- **Keep using the prox brightness-0 dim for doze darkness** — rejected. `video_displays_off_prox`
  is near-zero, not true black (a ~5 min `prox_timeout_timer` then does the real display-off).
  With the fan off, doze needs genuine zero emission immediately → sweep to the brightness floor
  (register 0) then `OLED_display_on_off(false)`.

## Consequences

- A flashed firmware contract (the command pair + latch semantics) the host depends on —
  hence the capability handshake and the permanent parked fallback (also the rollback path:
  firmware or host can be reverted independently).
- Idle power rises during doze (link + VXR + 75 Hz black scanout stay up). Accepted: from the
  PC's view it is ~a DP monitor left on; laptop users who truly unplug will OS-sleep, which
  unpowers the HMD anyway. The optional doze→park escalation (default off, 30 min) is the
  release valve.
- The host gains a `dozing` state alongside `parked`, and wake must know which depth it is
  leaving (`display-wake` ~340 ms vs full re-acquire 3–5 s).
- Modifies ADR-0002 (reclaim-into-doze, not yield-await-don) and extends ADR-0003 (doze is the
  default depth; doze-latch overrides the prox gate). See those records.
- Validation rides the camera bench (docs/photon-latency-bench.md): the warm parked→wake
  baseline is now **measured — ~4–6 s (4.07, 6.21 s), no cheaper than cold** (`panel_bench
  cycle`). POWER_OFF de-locks the link, so wake re-pays the full DSC/link retrain bounce; park
  buys nothing on wake. This confirms the premise — only never-POWER_OFF doze dodges the
  retrain — and sets doze's target to beat (~340 ms ⇒ ~12–18×). Doze-wake measures against the
  same `DISPLAYS_ON` + camera instrument once the firmware lands.
- **Doze-wake is now measured and validated** (firmware 0.4.0 flashed, `panel_bench doze`,
  2026-06-12): **~94 ms to `DISPLAYS_ON`** (93.2, 94.7 ms), camera photon onset ~110–310 ms — a
  **45–65× win** over the parked baseline, beating even the ~340 ms target. The status word stays
  `0x2318↔0x2319` with `DSC_ENABLED` set throughout (no retrain), the present loop holds 75 fps,
  and the camera shows a smooth ~600 ms fade to true black. The core firmware contract is proven
  on hardware. See docs/photon-latency-bench.md "Doze wake — the payoff".

## Open / deferred

- **No-present-free-run spike**: does NVAPI DirectMode hold the last surface (link alive) with
  an idle present queue, or does it TDR? If it holds, host doze cost ≈ 0 (present black once,
  idle to a heartbeat). If not, present black at mode rate (still cheap).
- **Cold-boot speed-up is a separate follow-on milestone** (the 500 ms guard delay, fan ramp,
  the per-trial DSC/link **retrain bounce** — the bounce is the one item that may pull VXR
  firmware into scope; spike parked for now).
