code wiki / (root) / nx_natstat.nx

nx_natstat.nx source

↩ module page · 471 lines · 19396 B

1// nx_natstat.nx -- NATURAL-IMAGE-STATISTICS photoreal metric = the REVERSE AI JUDGE. Real photographs have a 2// signature our old critic missed: DETAIL AT EVERY SCALE (a ~1/f amplitude spectrum -> roughly EQUAL detail 3// energy per octave). Clay/CG fails it two ways: too SMOOTH (fine + mid octaves empty) or GRAIN-ONLY (a spike 4// at the finest octave, smooth beneath). We measure the multi-scale detail profile + its balance (+ gradient 5// heavy-tail + colour spread) and return a graded PHOTOREAL LEVEL 0..1000. Uses: 6// (a) REVERSE JUDGE: maximise this to push Z-Image toward more-real output for games/VR (evaluator-in-reverse); 7// (b) "USE-AS-PHOTOREAL-TO-LEVEL": even a known-AI image is usable as photoreal up to the level it scores. 8// Integer, deterministic, no FFT (Laplacian-pyramid octave energies). license_tier: ORIGINAL 9import "nx_syscalls.nx" 10const K_MAGIC_65536: i64 = 65536 11const K_MAGIC_3200: i64 = 3200 12const K_MAGIC_6500: i64 = 6500 13 14func ns_lum(g: i64) -> i64 { return ((g & 255) + ((g >> 8) & 255) + ((g >> 16) & 255)) / 3 } 15func ns_abs(v: i64) -> i64 { if v < 0 { return 0 - v } return v } 16func ns_isqrt(v: i64) -> i64 { if v <= 0 { return 0 } var x: i64 = v; var y: i64 = (x + 1) / 2; while y < x { x = y; y = (x + v / x) / 2 } return x } 17 18// ★N1 NSS: MSCN (Mean-Subtracted Contrast-Normalized) coefficients + the GGD RATIO ρ=(E|x|)²/E[x²] (the core 19// BRISQUE/NIQE feature). For a NATURAL image the MSCN coefficients are ~unit-Gaussian -> ρ ≈ 0.637 (=2/π); 20// synthetic/CG deviates. Local mean+std over a 5x5 window; returns ρ in PER-MILLE (natural ~637). 21func ns_mscn_rho(fb: *i64, w: i64, h: i64) -> i64 { 22 let R: i64 = 2 23 var sum_abs: i64 = 0 24 var sum_sq: i64 = 0 25 var n: i64 = 0 26 var y: i64 = R 27 while y < h - R { 28 var x: i64 = R 29 while x < w - R { 30 var m: i64 = 0 31 var m2: i64 = 0 32 var dy: i64 = 0 - R 33 while dy <= R { 34 var dx: i64 = 0 - R 35 while dx <= R { 36 let v: i64 = ns_lum(fb[(y + dy) * w + (x + dx)]) 37 m = m + v 38 m2 = m2 + v * v 39 dx = dx + 1 40 } 41 dy = dy + 1 42 } 43 let mean: i64 = m / 25 44 var vr: i64 = m2 / 25 - mean * mean 45 if vr < 0 { vr = 0 } 46 let sd: i64 = ns_isqrt(vr) 47 let cval: i64 = ns_lum(fb[y * w + x]) 48 let mscn: i64 = (cval - mean) * 64 / (sd + 3) // fx64, C=3 stability 49 sum_abs = sum_abs + ns_abs(mscn) 50 sum_sq = sum_sq + mscn * mscn / 64 // Σ mscn² in fx64 51 n = n + 1 52 x = x + 1 53 } 54 y = y + 1 55 } 56 if n < 1 { return 0 } 57 if sum_sq < 1 { return 0 } 58 return sum_abs * sum_abs * 1000 / n / sum_sq / 64 // ρ per-mille (natural ~637) 59} 60// ★BRISQUE pairwise features: fill an MSCN buffer once, then read ρ + PAIRWISE-PRODUCT means (how adjacent MSCN 61// coeffs correlate -- the local-structure signal that ρ alone misses; all MSCN-derived => content-invariant). 62func ns_mscn_fill(fb: *i64, w: i64, h: i64, mbuf: *i64) -> i64 { 63 var i: i64 = 0 64 while i < w * h { mbuf[i] = 0; i = i + 1 } 65 let R: i64 = 2 66 var y: i64 = R 67 while y < h - R { 68 var x: i64 = R 69 while x < w - R { 70 var m: i64 = 0 71 var m2: i64 = 0 72 var dy: i64 = 0 - R 73 while dy <= R { 74 var dx: i64 = 0 - R 75 while dx <= R { 76 let v: i64 = ns_lum(fb[(y + dy) * w + (x + dx)]) 77 m = m + v 78 m2 = m2 + v * v 79 dx = dx + 1 80 } 81 dy = dy + 1 82 } 83 let mean: i64 = m / 25 84 var vr: i64 = m2 / 25 - mean * mean 85 if vr < 0 { vr = 0 } 86 mbuf[y * w + x] = (ns_lum(fb[y * w + x]) - mean) * 64 / (ns_isqrt(vr) + 3) // MSCN fx64 87 x = x + 1 88 } 89 y = y + 1 90 } 91 return 0 92} 93func ns_mscn_rho_buf(mbuf: *i64, w: i64, h: i64) -> i64 { 94 var sa: i64 = 0 95 var sq: i64 = 0 96 var n: i64 = 0 97 var y: i64 = 2 98 while y < h - 2 { 99 var x: i64 = 2 100 while x < w - 2 { 101 let m: i64 = mbuf[y * w + x] 102 sa = sa + ns_abs(m) 103 sq = sq + m * m / 64 104 n = n + 1 105 x = x + 1 106 } 107 y = y + 1 108 } 109 if n < 1 { return 0 } 110 if sq < 1 { return 0 } 111 return sa * sa * 1000 / n / sq / 64 112} 113// mean pairwise product of MSCN neighbours in direction (dx,dy), fx64. Natural images -> characteristically 114// negative (adjacent normalised coeffs anti-correlate) with a specific magnitude. 115func ns_mscn_pair(mbuf: *i64, w: i64, h: i64, dx: i64, dy: i64) -> i64 { 116 var s: i64 = 0 117 var n: i64 = 0 118 var y: i64 = 2 119 while y < h - 3 { 120 var x: i64 = 2 121 while x < w - 3 { 122 s = s + mbuf[y * w + x] * mbuf[(y + dy) * w + (x + dx)] / 64 123 n = n + 1 124 x = x + 1 125 } 126 y = y + 1 127 } 128 if n < 1 { return 0 } 129 return s / n 130} 131 132// mean |Laplacian| (centre - avg 4-neighbours) over the interior, in luminance, x100 for precision = octave detail energy 133func ns_detail(fb: *i64, w: i64, h: i64) -> i64 { 134 if w < 3 { return 0 } 135 if h < 3 { return 0 } 136 var s: i64 = 0 137 var n: i64 = 0 138 var y: i64 = 1 139 while y < h - 1 { 140 var x: i64 = 1 141 while x < w - 1 { 142 let c: i64 = ns_lum(fb[y * w + x]) 143 let nb: i64 = (ns_lum(fb[y * w + x - 1]) + ns_lum(fb[y * w + x + 1]) + ns_lum(fb[(y - 1) * w + x]) + ns_lum(fb[(y + 1) * w + x])) / 4 144 s = s + ns_abs(c - nb) 145 n = n + 1 146 x = x + 1 147 } 148 y = y + 1 149 } 150 if n < 1 { n = 1 } 151 return s * 100 / n 152} 153// 5x5 local std (activity) at (x,y) -- used for NIQE-style patch/subject selection. 154func ns_local_std(fb: *i64, w: i64, x: i64, y: i64) -> i64 { 155 var m: i64 = 0 156 var m2: i64 = 0 157 var dy: i64 = 0 - 2 158 while dy <= 2 { 159 var dx: i64 = 0 - 2 160 while dx <= 2 { 161 let v: i64 = ns_lum(fb[(y + dy) * w + (x + dx)]) 162 m = m + v 163 m2 = m2 + v * v 164 dx = dx + 1 165 } 166 dy = dy + 1 167 } 168 let mean: i64 = m / 25 169 var vr: i64 = m2 / 25 - mean * mean 170 if vr < 0 { vr = 0 } 171 return ns_isqrt(vr) 172} 173// ★AXIS 3: gradient KURTOSIS over ACTIVE (subject) pixels only -- NIQE-style patch selection excludes the flat 174// background/sky so the statistic reflects our SURFACE, not scene composition. Natural photos ~ kurtosis 7-32 175// (×100 = 700-3200); an over-smooth blob w/ sparse hard edges spikes FAR higher (flat regions + rare big edges). 176func ns_grad_kurt(fb: *i64, w: i64, h: i64) -> i64 { 177 var peak: i64 = 0 178 var y: i64 = 2 179 while y < h - 2 { 180 var x: i64 = 2 181 while x < w - 2 { 182 let s: i64 = ns_local_std(fb, w, x, y) 183 if s > peak { peak = s } 184 x = x + 3 185 } 186 y = y + 3 187 } 188 let thresh: i64 = peak / 10 // active = >=10% of peak local activity (skips flat bg) 189 var sg2: i64 = 0 190 var sg4: i64 = 0 191 var n: i64 = 0 192 y = 2 193 while y < h - 2 { 194 var x: i64 = 2 195 while x < w - 2 { 196 if ns_local_std(fb, w, x, y) > thresh { 197 let gx: i64 = ns_lum(fb[y * w + x + 1]) - ns_lum(fb[y * w + x - 1]) 198 let gy: i64 = ns_lum(fb[(y + 1) * w + x]) - ns_lum(fb[(y - 1) * w + x]) 199 let g: i64 = (ns_abs(gx) + ns_abs(gy)) / 4 200 let g2: i64 = g * g 201 sg2 = sg2 + g2 202 sg4 = sg4 + g2 * g2 203 n = n + 1 204 } 205 x = x + 1 206 } 207 y = y + 1 208 } 209 if n < 1 { return 0 } 210 let mg2: i64 = sg2 / n 211 let mg4: i64 = sg4 / n 212 if mg2 < 1 { return 0 } 213 return mg4 * 100 / (mg2 * mg2) 214} 215// ★AXIS 4: COLOURFULNESS (Hasler-Süsstrunk) -- natural photos sit in a characteristic colour-spread range 216// (not grey, not over-saturated). Returns colourfulness in the 0..~110 luminance scale. 217func ns_colorful(fb: *i64, w: i64, h: i64) -> i64 { 218 var srg: i64 = 0 219 var syb: i64 = 0 220 var srg2: i64 = 0 221 var syb2: i64 = 0 222 var n: i64 = 0 223 var i: i64 = 0 224 let tot: i64 = w * h 225 while i < tot { 226 let g: i64 = fb[i] 227 let rr: i64 = g & 255 228 let gg: i64 = (g >> 8) & 255 229 let bb: i64 = (g >> 16) & 255 230 let rg: i64 = rr - gg 231 let yb: i64 = (rr + gg) / 2 - bb 232 srg = srg + rg 233 syb = syb + yb 234 srg2 = srg2 + rg * rg 235 syb2 = syb2 + yb * yb 236 n = n + 1 237 i = i + 1 238 } 239 if n < 1 { return 0 } 240 let mrg: i64 = srg / n 241 let myb: i64 = syb / n 242 var vrg: i64 = srg2 / n - mrg * mrg 243 var vyb: i64 = syb2 / n - myb * myb 244 if vrg < 0 { vrg = 0 } 245 if vyb < 0 { vyb = 0 } 246 let sd: i64 = ns_isqrt(vrg + vyb) 247 let mm: i64 = ns_isqrt(mrg * mrg + myb * myb) 248 return sd + mm * 3 / 10 249} 250// ★AXIS 5: AI-FINGERPRINT -- generator upsampling leaves PERIODIC high-freq artifacts. Autocorrelate the 251// horizontal high-pass residual at lags 2 & 4; a real photo's residual decorrelates (~0), AI/grid spikes it. 252// Returns the mean |autocorrelation| at lag 2&4, per-mille (LOW = clean/real, HIGH = periodic artifact). 253func ns_ai_periodicity(fb: *i64, w: i64, h: i64) -> i64 { 254 var s0: i64 = 0 255 var s2: i64 = 0 256 var s4: i64 = 0 257 var y: i64 = 1 258 while y < h - 1 { 259 var x: i64 = 2 260 while x < w - 6 { 261 let hp0: i64 = ns_lum(fb[y * w + x]) - (ns_lum(fb[y * w + x - 1]) + ns_lum(fb[y * w + x + 1])) / 2 262 let hp2: i64 = ns_lum(fb[y * w + x + 2]) - (ns_lum(fb[y * w + x + 1]) + ns_lum(fb[y * w + x + 3])) / 2 263 let hp4: i64 = ns_lum(fb[y * w + x + 4]) - (ns_lum(fb[y * w + x + 3]) + ns_lum(fb[y * w + x + 5])) / 2 264 s0 = s0 + hp0 * hp0 265 s2 = s2 + hp0 * hp2 266 s4 = s4 + hp0 * hp4 267 x = x + 1 268 } 269 y = y + 1 270 } 271 if s0 < 1 { return 0 } 272 return (ns_abs(s2) * 1000 / s0 + ns_abs(s4) * 1000 / s0) / 2 273} 274// box-downsample by 2 into out (nw=w/2 x nh=h/2) 275func ns_down2(fb: *i64, w: i64, h: i64, out: *i64) -> i64 { 276 let nw: i64 = w / 2 277 let nh: i64 = h / 2 278 var y: i64 = 0 279 while y < nh { 280 var x: i64 = 0 281 while x < nw { 282 let a: i64 = fb[(y * 2) * w + x * 2] 283 let b: i64 = fb[(y * 2) * w + x * 2 + 1] 284 let c: i64 = fb[(y * 2 + 1) * w + x * 2] 285 let d: i64 = fb[(y * 2 + 1) * w + x * 2 + 1] 286 let rr: i64 = ((a & 255) + (b & 255) + (c & 255) + (d & 255)) / 4 287 let gg: i64 = (((a >> 8) & 255) + ((b >> 8) & 255) + ((c >> 8) & 255) + ((d >> 8) & 255)) / 4 288 let bb: i64 = (((a >> 16) & 255) + ((b >> 16) & 255) + ((c >> 16) & 255) + ((d >> 16) & 255)) / 4 289 out[y * nw + x] = rr + gg * 256 + bb * K_MAGIC_65536 290 x = x + 1 291 } 292 y = y + 1 293 } 294 return 0 295} 296// raw scale-invariance feature = the octave-ratio SPREAD (0 = perfect power law; large = CG). For corpus fitting. 297func ns_scale_spread(fb: *i64, w: i64, h: i64, buf1: *i64, buf2: *i64) -> i64 { 298 let e0: i64 = ns_detail(fb, w, h) 299 ns_down2(fb, w, h, buf1); let e1: i64 = ns_detail(buf1, w / 2, h / 2) 300 ns_down2(buf1, w / 2, h / 2, buf2); let e2: i64 = ns_detail(buf2, w / 4, h / 4) 301 ns_down2(buf2, w / 4, h / 4, buf1); let e3: i64 = ns_detail(buf1, w / 8, h / 8) 302 var d1: i64 = e1 303 var d2: i64 = e2 304 var d3: i64 = e3 305 if d1 < 1 { d1 = 1 } 306 if d2 < 1 { d2 = 1 } 307 if d3 < 1 { d3 = 1 } 308 let r1: i64 = e0 * 256 / d1 309 let r2: i64 = e1 * 256 / d2 310 let r3: i64 = e2 * 256 / d3 311 var mn: i64 = r1 312 var mx: i64 = r1 313 if r2 < mn { mn = r2 } 314 if r2 > mx { mx = r2 } 315 if r3 < mn { mn = r3 } 316 if r3 > mx { mx = r3 } 317 return mx - mn 318} 319// 1000 inside the natural band [lo,hi]; linear falloff over `edge` beyond either side. 320func ns_band(v: i64, lo: i64, hi: i64, edge: i64) -> i64 { 321 var d: i64 = 0 322 if v < lo { d = lo - v } 323 if v > hi { d = v - hi } 324 if d == 0 { return 1000 } 325 var s: i64 = 1000 - d * 1000 / edge 326 if s < 0 { s = 0 } 327 return s 328} 329// ★COMPREHENSIVE "is this photoreal?" -- fills out[0..4] with PER-AXIS scores (0..1000) so a graphics engine 330// can SEE which axes it fails (= its gaps), and returns the weighted OVERALL level. AI-fingerprint is axis 4, 331// ONE part of the whole, not the whole. out MUST hold >=5 i64. buf1/buf2 = scratch >= w*h*8 each. 332// axis0 SCALE-INVARIANCE (power-law spectrum) · axis1 MSCN GGD-ratio (local NSS) · axis2 GRADIENT-KURTOSIS 333// (edge heavy-tail; too-high = over-smooth CG) · axis3 COLOUR-NATURALNESS · axis4 AI-FINGERPRINT (freq artifact) 334func ns_assess(fb: *i64, w: i64, h: i64, buf1: *i64, buf2: *i64, out: *i64) -> i64 { 335 // --- axis0: scale-invariance via the octave-ratio power law --- 336 let e0: i64 = ns_detail(fb, w, h) 337 ns_down2(fb, w, h, buf1); let e1: i64 = ns_detail(buf1, w / 2, h / 2) 338 ns_down2(buf1, w / 2, h / 2, buf2); let e2: i64 = ns_detail(buf2, w / 4, h / 4) 339 ns_down2(buf2, w / 4, h / 4, buf1); let e3: i64 = ns_detail(buf1, w / 8, h / 8) 340 var a0: i64 = 0 341 if e3 >= 40 { 342 var d1: i64 = e1 343 var d2: i64 = e2 344 var d3: i64 = e3 345 if d1 < 1 { d1 = 1 } 346 if d2 < 1 { d2 = 1 } 347 if d3 < 1 { d3 = 1 } 348 let r1: i64 = e0 * 256 / d1 349 let r2: i64 = e1 * 256 / d2 350 let r3: i64 = e2 * 256 / d3 351 var mn: i64 = r1 352 var mx: i64 = r1 353 if r2 < mn { mn = r2 } 354 if r2 > mx { mx = r2 } 355 if r3 < mn { mn = r3 } 356 if r3 > mx { mx = r3 } 357 a0 = 1000 - (mx - mn) * 20 358 if a0 < 0 { a0 = 0 } 359 } 360 // --- axis1: MSCN FAMILY (BRISQUE) = ρ + horizontal/vertical pairwise-product means, all CORPUS-FIT + 361 // content-invariant. MIN => every MSCN statistic must be natural. Centers ρ=604 ph=18 pv=10 (12 real photos). 362 // Reuse buf2 (pyramid already done with it) as the MSCN coefficient buffer. 363 ns_mscn_fill(fb, w, h, buf2) 364 let rho: i64 = ns_mscn_rho_buf(buf2, w, h) 365 let ph: i64 = ns_mscn_pair(buf2, w, h, 1, 0) 366 let pv: i64 = ns_mscn_pair(buf2, w, h, 0, 1) 367 let pd1: i64 = ns_mscn_pair(buf2, w, h, 1, 1) 368 let pd2: i64 = ns_mscn_pair(buf2, w, h, 1, 0 - 1) 369 var s_rho: i64 = 1000 - ns_abs(rho - 604) * 14 / 10 370 var s_ph: i64 = 1000 - ns_abs(ph - 18) * 1000 / 40 371 var s_pv: i64 = 1000 - ns_abs(pv - 10) * 1000 / 35 372 var s_d1: i64 = 1000 - ns_abs(pd1 - 3) * 1000 / 30 // diagonals: near-zero + noisy -> GENTLE bands 373 var s_d2: i64 = 1000 - ns_abs(pd2 - 2) * 1000 / 30 374 if s_rho < 0 { s_rho = 0 } 375 if s_ph < 0 { s_ph = 0 } 376 if s_pv < 0 { s_pv = 0 } 377 if s_d1 < 0 { s_d1 = 0 } 378 if s_d2 < 0 { s_d2 = 0 } 379 var a1: i64 = s_rho 380 if s_ph < a1 { a1 = s_ph } 381 if s_pv < a1 { a1 = s_pv } 382 if s_d1 < a1 { a1 = s_d1 } 383 if s_d2 < a1 { a1 = s_d2 } 384 // --- axis2: gradient kurtosis in the natural band (too-high = over-smooth blob CG = OUR typical gap) --- 385 let a2: i64 = ns_band(ns_grad_kurt(fb, w, h), 700, K_MAGIC_3200, K_MAGIC_6500) 386 // --- axis3: colour naturalness -- GREY bites (tight low edge) but VIVID real photos pass (corpus has colour 387 // up to 96); only EXTREME oversaturation (>100, synthetic bars) bites on the high side --- 388 let cf: i64 = ns_colorful(fb, w, h) 389 var a3: i64 = 1000 390 if cf < 12 { a3 = ns_band(cf, 12, 58, 16) } 391 if cf > 100 { a3 = 1000 - (cf - 100) * 1000 / 40 } 392 if a3 < 0 { a3 = 0 } 393 // --- axis4: AI-fingerprint (periodic freq artifact; low=clean) --- 394 let a4: i64 = ns_band(ns_ai_periodicity(fb, w, h), 0, 130, 320) 395 out[0] = a0 396 out[1] = a1 397 out[2] = a2 398 out[3] = a3 399 out[4] = a4 400 // ★realism LEVEL = MIN of the CONTENT-INVARIANT axes {MSCN(1), colour(3), ai-fp(4)}. scale-invariance(0) + 401 // raw-kurtosis(2) are CONTENT-CONFOUNDED (busy scene vs smooth portrait -- corpus proved they crater real 402 // busy photos) so they stay in the gap-map (out[]) but are EXCLUDED from realism. MIN = you are only as 403 // photoreal as your WORST true-realism statistic -> no flattery (our clay's MSCN=408 tanks it, colour/ai 404 // can't rescue it), and real photos (all three axes natural) stay high. 405 var lvl: i64 = out[1] 406 if out[3] < lvl { lvl = out[3] } 407 if out[4] < lvl { lvl = out[4] } 408 return lvl 409} 410// thin wrapper: the single graded PHOTOREAL LEVEL 0..1000. 411func ns_photoreal(fb: *i64, w: i64, h: i64, buf1: *i64, buf2: *i64) -> i64 { 412 let out: *i64 = sys_mmap(64) as *i64 413 return ns_assess(fb, w, h, buf1, buf2, out) 414} 415// ★HARDENING (2026-07-23 red-team): lag-1 spatial autocorrelation of luminance, per-mille. Natural images are 416// locally COHERENT (neighbours correlated: smooth regions + structured edges) ~high; white NOISE ~0. This is the 417// axis MSCN/detail miss -- pure noise maxes MSCN but has ZERO coherence, so without this the grader rates noise 418// SEMI-REAL. Averages horizontal + vertical lag-1 correlation over the interior. 419func ns_coherence(fb: *i64, w: i64, h: i64) -> i64 { 420 let np: i64 = w*h 421 var sum: i64 = 0 422 var i: i64 = 0 423 while i < np { sum = sum + ns_lum(fb[i]); i = i + 1 } 424 let mu: i64 = sum / np 425 var num: i64 = 0 426 var den: i64 = 0 427 var y: i64 = 0 428 while y < h - 1 { 429 var x: i64 = 0 430 while x < w - 1 { 431 let c: i64 = ns_lum(fb[y*w+x]) - mu 432 let rt: i64 = ns_lum(fb[y*w+x+1]) - mu 433 let dn: i64 = ns_lum(fb[(y+1)*w+x]) - mu 434 num = num + c*rt + c*dn 435 den = den + c*c*2 436 x = x + 1 437 } 438 y = y + 1 439 } 440 if den < 1 { return 0 } 441 var r: i64 = num*1000/den 442 if r < 0 { r = 0 } 443 if r > 1000 { r = 1000 } 444 return r 445} 446// ★HARDENED assess (2026-07-23): the coherence-GATED photoreal level. ns_assess alone rates pure noise 447// SEMI-REAL (475) because MSCN maxes on noise; multiply by a coherence knee so an incoherent image CANNOT score 448// high. KNEE=900 = the lower edge of the MEASURED genuine-content coherence cluster (real photos + real renders 449// all 970-998; pure noise 0; a real photo wrecked with heavy noise 606) -> data-derived, not a dialled constant. 450// out[5]=coherence is written for transparency. Genuine content (coh>=900) is UNCHANGED; noise collapses to CLAY. 451func ns_assess2(fb: *i64, w: i64, h: i64, buf1: *i64, buf2: *i64, out: *i64) -> i64 { 452 let base: i64 = ns_assess(fb, w, h, buf1, buf2, out) 453 let coh: i64 = ns_coherence(fb, w, h) 454 out[5] = coh 455 var lvl: i64 = base 456 if coh < 900 { lvl = base * coh / 900 } 457 return lvl 458} 459func ns_verdict(level: i64) -> *u8 { 460 if level >= 650 { return "PHOTOREAL-GRADE" as *u8 } 461 if level >= 380 { return "SEMI-REAL" as *u8 } 462 if level >= 180 { return "STYLIZED-CG" as *u8 } 463 return "CLAY" as *u8 464} 465func ns_axisname(i: i64) -> *u8 { 466 if i == 0 { return "scale-invariance (multi-scale power law) -- add texture at ALL scales" as *u8 } 467 if i == 1 { return "MSCN GGD-ratio (local contrast statistics)" as *u8 } 468 if i == 2 { return "gradient-kurtosis -- too smooth: flat blobs + sparse edges, need pervasive micro-texture" as *u8 } 469 if i == 3 { return "colour-naturalness (grey / over-saturated)" as *u8 } 470 return "ai-fingerprint (periodic generation artifact)" as *u8 471}