Stop the late-night dim compounding across nights (#110) #111
No reviewers
Labels
No labels
area/ai
area/backend
area/frontend
area/infra
area/scheduler
area/wled
good-first-issue
priority/high
priority/low
priority/medium
type/bug
type/chore
type/ci-cd
type/docs
type/feature
type/qa
v1.0.0
v1.1.0
v1.2.0
v2.0.0
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
rbrooks/Iris-WLED!111
Loading…
Reference in a new issue
No description provided.
Delete branch "fix/dim-compounding"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Fixes #110. Observed on the dev deployment: brightness decayed 128 → 51 → 20 → 8 over three consecutive no-event nights.
Cause
run_dim_jobdimmed relative to the live controller value and never checked whether the lights were on. On a no-event evening the on job pushes{"on": false}, which leavesbriuntouched — so nothing reset the baseline and each night's dim multiplied the previous night's result.Two changes
Skip when the controller is off. There is nothing lit to dim, and the job now logs
skippedrather than reportingsuccessfor work with no effect. This alone stops the observed decay.Dim from the scheme, not the live value.
dim_statenow takes the baseline as an argument instead of reading it out of controller state, which forces the caller to say what it is dimming from:The baseline is the active scheme's
global_brightness, resolved exactly the way the on job resolves it so the two cannot disagree about which scheme is live. That makes the operation idempotent.The spec already had the answer
§5.2: "applies globally and overrides per-scheme brightness values proportionally." So proportional was right all along — the baseline was the part that was wrong.
Why the ambiguity survived
LATE_NIGHT_BRIGHTNESSwas being read two ways in the same codebase:dim_white_statetreats it as a percentage of 255, whiledim_statetreated it as a percentage of current. Both are defensible readings of "brightness 40", which is how they coexisted unnoticed. It is now documented as a percentage of the scheme's brightness in.env.exampleand the spec.A consequence worth noting: nights with no approved scheme fall back to a 255 baseline, so the
dim_whiteambient default now lands on the level it was already showing rather than being dimmed a second time — it previously came on at 40% and was then dimmed to 16% within the same night.Verification
ruff,ruff format,mypyclean; all seven pre-commit hooks pass.dim_stateapplied twice gives the same answer, andrun_dim_jobagainst an off controller pushes nothing and logsskipped.dim_statetests moved to the new signature.🤖 Generated with Claude Code
Observed on the dev deployment: brightness decayed 128 -> 51 -> 20 -> 8 over three consecutive no-event nights. `run_dim_job` dimmed relative to the live controller value and never checked whether the lights were on. On a no-event evening the on job pushes `{"on": false}`, which leaves `bri` untouched, so nothing reset the baseline and each night's dim multiplied the previous night's result. Two changes: - Skip when the controller is off. There is nothing lit to dim, and the job now logs `skipped` rather than reporting success for work that had no effect. - Dim relative to the active scheme's `global_brightness` instead of the live value, so the operation is idempotent. `dim_state` takes the baseline as an argument rather than reading it from controller state, which forces the caller to be explicit about what is being dimmed from. Spec §5.2 already specified the reduction as proportional -- "overrides per-scheme brightness values proportionally" -- so proportional was right and the baseline was what was wrong. `LATE_NIGHT_BRIGHTNESS` now says in `.env.example` and the spec that it is a percentage of the scheme's brightness, not of full scale. The setting was being read both ways: `dim_white_state` treats it as a percentage of 255 while `dim_state` treated it as a percentage of current, which is how the two interpretations coexisted unnoticed. Nights with no approved scheme fall back to 255 as the baseline, so the `dim_white` ambient default lands on the same level it was already showing instead of being dimmed a second time. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>