Color pipeline: final implementation tracker
Status
Implementation addendum: cross-phase contracts, merged in #4157 following review #4156. It clarifies stage ordering, source/format boundaries (including the separate container and generic enums), power/presentation semantics, and tier/ownership requirements. These contracts supplement the digest below; implementation phases and the measured hardware closure gate remain open.
This is the final implementation tracker for the FastLED color pipeline. All design deliberation is closed. #4032 (spec) and #4033 (decision record) are frozen historical references — do not reopen their questions; every open item was resolved and the resolutions are digested below. Implementation work happens in the phase sub-issues attached to this issue. Any decision that proves wrong under measurement gets a follow-up decision issue referencing the ID it revises — not a silent divergence.
The pipeline (normative)
RGB8 + declared source profile
-> decode to u16 linear (one semantic conversion: normalize + inverse transfer)
-> signed s16.16-class accumulators: source primaries -> XYZ (Bradford CAT) -> gamut map (OKLCh objective, matrix/LUT execution)
-> device solve against per-channel EmitterProfile (RGBW: white-preferred allocation)
-> brightness x power: one multiplicative linear scalar stage, chromaticity-preserving
-> chipset encoder: single final quantization into native wire format (per-chip 5-bit semantics, pipeline dithering)Streaming execution inside show() via PixelController — no RGB16 framebuffer. Legacy path byte-identical when color management is disabled. Tree-shakes to zero when unused.
Decision digest (complete — the "no more back and forth" list)
Accuracy and reference (A1, A3, B7, B12): Implementation fidelity: max ΔE2000 ≤ 0.5 and luminance ≤ 0.5% vs float64 reference at identity brightness, ≤ 1.0 ΔE2000 at 1/32 brightness, inclusive of the brightness/power stage. Profile accuracy: datasheet-derived median ≤ 3.0 / p95 ≤ 6.0; measured median ≤ 1.5 / max ≤ 4.0, at reference temperature. Relative colorimetry, profile white at full drive, dark surround. Bradford is the normative CAT. Gamut mapping: hue-preserving chroma compression in OKLCh computed by a host optimizer; embedded per-pixel path is matrix + clamp/compress (+ optional LUT) — iterative solves (incl. nnls3) never run per-pixel.
Working domain (B2, B3): u16 linear after decode; signed s16.16-class accumulators through XYZ/conversion/gamut stages with defined clamping; bounded RGB16 only after gamut mapping. "Quantize once" is enforced as: error budget vs reference + structural test that no managed-path intermediate is 8-bit. PQ/HLG are reserved names, rejected in v1.
Source semantics (§2, B6, B8): Ordinary buffers default linear; .fled video defaults {bt709, srgb, rgb, full} when metadata is absent. Legacy .fled files intentionally change appearance when CM is enabled; byte-identical applies only with CM disabled. Effects/palette math is defined in the buffer's declared space; cross-space compositing unsupported in v1; palettes carry no profile.
.fled contract (A4, B4, B5, C6): New pixel_format: rgb16_linear (16-bit LE, linear, full-range; transfer must be linear). Validator rejects transfer ∈ {linear, PQ, HLG} with rgb8, rejects range: limited with rgb8, scopes tuple inheritance to formats with defined default tuples, requires explicit matrix/range for non-RGB formats. video.color is advisory for rgb8 (old parsers ignore it safely); mandatory color semantics gate via new pixel_format values. Headless rejection: defined error code, serial log where available, dark output; best-effort playback only by explicit opt-in flag.
API binding (C1, C4, C5, C8.4, C8.5): Source profile, emitter profile, gamut policy ride per-channel on ChannelOptions, bound via FastLED.add<>() / Channel::create<>(); CFastLED holds only global defaults; no bare fl::set_* free functions. Profile mode and legacy setCorrection/setTemperature are mutually exclusive per channel (one-time warning on transition). setBrightness(b) = linear flux scalar b/255 (photometrically legacy-compatible); the power limiter composes multiplicatively at the same single chromaticity-preserving stage. Pipeline dithering and BINARY_DITHER are mutually exclusive per channel. Driver-only chipsets with no profile fall back to the legacy path with: one-time warning, queryable isColorManaged(), fallback flag in channel events/telemetry, and opt-in global strict mode that upgrades fallback to a hard error.
Identity and profiles (A2, C8): Canonical identity = part + package variant + W-channel CCT variant + provenance/report ID; production bin is metadata. Profiles are append-only. Hybrid IDs: canonical string ID in the JSON artifact (FastLED/datasheets, measured-profiles/, versioned schema with provenance fields); generator emits C++ symbols embedding it. Bare enum aliases float to best-available; versioned symbols pin. The enum template parameter is sugar resolving to a constexpr EmitterProfile stored in ChannelOptions; the same slot accepts a user-built inline struct (per-fixture calibration is first-class in v1). Only referenced profiles link; no JSON parser in firmware. Two registries: source-color names owned by the canonical ledmapper spec, emitter IDs by the datasheets generator.
Ingestion (C9): Ingest is a manual, PR-reviewed event (check/ingest modes). Generated files carry provenance headers (artifact ID, hash, datasheets commit, date). A standalone informational freshness checker lives outside bash lint: network-dependent, always exit 0, --json, degrades gracefully offline; run by a scheduled workflow that updates one tracking issue. Agent docs must mark its findings as status-not-errors (exempt from the fix-immediately policy); ingest only on explicit maintainer request.
Hardware model (B1, C3, B10): APA102 5-bit field = secondary slow-PWM (joint solve carries a flicker constraint / configurable floor); SK9822 = current gain (held fixed unless the profile carries current-vs-chromaticity data); unknown chips get the conservative treatment. RGBW/RGBWW: deterministic, per-profile-configurable allocation, default white-preferred, W included in the mapper's gamut volume, continuous under animation. Accuracy claims valid at the profile's reference temperature; thermal drift documented, temperature knob deferred.
Measurement gate (A2, B9, C7): Spectroradiometer (≤ ±0.002 xy on narrowband) required for primary/secondary chromaticity; plain colorimeter acceptable only for neutral/luminance tracking. The gate closes via a checked-in measurement report in FastLED/datasheets; CI validates schema + golden vectors only. Re-measurement triggers: new part, bin divergence beyond budget, out-of-budget validation.
Platform tiers (B11, §3): Explicit named phase for fixed-point re-implementation of colorimetric cache build and solve; TINY tier adds no per-controller state and links no float/solver symbols; bloat and throughput budgets recorded for AVR, SAMD51, ESP32, and high-throughput parallel output.
Phases
Dependency-ordered; each phase is a sub-issue and independently mergeable. (Sub-issue links are attached below via GitHub sub-issues.)
| Phase | Scope | Depends on |
|---|---|---|
| P1 (#4035) | Datasheet characterization report + versioned profile artifact schema (datasheets repo) | — |
| P2 (#4036) | Profile types + per-channel binding API in FastLED | — (schema fields co-designed with P1) |
| P3 (#4037) | Generator + ingest/freshness tooling | P1, P2 |
| P4 (#4038) | .fled color metadata: spec sync, parser, validation, rgb16_linear |
— |
| P5 (#4039) | Float64 host reference, golden vectors, RED baseline demonstrations | P2 (type names) |
| P6 (#4040) | Embedded streaming pipeline core: decode → s16.16 transforms → brightness/power | P2, P5 |
| P7 (#4041) | Gamut mapping + device solve (incl. RGBW allocation) | P5, P6 |
| P8 (#4042) | Chipset-aware quantization + dithering + per-chip 5-bit semantics | P6, P7 |
| P9 (#4043) | Fixed-point float-free tier + bloat/throughput budgets | P2, P6, P7 |
| P10 (#4044) | Hardware measurement gate, measured profiles, legacy/integration hardening (program closure) | all |
P10 is the program-level closure gate: per #4032, static datasheet analysis alone cannot close the implementation phase — measured hardware evidence is required.
Acceptance-criteria ownership (all 16 from #4032, as amended by #4033)
| #4032 criterion | Owner |
|---|---|
| RED→GREEN baseline: profile/transfer omission, RGB8 quantization loss, independent-gamma hue regression | P5 |
| Research report: datasheet catalog, driver-only parts, profile clustering | P1 |
Accuracy of TypicalLEDStrip / Typical8mmPixel / UncorrectedColor quantified |
P1 |
| Instrument measurement of WS2812B/SK6812-class + one high-bit-depth strip | P10 |
| Source-profile types: linear/custom primaries + named spaces, decoupled from chipset | P2 |
.fled spec/producer/mirror/parser/playback honor video.color; defaults, rejection |
P4 |
| Float64 host reference golden vectors | P5 |
| Embedded implementation within documented ΔE/luminance budget vs reference | P6 |
| Normative path: no intermediate RGB8 buffer, single final quantization (per B3 restatement) | P6 |
| In-gamut chromaticity preserved; neutrals neutral; out-of-gamut continuous | P7 |
| WS2812-class gamma/value/HUE16 + temporal dithering tests with error metrics | P8 |
| APA102/SK9822 HD + native 16-bit encoders consume wide output; monotonic; low-light resolution | P8 |
| Brightness/power limiting chromaticity-preserving; separate electrical model | P6 |
| Legacy output byte-identical with CM disabled | P10 |
| TINY tier: no per-controller state, no float/solver symbols; bloat/throughput budgets | P9 |
| Full C++ tests + representative hardware validation green | P10 |
No criterion is orphaned; none were dropped. The #4033 acceptance item "edit #4032's body to incorporate amendments" remains assigned to the maintainer and is not absorbed by this tracker.
References
- #4032 — spec (frozen)
- #4033 — decision record A1–A4, B1–B12, C1–C9 (frozen)
- Prior art: #3242, #3255, #3422, #2705, #909, #697
Source: FastLED/FastLED