STATE.md was five days stale. It now carries what is on the phone (viewtop r76, souveraine r333) and the four defects fixed on the glass: per-window capture was unreachable because quickshell holds wlr handles and the ext capture source is named by an ext handle with nothing correlating them; titles were never republished on commit; the border was a filled slab that relied on the client covering its middle; capture came back flipped. Also the packaging hole, which was the important one — viewtop's PKGBUILD existed and appeared in none of packages.yml's build lists, so nothing ever built it. Half closed: it builds now, but a viewtop commit still does not trigger a rebuild. TASK-15 question 1 is answered and closed: dropping the accelerometer claim took iio-sensor-proxy from 15.3% to 1.1%, sensors overall 17.2% to 2.6% of a core. Consequence noted against 08(g) — confidence loses accel's +0.3. New: 54, 55, 56, and 57/58 (Pi dock head, SMB2 OTG boost) with the USB notes they point at.
7 KiB
TASK 15 — Idle coordinator: freeze, doze, and scheduled wakeups
Status: open — design task. Carved 2026-07-21 from Casey's ask ("app freeze state for idle, the timeoffs and wakeups to check for notifications") plus the measured idle burn below.
Why (measured 2026-07-21, phone idle, screen locked)
culver9.8% CPU, 6:11 accumulated — the single biggest idle spinnerqs(shell) 4.5%,souveraine-player3.9%- cluster1 peaked 54.9 °C; Casey feels it in-pocket
- The mic power pin (TASK-07) holds codec sysclk + MICB2 from
boot — invisible to
top, real on the battery
A phone that is "off" should approach zero. Nothing on the device today even attempts that.
Re-measured 2026-08-04 — the biggest spinner is now OURS, and it is the sensors
41 minutes uptime, locked, screen off, viewtop session:
23.3% iio-sensor-proxy 9m35s of CPU in 41m of uptime
9.3% monitor-sensor --proximity --light --accel (parent: souveraine-sensord)
3-4% dbus-broker carrying the traffic between the two
~36% of one core, permanently, on a phone doing nothing. Dies 44–48 °C, battery 41 °C, and the SoC is nowhere near a trip point — the shallowest passive trip on this device is the GPU's at 85 °C. So this heat is not a thermal-policy problem, it is a spinning userspace, and no governor will touch it.
The cause is architectural, not a bug in either program. souveraine-sensord
spawns monitor-sensor --proximity --light --accel and holds it open forever.
Claiming the accelerometer is what makes iio-sensor-proxy poll continuously —
proximity and light are cheap and event-shaped, accel is polled to derive
orientation. And per DEVICE-STATE-MACHINE.md §4, accel.moving is evidence
the machine deliberately stopped branching on: "motion cannot tell a pocket
from an ear from a hand". We are burning a third of a core to keep producing a
reading no rule consumes.
The prior art is the reason the SLPI exists. Android's sensor HAL batches:
the sensor DSP accumulates samples into a hardware FIFO and wakes the AP once
per batch (batch(sensor, sampling_period, max_report_latency)), with wake-up
and non-wake-up variants so a screen-off phone is not woken per sample. Polling
from userspace on the application processor, through D-Bus, defeats the entire
point of having a sensor DSP — and this device has one (TASK-36, TASK-34).
Three questions to settle, in this order, before any freeze work:
-
Does anything still need the accelerometer claimed? — ANSWERED AND DONE 2026-08-04 (
6b67512). Measured on the phone, idle, screen on, same session, one flag apart:monitor-sensorflagsiio-sensor-proxy --proximity --light --accel15.3% --proximity --light1.1% Fourteen points of a core, continuously, for the reading §4 weights least (+0.3) and §9 calls a weak signal. The claim is what makes iio-sensor-proxy poll; nothing else here does.
monitor-sensoritself measured 0.8% and sensord 0.1% — the subprocess was never the cost, the claim was.The accelerometer source is dropped from
SOURCESinsrc/bin/souveraine-sensord.rs, along withparse_accel,accel_movingandMOTION_WINDOW— all three existed only to turn orientation into a motion edge. The 2026-07-27 stuck-Moving(true)finding is kept in comment because whatever reports motion next owes a decay too.This is not "accel is unwanted": it is that a subprocess holding a continuous claim is the wrong way to get it. See (2).
-
If it is wanted, does it go through the SLPI with batching rather than iio-sensor-proxy polling? That is TASK-36's host, and it turns a continuous poll into an interrupt per batch.
-
Is
monitor-sensorthe right transport at all, or does sensord read the IIO character device with a buffer and a trigger directly? A subprocess and a D-Bus round trip per reading is three context switches for one number.
This is the before number for this task now: 36% of a core and 45 °C at idle.
Shape of the thing
One coordinator (substrate side, likely a sessiond sibling or sessiond itself — it already owns lock state, which IS the idle signal) driving three tiers:
- Screen-off, tethered to lock. Lock → after a grace period,
freeze the app tier:
systemctl --user freeze app-*.scope(cgroup freezer; requires app launches to land in per-app scopes — they may already via uwsm/exec rules, verify). The shell, sessiond, modem stack, and the notification path stay unfrozen. Unlock → thaw everything, instantly. - Doze. After N min locked: stop the polling herd (culver first — find its idle loop regardless; 10% CPU while doing nothing is a bug on its own), drop Wi-Fi to power-save, let UPower's percentage polling coast. The lockscreen glance data (media, agent, notify cards) must still update on wake — cards read on unlock, they don't need background freshness.
- Suspend + scheduled wakeups — the end state. s2idle with RTC
alarms (
CLOCK_BOOTTIME_ALARMtimers in the coordinator, not rtcwake hacks): wake every M min → thaw the network fetchers for a bounded window (check Matrix/mail/notifications) → banner/lock-card anything that landed (NotifyEvents seam already exists) → re-freeze, re-suspend. Calls/SMS never wait for a poll window: modem IRQs are hardware wake sources. Blocked-by: TASK-09 FTS suspend race (interim-fixed, kernel-side) and the TASK-07 unpin (a pinned codec clock across suspend is either a wake veto or a lie).
Design questions to settle before code
- Freeze unit granularity: per-app scopes vs one
apps.slice. A slice freeze is one command and no bookkeeping; per-app lets Chatty stay live for SMS-adjacent work. Likely: freezeapps.slice, keep an allowlist slice (apps-wake.slice) for the few that may run locked. - Who is the notification fetcher during doze? If everything that polls is frozen, the wake window must thaw → settle → fetch → freeze; the coordinator needs a "fetch complete" signal, not a fixed sleep.
- culver: freeze it like any app, but ALSO find the spin — freezing a busy-loop just defers the heat to unlock time.
- Wi-Fi during suspend: WoWLAN on ath10k_snoc is unproven on this device; assume packets do NOT wake us, only RTC + modem do. Design for that and be pleasantly surprised otherwise.
- Interaction with charging: plugged-in can skip tiers 2–3 (it's the
desk-clock case; also the only time
grim/remote debugging works).
Acceptance
Locked overnight on battery: no app-tier CPU time accumulated, wake windows visible in the coordinator's log at the configured cadence, an SMS arriving mid-doze rings within seconds, and morning battery drop measurably better than the current baseline (capture tonight's number first — it is the before).
Connects to
TASK-08 device-state-manager (this IS its idle half — 08 keeps the profile/governor side), TASK-09 (suspend blocker), TASK-07's codec pin, NotifyEvents/LockNotifyCard (the landing surface for wake-window arrivals).