---
status: accepted (shipped 2026-06-11/12, 6be8b53..bef0417; iterated against field reports)
---

# Sleep/wake policy: the prox don is the wake signal

A wearable display must go dark when nobody is looking and come back the instant someone
is — without flapping in between. We chose the prox sensor as the single deliberate wake
signal and demoted head-motion wake to an off-by-default feature flag (`wake_on_motion`):
every supported unit has a prox, the don is an unambiguous statement of intent, and motion
wake fires from desk bumps and cable tugs.

Sleep entries: idle timeout (no head motion), tray "Sleep now", and launch-with-headset-
not-worn. All converge on one parked state — panels off, capture paused, tracking alive
(see **asleep** in CONTEXT.md).

Sleeping while worn gets special gating (field-specified): wake stays disarmed until the
prox releases, then a 30 s grace runs before a don may wake it again. The grace anchors at
the DOFF, not the sleep click. A don swallowed by the grace stays asleep until a full
doff + re-don after the grace — there is deliberately no "still worn when the grace
expires" shortcut (tried, rejected in the field: re-arming must pass through a prox
un-trigger). The tray "Wake up" bypasses all of this — an explicit click always wins.

## Considered options

- **Motion wake on by default (the original behavior)** — rejected: desk bumps wake the
  panels; and it had silently masked don-wake failures for an entire milestone, because
  motion always fired first.
- **Prox-only with no failsafe** — rejected: panels-off may take the MCU's telemetry down
  with the video signal (suspected, unconfirmed); a dead prox would then make sleep
  permanent. While parked with the prox silent > 5 s, motion wake arms itself regardless
  of the flag — the Watchman IMU streams independently of panel power, so it is the wake
  of last resort. (Also forced on when no prox hardware exists at all.)
- **Grace anchored at the sleep click** — rejected: the point of the grace is surviving
  the HANDLING after a doff, which can begin minutes after the click.

## Consequences

- Wake matrix: don (always, edge required) · tray "Wake up" (always) · motion (flag, or
  prox-dead failsafe, or no prox hardware) · idle-policy-disabled (idle parks only —
  never unparks a manual or launch park).
- Suppressed wakes are legible: a grace-swallowed don prints what happened and the
  doff+don recipe. Silent gating read as "wake is broken" in the field.
- Open question for the MCU: whether prox telemetry actually survives panel power-off.
  The failsafe console line is the instrument; if it fires routinely, the 30 s grace
  shape should be revisited against real MCU behavior.

## Evidence added 2026-06-12 (photon-latency bench, docs/photon-latency-bench.md)

- **Prox gates emission, not scanout, at cold start.** Unworn bringup: link fully
  healthy (75 fps presents, vsync pacing, zero free-run) and zero photons — firmware
  holds the OLEDs dark until prox says worn. The doff-kill (vsync dies → free-run) is a
  *transition* behavior; the cold-start dark state has no vsync symptom at all, so
  free-run detection cannot observe "user sees nothing".
- **Host-visible panel ground truth exists**: MCU telemetry bytes 24-25
  (`video_get_display_status`) bit0 `DISPLAYS_ON`, camera-validated to one video frame
  (`McuProx::displaysOn()`). Strictly better instrument than the failsafe console line
  for the open question above.
- **Telemetry survived panels-off throughout the bench** (control HID streamed at 50 ms
  the entire time panels were dark) — for the host-driven cold-start case. The
  doff-initiated power-down case remains the unverified half of the open question.
- **Wake-latency shape**: if scanout is alive while panels are prox-dark, firmware
  lights them ~50 ms after prox triggers; a full re-acquire/modeset costs 3-5 s of
  darkness (firmware/VXR video detect + DSC settle, retrain bounce every trial). A
  future "instant-on don" would keep the acquisition + scanout running while asleep and
  let prox gate emission — panel power policy and link power trade against don latency.
- Bench-only firmware controls: `'p'` disables prox gating (firmware believes worn,
  persists until `'['` or power cycle), `McuProx::setProxBypass()`. Never ship enabled.

## Amendment 2026-06-12 (doze — ADR-0005)

The default sleep depth becomes **doze** (warm pipeline, ~340 ms wake), not **parked**. This
policy's wake matrix carries over to doze unchanged — the entries below still hold — with three
clarifications:

- The **don is still the single wake signal**, routed by the host (`display-wake`) through this
  policy's grace/override logic. In doze the firmware's autonomous prox gate is **suppressed by
  the doze-latch** (ADR-0005), so the firmware does not wake emission on its own prox read; the
  host stays the one authority and the grace semantics are preserved (including doze-while-worn
  from tray "Sleep now").
- The **prox-dead → motion-wake failsafe** applies to doze exactly as to parked: a dead prox
  while dozing would starve the don edge, so motion wake must arm as the wake of last resort.
- The "panels-off may take MCU telemetry down" open question is now answered for the host-driven
  case (telemetry streamed throughout doze at 50 ms; bench-confirmed) — and doze keeps the
  pipeline up regardless, so the failsafe's *original* trigger (parked + prox silent) is the
  only place that concern still lives.

**Parked** remains reachable via the optional doze→park escalation (default off, 30 min),
SteamVR release (ADR-0002), and the doze capability fallback.
