nx_charjudge_lib.nx source
↩ module page · 653 lines · 29466 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
30// ★EYE-PAIR GEOMETRY. Every one of these was an absolute PIXEL literal, which made the face axis a function
31// of RENDER SIZE rather than of the subject: on ONE mesh, 256x384 scores face_axis 1000 and 512x768 scores
32// 0. A feature window measured in raw pixels judges the resolution, and it returns 0, which reads as "the
33// subject has no face". They are config now, and the defaults below are EXACTLY the previous literals, so
34// behaviour at any resolution that already worked is unchanged by construction.
35const CHJ_C_EYE_DY: i64 = 10 // max vertical misalignment of a candidate pair
36const CHJ_C_EYE_DXMIN: i64 = 11 // min horizontal separation of a pair
37const CHJ_C_EYE_DXMAX: i64 = 12 // max horizontal separation of a pair
38const CHJ_C_PROBE_FAR: i64 = 13 // axial centre-surround probe radius
39const CHJ_C_PROBE_DIAG: i64 = 14 // diagonal centre-surround probe radius
40const CHJ_C_DEDUP_R: i64 = 15 // candidate dedup radius
41const CHJ_C_BORDER: i64 = 16 // scan border margin
42const CHJ_C_STRIDE: i64 = 17 // scan stride
43const CHJ_C_PYR_MIN: i64 = 18 // smallest pyramid level edge the scan is still meaningful on
44// ★THE WIDTH THESE PIXEL GEOMETRIES WERE CALIBRATED AT. A centre-surround probe of FIXED radius is
45// scale-variant BY CONSTRUCTION: the feature it hunts (an eye) grows with the render, so at twice the
46// width the probe ring falls INSIDE the dark region and the candidate never fires. Measured on one mesh:
47// 256x384 yields 5 candidates and face_axis 1000; 512x768 yields at most 2 across five pyramid levels and
48// face_axis 0. Lengths are therefore scaled by w/ref_w at each level; ref_w=256 makes that scaling exactly
49// 1.0 at the width the numbers came from, so every previously-working resolution is unchanged.
50// ⚠eye_t is NOT scaled -- it is a CONTRAST threshold in luma, not a length.
51const CHJ_C_REF_W: i64 = 19
52// ★CONTOUR SMOOTHING SCALE, EXPRESSED AS A DIVISOR OF run_min RATHER THAN AS ITS OWN LENGTH. The contour
53// axis defines a contour as an orientation-stable run of at least run_min pixels, so the scale at which the
54// gradient is estimated must be a fixed fraction of that SAME length. Authoring it as an independent number
55// is how two calibrated constants silently drift apart the moment either one is retuned.
56const CHJ_C_CONTOUR_BLUR_DIV: i64 = 20
57const CHJ_NCONF: i64 = 21
58// out[] slot count. The CLI allocated a raw 96 bytes, which is exactly 12 slots -- one short of the
59// diagnostics below. A byte count where a slot count belongs is how an array grows past its buffer
60// without anyone noticing, because mmap rounds to a page and the overflow reads back plausible.
61const CHJ_NOUT: i64 = 16
62
63func chj_conf_defaults(cf: *i64) -> i64 {
64 cf[CHJ_C_SMOOTH_T] = 2
65 cf[CHJ_C_DETAIL_T] = 10
66 cf[CHJ_C_SMOOTH_DIV] = 150
67 cf[CHJ_C_DETAIL_DIV] = 300
68 cf[CHJ_C_PAL_DIV] = 512
69 cf[CHJ_C_EDGE_T] = 12
70 cf[CHJ_C_RUN_MIN] = 24
71 cf[CHJ_C_COH_DIV] = 60
72 cf[CHJ_C_EYE_T] = 45
73 cf[CHJ_C_FACE_REQ] = 1
74 cf[CHJ_C_EYE_DY] = 5
75 cf[CHJ_C_EYE_DXMIN] = 8
76 cf[CHJ_C_EYE_DXMAX] = 44
77 cf[CHJ_C_PROBE_FAR] = 4
78 cf[CHJ_C_PROBE_DIAG] = 3
79 cf[CHJ_C_DEDUP_R] = 6
80 cf[CHJ_C_BORDER] = 5
81 cf[CHJ_C_STRIDE] = 2
82 cf[CHJ_C_PYR_MIN] = 24
83 cf[CHJ_C_REF_W] = 256
84 // radius = run_min/8 -> a 7px kernel against a 24px run: wide enough to average out surface texture
85 // whose period is a few pixels, narrow enough that a contour of the length this axis calls coherent
86 // survives it. Validated below by re-running the two textured/untextured A/B pairs and the adversarial
87 // scramble and collage fixtures -- not chosen by taste.
88 cf[CHJ_C_CONTOUR_BLUR_DIV] = 8
89 return 0
90}
91func chj_keyeq(buf: *u8, at: i64, end: i64, key: *u8) -> i64 {
92 var k: i64 = 0
93 var i: i64 = at
94 while key[k] != (0 as u8) {
95 if i >= end { return 0 }
96 if buf[i] != key[k] { return 0 }
97 i = i + 1
98 k = k + 1
99 }
100 if i >= end { return 0 }
101 if buf[i] != (61 as u8) { return 0 }
102 return 1
103}
104func chj_slot_for(buf: *u8, at: i64, end: i64) -> i64 {
105 if chj_keyeq(buf, at, end, "eye_dy" as *u8) == 1 { return CHJ_C_EYE_DY }
106 if chj_keyeq(buf, at, end, "eye_dxmin" as *u8) == 1 { return CHJ_C_EYE_DXMIN }
107 if chj_keyeq(buf, at, end, "eye_dxmax" as *u8) == 1 { return CHJ_C_EYE_DXMAX }
108 if chj_keyeq(buf, at, end, "probe_far" as *u8) == 1 { return CHJ_C_PROBE_FAR }
109 if chj_keyeq(buf, at, end, "probe_diag" as *u8) == 1 { return CHJ_C_PROBE_DIAG }
110 if chj_keyeq(buf, at, end, "dedup_r" as *u8) == 1 { return CHJ_C_DEDUP_R }
111 if chj_keyeq(buf, at, end, "border" as *u8) == 1 { return CHJ_C_BORDER }
112 if chj_keyeq(buf, at, end, "stride" as *u8) == 1 { return CHJ_C_STRIDE }
113 if chj_keyeq(buf, at, end, "pyr_min" as *u8) == 1 { return CHJ_C_PYR_MIN }
114 if chj_keyeq(buf, at, end, "ref_w" as *u8) == 1 { return CHJ_C_REF_W }
115 if chj_keyeq(buf, at, end, "contour_blur_div" as *u8) == 1 { return CHJ_C_CONTOUR_BLUR_DIV }
116 if chj_keyeq(buf, at, end, "smooth_t" as *u8) == 1 { return CHJ_C_SMOOTH_T }
117 if chj_keyeq(buf, at, end, "detail_t" as *u8) == 1 { return CHJ_C_DETAIL_T }
118 if chj_keyeq(buf, at, end, "smooth_div" as *u8) == 1 { return CHJ_C_SMOOTH_DIV }
119 if chj_keyeq(buf, at, end, "detail_div" as *u8) == 1 { return CHJ_C_DETAIL_DIV }
120 if chj_keyeq(buf, at, end, "pal_div" as *u8) == 1 { return CHJ_C_PAL_DIV }
121 if chj_keyeq(buf, at, end, "edge_t" as *u8) == 1 { return CHJ_C_EDGE_T }
122 if chj_keyeq(buf, at, end, "run_min" as *u8) == 1 { return CHJ_C_RUN_MIN }
123 if chj_keyeq(buf, at, end, "coh_div" as *u8) == 1 { return CHJ_C_COH_DIV }
124 if chj_keyeq(buf, at, end, "eye_t" as *u8) == 1 { return CHJ_C_EYE_T }
125 if chj_keyeq(buf, at, end, "face_required" as *u8) == 1 { return CHJ_C_FACE_REQ }
126 return 0-1
127}
128func chj_conf_load(path: *u8, cf: *i64) -> i64 {
129 chj_conf_defaults(cf)
130 if (path as i64) == 0 { return 0 }
131 let lenp: *i64 = sys_mmap(16) as *i64
132 let buf: *u8 = sys_read_file(path, lenp)
133 if (buf as i64) == 0 { return 0 }
134 let n: i64 = lenp[0]
135 var i: i64 = 0
136 while i < n {
137 var e: i64 = i
138 var go: i64 = 1
139 while go == 1 {
140 if e >= n { go = 0 }
141 if go == 1 { if buf[e] == (10 as u8) { go = 0 } }
142 if go == 1 { e = e + 1 }
143 }
144 if e > i { if buf[i] != (35 as u8) {
145 let slot: i64 = chj_slot_for(buf, i, e)
146 if slot >= 0 {
147 var p: i64 = i
148 var col: i64 = 0-1
149 var g2: i64 = 1
150 while g2 == 1 {
151 if p >= e { g2 = 0 }
152 if g2 == 1 { if buf[p] == (61 as u8) { col = p; g2 = 0 } }
153 if g2 == 1 { p = p + 1 }
154 }
155 if col >= 0 {
156 var v: i64 = 0
157 var neg: i64 = 0
158 var q: i64 = col + 1
159 if q < e { if buf[q] == (45 as u8) { neg = 1; q = q + 1 } }
160 while q < e {
161 let c: i64 = buf[q] as i64
162 if c >= 48 { if c <= 57 { v = v*10 + (c-48) } }
163 q = q + 1
164 }
165 if neg == 1 { v = 0-v }
166 cf[slot] = v
167 }
168 }
169 } }
170 i = e + 1
171 }
172 return 0
173}
174
175func chj_luma(fb: *i64, w: i64, h: i64, luma: *i64) -> i64 {
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 luma[q] = (r*299 + g*587 + b*114)/1000
183 q = q + 1
184 }
185 return 0
186}
187
188// composition bimodality: 16px cells classed smooth/detail by mean |grad| per pixel. out[0]=smooth_permil out[1]=detail_permil
189func chj_cells(luma: *i64, w: i64, h: i64, cf: *i64, outp: *i64) -> i64 {
190 let cw: i64 = w/CHJ_CELL
191 let chh: i64 = h/CHJ_CELL
192 var smooth: i64 = 0
193 var detail: i64 = 0
194 var cy: i64 = 0
195 while cy < chh {
196 var cx: i64 = 0
197 while cx < cw {
198 var e: i64 = 0
199 var yy: i64 = 0
200 while yy < CHJ_CELL {
201 var xx: i64 = 0
202 while xx < CHJ_CELL {
203 let ix: i64 = cx*CHJ_CELL + xx
204 let iy: i64 = cy*CHJ_CELL + yy
205 if ix < w-1 { if iy < h-1 {
206 let l0: i64 = luma[iy*w + ix]
207 var gx: i64 = luma[iy*w + ix + 1] - l0
208 var gy: i64 = luma[(iy+1)*w + ix] - l0
209 if gx < 0 { gx = 0-gx }
210 if gy < 0 { gy = 0-gy }
211 e = e + gx + gy
212 } }
213 xx = xx + 1
214 }
215 yy = yy + 1
216 }
217 let me: i64 = e/(CHJ_CELL*CHJ_CELL)
218 if me < cf[CHJ_C_SMOOTH_T] { smooth = smooth + 1 }
219 if me > cf[CHJ_C_DETAIL_T] { detail = detail + 1 }
220 cx = cx + 1
221 }
222 cy = cy + 1
223 }
224 let ncell: i64 = cw*chh
225 if ncell < 1 { outp[0] = 0; outp[1] = 0; return 0 }
226 outp[0] = smooth*1000/ncell
227 outp[1] = detail*1000/ncell
228 return 0
229}
230
231func chj_palette(fb: *i64, w: i64, h: i64) -> i64 {
232 let seen: *u8 = sys_mmap(CHJ_BUCKETS)
233 var q: i64 = 0
234 while q < w*h {
235 let px: i64 = fb[q]
236 let r: i64 = px&255
237 let g: i64 = (px>>8)&255
238 let b: i64 = (px>>16)&255
239 seen[(r/16)*256 + (g/16)*16 + b/16] = 1 as u8
240 q = q + 1
241 }
242 var pal: i64 = 0
243 q = 0
244 while q < CHJ_BUCKETS { if seen[q] == (1 as u8) { pal = pal + 1 } q = q + 1 }
245 return pal
246}
247
248// contour coherence: permil of edge pixels on ORIENTATION-STABLE runs >= run_min. Plain run-length was
249// designed-out at gate time: dense noise is fully CONNECTED (runs grow trivially) and a patch-scramble's
250// aligned seams are long lines -- so a run only extends between neighbors in the SAME quantized gradient
251// orientation bin (4 bins). Noise dies at ~1/4 per step; real contours hold a bin over long arcs.
252// outp[0]=coherent_permil outp[1]=edge_total. returns the banded axis.
253func chj_contour(luma: *i64, w: i64, h: i64, cf: *i64, outp: *i64) -> i64 {
254 let et: i64 = cf[CHJ_C_EDGE_T]
255 let rm: i64 = cf[CHJ_C_RUN_MIN]
256 // ★CONTOURS ARE LOW-FREQUENCY STRUCTURE AND WERE BEING MEASURED ON THE HIGH-FREQUENCY CHANNEL.
257 // The orientation bin came from 1-pixel finite differences on RAW luma, so surface texture perturbs the
258 // bin ALONG a silhouette and the run breaks before it reaches run_min. Measured on two independent A/B
259 // pairs (the same body, untextured vs PBR-textured): coherent runs collapsed 304->13 and 644->52, so the
260 // axis reported an 8.9x and 6.8x REGRESSION for landing the skin bake while composition, palette and
261 // detail all improved. An axis that punishes the exact improvement it exists to reward is worse than no
262 // axis at all, because its verdict is authoritative and inverted.
263 // Smoothing first is also what makes the adversarial kills STRONGER, not weaker: per-pixel noise falls
264 // below edge_t entirely instead of being counted and then out-competed, while a patch seam is a real
265 // step edge that survives any small kernel.
266 var br: i64 = rm/cf[CHJ_C_CONTOUR_BLUR_DIV]
267 if br < 1 { br = 1 }
268 let bh: *i64 = sys_mmap(w*h*8) as *i64
269 let lb: *i64 = sys_mmap(w*h*8) as *i64
270 var by: i64 = 0
271 while by < h {
272 var bx: i64 = 0
273 while bx < w {
274 var acc: i64 = 0
275 var cnt: i64 = 0
276 var k: i64 = 0-br
277 while k <= br {
278 var sx: i64 = bx+k
279 if sx < 0 { sx = 0 }
280 if sx > w-1 { sx = w-1 }
281 acc = acc + luma[by*w+sx]
282 cnt = cnt + 1
283 k = k + 1
284 }
285 bh[by*w+bx] = acc/cnt
286 bx = bx + 1
287 }
288 by = by + 1
289 }
290 by = 0
291 while by < h {
292 var bx2: i64 = 0
293 while bx2 < w {
294 var acc2: i64 = 0
295 var cnt2: i64 = 0
296 var k2: i64 = 0-br
297 while k2 <= br {
298 var sy: i64 = by+k2
299 if sy < 0 { sy = 0 }
300 if sy > h-1 { sy = h-1 }
301 acc2 = acc2 + bh[sy*w+bx2]
302 cnt2 = cnt2 + 1
303 k2 = k2 + 1
304 }
305 lb[by*w+bx2] = acc2/cnt2
306 bx2 = bx2 + 1
307 }
308 by = by + 1
309 }
310 // the horizontal pass is scratch -- free it before the edge maps allocate. Resource excellence is a
311 // shipping criterion: this organ is forked once per image across a 169-render corpus, and an unfreed
312 // w*h*8 buffer is 4.4 MB per 1200x460 frame carried for the rest of the run for no reason.
313 sys_munmap(bh as *u8, w*h*8)
314 let emap: *u8 = sys_mmap(w*h)
315 let bmap: *u8 = sys_mmap(w*h)
316 var etotal: i64 = 0
317 var y: i64 = 0
318 while y < h-1 {
319 var x: i64 = 0
320 while x < w-1 {
321 let l0: i64 = lb[y*w+x]
322 let sgx: i64 = lb[y*w+x+1] - l0
323 let sgy: i64 = lb[(y+1)*w+x] - l0
324 var agx: i64 = sgx
325 if agx < 0 { agx = 0-agx }
326 var agy: i64 = sgy
327 if agy < 0 { agy = 0-agy }
328 if agx+agy > et {
329 emap[y*w+x] = 1 as u8
330 etotal = etotal + 1
331 // 8 orientation bins (sign gx, sign gy, |gx|>|gy|): 4 bins percolate through dense
332 // noise (58 pct continuation over 3 neighbors, supercritical -- gate T4 caught it);
333 // 8 bins = ~30 pct, subcritical, noise runs die exponentially, straight contours hold.
334 var bin: i64 = 0
335 if sgx < 0 { bin = bin + 4 }
336 if sgy < 0 { bin = bin + 2 }
337 if agx > agy { bin = bin + 1 }
338 bmap[y*w+x] = bin as u8
339 }
340 x = x + 1
341 }
342 y = y + 1
343 }
344 var coh: i64 = 0
345 let prev: *i64 = sys_mmap(w*8) as *i64
346 let curr: *i64 = sys_mmap(w*8) as *i64
347 let prevb: *i64 = sys_mmap(w*8) as *i64
348 let currb: *i64 = sys_mmap(w*8) as *i64
349 var i: i64 = 0
350 while i < w { prev[i] = 0; prevb[i] = 0-1; i = i + 1 }
351 y = 0
352 while y < h {
353 var x2: i64 = 0
354 while x2 < w {
355 var r: i64 = 0
356 var b: i64 = 0-1
357 if emap[y*w+x2] == (1 as u8) {
358 b = bmap[y*w+x2] as i64
359 var m: i64 = 0
360 if prevb[x2] == b { m = prev[x2] }
361 if x2 > 0 { if prevb[x2-1] == b { if prev[x2-1] > m { m = prev[x2-1] } } }
362 if x2 < w-1 { if prevb[x2+1] == b { if prev[x2+1] > m { m = prev[x2+1] } } }
363 r = m + 1
364 if r >= rm { coh = coh + 1 }
365 }
366 curr[x2] = r
367 currb[x2] = b
368 x2 = x2 + 1
369 }
370 var x3: i64 = 0
371 while x3 < w { prev[x3] = curr[x3]; prevb[x3] = currb[x3]; x3 = x3 + 1 }
372 y = y + 1
373 }
374 let prevh: *i64 = sys_mmap(h*8) as *i64
375 let currh: *i64 = sys_mmap(h*8) as *i64
376 let prevhb: *i64 = sys_mmap(h*8) as *i64
377 let currhb: *i64 = sys_mmap(h*8) as *i64
378 i = 0
379 while i < h { prevh[i] = 0; prevhb[i] = 0-1; i = i + 1 }
380 var x4: i64 = 0
381 while x4 < w {
382 var y2: i64 = 0
383 while y2 < h {
384 var r2: i64 = 0
385 var b2: i64 = 0-1
386 if emap[y2*w+x4] == (1 as u8) {
387 b2 = bmap[y2*w+x4] as i64
388 var m2: i64 = 0
389 if prevhb[y2] == b2 { m2 = prevh[y2] }
390 if y2 > 0 { if prevhb[y2-1] == b2 { if prevh[y2-1] > m2 { m2 = prevh[y2-1] } } }
391 if y2 < h-1 { if prevhb[y2+1] == b2 { if prevh[y2+1] > m2 { m2 = prevh[y2+1] } } }
392 r2 = m2 + 1
393 if r2 >= rm { coh = coh + 1 }
394 }
395 currh[y2] = r2
396 currhb[y2] = b2
397 y2 = y2 + 1
398 }
399 var y3: i64 = 0
400 while y3 < h { prevh[y3] = currh[y3]; prevhb[y3] = currhb[y3]; y3 = y3 + 1 }
401 x4 = x4 + 1
402 }
403 var perm: i64 = 0
404 if etotal > 0 { perm = coh*1000/etotal }
405 if etotal < w*h/400 { perm = 0 }
406 outp[0] = perm
407 outp[1] = etotal
408 // FREE THE SCRATCH. A census over a 169-render corpus multiplies every unfreed buffer by the corpus
409 // size; at ~1MP per frame this function alone held ~4.7 GB of address space for no reason, on a box
410 // already sitting at 720 permil swap. Resource excellence is a shipping criterion, not tidiness.
411 sys_munmap(lb as *u8, w*h*8)
412 sys_munmap(emap, w*h)
413 sys_munmap(bmap, w*h)
414 sys_munmap(prev as *u8, w*8)
415 sys_munmap(curr as *u8, w*8)
416 sys_munmap(prevb as *u8, w*8)
417 sys_munmap(currb as *u8, w*8)
418 sys_munmap(prevh as *u8, h*8)
419 sys_munmap(currh as *u8, h*8)
420 sys_munmap(prevhb as *u8, h*8)
421 sys_munmap(currhb as *u8, h*8)
422 var axis: i64 = perm*1000/cf[CHJ_C_COH_DIV]
423 if axis > 1000 { axis = 1000 }
424 return axis
425}
426
427// one face scan at the current scale: center-surround compact dark blobs -> level spaced pair
428func chj_face_scan(l: *i64, w: i64, h: i64, cf: *i64, ncout: *i64) -> i64 {
429 let eyet: i64 = cf[CHJ_C_EYE_T]
430 // ★SCALE-COVARIANT LENGTHS. Every geometric constant below is expressed at ref_w and scaled to the
431 // width actually being scanned, so the detector hunts the same FEATURE rather than the same pixel
432 // count. At w == ref_w the factor is exactly 1000/1000, which is why this changes nothing at the
433 // resolutions that already worked. Each result is floored at 1: a probe radius of zero reads the
434 // centre pixel against itself and would make every pixel a candidate.
435 // THE PYRAMID IS THE SCALE SEARCH; THE PROBE IS FIXED IN PIXELS. Scaling by the CURRENT level width
436 // made probe and pyramid cancel exactly, so every level re-ran one identical scan and the search was
437 // INERT. Scaling by the ORIGINAL width probes one large size at every level: measured 2026-08-15 that
438 // recovered a 640px face close-up (0 -> 1000) and LOST an 800px body render (1000 -> 0). Trading one
439 // false negative for another is not a fix. Fixed pixels + pyramid spans the whole size range.
440 let sc: i64 = 1000
441 var pf: i64 = cf[CHJ_C_PROBE_FAR]*sc/1000
442 if pf < 1 { pf = 1 }
443 var pd: i64 = cf[CHJ_C_PROBE_DIAG]*sc/1000
444 if pd < 1 { pd = 1 }
445 // the border must clear the widest probe or the surround reads outside the buffer. Derived from the
446 // probe radii rather than trusted from config: a border narrower than the probe is not a tuning
447 // choice, it is an out-of-bounds read.
448 var bd: i64 = cf[CHJ_C_BORDER]*sc/1000
449 if bd < pf { bd = pf }
450 if bd < pd { bd = pd }
451 var st: i64 = cf[CHJ_C_STRIDE]*sc/1000
452 if st < 1 { st = 1 }
453 var dr: i64 = cf[CHJ_C_DEDUP_R]*sc/1000
454 if dr < 1 { dr = 1 }
455 let cx: *i64 = sys_mmap(CHJ_CAND_MAX*8) as *i64
456 let cy: *i64 = sys_mmap(CHJ_CAND_MAX*8) as *i64
457 var nc: i64 = 0
458 var y: i64 = bd
459 while y < h-bd {
460 var x: i64 = bd
461 while x < w-bd {
462 let c: i64 = l[y*w+x]
463 var okc: i64 = 1
464 let p1: i64 = l[y*w + x+pf]
465 let p2: i64 = l[y*w + x-pf]
466 let p3: i64 = l[(y+pf)*w + x]
467 let p4: i64 = l[(y-pf)*w + x]
468 let p5: i64 = l[(y+pd)*w + x+pd]
469 let p6: i64 = l[(y+pd)*w + x-pd]
470 let p7: i64 = l[(y-pd)*w + x+pd]
471 let p8: i64 = l[(y-pd)*w + x-pd]
472 if p1 <= c + eyet/2 { okc = 0 }
473 if p2 <= c + eyet/2 { okc = 0 }
474 if p3 <= c + eyet/2 { okc = 0 }
475 if p4 <= c + eyet/2 { okc = 0 }
476 if p5 <= c + eyet/2 { okc = 0 }
477 if p6 <= c + eyet/2 { okc = 0 }
478 if p7 <= c + eyet/2 { okc = 0 }
479 if p8 <= c + eyet/2 { okc = 0 }
480 if okc == 1 { if (p1+p2+p3+p4+p5+p6+p7+p8)/8 - c > eyet {
481 var near: i64 = 0
482 var k: i64 = 0
483 while k < nc {
484 var ddx: i64 = cx[k]-x
485 if ddx < 0 { ddx = 0-ddx }
486 var ddy: i64 = cy[k]-y
487 if ddy < 0 { ddy = 0-ddy }
488 if ddx < dr { if ddy < dr { near = 1 } }
489 k = k + 1
490 }
491 if near == 0 { if nc < CHJ_CAND_MAX { cx[nc] = x; cy[nc] = y; nc = nc + 1 } }
492 } }
493 x = x + st
494 }
495 y = y + st
496 }
497 // report how many blob candidates this level produced, so a zero face verdict can be attributed:
498 // no candidates means the centre-surround probe never fired, a nonzero count with no pair means the
499 // separation window rejected them. Those are different defects with different fixes.
500 ncout[0] = nc
501 var i: i64 = 0
502 while i < nc {
503 var j: i64 = i+1
504 while j < nc {
505 var dyv: i64 = cy[i]-cy[j]
506 if dyv < 0 { dyv = 0-dyv }
507 var dxv: i64 = cx[i]-cx[j]
508 if dxv < 0 { dxv = 0-dxv }
509 // the separation window scales with the render for the same reason the probe does: an
510 // interpupillary distance is a length on the subject, not a pixel count.
511 var wdy: i64 = cf[CHJ_C_EYE_DY]*sc/1000
512 if wdy < 1 { wdy = 1 }
513 var wlo: i64 = cf[CHJ_C_EYE_DXMIN]*sc/1000
514 if wlo < 1 { wlo = 1 }
515 var whi: i64 = cf[CHJ_C_EYE_DXMAX]*sc/1000
516 if whi < wlo { whi = wlo }
517 if dyv <= wdy { if dxv >= wlo { if dxv <= whi {
518 // ★TWO DARK BLOBS ARE NOT A FACE. "compact, dark, level, correctly spaced" is satisfied
519 // trivially by a voxel landscape: measured, snap_craft.png -- a craft-world screenshot with
520 // no character face in it at all -- passed this gate and scored face 1000, while an actual
521 // face close-up scored 0. Between a real pair of eyes there is always lit skin, so the
522 // midpoint must be LIGHTER than both blobs by the same contrast the blob test already uses.
523 // eye_t is reused deliberately: one contrast threshold for one physical claim, never a
524 // second number to drift away from it.
525 // ONE BRIGHT PIXEL IS NOT A FACE EITHER. The single-midpoint form STILL passed a voxel
526 // landscape -- snap_craft.png measured face 1000 with no character in frame -- because
527 // between two dark blocks there is very often one bright pixel of sky or ground. A real
528 // pair of eyes is separated by a CONTINUOUS lit bridge of skin, so sample across it at the
529 // quarter, half and three-quarter points and require EVERY one lighter than BOTH blobs.
530 // Three samples cost nothing and are far harder for scenery to satisfy by accident.
531 let li: i64 = l[cy[i]*w+cx[i]]
532 let lj: i64 = l[cy[j]*w+cx[j]]
533 var bridge: i64 = 1
534 var bq: i64 = 1
535 while bq <= 3 {
536 let bxp: i64 = cx[i] + (cx[j]-cx[i])*bq/4
537 let byp: i64 = cy[i] + (cy[j]-cy[i])*bq/4
538 let bv: i64 = l[byp*w + bxp]
539 if bv - li <= eyet { bridge = 0 }
540 if bv - lj <= eyet { bridge = 0 }
541 bq = bq + 1
542 }
543 if bridge == 1 { return 1 }
544 } } }
545 j = j + 1
546 }
547 i = i + 1
548 }
549 return 0
550}
551
552func chj_face(luma: *i64, w: i64, h: i64, cf: *i64, diag: *i64) -> i64 {
553 var cw: i64 = w
554 var chh: i64 = h
555 var cur: *i64 = sys_mmap(w*h*8) as *i64
556 var i: i64 = 0
557 while i < w*h { cur[i] = luma[i]; i = i + 1 }
558 // ★THE PYRAMID RUNS TO ITS STRUCTURAL FLOOR, NOT TO AN ARBITRARY COUNT. This loop was capped at three
559 // levels, so the detector only ever covered eye separations of dxmin..4*dxmax at the original scale.
560 // That cap, not the acceptance window, is why the SAME mesh scored face_axis 1000 at 256x384 and 0 at
561 // 512x768: the pair was resolvable, just never at a level the loop reached. The real stop is pyr_min --
562 // the size below which a scan means nothing -- and it is now the only stop.
563 let ncp: *i64 = sys_mmap(16) as *i64
564 var done: i64 = 0
565 var levels: i64 = 0
566 var maxnc: i64 = 0
567 while done == 0 {
568 levels = levels + 1
569 ncp[0] = 0
570 let hit: i64 = chj_face_scan(cur, cw, chh, cf, ncp)
571 if ncp[0] > maxnc { maxnc = ncp[0] }
572 diag[0] = levels
573 diag[1] = maxnc
574 diag[2] = cw
575 if hit == 1 { return 1000 }
576 let nw: i64 = cw/2
577 let nh: i64 = chh/2
578 if nw < cf[CHJ_C_PYR_MIN] { done = 1 }
579 if nh < cf[CHJ_C_PYR_MIN] { done = 1 }
580 if done == 0 {
581 let nxt: *i64 = sys_mmap(nw*nh*8) as *i64
582 var y: i64 = 0
583 while y < nh {
584 var x: i64 = 0
585 while x < nw {
586 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
587 x = x + 1
588 }
589 y = y + 1
590 }
591 // the level just scanned is dead once the next is built -- free it before advancing, or a full
592 // pyramid holds every level it ever visited.
593 sys_munmap(cur as *u8, cw*chh*8)
594 cur = nxt
595 cw = nw
596 chh = nh
597 }
598 }
599 return 0
600}
601
602// the judge. out: 0 composition 1 palette_axis 2 contour_axis 3 face_axis 4 HEADLINE
603// 5 smooth_permil 6 detail_permil 7 pal_buckets 8 coherent_permil 9 edge_total
604func chj_judge(fb: *i64, w: i64, h: i64, confpath: *u8, out: *i64) -> i64 {
605 let cf: *i64 = sys_mmap(CHJ_NCONF*8) as *i64
606 chj_conf_load(confpath, cf)
607 let luma: *i64 = sys_mmap(w*h*8) as *i64
608 chj_luma(fb, w, h, luma)
609 let cellout: *i64 = sys_mmap(16) as *i64
610 chj_cells(luma, w, h, cf, cellout)
611 let sp: i64 = cellout[0]
612 let dp: i64 = cellout[1]
613 var sb: i64 = sp*1000/cf[CHJ_C_SMOOTH_DIV]
614 if sb > 1000 { sb = 1000 }
615 var db: i64 = dp*1000/cf[CHJ_C_DETAIL_DIV]
616 if db > 1000 { db = 1000 }
617 var comp: i64 = sb
618 if db < comp { comp = db }
619 let pal: i64 = chj_palette(fb, w, h)
620 var palax: i64 = pal*1000/cf[CHJ_C_PAL_DIV]
621 if palax > 1000 { palax = 1000 }
622 let conout: *i64 = sys_mmap(16) as *i64
623 let conax: i64 = chj_contour(luma, w, h, cf, conout)
624 // ALWAYS COMPUTE; SEPARATELY DECIDE WHETHER IT VOTES. Skipping the computation when the axis was
625 // disabled left out[3] at its initialiser of 1000 -- A CONSTANT WEARING THE SHAPE OF A MEASUREMENT,
626 // which reads as "face detected, perfectly" on every image including a landscape with nothing in it.
627 // Demoting an axis out of the verdict must not also stop measuring it, or the diagnostic that justified
628 // the demotion disappears at exactly the moment it is needed. The axis is now always measured and always
629 // reported with its attribution; face_required decides ONLY whether it may lower the headline.
630 let fdiag: *i64 = sys_mmap(32) as *i64
631 fdiag[0] = 0
632 fdiag[1] = 0
633 fdiag[2] = 0
634 let faceax: i64 = chj_face(luma, w, h, cf, fdiag)
635 out[10] = fdiag[0]
636 out[11] = fdiag[1]
637 out[12] = fdiag[2]
638 var head: i64 = comp
639 if palax < head { head = palax }
640 if conax < head { head = conax }
641 if cf[CHJ_C_FACE_REQ] == 1 { if faceax < head { head = faceax } }
642 out[0] = comp
643 out[1] = palax
644 out[2] = conax
645 out[3] = faceax
646 out[4] = head
647 out[5] = sp
648 out[6] = dp
649 out[7] = pal
650 out[8] = conout[0]
651 out[9] = conout[1]
652 return head
653}