code wiki / (root) / nx_skin_pbr_gate.nx

nx_skin_pbr_gate.nx source

↩ module page · 322 lines · 17529 B

1// nx_skin_pbr_gate.nx -- GATE for the PBR map set (charsim R4, debt 1786549493 residue b+d). 2// THE TOOTH THAT MATTERS IS T7: the four maps must differ from EACH OTHER. An implementation that 3// returned albedo four times would pass every other tooth here and produce four files, which is exactly 4// the "file count moved, nothing else did" failure the rung was written to avoid. 5// T1 proves F0 is DERIVED, not typed: it recomputes Fresnel from the declared index and compares. 6// T4 is the anti-vacuity tooth: a relief field that was constant would give perfect unit-length normals 7// and perfect determinism while encoding no surface at all. 8// The physics teeth need no fixture, so this gate does not duplicate the sibling texbake fixture. 9// license_tier: ORIGINAL expect_exit: 0 10import "nx_syscalls.nx" 11import "nx_nxa.nx" 12import "nx_nxa_texc_lib.nx" 13import "nx_nxa_texbake_lib.nx" 14import "nx_gate_verdict.nx" 15 16const SP_REF: *u8 = "sites/nishifamily/world/ref9d.nxa" 17const SP_TMP: *u8 = "/tmp/nxpbr_ref9d.nxa" 18const SP_RES: i64 = 128 19const SP_RES4K: i64 = 4096 20const SP_SEED: i64 = 7 21const SP_SEED2: i64 = 8 22const SP_HUE: i64 = 500 23const SP_F0_PUBLISHED: i64 = 28 // ((1.4-1)/(1.4+1))^2 = 0.0278 -> 28 per mille, the textbook skin F0 24const SP_F0_TOL: i64 = 2 25const SP_LEN_TOL: i64 = 12 // unit-length tolerance on the decoded normal (127 nominal) 26const SP_NOM: i64 = 127 27const SP_MID: i64 = 128 28const SP_SAMPLES: i64 = 64 29const SP_BADMAP: i64 = 99 30const SP_POLES: i64 = 5 31const SP_UNIT: i64 = 4096 // nx_trimesh represents 1.0 as 4096 in its normal fixed point 32const SP_DIAG: i64 = 2364 // 4096/sqrt(3), the unit diagonal in that same fixed point 33const SP_VEC_TOL: i64 = 140 // integer residue across two normalisations, ~3 percent of unit 34const SP_TILT: i64 = 1200 // a real tangent tilt, not a whisper 35 36// ---- T12..T15: THE CONVENTION TEETH (added 2026-08-22). Two independent readings of this file found 37// that T3/T10/T11 are ALL sign-blind to the green channel: T3 compares |dy|, T10 tilts by (0,0,UNIT) 38// so y is zero and -0 == 0, T11 only asserts "moved" and "unit length". A pipeline that flipped the 39// green channel -- i.e. wrote a DirectX (-Y) map where the shader reads OpenGL (+Y), or vice versa -- 40// would pass all eleven and render every bump as a dent. These teeth pin the SIGN. 41// THE FACT THEY PIN, read from the one owner of the encoding (ntb_encode_normal, nx_nxa_texbake_lib): 42// ny = 0 - dzy ; out3[1] = NTB_N_MID + NTB_N_SCALE * ny / len with dzy from dhy = h(y+1)-h(y-1) 43// i.e. a relief RISING toward +y encodes G BELOW the midpoint. That is n = (-h_x, -h_y, 1) stored +Y-up: 44// the OpenGL convention. The consumer (nx_trimesh.nx:697) decodes ety = (G - TM_NMID)*4096/TM_NSCL with 45// NO negation, so baker and shader agree today; this gate makes that agreement a measured property 46// instead of a coincidence. Every number below is DERIVED from the lib's own constants. 47// 48// THE FIXTURE RESOLUTION IS MEASURED, NOT CHOSEN -- and the first draft of this tooth would have been 49// VACUOUS. The encoder's slope is PHYSICAL (NTB_RELIEF_UM of height over 2*texel_um of run), so at a 50// coarse resolution a texel is so wide that even the full relief codomain rounds to G == midpoint: 51// at res=8 a texel is 212,500um and a 60um relief is a 0.00014 slope -> G=128 exactly, flat == rise 52// == fall, and nothing can be pinned. The gate therefore WALKS resolutions up from the smallest legal 53// bake (TXM_RES_MIN) by doubling until a full-range slope moves G by >= SP_RISE_STEPS, and PRINTS the 54// resolution it landed on. That number is a property of the encoder worth knowing on its own: the 55// coarsest bake at which the normal map carries any sign at all. 56const SP_CONV_RES_MIN: i64 = 8 // TXM_RES_MIN: the smallest bake the TEXM writer accepts 57const SP_CONV_RES_MAX: i64 = 4096 // SP_RES4K: the floor T8 already bakes; the walk never exceeds it 58const SP_CONV_STEP: i64 = 2 // the walk doubles -- every legal texture size is a power of two 59const SP_RISE_STEPS: i64 = 4 // quantisation steps the fixture gradient must move G by before the 60 // SIGN is asserted: 1 step is rounding noise, 4 is unambiguous, and 61 // it is a multiple of the encoder's own unit (one G step), not a slope 62const SP_GRAD_FULL: i64 = 1023 // NTB_H_RANGE: the full relief codomain, the steepest gradient the 63 // encoder can ever see, so the sign is asserted where |G-mid| is LARGEST 64 65func sp_abs(v: i64) -> i64 { if v < 0 { return 0 - v } return v } 66func sp_bytes(res: i64) -> i64 { return res*res*3 + 64 } 67func sp_ck(m: *u8, n: i64) -> i64 { 68 var c: i64 = 0 69 var i: i64 = 0 70 while i < n { c = (c*31 + (m[i] as i64)) % 1000000007; i = i + 1 } 71 return c 72} 73 74func main(argc: i64, argv: *i64) -> i64 { 75 let ctr: *i64 = gv_ctr() 76 gv_head("nx_skin_pbr -- the PBR map set: derived specular, region gloss, physical-relief normals" as *u8) 77 78 // T1 -- F0 is COMPUTED from the published refractive index, not typed in 79 let f0: i64 = ntb_f0_skin() 80 gv_puts(" F0 from IOR 1.4 = " as *u8); gv_num(f0); gv_puts(" per mille\n" as *u8) 81 var t1: i64 = 0 82 if sp_abs(f0 - SP_F0_PUBLISHED) <= SP_F0_TOL { t1 = 1 } 83 gv_check("T1 specular F0 is DERIVED by Fresnel from the skin index, not declared" as *u8, t1, ctr) 84 85 // T2 -- gloss discriminates by region, in the stated direction (T-zone oilier than limbs) 86 let rf: i64 = ntb_rough_of_region(NTB_FACE) 87 let rl: i64 = ntb_rough_of_region(NTB_LIMBS) 88 var t2: i64 = 0 89 if rf < rl { t2 = 1 } 90 gv_puts(" roughness face=" as *u8); gv_num(rf) 91 gv_puts(" limbs=" as *u8); gv_num(rl); gv_puts("\n" as *u8) 92 gv_check("T2 gloss regions discriminate: face smoother than limbs" as *u8, t2, ctr) 93 94 // T3 -- every emitted normal is unit length, and points mostly along the surface normal 95 let n3: *i64 = sys_mmap(3*8) as *i64 96 var t3: i64 = 1 97 var k: i64 = 0 98 while k < SP_SAMPLES { 99 ntb_normal_at(k*7, k*13, SP_RES4K, SP_SEED, n3) 100 let dx: i64 = n3[0] - SP_MID 101 let dy: i64 = n3[1] - SP_MID 102 let dz: i64 = n3[2] - SP_MID 103 let ln: i64 = tm_isqrt(dx*dx + dy*dy + dz*dz) 104 if sp_abs(ln - SP_NOM) > SP_LEN_TOL { t3 = 0 } 105 if dz <= sp_abs(dx) { t3 = 0 } 106 if dz <= sp_abs(dy) { t3 = 0 } 107 k = k + 1 108 } 109 gv_check("T3 normals are unit length and dominated by the surface axis" as *u8, t3, ctr) 110 111 // T4 -- ANTI-VACUITY: the relief field is LIVE. A constant height gives flawless normals and 112 // flawless determinism while describing no surface whatsoever. 113 var t4: i64 = 0 114 var seenx: i64 = 0 115 k = 0 116 while k < SP_SAMPLES { 117 ntb_normal_at(k*7, k*13, SP_RES4K, SP_SEED, n3) 118 if n3[0] != SP_MID { seenx = seenx + 1 } 119 k = k + 1 120 } 121 if seenx > 0 { t4 = 1 } 122 gv_puts(" non-flat normal samples = " as *u8); gv_num(seenx) 123 gv_puts(" of " as *u8); gv_num(SP_SAMPLES); gv_puts("\n" as *u8) 124 gv_check("T4 anti-vacuity: the relief field actually perturbs the normal" as *u8, t4, ctr) 125 126 // T5 -- neg-control-determinism on the relief 127 let m3: *i64 = sys_mmap(3*8) as *i64 128 ntb_normal_at(11, 23, SP_RES4K, SP_SEED, n3) 129 ntb_normal_at(11, 23, SP_RES4K, SP_SEED, m3) 130 var t5: i64 = 1 131 if n3[0] != m3[0] { t5 = 0 } 132 if n3[1] != m3[1] { t5 = 0 } 133 if n3[2] != m3[2] { t5 = 0 } 134 gv_check("T5 neg-control-determinism: one seed yields one normal, always" as *u8, t5, ctr) 135 136 // T6 -- the seed is load-bearing on the relief 137 ntb_normal_at(11, 23, SP_RES4K, SP_SEED2, m3) 138 var t6: i64 = 0 139 if n3[0] != m3[0] { t6 = 1 } 140 if n3[1] != m3[1] { t6 = 1 } 141 gv_check("T6 the seed changes the relief (grain is live, not frozen)" as *u8, t6, ctr) 142 143 // ---- renderer wire: the tangent basis ---- 144 // Five probe normals INCLUDING BOTH POLES. The -Z pole is the one a naive basis construction 145 // divides by zero on, so it is in the set on purpose. 146 let pv: *i64 = sys_mmap(SP_POLES*3*8) as *i64 147 pv[0] = 0; pv[1] = 0; pv[2] = SP_UNIT 148 pv[3] = 0; pv[4] = 0; pv[5] = 0 - SP_UNIT 149 pv[6] = SP_UNIT; pv[7] = 0; pv[8] = 0 150 pv[9] = 0; pv[10] = SP_UNIT; pv[11] = 0 151 pv[12] = SP_DIAG; pv[13] = SP_DIAG; pv[14] = SP_DIAG 152 let pn: *i64 = sys_mmap(3*8) as *i64 153 154 // T10 -- a straight-up tangent normal is an EXACT identity. This is the property that makes an 155 // unbound or flat normal map a no-op, and it must hold at the poles too. 156 var t10: i64 = 1 157 var pi: i64 = 0 158 while pi < SP_POLES { 159 let px: i64 = pv[pi*3] 160 let py: i64 = pv[pi*3+1] 161 let pz: i64 = pv[pi*3+2] 162 tm_perturb_normal(px, py, pz, 0, 0, SP_UNIT, pn) 163 if sp_abs(pn[0] - px) > SP_VEC_TOL { t10 = 0 } 164 if sp_abs(pn[1] - py) > SP_VEC_TOL { t10 = 0 } 165 if sp_abs(pn[2] - pz) > SP_VEC_TOL { t10 = 0 } 166 pi = pi + 1 167 } 168 gv_check("T10 a straight-up tangent normal is an exact identity, both poles included" as *u8, t10, ctr) 169 170 // T11 -- a TILTED tangent normal actually moves the shading normal, and the result stays unit length 171 var t11: i64 = 1 172 var moved: i64 = 0 173 pi = 0 174 while pi < SP_POLES { 175 let qx: i64 = pv[pi*3] 176 let qy: i64 = pv[pi*3+1] 177 let qz: i64 = pv[pi*3+2] 178 tm_perturb_normal(qx, qy, qz, SP_TILT, SP_TILT, SP_UNIT, pn) 179 let pl: i64 = tm_isqrt(pn[0]*pn[0] + pn[1]*pn[1] + pn[2]*pn[2]) 180 if sp_abs(pl - SP_UNIT) > SP_VEC_TOL { t11 = 0 } 181 if sp_abs(pn[0] - qx) > SP_VEC_TOL { moved = moved + 1 } 182 if sp_abs(pn[1] - qy) > SP_VEC_TOL { moved = moved + 1 } 183 pi = pi + 1 184 } 185 if moved == 0 { t11 = 0 } 186 gv_puts(" tilted-tangent perturbations that moved = " as *u8); gv_num(moved); gv_puts("\n" as *u8) 187 gv_check("T11 a tilted tangent normal perturbs the normal and stays unit length" as *u8, t11, ctr) 188 189 // ---- asset-backed teeth ---- 190 let rfd: i64 = sys_openat_rd(SP_REF) 191 var haveref: i64 = 0 192 if rfd >= 0 { haveref = 1; sys_close(rfd) } 193 if gv_need(SP_REF, haveref, ctr) == 1 { 194 let rcx: i64 = ntx_apply(SP_REF, SP_TMP) 195 let lp: *i64 = sys_mmap(16) as *i64 196 let b: *u8 = sys_read_file(SP_TMP, lp) 197 var t7: i64 = 0 198 var t8: i64 = 0 199 var t9: i64 = 0 200 if rcx == 0 { 201 if (b as i64) != 0 { 202 let flen: i64 = lp[0] 203 let nb: i64 = sp_bytes(SP_RES) 204 let ma: *u8 = sys_mmap(nb) 205 let ms: *u8 = sys_mmap(nb) 206 let mg: *u8 = sys_mmap(nb) 207 let mn: *u8 = sys_mmap(nb) 208 ntb_bake_pbr(b, flen, SP_RES, SP_SEED, SP_HUE, NTB_MAP_ALBEDO, ma) 209 ntb_bake_pbr(b, flen, SP_RES, SP_SEED, SP_HUE, NTB_MAP_SPEC, ms) 210 ntb_bake_pbr(b, flen, SP_RES, SP_SEED, SP_HUE, NTB_MAP_GLOSS, mg) 211 ntb_bake_pbr(b, flen, SP_RES, SP_SEED, SP_HUE, NTB_MAP_NORMAL, mn) 212 let ca: i64 = sp_ck(ma, nb - 64) 213 let cs: i64 = sp_ck(ms, nb - 64) 214 let cg: i64 = sp_ck(mg, nb - 64) 215 let cn: i64 = sp_ck(mn, nb - 64) 216 gv_puts(" map checksums albedo=" as *u8); gv_num(ca) 217 gv_puts(" spec=" as *u8); gv_num(cs) 218 gv_puts(" gloss=" as *u8); gv_num(cg) 219 gv_puts(" normal=" as *u8); gv_num(cn); gv_puts("\n" as *u8) 220 t7 = 1 221 if ca == cs { t7 = 0 } 222 if ca == cg { t7 = 0 } 223 if ca == cn { t7 = 0 } 224 if cs == cg { t7 = 0 } 225 if cs == cn { t7 = 0 } 226 if cg == cn { t7 = 0 } 227 // T8 -- THE 4096 FLOOR, actually baked rather than assumed from a knob 228 let n4: i64 = sp_bytes(SP_RES4K) 229 let big: *u8 = sys_mmap(n4) 230 let rc4: i64 = ntb_bake_pbr(b, flen, SP_RES4K, SP_SEED, SP_HUE, NTB_MAP_NORMAL, big) 231 let c4: i64 = sp_ck(big, n4 - 64) 232 gv_puts(" 4096 normal bake rc=" as *u8); gv_num(rc4) 233 gv_puts(" bytes=" as *u8); gv_num(n4 - 64) 234 gv_puts(" ck=" as *u8); gv_num(c4); gv_puts("\n" as *u8) 235 if rc4 == 0 { if c4 != 0 { t8 = 1 } } 236 sys_munmap(big, n4) 237 // T9 -- neg-control: an unknown map id REFUSES, it does not silently emit albedo 238 let rcb: i64 = ntb_bake_pbr(b, flen, SP_RES, SP_SEED, SP_HUE, SP_BADMAP, ma) 239 if rcb < 0 { t9 = 1 } 240 } 241 } 242 gv_check("T7 the four maps DIFFER from each other (not four copies of albedo)" as *u8, t7, ctr) 243 gv_check("T8 the 4096 floor is BAKED and non-empty, not assumed from a knob" as *u8, t8, ctr) 244 gv_check("T9 neg-control-badmap: an unknown map id refuses instead of emitting albedo" as *u8, t9, ctr) 245 } 246 247 248 // ---- convention teeth ---- 249 // Walk resolutions until a full-range slope is REPRESENTABLE (leaves the midpoint by SP_RISE_STEPS). 250 let cn: *i64 = sys_mmap(3*8) as *i64 251 var cres: i64 = SP_CONV_RES_MIN 252 var g_rise: i64 = NTB_N_MID 253 var g_fall: i64 = NTB_N_MID 254 var g_flat: i64 = NTB_N_MID 255 var found_res: i64 = 0 256 while cres <= SP_CONV_RES_MAX { 257 ntb_encode_normal(0, SP_GRAD_FULL, cres, cn) 258 let gr: i64 = cn[1] 259 ntb_encode_normal(0, 0 - SP_GRAD_FULL, cres, cn) 260 let gf: i64 = cn[1] 261 ntb_encode_normal(0, 0, cres, cn) 262 let g0: i64 = cn[1] 263 if found_res == 0 { 264 if sp_abs(gr - NTB_N_MID) >= SP_RISE_STEPS { if sp_abs(gf - NTB_N_MID) >= SP_RISE_STEPS { 265 found_res = cres 266 g_rise = gr 267 g_fall = gf 268 g_flat = g0 269 } } 270 } 271 cres = cres * SP_CONV_STEP 272 } 273 gv_puts(" sign-visible resolution (first bake where a full-range slope moves G by >= SP_RISE_STEPS) = " as *u8) 274 gv_num(found_res); gv_puts("\n" as *u8) 275 gv_puts(" G channel at that res: flat=" as *u8); gv_num(g_flat) 276 gv_puts(" rising(+y)=" as *u8); gv_num(g_rise) 277 gv_puts(" falling(-y)=" as *u8); gv_num(g_fall) 278 gv_puts(" midpoint(NTB_N_MID)=" as *u8); gv_num(NTB_N_MID); gv_puts("\n" as *u8) 279 280 // T12 -- ANTI-VACUITY FIRST: some legal resolution must exist where the full-range slope leaves the 281 // midpoint by SP_RISE_STEPS in BOTH directions AND flat encodes AT the midpoint. A fixture that never 282 // reached that condition would make the sign teeth below pass-or-fail on rounding noise. 283 var t12: i64 = 0 284 if found_res > 0 { if g_flat == NTB_N_MID { t12 = 1 } } 285 gv_check("T12 anti-vacuity: a legal resolution exists where flat is AT midpoint and a full-range slope moves G by >= SP_RISE_STEPS both ways" as *u8, t12, ctr) 286 287 // T13 -- THE CONVENTION PIN. A relief rising toward +y must encode G BELOW NTB_N_MID. That is the 288 // +Y-up (OpenGL) tangent-space convention the consumer decodes without negation. This tooth is on 289 // the SIGN of (G - mid); |x| cannot pass it. 290 var t13: i64 = 0 291 if found_res > 0 { if g_rise < NTB_N_MID { t13 = 1 } } 292 gv_check("T13 CONVENTION: a relief rising toward +y encodes G BELOW midpoint (+Y-up / OpenGL, what nx_trimesh decodes)" as *u8, t13, ctr) 293 294 // T14 -- neg-control via gv_bite: the NEGATED slope must land on the OTHER side. bad = the falling 295 // slope reads ABOVE mid (it must, if the encoder is signed); good = the rising slope does NOT read 296 // above mid. A tooth that accepted both signs would be vacuous and gv_bite names it as such. 297 var fell_above: i64 = 0 298 if found_res > 0 { if g_fall > NTB_N_MID { fell_above = 1 } } 299 var rose_above: i64 = 0 300 if g_rise > NTB_N_MID { rose_above = 1 } 301 gv_bite("T14 neg-control-sign: the negated slope lands on the OTHER side of midpoint (a flipped or sign-blind encoder cannot pass both)" as *u8, fell_above, rose_above, ctr) 302 303 // T15 -- COLOUR SPACE: NAMED ABSENCE, printed on every run, neither PASS nor FAIL nor SKIP. 304 // Measured 2026-08-22 at corpus_complete=1 over 23,021 files: the texture pipeline 305 // (nx_nxa_texbake_lib, nx_nxa_texc_lib, nx_nxa_texm, nx_trimesh, nx_nxa) carries NO colour-space 306 // tag anywhere -- TEXM's header words are NMAPS/RES/MODEL/SEED/HUE/BLOBW and nothing says which maps 307 // are sRGB (albedo) and which are linear (spec/gloss/normal). nx_aces_native.nx already exports 308 // nx_aces_srgb_to_linear_q14 / nx_aces_linear_to_srgb_q14 and nothing here imports them. 309 // WHY THIS IS A PRINT AND NOT gv_need: a first draft used gv_need, and gv_need's contract is that 310 // ANY missing precondition turns the WHOLE verdict to SKIP -- which would have blocked the fourteen 311 // real proofs above on a gap in a SIBLING writer. A SKIP that hides fourteen greens is as wrong as a 312 // PASS that blesses a hole. So the absence is NAMED here in the output where every reader sees it, 313 // and the day nx_nxa_texm records a colour-space word this line becomes a gv_check. 314 gv_puts(" T15 colour-space tag: ABSENT from the pipeline (TEXM header words NMAPS/RES/MODEL/SEED/HUE/BLOBW carry none; 315" as *u8) 316 gv_puts(" nx_aces_native.nx has the sRGB<->linear pair and nothing here imports it) -- NAMED GAP, not asserted 317" as *u8) 318 319 let rc: i64 = gv_verdict("SKIN-PBR" as *u8, ctr, "derived specular, region gloss, physical-relief normals" as *u8) 320 sys_exit(rc) 321 return rc 322}