nx_charjudge_lib.nx source
↩ module page · 432 lines · 15879 B
1// nx_charjudge_lib.nx -- CHARACTER-IMAGE JUDGE v2 core (operator 2026-08-04: "eat the debt and build to
2// actually get to sota"). v1 (composition-bimodality x palette) reproduced the human ordering and killed the
3// patch-scramble that fooled ns_assess -- but had a NAMED gameable vector: a designed smooth+noise+palette
4// collage. v2 adds the axes that close it:
5// CONTOUR COHERENCE -- permil of edge pixels on runs >= run_min (row+col propagation). A scramble cannot
6// carry a contour past its patch size; per-pixel noise has edges but no runs.
7// FACE PRESENCE -- multi-scale center-surround dark-blob (iris/pupil class, anime AND photo) + PAIR geometry
8// (two compact blobs, level, spaced). No face = capability floor 0 (a blob with no face is not 40pc of a
9// character). Ring compactness (all 8 ring points lighter) rejects line-art edges.
10// HEADLINE = MIN(composition, palette, contour, face). Thresholds = CONF ROWS (knowledge/charjudge.conf,
11// key=value, # comments; code defaults = the 2026-08-04 battery calibration) per rules 11+17.
12// All integer, zero third-party. Plain-if only in this lib (imported-else parser landmine, debt 1784673261).
13// Buffers are mmap-per-call, bounded by call count (CLI=1, gate<16) -- not a daemon lib.
14import "nx_syscalls.nx"
15
16const CHJ_CELL: i64 = 16
17const CHJ_BUCKETS: i64 = 4096
18const CHJ_CAND_MAX: i64 = 128
19const CHJ_MAGIC_65536: i64 = 65536
20const CHJ_C_SMOOTH_T: i64 = 0
21const CHJ_C_DETAIL_T: i64 = 1
22const CHJ_C_SMOOTH_DIV: i64 = 2
23const CHJ_C_DETAIL_DIV: i64 = 3
24const CHJ_C_PAL_DIV: i64 = 4
25const CHJ_C_EDGE_T: i64 = 5
26const CHJ_C_RUN_MIN: i64 = 6
27const CHJ_C_COH_DIV: i64 = 7
28const CHJ_C_EYE_T: i64 = 8
29const CHJ_C_FACE_REQ: i64 = 9
30const CHJ_NCONF: i64 = 10
31
32func chj_conf_defaults(cf: *i64) -> i64 {
33 cf[CHJ_C_SMOOTH_T] = 2
34 cf[CHJ_C_DETAIL_T] = 10
35 cf[CHJ_C_SMOOTH_DIV] = 150
36 cf[CHJ_C_DETAIL_DIV] = 300
37 cf[CHJ_C_PAL_DIV] = 512
38 cf[CHJ_C_EDGE_T] = 12
39 cf[CHJ_C_RUN_MIN] = 24
40 cf[CHJ_C_COH_DIV] = 60
41 cf[CHJ_C_EYE_T] = 45
42 cf[CHJ_C_FACE_REQ] = 1
43 return 0
44}
45func chj_keyeq(buf: *u8, at: i64, end: i64, key: *u8) -> i64 {
46 var k: i64 = 0
47 var i: i64 = at
48 while key[k] != (0 as u8) {
49 if i >= end { return 0 }
50 if buf[i] != key[k] { return 0 }
51 i = i + 1
52 k = k + 1
53 }
54 if i >= end { return 0 }
55 if buf[i] != (61 as u8) { return 0 }
56 return 1
57}
58func chj_slot_for(buf: *u8, at: i64, end: i64) -> i64 {
59 if chj_keyeq(buf, at, end, "smooth_t" as *u8) == 1 { return CHJ_C_SMOOTH_T }
60 if chj_keyeq(buf, at, end, "detail_t" as *u8) == 1 { return CHJ_C_DETAIL_T }
61 if chj_keyeq(buf, at, end, "smooth_div" as *u8) == 1 { return CHJ_C_SMOOTH_DIV }
62 if chj_keyeq(buf, at, end, "detail_div" as *u8) == 1 { return CHJ_C_DETAIL_DIV }
63 if chj_keyeq(buf, at, end, "pal_div" as *u8) == 1 { return CHJ_C_PAL_DIV }
64 if chj_keyeq(buf, at, end, "edge_t" as *u8) == 1 { return CHJ_C_EDGE_T }
65 if chj_keyeq(buf, at, end, "run_min" as *u8) == 1 { return CHJ_C_RUN_MIN }
66 if chj_keyeq(buf, at, end, "coh_div" as *u8) == 1 { return CHJ_C_COH_DIV }
67 if chj_keyeq(buf, at, end, "eye_t" as *u8) == 1 { return CHJ_C_EYE_T }
68 if chj_keyeq(buf, at, end, "face_required" as *u8) == 1 { return CHJ_C_FACE_REQ }
69 return 0-1
70}
71func chj_conf_load(path: *u8, cf: *i64) -> i64 {
72 chj_conf_defaults(cf)
73 if (path as i64) == 0 { return 0 }
74 let lenp: *i64 = sys_mmap(16) as *i64
75 let buf: *u8 = sys_read_file(path, lenp)
76 if (buf as i64) == 0 { return 0 }
77 let n: i64 = lenp[0]
78 var i: i64 = 0
79 while i < n {
80 var e: i64 = i
81 var go: i64 = 1
82 while go == 1 {
83 if e >= n { go = 0 }
84 if go == 1 { if buf[e] == (10 as u8) { go = 0 } }
85 if go == 1 { e = e + 1 }
86 }
87 if e > i { if buf[i] != (35 as u8) {
88 let slot: i64 = chj_slot_for(buf, i, e)
89 if slot >= 0 {
90 var p: i64 = i
91 var col: i64 = 0-1
92 var g2: i64 = 1
93 while g2 == 1 {
94 if p >= e { g2 = 0 }
95 if g2 == 1 { if buf[p] == (61 as u8) { col = p; g2 = 0 } }
96 if g2 == 1 { p = p + 1 }
97 }
98 if col >= 0 {
99 var v: i64 = 0
100 var neg: i64 = 0
101 var q: i64 = col + 1
102 if q < e { if buf[q] == (45 as u8) { neg = 1; q = q + 1 } }
103 while q < e {
104 let c: i64 = buf[q] as i64
105 if c >= 48 { if c <= 57 { v = v*10 + (c-48) } }
106 q = q + 1
107 }
108 if neg == 1 { v = 0-v }
109 cf[slot] = v
110 }
111 }
112 } }
113 i = e + 1
114 }
115 return 0
116}
117
118func chj_luma(fb: *i64, w: i64, h: i64, luma: *i64) -> i64 {
119 var q: i64 = 0
120 while q < w*h {
121 let px: i64 = fb[q]
122 let r: i64 = px&255
123 let g: i64 = (px>>8)&255
124 let b: i64 = (px>>16)&255
125 luma[q] = (r*299 + g*587 + b*114)/1000
126 q = q + 1
127 }
128 return 0
129}
130
131// composition bimodality: 16px cells classed smooth/detail by mean |grad| per pixel. out[0]=smooth_permil out[1]=detail_permil
132func chj_cells(luma: *i64, w: i64, h: i64, cf: *i64, outp: *i64) -> i64 {
133 let cw: i64 = w/CHJ_CELL
134 let chh: i64 = h/CHJ_CELL
135 var smooth: i64 = 0
136 var detail: i64 = 0
137 var cy: i64 = 0
138 while cy < chh {
139 var cx: i64 = 0
140 while cx < cw {
141 var e: i64 = 0
142 var yy: i64 = 0
143 while yy < CHJ_CELL {
144 var xx: i64 = 0
145 while xx < CHJ_CELL {
146 let ix: i64 = cx*CHJ_CELL + xx
147 let iy: i64 = cy*CHJ_CELL + yy
148 if ix < w-1 { if iy < h-1 {
149 let l0: i64 = luma[iy*w + ix]
150 var gx: i64 = luma[iy*w + ix + 1] - l0
151 var gy: i64 = luma[(iy+1)*w + ix] - l0
152 if gx < 0 { gx = 0-gx }
153 if gy < 0 { gy = 0-gy }
154 e = e + gx + gy
155 } }
156 xx = xx + 1
157 }
158 yy = yy + 1
159 }
160 let me: i64 = e/(CHJ_CELL*CHJ_CELL)
161 if me < cf[CHJ_C_SMOOTH_T] { smooth = smooth + 1 }
162 if me > cf[CHJ_C_DETAIL_T] { detail = detail + 1 }
163 cx = cx + 1
164 }
165 cy = cy + 1
166 }
167 let ncell: i64 = cw*chh
168 if ncell < 1 { outp[0] = 0; outp[1] = 0; return 0 }
169 outp[0] = smooth*1000/ncell
170 outp[1] = detail*1000/ncell
171 return 0
172}
173
174func chj_palette(fb: *i64, w: i64, h: i64) -> i64 {
175 let seen: *u8 = sys_mmap(CHJ_BUCKETS)
176 var q: i64 = 0
177 while q < w*h {
178 let px: i64 = fb[q]
179 let r: i64 = px&255
180 let g: i64 = (px>>8)&255
181 let b: i64 = (px>>16)&255
182 seen[(r/16)*256 + (g/16)*16 + b/16] = 1 as u8
183 q = q + 1
184 }
185 var pal: i64 = 0
186 q = 0
187 while q < CHJ_BUCKETS { if seen[q] == (1 as u8) { pal = pal + 1 } q = q + 1 }
188 return pal
189}
190
191// contour coherence: permil of edge pixels on ORIENTATION-STABLE runs >= run_min. Plain run-length was
192// designed-out at gate time: dense noise is fully CONNECTED (runs grow trivially) and a patch-scramble's
193// aligned seams are long lines -- so a run only extends between neighbors in the SAME quantized gradient
194// orientation bin (4 bins). Noise dies at ~1/4 per step; real contours hold a bin over long arcs.
195// outp[0]=coherent_permil outp[1]=edge_total. returns the banded axis.
196func chj_contour(luma: *i64, w: i64, h: i64, cf: *i64, outp: *i64) -> i64 {
197 let et: i64 = cf[CHJ_C_EDGE_T]
198 let rm: i64 = cf[CHJ_C_RUN_MIN]
199 let emap: *u8 = sys_mmap(w*h)
200 let bmap: *u8 = sys_mmap(w*h)
201 var etotal: i64 = 0
202 var y: i64 = 0
203 while y < h-1 {
204 var x: i64 = 0
205 while x < w-1 {
206 let l0: i64 = luma[y*w+x]
207 let sgx: i64 = luma[y*w+x+1] - l0
208 let sgy: i64 = luma[(y+1)*w+x] - l0
209 var agx: i64 = sgx
210 if agx < 0 { agx = 0-agx }
211 var agy: i64 = sgy
212 if agy < 0 { agy = 0-agy }
213 if agx+agy > et {
214 emap[y*w+x] = 1 as u8
215 etotal = etotal + 1
216 // 8 orientation bins (sign gx, sign gy, |gx|>|gy|): 4 bins percolate through dense
217 // noise (58 pct continuation over 3 neighbors, supercritical -- gate T4 caught it);
218 // 8 bins = ~30 pct, subcritical, noise runs die exponentially, straight contours hold.
219 var bin: i64 = 0
220 if sgx < 0 { bin = bin + 4 }
221 if sgy < 0 { bin = bin + 2 }
222 if agx > agy { bin = bin + 1 }
223 bmap[y*w+x] = bin as u8
224 }
225 x = x + 1
226 }
227 y = y + 1
228 }
229 var coh: i64 = 0
230 let prev: *i64 = sys_mmap(w*8) as *i64
231 let curr: *i64 = sys_mmap(w*8) as *i64
232 let prevb: *i64 = sys_mmap(w*8) as *i64
233 let currb: *i64 = sys_mmap(w*8) as *i64
234 var i: i64 = 0
235 while i < w { prev[i] = 0; prevb[i] = 0-1; i = i + 1 }
236 y = 0
237 while y < h {
238 var x2: i64 = 0
239 while x2 < w {
240 var r: i64 = 0
241 var b: i64 = 0-1
242 if emap[y*w+x2] == (1 as u8) {
243 b = bmap[y*w+x2] as i64
244 var m: i64 = 0
245 if prevb[x2] == b { m = prev[x2] }
246 if x2 > 0 { if prevb[x2-1] == b { if prev[x2-1] > m { m = prev[x2-1] } } }
247 if x2 < w-1 { if prevb[x2+1] == b { if prev[x2+1] > m { m = prev[x2+1] } } }
248 r = m + 1
249 if r >= rm { coh = coh + 1 }
250 }
251 curr[x2] = r
252 currb[x2] = b
253 x2 = x2 + 1
254 }
255 var x3: i64 = 0
256 while x3 < w { prev[x3] = curr[x3]; prevb[x3] = currb[x3]; x3 = x3 + 1 }
257 y = y + 1
258 }
259 let prevh: *i64 = sys_mmap(h*8) as *i64
260 let currh: *i64 = sys_mmap(h*8) as *i64
261 let prevhb: *i64 = sys_mmap(h*8) as *i64
262 let currhb: *i64 = sys_mmap(h*8) as *i64
263 i = 0
264 while i < h { prevh[i] = 0; prevhb[i] = 0-1; i = i + 1 }
265 var x4: i64 = 0
266 while x4 < w {
267 var y2: i64 = 0
268 while y2 < h {
269 var r2: i64 = 0
270 var b2: i64 = 0-1
271 if emap[y2*w+x4] == (1 as u8) {
272 b2 = bmap[y2*w+x4] as i64
273 var m2: i64 = 0
274 if prevhb[y2] == b2 { m2 = prevh[y2] }
275 if y2 > 0 { if prevhb[y2-1] == b2 { if prevh[y2-1] > m2 { m2 = prevh[y2-1] } } }
276 if y2 < h-1 { if prevhb[y2+1] == b2 { if prevh[y2+1] > m2 { m2 = prevh[y2+1] } } }
277 r2 = m2 + 1
278 if r2 >= rm { coh = coh + 1 }
279 }
280 currh[y2] = r2
281 currhb[y2] = b2
282 y2 = y2 + 1
283 }
284 var y3: i64 = 0
285 while y3 < h { prevh[y3] = currh[y3]; prevhb[y3] = currhb[y3]; y3 = y3 + 1 }
286 x4 = x4 + 1
287 }
288 var perm: i64 = 0
289 if etotal > 0 { perm = coh*1000/etotal }
290 if etotal < w*h/400 { perm = 0 }
291 outp[0] = perm
292 outp[1] = etotal
293 var axis: i64 = perm*1000/cf[CHJ_C_COH_DIV]
294 if axis > 1000 { axis = 1000 }
295 return axis
296}
297
298// one face scan at the current scale: center-surround compact dark blobs -> level spaced pair
299func chj_face_scan(l: *i64, w: i64, h: i64, cf: *i64) -> i64 {
300 let eyet: i64 = cf[CHJ_C_EYE_T]
301 let cx: *i64 = sys_mmap(CHJ_CAND_MAX*8) as *i64
302 let cy: *i64 = sys_mmap(CHJ_CAND_MAX*8) as *i64
303 var nc: i64 = 0
304 var y: i64 = 5
305 while y < h-5 {
306 var x: i64 = 5
307 while x < w-5 {
308 let c: i64 = l[y*w+x]
309 var okc: i64 = 1
310 let p1: i64 = l[y*w + x+4]
311 let p2: i64 = l[y*w + x-4]
312 let p3: i64 = l[(y+4)*w + x]
313 let p4: i64 = l[(y-4)*w + x]
314 let p5: i64 = l[(y+3)*w + x+3]
315 let p6: i64 = l[(y+3)*w + x-3]
316 let p7: i64 = l[(y-3)*w + x+3]
317 let p8: i64 = l[(y-3)*w + x-3]
318 if p1 <= c + eyet/2 { okc = 0 }
319 if p2 <= c + eyet/2 { okc = 0 }
320 if p3 <= c + eyet/2 { okc = 0 }
321 if p4 <= c + eyet/2 { okc = 0 }
322 if p5 <= c + eyet/2 { okc = 0 }
323 if p6 <= c + eyet/2 { okc = 0 }
324 if p7 <= c + eyet/2 { okc = 0 }
325 if p8 <= c + eyet/2 { okc = 0 }
326 if okc == 1 { if (p1+p2+p3+p4+p5+p6+p7+p8)/8 - c > eyet {
327 var near: i64 = 0
328 var k: i64 = 0
329 while k < nc {
330 var ddx: i64 = cx[k]-x
331 if ddx < 0 { ddx = 0-ddx }
332 var ddy: i64 = cy[k]-y
333 if ddy < 0 { ddy = 0-ddy }
334 if ddx < 6 { if ddy < 6 { near = 1 } }
335 k = k + 1
336 }
337 if near == 0 { if nc < CHJ_CAND_MAX { cx[nc] = x; cy[nc] = y; nc = nc + 1 } }
338 } }
339 x = x + 2
340 }
341 y = y + 2
342 }
343 var i: i64 = 0
344 while i < nc {
345 var j: i64 = i+1
346 while j < nc {
347 var dyv: i64 = cy[i]-cy[j]
348 if dyv < 0 { dyv = 0-dyv }
349 var dxv: i64 = cx[i]-cx[j]
350 if dxv < 0 { dxv = 0-dxv }
351 if dyv <= 5 { if dxv >= 8 { if dxv <= 44 { return 1 } } }
352 j = j + 1
353 }
354 i = i + 1
355 }
356 return 0
357}
358
359func chj_face(luma: *i64, w: i64, h: i64, cf: *i64) -> i64 {
360 var cw: i64 = w
361 var chh: i64 = h
362 var cur: *i64 = sys_mmap(w*h*8) as *i64
363 var i: i64 = 0
364 while i < w*h { cur[i] = luma[i]; i = i + 1 }
365 var done: i64 = 0
366 var sc: i64 = 0
367 while sc < 3 {
368 if done == 0 { if chj_face_scan(cur, cw, chh, cf) == 1 { return 1000 } }
369 let nw: i64 = cw/2
370 let nh: i64 = chh/2
371 if nw < 24 { done = 1 }
372 if nh < 24 { done = 1 }
373 if done == 0 {
374 let nxt: *i64 = sys_mmap(nw*nh*8) as *i64
375 var y: i64 = 0
376 while y < nh {
377 var x: i64 = 0
378 while x < nw {
379 nxt[y*nw+x] = (cur[(2*y)*cw + 2*x] + cur[(2*y)*cw + 2*x+1] + cur[(2*y+1)*cw + 2*x] + cur[(2*y+1)*cw + 2*x+1])/4
380 x = x + 1
381 }
382 y = y + 1
383 }
384 cur = nxt
385 cw = nw
386 chh = nh
387 }
388 sc = sc + 1
389 }
390 return 0
391}
392
393// the judge. out: 0 composition 1 palette_axis 2 contour_axis 3 face_axis 4 HEADLINE
394// 5 smooth_permil 6 detail_permil 7 pal_buckets 8 coherent_permil 9 edge_total
395func chj_judge(fb: *i64, w: i64, h: i64, confpath: *u8, out: *i64) -> i64 {
396 let cf: *i64 = sys_mmap(CHJ_NCONF*8) as *i64
397 chj_conf_load(confpath, cf)
398 let luma: *i64 = sys_mmap(w*h*8) as *i64
399 chj_luma(fb, w, h, luma)
400 let cellout: *i64 = sys_mmap(16) as *i64
401 chj_cells(luma, w, h, cf, cellout)
402 let sp: i64 = cellout[0]
403 let dp: i64 = cellout[1]
404 var sb: i64 = sp*1000/cf[CHJ_C_SMOOTH_DIV]
405 if sb > 1000 { sb = 1000 }
406 var db: i64 = dp*1000/cf[CHJ_C_DETAIL_DIV]
407 if db > 1000 { db = 1000 }
408 var comp: i64 = sb
409 if db < comp { comp = db }
410 let pal: i64 = chj_palette(fb, w, h)
411 var palax: i64 = pal*1000/cf[CHJ_C_PAL_DIV]
412 if palax > 1000 { palax = 1000 }
413 let conout: *i64 = sys_mmap(16) as *i64
414 let conax: i64 = chj_contour(luma, w, h, cf, conout)
415 var faceax: i64 = 1000
416 if cf[CHJ_C_FACE_REQ] == 1 { faceax = chj_face(luma, w, h, cf) }
417 var head: i64 = comp
418 if palax < head { head = palax }
419 if conax < head { head = conax }
420 if faceax < head { head = faceax }
421 out[0] = comp
422 out[1] = palax
423 out[2] = conax
424 out[3] = faceax
425 out[4] = head
426 out[5] = sp
427 out[6] = dp
428 out[7] = pal
429 out[8] = conout[0]
430 out[9] = conout[1]
431 return head
432}