UIScreen gains onDisplayOff()/onDisplayOn() hooks. JoystickUITask tracks
display state and dispatches them on transition — at top of loop()
(catches display.c's auto-off, which fires behind our back) and right
after _display.turnOn() in the wake path (immediate resume).
Override in SnakeScreen and GPSSettingsScreen: stop their periodic
k_timers while the screen is off, restart on wake. Game state and
GPS-fix state are preserved across sleep. Snake doesn't crash into a
wall five seconds after the screen sleeps anymore.
Other screen timers (Countdown alarm, Contacts/Admin response timeout,
the global lock timer) intentionally keep running — their job is to
fire while the user is idle.
Also: wake-from-off now reschedules the lock timer if not already
locked, so a keypress near the end of the lock window gives you a
fresh LOCK_AFTER_MS instead of being immediately re-locked.
s_scan_sent (one-shot per boot) becomes s_last_scan_ms; RepeatersScreen
onEnter() re-runs the discover if there has never been a scan or the
last one is older than REPEATER_RESCAN_AFTER_MS (60s). Walking away
and returning now gives a fresh list instead of stale results.
Also fixes the manual KEY_ENTER_LONG rescan: after Phase C removed
the poll() that re-checked the flag, just unsetting it no longer
triggered anything. Now it calls doScan() directly.
UIScreen gains onEnter()/onExit() lifecycle hooks; poll() and the
_curr->poll() call in the main loop are removed. Each screen with
periodic or deadline-based work owns its own k_timer:
- one-shot timers: Splash dismiss, Countdown alarm, Contacts ping
timeout, RepeaterAdmin cmd/login timeout, Unread preview expiry
- periodic timers: Snake tick, GPSSettings sample
- onEnter()-only: Repeaters discover, Doom start
- deleted: Home, Stopwatch (were empty)
Timer ISR callbacks only signal _task->notify() — never mutate
screen state. Main-thread render() handles transitions. Setting
_curr now fires onExit on the outgoing screen and onEnter on the
incoming one, so timers are scoped to screen lifetime and can't
fire stale events on the wrong screen.