#4034·FastLED

Color pipeline: final implementation tracker

Author: zackeesCreated Aug 25, 2026Updated Sep 19, 2026

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