ADR-0002: Gamma Belongs to the Firmware, Not the Wire
| Status | Accepted |
| Date | 2026-09-07 |
| Scope | Which layer makes which color decision between a cover image and the strip. |
| Backfilled | Decided with the first vertical and argued in the project READMEs and Boundaries; written up here on 2026-10-10. |
| Corrected | 2026-10-13: the consequence about OKLab said the conversion is implemented three times, in Python, Go and C. It is in two, and never was in the firmware. The decision is unchanged. |
| Related | ADR-0001 |
Context
A color passes through three programs on its way from a sleeve to a strip:
palette extracts it, lightd turns swatches into a scene, and the firmware
drives the LEDs. Several corrections have to happen somewhere along the way —
rejecting dull swatches, boosting chroma, mapping into the strip’s gamut, and
gamma, because WS2812 output is linear in duty cycle and perception is not.
Options considered
Correct everything in lightd. One place, easy to tune. But the right
gamma curve is a property of one driver and one diffuser, so lightd would
need to know which hardware each stand has, and the colors on the wire would
no longer be the colors intended.
Correct everything in palette. It already does color math. But then it
could not be tuned against a folder of JPEGs with no hardware in mind.
Split by what each layer knows.
Decision
Each layer owns the decisions it has the knowledge for, and is deliberately ignorant of the layer below:
paletteknows nothing about music, MQTT or LEDs. Image bytes in, perceptual color data out. No role assignment, no LED correction.lightdowns the hardware-facing color choices that are the same for any strip: rejecting low-chroma swatches, chroma boost in OKLab, and gamut mapping by walking chroma down, never by clipping channels.- The firmware owns gamma.
Consequences
palettecan be developed and tested with no infrastructure at all.- What
mosquitto_subshows is the color actually intended. - A stand with a different strip or diffuser needs only its own curve.
paletteandlightdboth work in OKLab, a color space built to match what a person sees, because the questions they ask (which colors a sleeve is made of, whether a swatch is a color or a gray, how to make one more vivid without shifting its hue) have no good answer in red, green and blue. Each carries its own copy of the conversion, in Python and in Go, so that neither needs the other to build or run. A test inlightdholds its copy to values taken frompalette’s; a change to the math in one is a change to the other and to those values. The firmware has no copy: it is sent colors already chosen and mixes them as they are (Layer Boundaries).- New knowledge has to be placed deliberately. Track and album metadata, for instance, belong to none of these three layers (ADR-0006, ADR-0009).