nx_colorsci_cat_gate.nx
buildroot/runtime/nx_colorsci_cat_gate.nx
about
nx_colorsci_cat_gate.nx -- THE GATE FOR CHROMATIC ADAPTATION in the colour-science ruler, 2026-09-03.
WHY THIS EXISTS. The ICC PCS is D50 by the spec and real captures are made under other illuminants, most
often D65. The profile path in nx_colorsci_lib applies matrix plus TRC only, so before this rung a colour
measured under a non-PCS white carried a white-point error into every downstream gamut and CIEDE2000
judgement. The error never announced itself: every number stayed in range and merely read slightly wrong.
cs_cat_* closes that, and this gate is what makes the closure evidence rather than a claim.
SUBJECT: cs_cat_cone, cs_cat_build, cs_cat_apply and cs_cat_xyz in-process (the lib has no main).
Teeth, in order:
T1 Bradford D65 to D50 builds at all.
T2 KAT: all NINE coefficients against the published Bradford D65-to-D50 matrix. A mistyped cone matrix
or a transposed multiply cannot pass nine independent coefficients.
T3 THE DEFINING INVARIANT: the transform carries the SOURCE white exactly onto the DESTINATION white.
This one needs no external table -- it is what chromatic adaptation MEANS, so it holds even if every
published digit above were mistyped, and the two teeth therefore fail independently.
T4 adapting a white to ITSELF is the identity matrix (the diagonal gain degenerates to one).
T5 round trip: D65 to D50 and back returns the probe.
T6 ANTI-VACUITY NEG-CONTROL: CAT02 must NOT equal Bradford. Without this a cs_cat_cone that ignored its
argument and always wrote Bradford would pass every other tooth on this page.
T7 the invariant of T3 holds for CAT02 too, so T6 cannot pass by CAT02 being merely wrong.
T8 NEG-CONTROL: an unknown transform id REFUSES (returns 0) instead of defaulting to Bradford.
T9 NEG-CONTROL: a zero source white REFUSES instead of dividing by zero or emitting an identity.
Tolerance is the half-ulp of the published seven-decimal matrix widened for Q30 rounding; the coefficients
of the two transforms differ by ~1e-2, two orders above it, so T6 cannot pass on rounding.
license_tier: ORIGINAL No hw writes (Rule 26). expect_exit: 0
dependencies 3 imports · 0 importers
imports: nx_syscalls.nxnx_gate_verdict.nxnx_colorsci_lib.nx
imported by: nobody (leaf or entry point)
call flow from main pre-order; caps 40 nodes / depth 6 declared; ↻ = already shown
structs
| none |
consts
| 31 | const CGC_SLOT: i64 = 8 |
| 32 | const CGC_M: i64 = 9 |
| 33 | const CGC_V: i64 = 3 |
| 34 | const CGC_MICRO: i64 = 1000000 |
| 35 | const CGC_TOL_MICRO: i64 = 500 |
| 36 | const CGC_RT_TOL_MICRO: i64 = 500 |
| 38 | const CGC_D65_X: i64 = 950470 |
| 39 | const CGC_D65_Y: i64 = 1000000 |
| 40 | const CGC_D65_Z: i64 = 1088830 |
| 41 | const CGC_D50_X: i64 = 964220 |
| 42 | const CGC_D50_Y: i64 = 1000000 |
| 43 | const CGC_D50_Z: i64 = 825210 |
| 45 | const CGC_KAT_0: i64 = 1047811 |
| 46 | const CGC_KAT_1: i64 = 22887 |
| 47 | const CGC_KAT_2: i64 = 0 - 50127 |
| 48 | const CGC_KAT_3: i64 = 29542 |
| 49 | const CGC_KAT_4: i64 = 990484 |
| 50 | const CGC_KAT_5: i64 = 0 - 17049 |
| 51 | const CGC_KAT_6: i64 = 0 - 9235 |
| 52 | const CGC_KAT_7: i64 = 15044 |
| 53 | const CGC_KAT_8: i64 = 752132 |
| 54 | const CGC_BAD_ID: i64 = 99 |
| 55 | const CGC_PROBE_X: i64 = 400000 |
| 56 | const CGC_PROBE_Y: i64 = 350000 |
| 57 | const CGC_PROBE_Z: i64 = 300000 |
functions
| 59 | func cgc_q(micro: i64) -> i64 { return fq_div(fq_from_int(micro), fq_from_int(CGC_MICRO)) } |
| 60 | func cgc_abs(v: i64) -> i64 { if v < 0 { return 0 - v } return v } |
| 61 | func cgc_within(q: i64, micro: i64, tol: i64) -> i64 |
| 66 | func main(argc: i64, argv: *i64) -> i64 |