fix(color): adversarial design review of the profiled pipeline contracts (#4034)
Context
Adversarial design review of #4032 and its complete implementation complex: frozen decision record #4033, authoritative tracker #4034, and phases #4035–#4044. This is a follow-up decision/review issue under the tracker's revision rule, not a proposal to restart the original discussion.
Reviewed against FastLED master 9de01d3547b5c6864bfd2227b09e0dedd96c07f0 (also the local checkout), current issue bodies/comments, the canonical ledmapper contract, and linked implementation PRs on 2026-09-05. Two rounds: reconstruct decisions and implementation status; then challenge contracts against actual call paths and counterexamples. Findings below distinguish current code defects from future design gaps. Source tracing and arithmetic checks were performed; no C++ tests, hardware measurements, or implementation changes were performed for this review.
Actual program status
All ten phases remain OPEN. Timeline inspection found a linked implementation PR only for P4: FastLED #4045, with ledmapper #508, merged August 28. These deliberately implement carry/validate only: format sizing, metadata resolution, producer validation, and diagnostics. They do not apply profiles during playback. No color-profile schema, generated profile artifacts, or measurement reports were found in the inspected FastLED/datasheets main tree. This is not evidence that all unlinked work is absent.
| Work | Status / dependency |
|---|---|
| P1 #4035; P2 #4036 | Research/schema and API foundations; no linked implementation found |
| P3 #4037 | Generator/ingestion; depends on P1/P2 |
| P4 #4038 | Partial metadata implementation landed; rendering and release integration remain |
| P5 #4039 | Reference/golden vectors; no linked implementation found |
| P6 #4040; P7 #4041; P8 #4042 | Core, gamut/device solve, encoders; depend on the preceding foundations |
| P9 #4043 | Fixed-point/footprint contracts still require proof |
| P10 #4044 | Hardware and integration closure remains mandatory |
The known WASM release cascade is still incomplete: FastLED pins gfx-v0.1.1, and the latest ledmapper release remains that July release, predating rgb16_linear. This is existing tracked work, not a new discovery or a reason to declare P4 complete.
Proposal
Resolve the following findings in a decision addendum, assign their tests to the existing phases, and fix the independently actionable reader defect first. HIGH means a broken guarantee or a contract that should be settled before dependent implementation; MEDIUM means a required boundary specification. These are review priorities, not claims of production incidents.
R1 — HIGH: unknown-format rejection is false for the legacy video reader
Current code defect; revises C6; owners P4/P10. C6 relies on old readers rejecting the new pixel format. However, PixelStream::begin recognizes only FLED v1/RGB8. Unsupported format/version or rejected envelope bounds rewind to byte zero and succeed as headerless RGB. A seekable rgb16_linear file with sufficient bytes is therefore treated as raw data: readPixel() starts with ASCII FLE, or RGB (70,76,69). This is a third relevant reader path beyond the C++ bundle parser and WASM package described in the routing docs.
Resolution: distinguish absent magic from recognized-but-invalid/unsupported FLED. Preserve raw fallback only for non-FLED input; fail with a structured reason after recognizing FLED magic. Apply the same policy to malformed headers and define non-seekable input handling. Historical binaries cannot be repaired by adding a format enum: narrow C6's compatibility claim and document producer/deployment compatibility.
Evidence required: RED→GREEN fixtures for valid RGB8, valid raw RGB, format 0x05, future format/version, truncated header/envelope, and streamed input. Verify unsupported containers never expose header bytes as LEDs.
R2 — HIGH: response compensation and brightness are ordered incompatibly
Design contradiction; revises C4 and stage ordering; owners P2/P5/P6/P7/P8. P2 permits per-channel nonlinear code-to-light response; P7 includes response compensation in the device solve; P6 then multiplies solved values by brightness and prohibits downstream independent channel rescaling.
Counterexample: F_R(d)=d, F_G(d)=d². A full equal-light target uses compensated drives (1,1). Scaling those drives by 1/4 produces light (1/4,1/16), not (1/4,1/4). Correct dimmed drives are (1/4,1/2). A shared scalar preserves chromaticity only while the quantities being scaled are linear emitter light contributions.
Resolution: type the device-solve output as normalized linear emitter flux; apply brightness/power there; then apply inverse physical response and native encoding. Distinguish this necessary response inversion from prohibited artistic gamma or repeated legacy correction. The electrical estimator must evaluate the resulting drive codes, not equate light with current.
Evidence required: unequal synthetic response curves, neutral ramps, brightness zero/one-half/one-thirty-second/full, and response LUT plateaus/inversion rules. Test reconstructed emitted light as well as intermediate values.
R3 — HIGH: streaming has no defined way to determine the shared power scalar before emission
Architecture gap; revises C2/C4; owners P2/P6/P8. Accurate demand depends on profile conversion, RGBW allocation, response inversion, and native current settings across all channels. The current show ordering calls the power callback before controller traversal; the estimator reads raw CRGB and returns an eight-bit brightness. A per-pixel transform alone cannot know later channels' solved demand before the first is emitted.
Resolution: specify two-pass streaming or a demonstrably conservative estimator. Recommended first contract: prepass over stable source/profile inputs, compute a wide internal scalar, then encode using that scalar; prepass must not advance dithering. For nonlinear electrical models, define how the scalar is solved or bounded. Include managed and legacy channels on the same budget and account for prepass cost in throughput gates.
Evidence required: same source with different emitter profiles/W allocations, controller-order invariance, sub-eight-bit power headroom, and stable inputs/cache versions across both passes.
R4 — HIGH: the power ceiling does not say what is actually bounded
Electrical/quantization contract gap; revises C4/C5; owners P6/P8/P10. Fixed idle consumption cannot be reduced by multiplying LED flux. The current model includes idle power, while aggregate brightness scaling also scales that contribution. Separately, a pre-quantization mean-light budget does not guarantee a per-latched-frame current bound: many sub-LSB emitters can dither upward together.
Resolution: separate fixed baseline from controllable consumption; define behavior when the requested budget is below baseline. Declare whether the limit concerns per-latched-frame average demand or a specified multi-frame mean, and state that internal PWM transients are a separate consideration. Account for rounding/dither headroom or coordinate allocation to honor the chosen bound. Do not promise electrical safety from an unspecified average.
Evidence required: brightness zero, budget below idle, and many synchronized low-code pixels across channels, comparing final encoded demand against the declared bound.
R5 — HIGH: rgb16 media has no typed route into the normative pipeline
Cross-phase interface gap; amends A4/B8/C1/C2; owners P2/P4/P6. P4 introduces six-byte rgb16_linear; P6 describes only RGB8 + profile -> u16. The existing video draw API, frame reader, and channel configuration use CRGB/RGB8. Supporting a container enum does not preserve its low bits through that interface.
Resolution: define a typed source view/stream carrying precision, stride, resolved source profile, and lifetime into the managed path. Linear16 input must bypass RGB8 conversion. Decide precedence between file metadata and channel/default source settings, and invalidate caches when a channel switches files or returns to generated effects. B8's decode-before-overlay promise also needs a wide composition boundary or an explicit v1 rejection; decoding only inside show() is too late for an earlier CRGB overlay. Ordinary same-space encoded interpolation is not itself a violation of B8.
Evidence required: adjacent values such as 0x1200 and 0x1201 survive actual Video→Channel→native16 identity output; successive files with different source profiles rebind correctly; unsupported mixed-space overlays fail explicitly.
R6 — HIGH: custom profiles have shape validation but no numerical admission contract
Validation/precision gap; amends B2 and profile schema; owners P1/P2/P4/P5/P9. readXy/readCustomPrimaries check pair shape and numeric conversion, not usable chromaticity geometry or conditioning. Collinear RGB, zero y, and extreme coefficients can pass declaration resolution. Existing matrix construction is not an admission proof for the proposed bounded fixed-point domain. Its cache builder can also treat source-matrix failure as absence of a source space, which must not silently become managed-path behavior.
Resolution: distinguish syntactically valid metadata from a supported, computable transform. Specify finite/range checks, invertibility/conditioning and coefficient bounds, positive usable emitter capacities, and valid response LUTs before installation. Decide explicitly whether imaginary source primaries are supported rather than banning them accidentally. Near-singular transforms need deterministic rejection or a separately bounded path, not arbitrary clamping that destroys the accuracy promise.
Evidence required: degenerate and near-degenerate primaries, zero/negative/overflowing values, non-finite API-built profiles, invalid response curves, and matrices at the representable boundary; no silent native-drive fallback, NaNs, or fixed-point overflow.
R7 — HIGH: Bradford-to-device-white and OKLCh do not yet share a reference-white contract
Color-science interface gap; amends A1/A3/B7; owners P1/P5/P7. The pipeline adapts source XYZ to profile white, then uses OKLCh. Standard Oklab's XYZ input assumes D65 and white Y=1, as specified by its author's definition. Feeding arbitrary warm/profile-white XYZ directly into those matrices does not make the target neutral axis zero-chroma. Naming Bradford and OKLCh independently does not settle that interface.
Resolution: define the mapping-space white and explicit transforms into/out of it, applying them consistently to targets and the emitter gamut. Distinguish physical all-emitter-full output, selected rendering white, target-white override, and maximum achievable neutral luminance—particularly for RGBWW, where these are not automatically the same. Define the exact luminance-versus-chroma objective and tie-breaks used to generate P5 goldens; naming a space alone is not an algorithm.
Evidence required: D65 and warm-white profiles, neutral ramps, RGBWW allocations, and target-white overrides preserve the selected neutral axis and satisfy the same normalization in reference and embedded paths.
R8 — MEDIUM: fidelity budgets omit the quantization and presentation-time measurement boundary
Acceptance gap; amends A1/B3/C5; owners P5/P6/P8/P10. Specify whether comparison is pre-quantization arithmetic, against a reference with identical native quantization, or against ideal emitted light. These are different claims. For identity linear16 input 1/65535, nearest 8-bit PWM output is zero: 100% relative luminance error. Matching that target by alternating native codes 0/1 needs one code-1 frame per 257 equal-duration frames—about 4.28 seconds at 60 Hz. This arithmetic is not a measured hardware claim, but proves that unrestricted dark-level fidelity and bounded flicker cannot both be inferred from wider arithmetic.
Actual display timing matters: channel encoding can drop a frame before enqueue. Advancing dither on attempted rather than presented frames can bias output.
Resolution: separate numerical fidelity from native-output/profile accuracy; define the luminance-error denominator and black floor, cadence, observation window, and unsupported low-light region. Evaluate time-weighted emitted XYZ before computing perceptual error; define dither behavior under skipped frames and variable dwell.
Evidence required: low-code ramps with regular/irregular presentation, dropped submissions, and explicit no-dither tests. Report fidelity/flicker tradeoffs instead of letting a quantized reference conceal optical error.
R9 — MEDIUM: runtime profile pointers and zero-state TINY promises need a compatible API subset
Ownership/tier gap; amends C8.4/B11; owners P2/P9. P2 stores a profile pointer and accepts a user-built inline struct; P9 requires no additional per-controller state even for minimal managed operation. Neither specifies profile lifetime, immutability, cache invalidation, or how distinct runtime profiles remain associated with multiple channels without storage. This is not an observed async memory bug: current channel encoding materializes owned wire bytes before enqueue.
Resolution: specify ownership for user profiles and LUTs and reject temporaries or copy/own them safely. Define a compile-time/static-profile TINY subset or explicitly budget runtime storage; do not postpone an ABI constraint until P9 after P2 ships. Include existing ChannelOptions::mGamma in the managed-mode compatibility policy, alongside correction/temperature/dither, so a legacy default cannot add unintended shaping.
Evidence required: profile/lut lifetime and rebind tests; two different channels; sizeof/symbol/bloat checks for disabled, static-minimal, and runtime-managed configurations.
R10 — MEDIUM: the landed media policy and frozen tracker disagree
Contract drift; revises C6/headless policy; owner P4. The landed mirror, matching the canonical change, permits fallback on unresolvable advisory RGB metadata. #4034 requires unknown-profile rejection/dark output and best effort only through explicit opt-in. The format table also supplies an absent-metadata default for rgb16_linear, while forward-compatibility prose calls it meaningless without its declaration; the resolver/tests accept absence. Implementers following different authoritative documents will produce different players.
Resolution: retain strict-by-default managed playback with explicit best-effort opt-in unless a named decision changes it; distinguish parsing a bundle from admitting it for rendering. Keep the already implemented linear16 default tuple if absence is intended, and rewrite the contradictory prose. Document one format×declaration×consumer-mode decision table shared with ledmapper. On playback rejection, explicitly transmit/clear dark output; merely skipping show() can leave the preceding LED frame latched.
Evidence required: identical contract fixtures in both repos covering absence, partial keys, unknown values, strict/best-effort mode, and invalid input after a previously lit frame. Include field and rejected value in consumer diagnostics.
D1 — Agreed follow-up: separate container pixel formats from generic storage
User-approved design clarification for R5; owners P2/P4/P6. Keep fl::fled::PixelFormat as the enum owned by the .fled data structure and its stable serialized identifiers. Map it explicitly to a separate generic FastLED storage descriptor (fl::PixelFormat, proposed public API), usable by frame buffers, video sources, channels, and encoders.
| Container format | Generic storage | Color semantics retained separately |
|---|---|---|
fl::fled::PixelFormat::Rgb8 (0x00) |
fl::PixelFormat::Rgb8 |
Resolved source profile; absent metadata defaults to BT.709 primaries, sRGB transfer, RGB matrix, full range |
fl::fled::PixelFormat::Rgb16Linear (0x05) |
fl::PixelFormat::Rgb16 |
Linear transfer, full range, resolved primaries/white; payload components are 16-bit little-endian |
Generic enum names above describe the intended API, not an assertion that it has already landed. Matching underlying values where practical is welcome, but numeric equality is an implementation convenience, not a public compatibility guarantee. Callers use checked, explicit mapping rather than casting between enums. .fled wire assignments remain stable even if generic FastLED formats evolve.
The conversion must retain the resolved source profile and byte-order/layout information alongside generic storage. Mapping Rgb16Linear to Rgb16 must not discard its mandatory linear semantics or reinterpret little-endian payload bytes as native-endian words without conversion. Reverse serialization validates storage plus profile against the selected container format; generic Rgb16 alone does not establish that samples are linear.
resolveVideoColor() remains in fl::fled because it interprets that container's schema/defaults and should accept fl::fled::PixelFormat directly through a typed overload or signature. Raw-byte validation belongs at the parse boundary. Expose the container enum through a supported public header so sketches need neither magic numbers nor a detail/ include:
// Intended public API after implementation:
auto status = fl::fled::resolveVideoColor(
envelope,
fl::fled::PixelFormat::Rgb8,
&color
);Evidence required: typed call coverage; stable wire IDs; checked rejection of unknown/unmappable formats; storage/profile/endianness-preserving round trips; and tests that explicit mapping remains correct without relying on cross-enum numeric equality.
Acceptance criteria
- Publish an addendum resolving R1–R10 with amended decision IDs and phase ownership; preserve frozen history and link the addendum from the implementation tracker.
- For code fixes, capture focused RED→GREEN evidence before implementation; begin with R1's existing-reader defect.
- Settle R2/R3/R5/R6/R7 interfaces before freezing P2/P5/P6 APIs or golden vectors; add explicit cross-phase tests, not only isolated math tests.
- Implement D1's distinct container/generic format types and checked mappings, expose a public typed
resolveVideoColor()call, and preserve source-color and byte-order semantics across conversion. - Define native-output measurement and electrical bounds,
Source: FastLED/FastLED