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}