nx_axisvar.nx source
↩ module page · 265 lines · 11493 B
1// nx_axisvar.nx -- DOES THIS JUDGE'S AXIS ACTUALLY VARY? A per-axis distribution census over a whole
2// directory of renders, so a fitness built on those axes can be justified by measurement instead of
3// by three hand-picked examples.
4//
5// nx_axisvar <dir> <prefix> <suffix> e.g. nx_axisvar /tmp bodyfit_ .png
6//
7// WHY IT EXISTS, MEASURED 2026-08-14: a body-evolution run improved 3 points in 10 generations and
8// LOST to random search at equal budget. A 5-point sweep suggested the reason -- contour_axis sat at
9// its ceiling and face_axis was binary -- but five renders is not a population, and changing a shipped
10// fitness on five samples is the same error as tuning until a green appears. The search had already
11// written ~132 renders; this reads ALL of them.
12//
13// ★★★IT REPORTS NUMBERS, NOT A VERDICT. An uncalibrated classifier must publish its distribution and
14// let the reader judge. The ONE verdict it does emit is not a threshold but a FACT: an axis with
15// distinct==1 took the same value on every single input, so it cannot discriminate anything, and
16// including it in a mean dilutes every other axis while contributing nothing.
17//
18// ★NO SILENT CAPS. The directory walk LOOPS getdents64 until it returns 0 -- one call is a prefix of a
19// directory, not a listing, and publishing that prefix's count as a total is how a census becomes a
20// sample nobody knows is partial. The distinct-value table is bounded and says so when it overflows.
21// ★A FAILED JUDGE IS COUNTED AND NAMED, never folded into the population: judged + failed must equal
22// matched, and the sum is printed so the partition can be checked.
23// exit 0 all matched files judged | 2 usage | 3 cannot open dir | 4 nothing matched | 5 some failed
24// license_tier: ORIGINAL. No hw writes (Rule 26).
25import "nx_syscalls.nx"
26import "nx_tool_run.nx"
27
28const AV_NAXIS: i64 = 4
29const AV_DIRBUF: i64 = 262144
30const AV_OUTCAP: i64 = 65536
31const AV_JUDGE_TIMEOUT_MS: i64 = 60000
32// bounded distinct-value table per axis; an overflow is REPORTED, never silently truncated.
33const AV_MAXDISTINCT: i64 = 64
34const AV_JUDGE: *u8 = "/volume1/homes/elderwesto/nishihost/nx_charjudge.elf"
35// dirent64: d_reclen is a u16 at byte 16, the NUL-terminated name starts at byte 19.
36const AV_DIRENT_RECLEN_OFF: i64 = 16
37const AV_DIRENT_NAME_OFF: i64 = 19
38
39func av_len(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n }
40func av_puts(s: *u8) -> i64 { sys_write(1, s, av_len(s)); return 0 }
41func av_pn(v: i64) -> i64 {
42 var m: i64 = v
43 if m < 0 { av_puts("-" as *u8); m = 0 - m }
44 let t: *u8 = sys_mmap(32)
45 var k: i64 = 0
46 if m == 0 { t[0] = 48 as u8; k = 1 }
47 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 }
48 let o: *u8 = sys_mmap(32)
49 var i: i64 = 0
50 while i < k { o[i] = t[k - 1 - i]; i = i + 1 }
51 sys_write(1, o, k)
52 return 0
53}
54func av_rdint(b: *u8, pos: *i64, end: i64) -> i64 {
55 var i: i64 = pos[0]
56 var go: i64 = 1
57 while go == 1 {
58 if i >= end { go = 0 } else {
59 let c: i64 = b[i] as i64
60 if c == 45 { go = 0 } else { if c >= 48 { if c <= 57 { go = 0 } else { i = i+1 } } else { i = i+1 } }
61 }
62 }
63 var sg: i64 = 1
64 if i < end { if (b[i] as i64) == 45 { sg = 0 - 1; i = i + 1 } }
65 var v: i64 = 0
66 var g2: i64 = 1
67 while g2 == 1 {
68 if i >= end { g2 = 0 } else {
69 let c2: i64 = b[i] as i64
70 if c2 >= 48 { if c2 <= 57 { v = v*10 + (c2-48); i = i+1 } else { g2 = 0 } } else { g2 = 0 }
71 }
72 }
73 pos[0] = i
74 return v*sg
75}
76// LAST anchored "<key>": in out[0..n). Anchored on the field name, taken positionally last -- a
77// greedy 'nearest number' parse silently starts measuring a different field than the one it names.
78func av_field(out: *u8, n: i64, key: *u8, found: *i64) -> i64 {
79 found[0] = 0
80 let kl: i64 = av_len(key)
81 var pos: i64 = 0 - 1
82 var j: i64 = 0
83 while j + kl + 3 <= n {
84 if out[j] == (34 as u8) {
85 var m: i64 = 0
86 var ok: i64 = 1
87 var scan: i64 = 1
88 while scan == 1 {
89 if m >= kl { scan = 0 } else {
90 if out[j + 1 + m] != key[m] { ok = 0; scan = 0 } else { m = m + 1 }
91 }
92 }
93 if ok == 1 { if out[j + 1 + kl] == (34 as u8) { if out[j + 2 + kl] == (58 as u8) { pos = j + 3 + kl } } }
94 }
95 j = j + 1
96 }
97 if pos < 0 { return 0 }
98 let p2: *i64 = sys_mmap(16) as *i64
99 p2[0] = pos
100 let v: i64 = av_rdint(out, p2, n)
101 found[0] = 1
102 return v
103}
104func av_starts(name: *u8, pre: *u8) -> i64 {
105 var i: i64 = 0
106 while pre[i] != (0 as u8) {
107 if name[i] == (0 as u8) { return 0 }
108 if name[i] != pre[i] { return 0 }
109 i = i + 1
110 }
111 return 1
112}
113func av_ends(name: *u8, suf: *u8) -> i64 {
114 let nl: i64 = av_len(name)
115 let sl: i64 = av_len(suf)
116 if sl > nl { return 0 }
117 var i: i64 = 0
118 while i < sl {
119 if name[nl - sl + i] != suf[i] { return 0 }
120 i = i + 1
121 }
122 return 1
123}
124// record v into axis a's distinct table; returns 1 if the table overflowed (reported, never hidden)
125func av_note(dist: *i64, dn: *i64, a: i64, v: i64) -> i64 {
126 let base: i64 = a * AV_MAXDISTINCT
127 var i: i64 = 0
128 while i < dn[a] {
129 if dist[base + i] == v { return 0 }
130 i = i + 1
131 }
132 if dn[a] >= AV_MAXDISTINCT { return 1 }
133 dist[base + dn[a]] = v
134 dn[a] = dn[a] + 1
135 return 0
136}
137func av_axname(a: i64) -> *u8 {
138 if a == 0 { return "composition" as *u8 }
139 if a == 1 { return "palette_axis" as *u8 }
140 if a == 2 { return "contour_axis" as *u8 }
141 return "face_axis" as *u8
142}
143
144func main(argc: i64, argv: *i64) -> i64 {
145 if argc < 4 {
146 av_puts("usage: nx_axisvar <dir> <prefix> <suffix> e.g. nx_axisvar /tmp bodyfit_ .png\n" as *u8)
147 sys_exit(2)
148 return 2
149 }
150 let dir: *u8 = argv[1] as *u8
151 let pre: *u8 = argv[2] as *u8
152 let suf: *u8 = argv[3] as *u8
153 let fd: i64 = sys_openat_rd(dir)
154 if fd < 0 { av_puts("AXISVAR REFUSE: cannot open directory\n" as *u8); sys_exit(3); return 3 }
155
156 let amin: *i64 = sys_mmap(AV_NAXIS*8) as *i64
157 let amax: *i64 = sys_mmap(AV_NAXIS*8) as *i64
158 let asum: *i64 = sys_mmap(AV_NAXIS*8) as *i64
159 let dist: *i64 = sys_mmap(AV_NAXIS*AV_MAXDISTINCT*8) as *i64
160 let dn: *i64 = sys_mmap(AV_NAXIS*8) as *i64
161 let ovf: *i64 = sys_mmap(AV_NAXIS*8) as *i64
162 var a: i64 = 0
163 while a < AV_NAXIS { amin[a] = 0; amax[a] = 0; asum[a] = 0; dn[a] = 0; ovf[a] = 0; a = a + 1 }
164
165 let dbuf: *u8 = sys_mmap(AV_DIRBUF)
166 let path: *u8 = sys_mmap(4096)
167 let out: *u8 = sys_mmap(AV_OUTCAP)
168 let olen: *i64 = sys_mmap(16) as *i64
169 let found: *i64 = sys_mmap(16) as *i64
170 let av3: *i64 = sys_mmap(64) as *i64
171 var matched: i64 = 0
172 var judged: i64 = 0
173 var failed: i64 = 0
174
175 // ★LOOP UNTIL getdents64 RETURNS 0. One call returns a PREFIX of a large directory; treating that
176 // as the listing is how a census silently becomes a sample and publishes a partial count as a total.
177 var more: i64 = 1
178 while more == 1 {
179 let n: i64 = sys_getdents64(fd, dbuf, AV_DIRBUF)
180 if n <= 0 { more = 0 } else {
181 var off: i64 = 0
182 while off < n {
183 let base: i64 = dbuf as i64
184 let rec: *u8 = (base + off) as *u8
185 let reclen: i64 = (rec[AV_DIRENT_RECLEN_OFF] as i64) + ((rec[AV_DIRENT_RECLEN_OFF+1] as i64) << 8)
186 let nm: *u8 = (base + off + AV_DIRENT_NAME_OFF) as *u8
187 if av_starts(nm, pre) == 1 { if av_ends(nm, suf) == 1 {
188 matched = matched + 1
189 var po: i64 = 0
190 var di: i64 = 0
191 while dir[di] != (0 as u8) { path[po] = dir[di]; po = po + 1; di = di + 1 }
192 path[po] = 47 as u8; po = po + 1
193 var ni: i64 = 0
194 while nm[ni] != (0 as u8) { path[po] = nm[ni]; po = po + 1; ni = ni + 1 }
195 path[po] = 0 as u8
196 av3[0] = AV_JUDGE as i64
197 av3[1] = path as i64
198 av3[2] = "axisvar" as *u8 as i64
199 av3[3] = 0
200 olen[0] = 0
201 if tr_run_capture_to(AV_JUDGE, av3, out, AV_OUTCAP, olen, AV_JUDGE_TIMEOUT_MS) != 0 {
202 failed = failed + 1
203 } else {
204 var got: i64 = 0
205 var k: i64 = 0
206 while k < AV_NAXIS {
207 let v: i64 = av_field(out, olen[0], av_axname(k), found)
208 if found[0] == 1 {
209 got = got + 1
210 if judged == 0 { amin[k] = v; amax[k] = v }
211 if v < amin[k] { amin[k] = v }
212 if v > amax[k] { amax[k] = v }
213 asum[k] = asum[k] + v
214 if av_note(dist, dn, k, v) == 1 { ovf[k] = 1 }
215 }
216 k = k + 1
217 }
218 if got == AV_NAXIS { judged = judged + 1 } else { failed = failed + 1 }
219 }
220 } }
221 if reclen <= 0 { off = n } else { off = off + reclen }
222 }
223 }
224 }
225 sys_close(fd)
226
227 av_puts("=== nx_axisvar dir=" as *u8); av_puts(dir)
228 av_puts(" prefix=" as *u8); av_puts(pre)
229 av_puts(" suffix=" as *u8); av_puts(suf); av_puts(" ===\n" as *u8)
230 av_puts("matched=" as *u8); av_pn(matched)
231 av_puts(" judged=" as *u8); av_pn(judged)
232 av_puts(" failed=" as *u8); av_pn(failed)
233 av_puts(" sum=" as *u8); av_pn(judged + failed)
234 if judged + failed == matched { av_puts(" partition=RECONCILES\n" as *u8) } else { av_puts(" partition=LEAK\n" as *u8) }
235 if judged == 0 {
236 av_puts("AXISVAR REFUSE: nothing judged -- a distribution over zero inputs is not a measurement\n" as *u8)
237 sys_exit(4)
238 return 4
239 }
240 a = 0
241 while a < AV_NAXIS {
242 av_puts(" " as *u8); av_puts(av_axname(a))
243 av_puts(" min=" as *u8); av_pn(amin[a])
244 av_puts(" max=" as *u8); av_pn(amax[a])
245 av_puts(" range=" as *u8); av_pn(amax[a] - amin[a])
246 av_puts(" mean=" as *u8); av_pn(asum[a] / judged)
247 av_puts(" distinct=" as *u8); av_pn(dn[a])
248 if ovf[a] == 1 { av_puts("+ (table full, count is a FLOOR)" as *u8) }
249 // ★THE VERDICT IS BOUND TO ITS DENOMINATOR. At judged==1 every axis is trivially 'constant',
250 // and emitting that would be a true statement carrying no information -- the same shape as a
251 // tooth that passes on the empty set. n>1 is not a tuned threshold, it is the definition of
252 // having something to vary ACROSS. Caught by this organ's own first narrow run, which
253 // reported all four axes CONSTANT over a single render.
254 if judged > 1 {
255 // a FACT, not a threshold: one value over every input cannot discriminate anything, and
256 // averaging it in dilutes every other axis for nothing.
257 if dn[a] == 1 { av_puts(" CONSTANT-ACROSS-ALL-INPUTS" as *u8) }
258 } else { av_puts(" (n=1: variance UNDEFINED, not constant)" as *u8) }
259 av_puts("\n" as *u8)
260 a = a + 1
261 }
262 if failed > 0 { sys_exit(5); return 5 }
263 sys_exit(0)
264 return 0
265}