Watch
1
0
Fork
You've already forked SouveraineOS
0

device-state: §12 — where ambient light enters, and why it is an Action not a daemon

This commit is contained in:
Fimeg 2026-07-26 18:29:57 -04:00
commit 736567c0d4

View file

@ -821,3 +821,77 @@ verify across a rotation. Proven on the device 2026-07-26 — 14 entries verify,
and editing one byte of entry 2 reports `seq 1 hash does not cover its body`
and exits 1. The same contract reads `SessionAudit.qml`'s chain, which has
never been checked either.
---
## 12. Ambient light — where it belongs in the chain (2026-07-26)
Light is already half-modelled: `SensorSource::Light` exists, `light_changing`
carries +0.2 in §4's arithmetic, and §10 tracks its health. It has no reporter,
so it sits at `Unknown` forever. Adding auto-brightness means answering two
separate questions, and conflating them is how this goes wrong.
### Two consumers, one input
| | question | already exists |
|---|---|---|
| evidence | is the light *changing*? (someone is here) | yes — §4, unfed |
| actuation | how bright *should* the panel be? | no |
A change-detector cannot drive brightness and an absolute lux reading is weak
evidence of presence. Both come off the same sensor; neither substitutes.
### It must be an Action, not a daemon
The tempting shape is a small auto-brightness service reading lux and calling
`brightnessctl`. That is an **eighth blind actor** and §1 already says what
happens: sessiond owns the panel's brightness today — `Dim` captures the
current value, `Restore` puts it back, and the code comment on that capture
records what it cost to get right. A second writer makes the captured value a
lie the moment it writes, and the failure looks exactly like the one already
recorded there: "a tap-to-dismiss came back at a different level than it
started."
So: lux enters through `sensor_input` like proximity, and auto-brightness
leaves as an `Action` computed in `tick()`, executed by the same executor
table that owns `Dim`/`Restore`. One writer, one trail.
### The interaction that has to be stated
`Dim` is a deliberate departure from the correct brightness, so auto-brightness
must be **suspended between `Dim` and `Restore`**, not merely rate-limited.
Otherwise the dim is fought by the ambient controller, and `Restore` puts back
a value auto-brightness has since superseded.
The rule: auto-brightness proposes only while the panel is undimmed and the
device is not `Locked`. On `Restore` it re-evaluates from current lux rather
than replaying the captured number — the capture exists to undo *our* dim, not
to pin the panel to a stale room.
### Hysteresis, for the same reason proximity has it
§9.5's lesson applies directly: an unsmoothed sensor driving an actuator
produces exactly the flapping proximity produced. Lux is continuous and noisy —
a hand passing over the sensor is a 1-second event. Ramp, do not step, and
require a sustained delta before moving at all. The numbers should come off the
trail the way `PROXIMITY_NEAR_DEBOUNCE` did, not out of this document.
### The reporter is the same open question as proximity's
`blueline-proximity-lock` is a shell script on a `sleep 30` loop feeding
`sensor_input`, and §10's heartbeat requirement is implemented inside it. A
light reporter needs the identical contract — heartbeat inside
`SOURCE_DOWN_AFTER`, seed from a startup probe — which is the second copy of
that logic and the argument for one reporter process serving every
iio-sensor-proxy source instead of one script per sensor. Decide that before
writing the second script, not after the third.
### Open
- Manual override: a user who sets brightness by hand has stated a preference.
Auto-brightness must yield to it, and for how long is a policy decision
nobody has taken. Android uses the manual value as a bias on the curve
rather than a suspension; that is probably right here too.
- Whether lux belongs in the confidence table at all once it drives an
actuator, or whether `light_changing` should be derived from the same
smoothed signal auto-brightness uses rather than a second raw one.