code wiki / (root) / nx_colorsci_gate.nx

nx_colorsci_gate.nx source

↩ module page · 424 lines · 26222 B

1// nx_colorsci_gate.nx -- THE GATE FOR THE COLOUR-SCIENCE RULER (nx_colorsci_lib + nx_fixq30_lib), 2026-08-24. 2// 3// SUBJECT: the libs in-process (they have no main; the ruler IS the code under test). Teeth, in order: 4// T1 the DERIVED constants against their published digits -- pi (Machin) and e to 1e-7, and the identities 5// sqrt(2)^2 = 2, atan2(0,-1) = pi, sin(pi/2) = 1, cos(pi/3) = 1/2, cbrt(27) = 3, ln(exp(3)) = 3. A CORDIC 6// whose table was mistyped cannot pass these; a series that stopped early cannot either. 7// T2 CIEDE2000 against Sharma-Wu-Dalal 2005 Table I: ALL 34 pairs (gv_subjects binds the denominator), both 8// directions (the formula is symmetric by construction, and pairs 7/8 exist to prove it), and the table's 9// h_bar_prime column (the arctangent branch handling pairs 9-16 exist to trap). 10// T3 NEG-CONTROL: pair 1 against a WRONG expectation (+0.5 dE00) must FAIL the tolerance -- a tolerance so 11// wide that anything passes would read green on every KAT row and prove nothing. 12// T4 ICC: the mirrored sRGB2014.icc (color.org) parses; rXYZ+gXYZ+bXYZ sum to wtpt within s15Fixed16 13// resolution (an ICC structural invariant, not a typed expectation); white reads L=100 a=b=0; black L=0; 14// EVERY one of the 256 greys reads a=b=0 (the whole population, not a sample); rgb->lab->rgb round-trips 15// within one code over all greys and the eight cube corners. 16// T5 the gamut descriptor: derived from the profile at the conf's sampling; every sample it was built from 17// reads INSIDE; a chroma no display reaches reads OUTSIDE; bite-proven both ways; chroma clipping brings 18// the outside probe inside at the same L and hue. 19// Thresholds: the KAT tolerance is the half-ulp of a four-decimal table plus Q30 headroom; everything else is a 20// structural identity. license_tier: ORIGINAL No hw writes (Rule 26). expect_exit: 0 21import "nx_syscalls.nx" 22import "nx_gate_verdict.nx" 23import "nx_colorsci_lib.nx" 24 25const CG_KAT_PATH: *u8 = "knowledge/colorsci_ciede2000_kat.tsv" 26const CG_PROFILE: *u8 = "knowledge/fetched/cmp_phototwin_srgb2014.b64" // base64 transport of sRGB2014.icc (Google skcms mirror of color.org) 27const CG_CONF: *u8 = "knowledge/colorsci.conf" 28const CG_KAT_ROWS: i64 = 34 // Table I declares 34 pairs; another count is a partial table, refused 29const CG_KAT_FIELDS: i64 = 9 30const CG_F_L1: i64 = 1 31const CG_F_A1: i64 = 2 32const CG_F_B1: i64 = 3 33const CG_F_L2: i64 = 4 34const CG_F_A2: i64 = 5 35const CG_F_B2: i64 = 6 36const CG_F_HBAR: i64 = 7 37const CG_F_DE: i64 = 8 38const CG_DE_TOL_MICRO: i64 = 60 // half-ulp of a 4-decimal table (50) + Q30 arithmetic headroom (10) 39const CG_H_TOL_MICRO: i64 = 1000 // MEASURED 2026-08-24: the table's h' column is NOT four-decimal exact -- pair 21's own printed 40 // a'=4.7596, b=0.5854 give h'=7.0120 by direct arctangent while the table prints 7.0113; so the 41 // column is good to ~1e-3 degree, and that is the bound applied (dE00 itself holds at 6e-5) 42const CG_NANO: i64 = 1000000000 43const CG_PI_NANO: i64 = 3141592654 // pi, published digits (the KAT oracle for the Machin derivation) 44const CG_E_NANO: i64 = 2718281828 // e, published digits 45const CG_NANO_TOL: i64 = 100 // 1e-7: the Q30 series accumulate ~4e-8, doubled 46const CG_THREE: i64 = 3 47const CG_TWO: i64 = 2 48const CG_SQRT_ARG: i64 = 2 49const CG_CBRT_ARG: i64 = 27 50const CG_LN_ARG: i64 = 3 51// MEASURED 2026-08-24 on the mirrored sRGB2014.icc itself: rXYZ+gXYZ+bXYZ = (0.964279, 0.999969, 0.825088) against 52// wtpt (0.964202, 1.000000, 0.824905) -- the canonical profile's own primaries sum to its white point only to 1.8e-4 53// (the Bradford-adapted primaries were rounded independently of the white tag). That discrepancy, not s15Fixed16 54// resolution, is the structural bound; 2^18 Q30 units = 2.4e-4 carries it with headroom and is still 1000x below 55// any colour anyone can see. It propagates through f'(1) = 1/3 and the 200x b-scale to |b| ~ 0.012 on white 56// (measured 0.017), so the neutral-axis tolerance is 0.05 with the same headroom. 57const CG_WHITE_TOL_MICRO: i64 = 50000 58const CG_L_WHITE: i64 = 100 59const CG_S15_SUM_TOL_Q30: i64 = 262144 60const CG_GREYS: i64 = 256 61const CG_CODE_MAX: i64 = 255 62const CG_CODE_TOL: i64 = 1 63const CG_CORNERS: i64 = 8 64const CG_TAB: i64 = 9 65const CG_NL: i64 = 10 66const CG_HASH: i64 = 35 67const CG_ROW_SLOTS: i64 = 16 68const CG_BITE_DE_OFFSET_MICRO: i64 = 500000 // the planted-wrong expectation: +0.5 dE00 69const CG_OUT_L: i64 = 50 70const CG_OUT_A: i64 = 150 // a chroma of 150 at L=50: outside every display-class gamut 71const CG_GREY_MID: i64 = 128 // a mid grey: strictly inside on every channel 72const CG_BOUNDARY_SLACK_PERMIL: i64 = 1 // a cube-face sample sits ON the boundary: 1000 permil plus/minus one unit of Q30 rounding 73const CG_PERMIL: i64 = 1000 74const CG_SLOT: i64 = 8 75const CG_KAT_CAP: i64 = 65536 76 77func cg_nano(x: i64) -> i64 { if x >= 0 { return (x * CG_NANO) >> FQ_SHIFT } return 0 - (((0 - x) * CG_NANO) >> FQ_SHIFT) } 78func cg_abs(v: i64) -> i64 { if v < 0 { return 0 - v } return v } 79// tab field idx of line [s,e) -> box[0]=off box[1]=len; 0 if absent 80func cg_field(buf: *u8, s: i64, e: i64, idx: i64, box: *i64) -> i64 { 81 var p: i64 = s 82 var f: i64 = 0 83 while f < idx { 84 var go: i64 = 1 85 while go == 1 { 86 if p >= e { go = 0 } else { if (buf[p] as i64) == CG_TAB { p = p + 1; go = 0 } else { p = p + 1 } } 87 } 88 f = f + 1 89 } 90 if p >= e { box[0] = 0; box[1] = 0; return 0 } 91 var q: i64 = p 92 var g2: i64 = 1 93 while g2 == 1 { 94 if q >= e { g2 = 0 } else { if (buf[q] as i64) == CG_TAB { g2 = 0 } else { q = q + 1 } } 95 } 96 box[0] = p 97 box[1] = q - p 98 return 1 99} 100func cg_fld_q30(buf: *u8, s: i64, e: i64, idx: i64, box: *i64) -> i64 { 101 cg_field(buf, s, e, idx, box) 102 return fq_from_micro(fq_parse_micro(buf, box[0], box[1])) 103} 104func cg_within(x: i64, y: i64, tol_micro: i64) -> i64 { 105 if cg_abs(fq_to_micro(x - y)) <= tol_micro { return 1 } 106 return 0 107} 108 109func main(argc: i64, argv: *i64) -> i64 { 110 let ctr: *i64 = gv_ctr() 111 gv_head("nx_colorsci gate -- CIELAB, CIEDE2000, ICC matrix/TRC and the gamut descriptor, KAT against the published table" as *u8) 112 let cs: *i64 = cs_ctx() 113 let fq: *i64 = cs_fq(cs) 114 115 // ---- T1: derived constants and identities ---- 116 let pin: i64 = cg_nano(fq[FQ_C_PI]) 117 gv_puts(" [T1] pi_nano=" as *u8); gv_num(pin); gv_puts(" e_nano=" as *u8) 118 let en: i64 = cg_nano(fq_exp(fq, FQ_ONE)) 119 gv_num(en); gv_puts("\n" as *u8) 120 var t: i64 = 0 121 if cg_abs(pin - CG_PI_NANO) <= CG_NANO_TOL { t = 1 } 122 gv_check("pi-derived-by-machin-matches-published-digits-to-1e-7" as *u8, t, ctr) 123 // fq_div DOMAIN GUARD (2026-08-24): 1 / 2^24 with the denominator handed in at 2^54 (out of the 2^47 contract) must come 124 // back as exactly 64 (= 2^30 / 2^24). Before the guard the remainder step wrapped and the answer was garbage. 125 let big_den: i64 = FQ_DIV_MAX_DEN * 128 126 let dq: i64 = fq_div(FQ_ONE, big_den) 127 gv_puts(" fq_div_domain: den=2^54 got=" as *u8); gv_num(dq); gv_puts(" want=64\n" as *u8) 128 gv_check("fq_div-out-of-contract-denominator-reduces-instead-of-wrapping (1/2^24 == 64 in Q30)" as *u8, (dq == 64) as i64, ctr) 129 t = 0 130 if cg_abs(en - CG_E_NANO) <= CG_NANO_TOL { t = 1 } 131 gv_check("e-derived-by-series-matches-published-digits-to-1e-7" as *u8, t, ctr) 132 let s2: i64 = fq_sqrt(fq_from_int(CG_SQRT_ARG)) 133 t = 0 134 if cg_abs(cg_nano(fq_mul(s2, s2) - fq_from_int(CG_SQRT_ARG))) <= CG_NANO_TOL { t = 1 } 135 gv_check("sqrt-two-squared-is-two" as *u8, t, ctr) 136 t = 0 137 if cg_abs(cg_nano(fq_atan2(fq, 0, 0 - FQ_ONE) - fq[FQ_C_PI])) <= CG_NANO_TOL { t = 1 } 138 gv_check("cordic-atan2-of-minus-one-is-pi" as *u8, t, ctr) 139 t = 0 140 if cg_abs(cg_nano(fq_sin(fq, fq[FQ_C_PI] / CG_TWO) - FQ_ONE)) <= CG_NANO_TOL { t = 1 } 141 gv_check("cordic-sin-half-pi-is-one" as *u8, t, ctr) 142 t = 0 143 if cg_abs(cg_nano(fq_cos(fq, fq[FQ_C_PI] / CG_THREE) - FQ_HALF)) <= CG_NANO_TOL { t = 1 } 144 gv_check("cordic-cos-third-pi-is-half" as *u8, t, ctr) 145 t = 0 146 if cg_abs(cg_nano(fq_cbrt(fq, fq_from_int(CG_CBRT_ARG)) - fq_from_int(CG_THREE))) <= CG_NANO_TOL { t = 1 } 147 gv_check("cbrt-27-is-3" as *u8, t, ctr) 148 t = 0 149 if cg_abs(cg_nano(fq_ln(fq, fq_exp(fq, fq_from_int(CG_LN_ARG))) - fq_from_int(CG_LN_ARG))) <= CG_NANO_TOL { t = 1 } 150 gv_check("ln-exp-3-is-3" as *u8, t, ctr) 151 152 // ---- T2: the published table, every row, both directions ---- 153 let lp: *i64 = sys_mmap(CG_SLOT * CG_TWO) as *i64 154 let kb: *u8 = sys_read_file(CG_KAT_PATH, lp) 155 var have_kat: i64 = 0 156 if (kb as i64) != 0 { if lp[0] > 0 { have_kat = 1 } } 157 let box: *i64 = sys_mmap(CG_SLOT * CG_TWO) as *i64 158 let o4: *i64 = sys_mmap(CG_SLOT * CG_ROW_SLOTS) as *i64 159 let o4b: *i64 = sys_mmap(CG_SLOT * CG_ROW_SLOTS) as *i64 160 var rows: i64 = 0 161 var within: i64 = 0 162 var sym: i64 = 0 163 var hue_ok: i64 = 0 164 var worst_pair: i64 = 0 165 var worst_micro: i64 = 0 166 var bite_bad: i64 = 0 167 var bite_good: i64 = 1 168 if have_kat == 1 { 169 let n: i64 = lp[0] 170 var p: i64 = 0 171 while p < n { 172 var e: i64 = p 173 while e < n { if (kb[e] as i64) == CG_NL { break } e = e + 1 } 174 if e > p { if (kb[p] as i64) != CG_HASH { 175 let L1: i64 = cg_fld_q30(kb, p, e, CG_F_L1, box) 176 let a1: i64 = cg_fld_q30(kb, p, e, CG_F_A1, box) 177 let b1: i64 = cg_fld_q30(kb, p, e, CG_F_B1, box) 178 let L2: i64 = cg_fld_q30(kb, p, e, CG_F_L2, box) 179 let a2: i64 = cg_fld_q30(kb, p, e, CG_F_A2, box) 180 let b2: i64 = cg_fld_q30(kb, p, e, CG_F_B2, box) 181 let hb: i64 = cg_fld_q30(kb, p, e, CG_F_HBAR, box) 182 let de: i64 = cg_fld_q30(kb, p, e, CG_F_DE, box) 183 cg_field(kb, p, e, 0, box) 184 let pair: i64 = fq_parse_micro(kb, box[0], box[1]) / FQ_MICRO 185 cs_de2000_ex(cs, L1, a1, b1, L2, a2, b2, o4) 186 cs_de2000_ex(cs, L2, a2, b2, L1, a1, b1, o4b) 187 let dm: i64 = cg_abs(fq_to_micro(o4[0] - de)) 188 if dm > worst_micro { worst_micro = dm; worst_pair = pair } 189 if dm <= CG_DE_TOL_MICRO { within = within + 1 } 190 if cg_abs(fq_to_micro(o4[0] - o4b[0])) <= CG_DE_TOL_MICRO { sym = sym + 1 } 191 if cg_within(o4[1], hb, CG_H_TOL_MICRO) == 1 { hue_ok = hue_ok + 1 } 192 if rows == 0 { 193 // neg-control on the first pair: a planted +0.5 expectation must be rejected, the real one accepted 194 if cg_abs(fq_to_micro(o4[0] - (de + fq_from_micro(CG_BITE_DE_OFFSET_MICRO)))) > CG_DE_TOL_MICRO { bite_bad = 1 } 195 if dm <= CG_DE_TOL_MICRO { bite_good = 0 } 196 } 197 gv_puts(" pair " as *u8); gv_num(pair); gv_puts(" de_micro=" as *u8); gv_num(fq_to_micro(o4[0])) 198 gv_puts(" expected_micro=" as *u8); gv_num(fq_to_micro(de)); gv_puts(" hbar_micro=" as *u8); gv_num(fq_to_micro(o4[1])) 199 gv_puts("\n" as *u8) 200 rows = rows + 1 201 } } 202 p = e + 1 203 } 204 } 205 gv_need("ciede2000 KAT table readable (knowledge/colorsci_ciede2000_kat.tsv)" as *u8, have_kat, ctr) 206 gv_subjects("ciede2000-kat-pairs" as *u8, rows, ctr) 207 gv_puts(" [T2] rows=" as *u8); gv_num(rows); gv_puts(" within=" as *u8); gv_num(within) 208 gv_puts(" symmetric=" as *u8); gv_num(sym); gv_puts(" hue_ok=" as *u8); gv_num(hue_ok) 209 gv_puts(" worst_pair=" as *u8); gv_num(worst_pair); gv_puts(" worst_micro=" as *u8); gv_num(worst_micro); gv_puts("\n" as *u8) 210 t = 0 211 if rows == CG_KAT_ROWS { t = 1 } 212 gv_check("kat-table-carries-all-34-published-pairs" as *u8, t, ctr) 213 t = 0 214 if rows > 0 { if within == rows { t = 1 } } 215 gv_check("de2000-matches-every-published-pair-within-half-ulp" as *u8, t, ctr) 216 t = 0 217 if rows > 0 { if sym == rows { t = 1 } } 218 gv_check("de2000-symmetric-on-every-pair" as *u8, t, ctr) 219 t = 0 220 if rows > 0 { if hue_ok == rows { t = 1 } } 221 gv_check("h-bar-prime-matches-every-published-pair" as *u8, t, ctr) 222 gv_bite("neg-control-de2000-planted-wrong-expectation-is-rejected" as *u8, bite_bad, bite_good, ctr) 223 224 // ---- T4: ICC matrix/TRC profile ---- 225 let prof: *i64 = cs_icc_load(cs, CG_PROFILE) 226 gv_puts(" [T4] icc bytes=" as *u8); gv_num(prof[CS_P_BYTES]); gv_puts(" tags=" as *u8); gv_num(prof[CS_P_TAGS]) 227 gv_puts(" have=" as *u8); gv_num(prof[CS_P_HAVE]); gv_puts(" ok=" as *u8); gv_num(prof[CS_P_OK]); gv_puts("\n" as *u8) 228 let icc_ok: i64 = gv_need("sRGB2014.icc readable with rXYZ gXYZ bXYZ wtpt rTRC gTRC bTRC (knowledge/fetched)" as *u8, prof[CS_P_OK], ctr) 229 let lab: *i64 = sys_mmap(CG_SLOT * CG_THREE) as *i64 230 let rgb: *i64 = sys_mmap(CG_SLOT * CG_THREE) as *i64 231 if icc_ok == 1 { 232 let sx: i64 = prof[CS_P_MAT] + prof[CS_P_MAT + 1] + prof[CS_P_MAT + 2] 233 let sy: i64 = prof[CS_P_MAT + 3] + prof[CS_P_MAT + 4] + prof[CS_P_MAT + 5] 234 let sz: i64 = prof[CS_P_MAT + 6] + prof[CS_P_MAT + 7] + prof[CS_P_MAT + 8] 235 gv_puts(" wtpt_micro=" as *u8); gv_num(fq_to_micro(prof[CS_P_WTPT])); gv_puts("," as *u8); gv_num(fq_to_micro(prof[CS_P_WTPT + 1])); gv_puts("," as *u8); gv_num(fq_to_micro(prof[CS_P_WTPT + 2])) 236 gv_puts(" colsum_micro=" as *u8); gv_num(fq_to_micro(sx)); gv_puts("," as *u8); gv_num(fq_to_micro(sy)); gv_puts("," as *u8); gv_num(fq_to_micro(sz)); gv_puts("\n" as *u8) 237 t = 0 238 if cg_abs(sx - prof[CS_P_WTPT]) <= CG_S15_SUM_TOL_Q30 { if cg_abs(sy - prof[CS_P_WTPT + 1]) <= CG_S15_SUM_TOL_Q30 { if cg_abs(sz - prof[CS_P_WTPT + 2]) <= CG_S15_SUM_TOL_Q30 { t = 1 } } } 239 gv_check("icc-primaries-sum-to-the-white-point-within-s15fixed16" as *u8, t, ctr) 240 // THE DIRECT DETECTOR for the 2026-08-24 defect: a TRC table parsed through an overflowing divide is not monotone. 241 let mlr: *i64 = prof[CS_P_LUT_R] as *i64 242 let mlg: *i64 = prof[CS_P_LUT_G] as *i64 243 let mlb: *i64 = prof[CS_P_LUT_B] as *i64 244 var mono: i64 = 1 245 var mono_break: i64 = 0 - 1 246 var tc: i64 = 1 247 while tc < CS_LUT_ENTRIES { 248 if mlr[tc] < mlr[tc - 1] { if mono == 1 { mono_break = tc } mono = 0 } 249 if mlg[tc] < mlg[tc - 1] { if mono == 1 { mono_break = tc } mono = 0 } 250 if mlb[tc] < mlb[tc - 1] { if mono == 1 { mono_break = tc } mono = 0 } 251 tc = tc + 1 252 } 253 gv_puts(" trc_monotone=" as *u8); gv_num(mono); gv_puts(" first_break_code=" as *u8); gv_num(mono_break); gv_puts(" lut_mid_micro=" as *u8); gv_num(fq_to_micro(mlr[CG_GREY_MID])); gv_puts("\n" as *u8) 254 gv_check("trc-table-monotone-non-decreasing-over-all-256-codes (a garbage LUT cannot pass)" as *u8, mono, ctr) 255 cs_prof_rgb_to_lab(cs, prof, CG_CODE_MAX, CG_CODE_MAX, CG_CODE_MAX, lab) 256 gv_puts(" white_lab_micro=" as *u8); gv_num(fq_to_micro(lab[0])); gv_puts("," as *u8); gv_num(fq_to_micro(lab[1])); gv_puts("," as *u8); gv_num(fq_to_micro(lab[2])); gv_puts("\n" as *u8) 257 t = 0 258 if cg_within(lab[0], fq_from_int(CG_L_WHITE), CG_WHITE_TOL_MICRO) == 1 { if cg_abs(fq_to_micro(lab[1])) <= CG_WHITE_TOL_MICRO { if cg_abs(fq_to_micro(lab[2])) <= CG_WHITE_TOL_MICRO { t = 1 } } } 259 gv_check("profile-white-reads-L100-a0-b0" as *u8, t, ctr) 260 cs_prof_rgb_to_lab(cs, prof, 0, 0, 0, lab) 261 t = 0 262 if cg_abs(fq_to_micro(lab[0])) <= CG_WHITE_TOL_MICRO { t = 1 } 263 gv_check("profile-black-reads-L0" as *u8, t, ctr) 264 var grey_ok: i64 = 0 265 var rt_ok: i64 = 0 266 var diag: i64 = 0 267 var c: i64 = 0 268 while c < CG_GREYS { 269 cs_prof_rgb_to_lab(cs, prof, c, c, c, lab) 270 if cg_abs(fq_to_micro(lab[1])) <= CG_WHITE_TOL_MICRO { if cg_abs(fq_to_micro(lab[2])) <= CG_WHITE_TOL_MICRO { grey_ok = grey_ok + 1 } } 271 cs_prof_lab_to_rgb(cs, prof, lab[0], lab[1], lab[2], rgb) 272 var okc: i64 = 0 273 if cg_abs(rgb[0] - c) <= CG_CODE_TOL { if cg_abs(rgb[1] - c) <= CG_CODE_TOL { if cg_abs(rgb[2] - c) <= CG_CODE_TOL { okc = 1 } } } 274 if okc == 1 { rt_ok = rt_ok + 1 } else { 275 // PRINT THE VALUES, NOT JUST PASS/FAIL: the first few failing greys, with their Lab and the codes that came back 276 if diag < CG_THREE { 277 gv_puts(" roundtrip-miss code=" as *u8); gv_num(c); gv_puts(" lab_micro=" as *u8); gv_num(fq_to_micro(lab[0])); gv_puts("," as *u8); gv_num(fq_to_micro(lab[1])); gv_puts("," as *u8); gv_num(fq_to_micro(lab[2])) 278 gv_puts(" back=" as *u8); gv_num(rgb[0]); gv_puts("," as *u8); gv_num(rgb[1]); gv_puts("," as *u8); gv_num(rgb[2]) 279 let dxyz: *i64 = sys_mmap(CG_SLOT * CG_THREE) as *i64 280 cs_xyz_from_lab(cs, lab[0], lab[1], lab[2], prof[CS_P_WTPT], prof[CS_P_WTPT + 1], prof[CS_P_WTPT + 2], dxyz) 281 gv_puts(" xyz_micro=" as *u8); gv_num(fq_to_micro(dxyz[0])); gv_puts("," as *u8); gv_num(fq_to_micro(dxyz[1])); gv_puts("," as *u8); gv_num(fq_to_micro(dxyz[2])) 282 let dlin: *i64 = sys_mmap(CG_SLOT * CG_THREE) as *i64 283 cs_prof_lab_to_lin(cs, prof, lab[0], lab[1], lab[2], dlin) 284 gv_puts(" lin_micro=" as *u8); gv_num(fq_to_micro(dlin[0])); gv_puts("," as *u8); gv_num(fq_to_micro(dlin[1])); gv_puts("," as *u8); gv_num(fq_to_micro(dlin[2])) 285 let fyd: i64 = fq_div(lab[0] + fq_from_int(CS_LAB_L_OFFSET), fq_from_int(CS_LAB_L_SCALE)) 286 gv_puts(" fy_micro=" as *u8); gv_num(fq_to_micro(fyd)); gv_puts(" finv_micro=" as *u8); gv_num(fq_to_micro(cs_lab_finv(cs, fyd))) 287 gv_puts(" inv0_micro=" as *u8); gv_num(fq_to_micro(prof[CS_P_INV])); gv_puts("," as *u8); gv_num(fq_to_micro(prof[CS_P_INV + 1])); gv_puts("," as *u8); gv_num(fq_to_micro(prof[CS_P_INV + 2])); gv_puts("\n" as *u8) 288 diag = diag + 1 289 } 290 } 291 c = c + 1 292 } 293 var corner_ok: i64 = 0 294 var k: i64 = 0 295 while k < CG_CORNERS { 296 let cr: i64 = (k & 1) * CG_CODE_MAX 297 let cg: i64 = ((k >> 1) & 1) * CG_CODE_MAX 298 let cb: i64 = ((k >> CG_TWO) & 1) * CG_CODE_MAX 299 cs_prof_rgb_to_lab(cs, prof, cr, cg, cb, lab) 300 cs_prof_lab_to_rgb(cs, prof, lab[0], lab[1], lab[2], rgb) 301 if cg_abs(rgb[0] - cr) <= CG_CODE_TOL { if cg_abs(rgb[1] - cg) <= CG_CODE_TOL { if cg_abs(rgb[2] - cb) <= CG_CODE_TOL { corner_ok = corner_ok + 1 } } } 302 k = k + 1 303 } 304 gv_puts(" greys_neutral=" as *u8); gv_num(grey_ok); gv_puts("/" as *u8); gv_num(CG_GREYS); gv_puts(" roundtrip_greys=" as *u8); gv_num(rt_ok) 305 gv_puts(" roundtrip_corners=" as *u8); gv_num(corner_ok); gv_puts("/" as *u8); gv_num(CG_CORNERS); gv_puts("\n" as *u8) 306 t = 0 307 if grey_ok == CG_GREYS { t = 1 } 308 gv_check("every-grey-code-reads-neutral-a0-b0 (256 of 256, no sample)" as *u8, t, ctr) 309 t = 0 310 if rt_ok == CG_GREYS { if corner_ok == CG_CORNERS { t = 1 } } 311 gv_check("rgb-lab-rgb-round-trips-within-one-code (greys and corners)" as *u8, t, ctr) 312 } 313 314 // ---- T5: gamut descriptor from the profile, conf-driven ---- 315 let hs: i64 = cs_conf_int(CG_CONF, "gbd_hue_sectors" as *u8) 316 let es: i64 = cs_conf_int(CG_CONF, "gbd_elev_sectors" as *u8) 317 let steps: i64 = cs_conf_int(CG_CONF, "gbd_cube_steps" as *u8) 318 let iters: i64 = cs_conf_int(CG_CONF, "clip_iters" as *u8) 319 var conf_ok: i64 = 1 320 if hs == CS_CONF_MISS { conf_ok = 0 } 321 if es == CS_CONF_MISS { conf_ok = 0 } 322 if steps == CS_CONF_MISS { conf_ok = 0 } 323 if iters == CS_CONF_MISS { conf_ok = 0 } 324 let have_conf: i64 = gv_need("knowledge/colorsci.conf carries gbd_hue_sectors gbd_elev_sectors gbd_cube_steps clip_iters" as *u8, conf_ok, ctr) 325 if icc_ok == 1 { if have_conf == 1 { 326 let g: *i64 = cs_gbd_alloc(hs, es) 327 let added: i64 = cs_gbd_from_profile(cs, prof, g, steps) 328 let expect_n: i64 = CS_CUBE_FACES * (steps + 1) * (steps + 1) 329 let unsampled: i64 = cs_gbd_unsampled(g) 330 gv_puts(" [T5] gbd sectors=" as *u8); gv_num(hs * es); gv_puts(" samples=" as *u8); gv_num(added); gv_puts(" unsampled_sectors=" as *u8); gv_num(unsampled); gv_puts("\n" as *u8) 331 t = 0 332 if added == expect_n { t = 1 } 333 gv_check("gbd-sampled-every-cube-face-point (6 faces x (steps+1)^2)" as *u8, t, ctr) 334 var inside_n: i64 = 0 335 var face: i64 = 0 336 while face < CS_CUBE_FACES { 337 var u: i64 = 0 338 while u <= steps { 339 var v: i64 = 0 340 while v <= steps { 341 let cu: i64 = (u * CG_CODE_MAX) / steps 342 let cv: i64 = (v * CG_CODE_MAX) / steps 343 var r: i64 = cu 344 var gg: i64 = cv 345 var bb: i64 = 0 346 if face == 0 { r = 0; gg = cu; bb = cv } 347 if face == 1 { r = CG_CODE_MAX; gg = cu; bb = cv } 348 if face == 2 { r = cu; gg = 0; bb = cv } 349 if face == 3 { r = cu; gg = CG_CODE_MAX; bb = cv } 350 if face == 4 { r = cu; gg = cv; bb = 0 } 351 if face == 5 { r = cu; gg = cv; bb = CG_CODE_MAX } 352 cs_prof_rgb_to_lab(cs, prof, r, gg, bb, lab) 353 let pm: i64 = cs_gbd_permil(cs, g, lab[0], lab[1], lab[2]) 354 if pm != CS_G_UNSAMPLED { if pm <= CG_PERMIL { inside_n = inside_n + 1 } } 355 v = v + 1 356 } 357 u = u + 1 358 } 359 face = face + 1 360 } 361 gv_puts(" samples_inside=" as *u8); gv_num(inside_n); gv_puts("/" as *u8); gv_num(added); gv_puts("\n" as *u8) 362 t = 0 363 if inside_n == added { t = 1 } 364 gv_check("every-sample-the-descriptor-was-built-from-reads-inside" as *u8, t, ctr) 365 // ---- the EXACT matrix-gamut test (the ruler phototwin uses); the descriptor above is informational for matrix 366 // profiles and load-bearing only for measured swatch gamuts. MEASURED 2026-08-24: 536 of 648 sectors unsampled 367 // at 16 steps, and the (50,150,0) probe fell in an unsampled sector -- a descriptor cannot be the oracle here. 368 let pout: i64 = cs_prof_gamut_permil(cs, prof, fq_from_int(CG_OUT_L), fq_from_int(CG_OUT_A), 0) 369 cs_prof_rgb_to_lab(cs, prof, CG_CODE_MAX, 0, 0, lab) 370 let pred: i64 = cs_prof_gamut_permil(cs, prof, lab[0], lab[1], lab[2]) 371 cs_prof_rgb_to_lab(cs, prof, CG_GREY_MID, CG_GREY_MID, CG_GREY_MID, lab) 372 let pgrey: i64 = cs_prof_gamut_permil(cs, prof, lab[0], lab[1], lab[2]) 373 gv_puts(" exact: outside_probe_permil=" as *u8); gv_num(pout); gv_puts(" red_corner_permil=" as *u8); gv_num(pred); gv_puts(" mid_grey_permil=" as *u8); gv_num(pgrey); gv_puts("\n" as *u8) 374 var fired_bad: i64 = 0 375 if pout > CG_PERMIL { fired_bad = 1 } 376 var fired_good: i64 = 0 377 if pred > CG_PERMIL + CG_BOUNDARY_SLACK_PERMIL { fired_good = 1 } 378 gv_bite("neg-control-exact-gamut-fires-outside-silent-on-red-corner" as *u8, fired_bad, fired_good, ctr) 379 t = 0 380 if pgrey < CG_PERMIL { if pgrey > 0 { t = 1 } } 381 gv_check("exact-gamut-mid-grey-reads-strictly-inside (headroom on every channel)" as *u8, t, ctr) 382 // every cube-face sample reads on or inside the boundary through the exact test (the cube is the gamut) 383 var exact_in: i64 = 0 384 var f2: i64 = 0 385 while f2 < CS_CUBE_FACES { 386 var u2: i64 = 0 387 while u2 <= steps { 388 var v2: i64 = 0 389 while v2 <= steps { 390 let cu2: i64 = (u2 * CG_CODE_MAX) / steps 391 let cv2: i64 = (v2 * CG_CODE_MAX) / steps 392 var r2: i64 = cu2 393 var g3: i64 = cv2 394 var b3: i64 = 0 395 if f2 == 0 { r2 = 0; g3 = cu2; b3 = cv2 } 396 if f2 == 1 { r2 = CG_CODE_MAX; g3 = cu2; b3 = cv2 } 397 if f2 == 2 { r2 = cu2; g3 = 0; b3 = cv2 } 398 if f2 == 3 { r2 = cu2; g3 = CG_CODE_MAX; b3 = cv2 } 399 if f2 == 4 { r2 = cu2; g3 = cv2; b3 = 0 } 400 if f2 == 5 { r2 = cu2; g3 = cv2; b3 = CG_CODE_MAX } 401 cs_prof_rgb_to_lab(cs, prof, r2, g3, b3, lab) 402 if cs_prof_gamut_permil(cs, prof, lab[0], lab[1], lab[2]) <= CG_PERMIL + CG_BOUNDARY_SLACK_PERMIL { exact_in = exact_in + 1 } 403 v2 = v2 + 1 404 } 405 u2 = u2 + 1 406 } 407 f2 = f2 + 1 408 } 409 gv_puts(" exact_samples_inside=" as *u8); gv_num(exact_in); gv_puts("/" as *u8); gv_num(added); gv_puts("\n" as *u8) 410 t = 0 411 if exact_in == added { t = 1 } 412 gv_check("exact-gamut-reads-every-cube-face-sample-on-or-inside (the cube is the gamut)" as *u8, t, ctr) 413 let mapped: *i64 = sys_mmap(CG_SLOT * CG_THREE) as *i64 414 let before: i64 = cs_prof_gamut_clip(cs, prof, fq_from_int(CG_OUT_L), fq_from_int(CG_OUT_A), 0, iters, mapped) 415 let after: i64 = cs_prof_gamut_permil(cs, prof, mapped[0], mapped[1], mapped[2]) 416 gv_puts(" clip before_permil=" as *u8); gv_num(before); gv_puts(" after_permil=" as *u8); gv_num(after) 417 gv_puts(" mapped_micro=" as *u8); gv_num(fq_to_micro(mapped[0])); gv_puts("," as *u8); gv_num(fq_to_micro(mapped[1])); gv_puts("," as *u8); gv_num(fq_to_micro(mapped[2])); gv_puts("\n" as *u8) 418 t = 0 419 if before > CG_PERMIL { if after <= CG_PERMIL { if mapped[0] == fq_from_int(CG_OUT_L) { if mapped[2] == 0 { if mapped[1] > 0 { t = 1 } } } } } 420 gv_check("chroma-clip-brings-the-outside-probe-inside-at-the-same-L-and-hue" as *u8, t, ctr) 421 } } 422 423 return gv_verdict("colorsci" as *u8, ctr, "CIELAB + CIEDE2000 + ICC + gamut descriptor proven against the published table and the mirrored profile" as *u8) 424}