wake-alarm/DOCS-architecture.md
Krzysztof kuhy Rudnicki 82abbefa01 Add the continuation prompt for the remaining hardening phases
The PC shut down mid-session. This is the entry point for picking the work
back up without the transcript: what is done, what is left in what order, the
decisions already settled, and the traps that actually cost time.

Points at ~/.claude/plans/wake-alarm-still-does-witty-toucan.md for detail
rather than duplicating it, so the two cannot drift apart.

Every factual claim in it was re-checked against the tree, not written from
memory -- including the one that matters most: _reactivate.py is finished and
tested but is called by NOTHING (it appears only in a _startup.py docstring),
so item 6 is not actually fixed at runtime yet. That is step 1 for the next
session.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EckWmyfetYq3Mi8MAnUudh
2026-08-27 20:05:14 +02:00

2.2 KiB

wake_alarm architecture

Reference for the module layout after the 2026-08-26 split, and the two failure modes that motivated it.

Where behaviour lives

_alarm.py owns the window and the dismiss flow and nothing else. When looking for behaviour, start here:

Question Module
Should the alarm run at all today? _gating (no time gate — that is _trigger)
What should it sound like right now? _escalation (pure), driven by _beep_loop
Why did it go quiet? _escalation — the failsafe at FAILSAFE_SILENCE_SECONDS
Why is there no sound on HDMI? _sinks + _reactivate (see below)
Challenge presentation / rounds _rounds, _countdown, _dismiss_view
Recurring timers _alarm_timers (a mixin; they reschedule via root.after)
Screen flash _screen_flash (~1.3 Hz, deliberately under the 3 Hz threshold)
Startup hardware _startup (display, brightness, plug + pre-roll)
Structured logs _logging_setup, _log_sink, _log_dedupe, _correlation

The audio failure this fixed

_activate_all_outputs() ran once, at startup, and _parse_profile_line drops any profile reporting available: no. At wake time the monitor has not finished its HDMI handshake, so the profile is skipped — and while a card's profile is off its sink does not exist, so _play_on_all_sinks's per-cycle re-enumeration can never find it. _reactivate re-runs activation on a decaying schedule and logs the enumerated sink list each pass.

A powered-off speaker is not detectable from software — the analog sink is present and IDLE either way. That is a hardware problem; see DOCS-speaker-recommendation.md.

Timing constants that must not collide

FAILSAFE_SILENCE_SECONDS (300) silences audio only; the lock, challenge and workout gate persist. Two invariants are locked in by tests in test_escalation.py, because breaking either silently guts the alarm:

  • PHASE_MEDIUM_END * 60 < FAILSAFE_SILENCE_SECONDS — otherwise the escalation never leaves its softest rung before going quiet.
  • ESCAPE_OFFER_AFTER_SECONDS (900) > FAILSAFE_SILENCE_SECONDS — otherwise the escape hatch appears exactly as the alarm stops being annoying.