nx_bodyfit.nx source
↩ module page · 701 lines · 34425 B
1// nx_bodyfit.nx -- THE LOOP: a GENOME becomes a FITNESS by generating a body, rendering it, and
2// letting the judge score it. This is the piece that was missing between nx_evolve and the graphics
3// lane; every other part already existed and had never been connected.
4//
5// nx_bodyfit <height> <radial> <sub> <relief> <fat> -> prints FITNESS=<int> on success
6//
7// Drives, never re-implements: nx_body_gen (generator) -> nx_anat_sov (renderer) -> nx_charjudge
8// (evaluator). Each is a promoted organ with its own gate; this owns only the WIRING and the CLAMP.
9//
10// ★FITNESS IS CHARJUDGE-1000 SO 0 IS THE OPTIMUM. nx_evolve's whole convention is 'higher is better,
11// 0 = optimum', and its convergence test is best==0. A raw 0..1000 judge score would never reach 0, so
12// the harness could never say CONVERGED and every run would read PARTIAL regardless of quality.
13// Shifting here keeps ONE convention across the estate instead of teaching the harness a second one.
14//
15// ★ON ANY FAILED STEP IT PRINTS NOTHING AND EXITS NON-ZERO. nx_evolve then scores the candidate with
16// its EV_FIT_UNREADABLE sentinel, which is worse than any reachable fitness, so a broken candidate
17// loses every tournament. Printing a plausible number on failure is how a broken evaluator quietly
18// takes over a population.
19//
20// ★EVERY BOUND COMES FROM knowledge/bodyfit.conf. This organ carries no search envelope of its own.
21// exit 0 ok | 2 usage | 3 conf refused | 4 generate failed | 5 render failed | 6 judge failed
22// license_tier: ORIGINAL. No hw writes (Rule 26).
23import "nx_syscalls.nx"
24import "nx_tool_run.nx"
25
26const BF_NGENE: i64 = 5
27const BF_G_HEIGHT: i64 = 0
28const BF_G_RADIAL: i64 = 1
29const BF_G_SUB: i64 = 2
30const BF_G_RELIEF: i64 = 3
31const BF_G_FAT: i64 = 4
32const BF_NSET: i64 = 8
33// ★★★REQUIRED vs PRESENT ARE DIFFERENT COUNTS, AND CONFLATING THEM MAKES EVERY CONF ADDITION A
34// BREAKING CHANGE. Measured this session: adding two keys to bodyfit.conf before promoting the binary
35// took the LIVE organ to rc=3 on every call, because the parser demanded an EXACT count. Settings below
36// this index are REQUIRED; anything above it is OPTIONAL with a default, so a new binary reads an old
37// conf and an old binary is never handed a key it will refuse.
38const BF_NSET_REQ: i64 = 4
39const BF_S_W: i64 = 0
40const BF_S_H: i64 = 1
41const BF_S_RLO: i64 = 2
42const BF_S_RHI: i64 = 3
43// ★OBJECTIVE WEIGHTS ARE DATA. Which axes the SEARCH climbs is a policy choice that must be reviewable,
44// and it is space-dependent: composition was the continuous axis in KNOB space (range 97, 17 distinct)
45// and contour was saturated; in CANON space contour is continuous (range 384, 17 distinct) and
46// composition is the flat one. An objective hardcoded from one census silently becomes wrong when the
47// genome changes the space. Defaults reproduce the shipped comp-only fitness EXACTLY.
48const BF_S_WCOMP: i64 = 4
49const BF_S_WPAL: i64 = 5
50const BF_S_WCON: i64 = 6
51const BF_S_WFACE: i64 = 7
52const BF_CONF: *u8 = "knowledge/bodyfit.conf"
53const BF_BODYGEN: *u8 = "/volume1/homes/elderwesto/nishihost/nx_body_gen.elf"
54const BF_ANAT: *u8 = "/volume1/homes/elderwesto/nishihost/nx_anat_sov.elf"
55const BF_JUDGE: *u8 = "/volume1/homes/elderwesto/nishihost/nx_charjudge.elf"
56const BF_CANON: *u8 = "knowledge/canon_male.dat"
57// ★CANON MODE targets canon_merge2.dat, NOT canon_male.dat, and the choice is MEASURED not stylistic:
58// canon_male is 5 parts / 45 rings and nx_canon_solver REFUSES it against the only rules file that
59// exists (canon_face_rules names p15/p3, which canon_male does not declare -- verified live, it prints
60// PART-SOLVER-REFUSE rather than passing silently). canon_merge2 is 16 parts / 106 rings AND solves
61// CLEAN, so it is the only canon in the estate that carries both a richer genome and a real constraint.
62const BF_CANON_RINGS: *u8 = "knowledge/canon_merge2.dat"
63// Canon source is data-selected; the path file is reviewed/provenance-bearing and falls back to the historical name.
64const BF_CANON_PATH: *u8 = "knowledge/bodyfit_canon.path"
65const BF_RULES: *u8 = "knowledge/canon_face_rules.conf"
66const BF_SOLVER: *u8 = "/volume1/homes/elderwesto/nishihost/nx_canon_solver.elf"
67// knob genes are pinned mid-envelope in canon mode so the ONLY thing varying is the canon itself.
68const BF_CANON_HOLD: i64 = 500
69const BF_OUTCAP: i64 = 65536
70// a render is measured in seconds, not minutes; a step that exceeds this is wedged, not slow.
71const BF_STEP_TIMEOUT_MS: i64 = 120000
72// the judge's own scale: CHARJUDGE is reported in per-mille, so 1000 is its ceiling.
73const BF_JUDGE_CEIL: i64 = 1000
74// genome resolution: a gene is a position in per-mille of its declared envelope.
75const BF_PERMIL: i64 = 1000
76// the judge reports four axes; fitness is their MEAN (see the scoring block for why, not the veto).
77const BF_NAXIS: i64 = 4
78
79func bf_len(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n }
80// Trim a reviewed path artifact without accepting trailing transport whitespace as part of the filename.
81func bf_trim_path(b: *u8, n: i64) -> i64 { var k: i64 = n; var done: i64 = 0; while k > 0 { if done == 0 { let c: i64 = b[k-1] as i64; if c == 10 { k = k - 1 } else { if c == 13 { k = k - 1 } else { if c == 32 { k = k - 1 } else { done = 1 } } } } } b[k] = 0 as u8; return k }
82func bf_puts(s: *u8) -> i64 { sys_write(1, s, bf_len(s)); return 0 }
83func bf_epn(v: i64) -> i64 {
84 var m: i64 = v
85 if m < 0 { sys_write(2, "-" as *u8, 1); m = 0 - m }
86 let t: *u8 = sys_mmap(32)
87 var k: i64 = 0
88 if m == 0 { t[0] = 48 as u8; k = 1 }
89 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 }
90 let o: *u8 = sys_mmap(32)
91 var i: i64 = 0
92 while i < k { o[i] = t[k - 1 - i]; i = i + 1 }
93 sys_write(2, o, k)
94 return 0
95}
96func bf_eputs(s: *u8) -> i64 { sys_write(2, s, bf_len(s)); return 0 }
97// write v as decimal into d at off, NUL-terminated; returns bytes written incl the NUL
98func bf_num(d: *u8, off: i64, v: i64) -> i64 {
99 let base: i64 = d as i64
100 let s: *u8 = (base + off) as *u8
101 var m: i64 = v
102 var w: i64 = 0
103 if m < 0 { s[0] = 45 as u8; w = 1; m = 0 - m }
104 let t: *u8 = sys_mmap(32)
105 var k: i64 = 0
106 if m == 0 { t[0] = 48 as u8; k = 1 }
107 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 }
108 while k > 0 { k = k - 1; s[w] = t[k]; w = w + 1 }
109 s[w] = 0 as u8
110 return w + 1
111}
112func bf_app(d: *u8, off: i64, s: *u8) -> i64 {
113 var o: i64 = off
114 var i: i64 = 0
115 while s[i] != (0 as u8) { d[o] = s[i]; o = o + 1; i = i + 1 }
116 return o
117}
118func bf_atoi(s: *u8) -> i64 {
119 var v: i64 = 0
120 var i: i64 = 0
121 var sg: i64 = 1
122 if s[0] == (45 as u8) { sg = 0 - 1; i = 1 }
123 while s[i] != (0 as u8) {
124 let c: i64 = s[i] as i64
125 if c >= 48 { if c <= 57 { v = v*10 + (c - 48) } }
126 i = i + 1
127 }
128 return v*sg
129}
130func bf_keyis(b: *u8, p: i64, len: i64, key: *u8) -> i64 {
131 var i: i64 = 0
132 while key[i] != (0 as u8) {
133 if p + i >= len { return 0 }
134 if b[p+i] != key[i] { return 0 }
135 i = i + 1
136 }
137 return 1
138}
139func bf_rdint(b: *u8, pos: *i64, end: i64) -> i64 {
140 var i: i64 = pos[0]
141 var go: i64 = 1
142 while go == 1 {
143 if i >= end { go = 0 } else {
144 let c: i64 = b[i] as i64
145 if c == 45 { go = 0 } else { if c >= 48 { if c <= 57 { go = 0 } else { i = i+1 } } else { i = i+1 } }
146 }
147 }
148 var sg: i64 = 1
149 if i < end { if (b[i] as i64) == 45 { sg = 0 - 1; i = i + 1 } }
150 var v: i64 = 0
151 var g2: i64 = 1
152 while g2 == 1 {
153 if i >= end { g2 = 0 } else {
154 let c2: i64 = b[i] as i64
155 if c2 >= 48 { if c2 <= 57 { v = v*10 + (c2-48); i = i+1 } else { g2 = 0 } } else { g2 = 0 }
156 }
157 }
158 pos[0] = i
159 return v*sg
160}
161func bf_gslot(b: *u8, p: i64, len: i64) -> i64 {
162 if bf_keyis(b, p, len, "height" as *u8) == 1 { return BF_G_HEIGHT }
163 if bf_keyis(b, p, len, "radial" as *u8) == 1 { return BF_G_RADIAL }
164 if bf_keyis(b, p, len, "sub" as *u8) == 1 { return BF_G_SUB }
165 if bf_keyis(b, p, len, "relief" as *u8) == 1 { return BF_G_RELIEF }
166 if bf_keyis(b, p, len, "fat" as *u8) == 1 { return BF_G_FAT }
167 return 0 - 1
168}
169func bf_sslot(b: *u8, p: i64, len: i64) -> i64 {
170 if bf_keyis(b, p, len, "render_w" as *u8) == 1 { return BF_S_W }
171 if bf_keyis(b, p, len, "render_h" as *u8) == 1 { return BF_S_H }
172 if bf_keyis(b, p, len, "ring_lo" as *u8) == 1 { return BF_S_RLO }
173 if bf_keyis(b, p, len, "ring_hi" as *u8) == 1 { return BF_S_RHI }
174 if bf_keyis(b, p, len, "obj_comp" as *u8) == 1 { return BF_S_WCOMP }
175 if bf_keyis(b, p, len, "obj_palette" as *u8) == 1 { return BF_S_WPAL }
176 if bf_keyis(b, p, len, "obj_contour" as *u8) == 1 { return BF_S_WCON }
177 if bf_keyis(b, p, len, "obj_face" as *u8) == 1 { return BF_S_WFACE }
178 return 0 - 1
179}
180// parse E/S rows. Returns filled-count, or -1 on an unknown key.
181func bf_parse(b: *u8, len: i64, gmin: *i64, gmax: *i64, sset: *i64, seen: *i64) -> i64 {
182 let pos: *i64 = sys_mmap(16) as *i64
183 var i: i64 = 0
184 var bol: i64 = 1
185 var bad: i64 = 0
186 var n: i64 = 0
187 while i < len {
188 if bol == 1 {
189 let c0: i64 = b[i] as i64
190 if c0 == 69 {
191 var p: i64 = i + 1
192 var sk: i64 = 1
193 while sk == 1 { if p >= len { sk = 0 } else { if (b[p] as i64) == 32 { p = p + 1 } else { sk = 0 } } }
194 let s: i64 = bf_gslot(b, p, len)
195 if s < 0 { bad = 1 } else {
196 pos[0] = p
197 gmin[s] = bf_rdint(b, pos, len)
198 gmax[s] = bf_rdint(b, pos, len)
199 if seen[s] == 0 { seen[s] = 1; n = n + 1 }
200 i = pos[0]
201 }
202 }
203 if c0 == 83 {
204 var p2: i64 = i + 1
205 var sk2: i64 = 1
206 while sk2 == 1 { if p2 >= len { sk2 = 0 } else { if (b[p2] as i64) == 32 { p2 = p2 + 1 } else { sk2 = 0 } } }
207 let s2: i64 = bf_sslot(b, p2, len)
208 if s2 < 0 { bad = 1 } else {
209 pos[0] = p2
210 sset[s2] = bf_rdint(b, pos, len)
211 if seen[BF_NGENE + s2] == 0 { seen[BF_NGENE + s2] = 1; n = n + 1 }
212 i = pos[0]
213 }
214 }
215 }
216 if (b[i] as i64) == 10 { bol = 1 } else { bol = 0 }
217 i = i + 1
218 }
219 if bad == 1 { return 0 - 1 }
220 return n
221}
222// find the LAST "<key>": in out[0..n) and read the integer after it.
223// ★ANCHORED ON THE FIELD NAME AND TAKEN POSITIONALLY LAST. The judge prints several axes plus a raw
224// block; a greedy 'nearest number' parse reads whichever value happens to trail the JSON, which is how
225// a parser silently starts measuring a different axis than the one it names.
226func bf_field(out: *u8, n: i64, key: *u8, found: *i64) -> i64 {
227 found[0] = 0
228 let kl: i64 = bf_len(key)
229 var pos: i64 = 0 - 1
230 var j: i64 = 0
231 while j + kl + 3 <= n {
232 if out[j] == (34 as u8) {
233 var m: i64 = 0
234 var ok: i64 = 1
235 var scan: i64 = 1
236 while scan == 1 {
237 if m >= kl { scan = 0 } else {
238 if out[j + 1 + m] != key[m] { ok = 0; scan = 0 } else { m = m + 1 }
239 }
240 }
241 if ok == 1 { if out[j + 1 + kl] == (34 as u8) { if out[j + 2 + kl] == (58 as u8) { pos = j + 3 + kl } } }
242 }
243 j = j + 1
244 }
245 if pos < 0 { return 0 }
246 let p2: *i64 = sys_mmap(16) as *i64
247 p2[0] = pos
248 let v: i64 = bf_rdint(out, p2, n)
249 found[0] = 1
250 return v
251}
252// Sum every `delta=<n>` the solver reported. ANCHORED on the field name and summed over ALL matches:
253// a single-match parse would report one part's drift as if it were the whole canon's, and a greedy
254// last-match parse would report whichever part happened to print last.
255func bf_sum_delta(b: *u8, n: i64) -> i64 {
256 let pos: *i64 = sys_mmap(16) as *i64
257 var j: i64 = 0
258 var tot: i64 = 0
259 while j + 6 <= n {
260 var hit: i64 = 0
261 if b[j] == (100 as u8) {
262 if b[j+1] == (101 as u8) { if b[j+2] == (108 as u8) { if b[j+3] == (116 as u8) {
263 if b[j+4] == (97 as u8) { if b[j+5] == (61 as u8) { hit = 1 } } } } }
264 }
265 if hit == 1 {
266 pos[0] = j + 6
267 let v: i64 = bf_rdint(b, pos, n)
268 var av: i64 = v
269 if av < 0 { av = 0 - av }
270 tot = tot + av
271 }
272 j = j + 1
273 }
274 return tot
275}
276func bf_streq(a: *u8, b: *u8) -> i64 {
277 var i: i64 = 0
278 var eq: i64 = 1
279 var go: i64 = 1
280 while go == 1 {
281 let ca: i64 = a[i] as i64
282 let cb: i64 = b[i] as i64
283 if ca != cb { eq = 0; go = 0 } else {
284 if ca == 0 { go = 0 } else { i = i + 1 }
285 }
286 }
287 return eq
288}
289// ★★★CANON MODE: THE GENOME IS A PER-PART RING SCALE. One gene per canon part scales that part's ring
290// radii as a unit, so the body stays anatomically coherent. 212 independent radii would evolve noise
291// and nothing downstream would refuse it. THIS is the structured genome the evolved-creature work is
292// about, and structure is what lets selection beat sampling -- the 5-knob genome had none, which is
293// why it lost to random search at equal budget (measured, debt 1786731598).
294// ★IT REFUSES ON OVERFLOW, NEVER TRUNCATES. A silently short canon would still parse, still render,
295// and still score -- a corrupt body that looks like a real candidate is worse than no candidate.
296// Returns parts seen, or 0-1 if the destination could not hold the result.
297func bf_canon_apply(src: *u8, slen: i64, g: *i64, ng: i64, lo: i64, hi: i64,
298 dst: *u8, dcap: i64, dlen: *i64) -> i64 {
299 let pos: *i64 = sys_mmap(16) as *i64
300 var i: i64 = 0
301 var o: i64 = 0
302 var part: i64 = 0 - 1
303 var over: i64 = 0
304 while i < slen {
305 var e: i64 = i
306 var seek: i64 = 1
307 while seek == 1 {
308 if e >= slen { seek = 0 } else {
309 if (src[e] as i64) == 10 { seek = 0 } else { e = e + 1 }
310 }
311 }
312 let c0: i64 = src[i] as i64
313 if c0 == 80 { part = part + 1 }
314 if o + 96 > dcap { over = 1 }
315 if over == 0 {
316 if c0 == 82 {
317 pos[0] = i
318 let ry: i64 = bf_rdint(src, pos, e)
319 let rx: i64 = bf_rdint(src, pos, e)
320 let rz: i64 = bf_rdint(src, pos, e)
321 let ra: i64 = bf_rdint(src, pos, e)
322 let rb: i64 = bf_rdint(src, pos, e)
323 var sc: i64 = BF_PERMIL
324 if part >= 0 { if part < ng { sc = lo + (hi - lo) * g[part] / BF_PERMIL } }
325 var na: i64 = ra * sc / BF_PERMIL
326 var nb: i64 = rb * sc / BF_PERMIL
327 if na < 1 { na = 1 }
328 if nb < 1 { nb = 1 }
329 dst[o] = 82 as u8; o = o + 1
330 dst[o] = 32 as u8; o = o + 1
331 o = o + bf_num(dst, o, ry) - 1
332 dst[o] = 32 as u8; o = o + 1
333 o = o + bf_num(dst, o, rx) - 1
334 dst[o] = 32 as u8; o = o + 1
335 o = o + bf_num(dst, o, rz) - 1
336 dst[o] = 32 as u8; o = o + 1
337 o = o + bf_num(dst, o, na) - 1
338 dst[o] = 32 as u8; o = o + 1
339 o = o + bf_num(dst, o, nb) - 1
340 dst[o] = 10 as u8; o = o + 1
341 } else {
342 var k: i64 = i
343 while k < e { dst[o] = src[k]; o = o + 1; k = k + 1 }
344 dst[o] = 10 as u8; o = o + 1
345 }
346 }
347 i = e + 1
348 }
349 dlen[0] = o
350 if over == 1 { return 0 - 1 }
351 return part + 1
352}
353func bf_run(path: *u8, av: *i64, out: *u8, olen: *i64) -> i64 {
354 olen[0] = 0
355 return tr_run_capture_to(path, av, out, BF_OUTCAP, olen, BF_STEP_TIMEOUT_MS)
356}
357
358func main(argc: i64, argv: *i64) -> i64 {
359 var canonmode: i64 = 0
360 if argc >= 2 { if bf_streq(argv[1] as *u8, "canon" as *u8) == 1 { canonmode = 1 } }
361 if canonmode == 0 {
362 if argc < BF_NGENE + 1 {
363 bf_eputs("usage: nx_bodyfit <height> <radial> <sub> <relief> <fat> -- prints FITNESS=<charjudge-1000>\n" as *u8)
364 bf_eputs(" nx_bodyfit canon <g0> <g1> ... -- per-mille ring scale, one gene per canon part\n" as *u8)
365 sys_exit(2)
366 return 2
367 }
368 }
369 if canonmode == 1 {
370 if argc < 3 {
371 bf_eputs("usage: nx_bodyfit canon <g0> <g1> ... -- per-mille ring scale, one gene per canon part\n" as *u8)
372 sys_exit(2)
373 return 2
374 }
375 }
376 let gmin: *i64 = sys_mmap(BF_NGENE*8) as *i64
377 let gmax: *i64 = sys_mmap(BF_NGENE*8) as *i64
378 let sset: *i64 = sys_mmap(BF_NSET*8) as *i64
379 // ★DEFAULTS BEFORE PARSE, so an OLD conf declaring none of the optional weights still yields exactly
380 // the shipped comp-only objective instead of an all-zero one. An objective that silently became zero
381 // would score every candidate identically -- and the search would still report a best-of-generation.
382 sset[BF_S_WCOMP] = 1
383 sset[BF_S_WPAL] = 0
384 sset[BF_S_WCON] = 0
385 sset[BF_S_WFACE] = 0
386 let seen: *i64 = sys_mmap((BF_NGENE+BF_NSET)*8) as *i64
387 let clen: *i64 = sys_mmap(16) as *i64
388 let cbuf: *u8 = sys_read_file(BF_CONF, clen)
389 if (cbuf as i64) == 0 { bf_eputs("BODYFIT REFUSE: cannot read knowledge/bodyfit.conf -- refusing to search with a built-in envelope\n" as *u8); sys_exit(3); return 3 }
390 let nf: i64 = bf_parse(cbuf, clen[0], gmin, gmax, sset, seen)
391 if nf < 0 { bf_eputs("BODYFIT REFUSE: bodyfit.conf carries an UNKNOWN key -- refusing rather than clamping on a silent zero\n" as *u8); sys_exit(3); return 3 }
392 if nf < BF_NGENE + BF_NSET_REQ { bf_eputs("BODYFIT REFUSE: bodyfit.conf is INCOMPLETE -- every gene bound and REQUIRED setting must be declared\n" as *u8); sys_exit(3); return 3 }
393
394 // ★GENES ARE PER-MILLE POSITIONS IN THE ENVELOPE, NOT RAW VALUES. The search then works in ONE
395 // uniform 0..1000 space for every gene regardless of its real units, so a single mutation step is
396 // the same PROPORTIONAL move whether the gene is a stature in millimetres or a count of radial
397 // segments. Raw-valued genes would need a per-gene step size the harness has no way to know, and a
398 // harness that must know its problem's units is a harness that can only search one problem.
399 // Clamping first means the SEARCH may propose anything while the PIPELINE only ever sees a legal
400 // body: an out-of-range proposal costs a wasted evaluation, never a broken run.
401 let g: *i64 = sys_mmap(BF_NGENE*8) as *i64
402 var i: i64 = 0
403 while i < BF_NGENE {
404 var pm: i64 = BF_CANON_HOLD
405 if canonmode == 0 { pm = bf_atoi(argv[i + 1] as *u8) }
406 if pm < 0 { pm = 0 }
407 if pm > BF_PERMIL { pm = BF_PERMIL }
408 g[i] = gmin[i] + (gmax[i] - gmin[i]) * pm / BF_PERMIL
409 i = i + 1
410 }
411 // ★IN CANON MODE THE KNOBS ARE PINNED MID-ENVELOPE so the ONLY thing varying is the canon. A run
412 // that moved both would not be able to say which one moved the objective.
413 // Canon genes are argv[2..], clamped to per-mille exactly like the knob genes. The buffer is sized
414 // from argc, so there is no gene-count ceiling to guess at.
415 let ncg: *i64 = sys_mmap(16) as *i64
416 ncg[0] = 0
417 let cg: *i64 = sys_mmap((argc + 2) * 8) as *i64
418 if canonmode == 1 {
419 var q: i64 = 2
420 while q < argc {
421 var pv: i64 = bf_atoi(argv[q] as *u8)
422 if pv < 0 { pv = 0 }
423 if pv > BF_PERMIL { pv = BF_PERMIL }
424 cg[ncg[0]] = pv
425 ncg[0] = ncg[0] + 1
426 q = q + 1
427 }
428 }
429 // scratch names carry the clamped genome, so a re-evaluation of the SAME candidate reuses the same
430 // files and two different candidates can never collide.
431 // ★★CANON RENDERS CARRY A DIFFERENT PREFIX ON PURPOSE. nx_axisvar censuses a directory BY PREFIX, so
432 // writing both populations under bodyfit_ would silently merge two different experiments into one
433 // distribution, and every axis figure taken from it would describe neither population.
434 let nmcap: i64 = 512 + argc * 12
435 let mesh: *u8 = sys_mmap(nmcap)
436 let png: *u8 = sys_mmap(nmcap)
437 let cpath: *u8 = sys_mmap(nmcap)
438 var pfx: *u8 = "/tmp/bodyfit_" as *u8
439 if canonmode == 1 { pfx = "/tmp/canonfit_" as *u8 }
440 var mo: i64 = bf_app(mesh, 0, pfx)
441 var po: i64 = bf_app(png, 0, pfx)
442 var co: i64 = bf_app(cpath, 0, pfx)
443 i = 0
444 while i < BF_NGENE {
445 let w1: i64 = bf_num(mesh, mo, g[i])
446 mo = mo + w1 - 1
447 let w2: i64 = bf_num(png, po, g[i])
448 po = po + w2 - 1
449 let w3: i64 = bf_num(cpath, co, g[i])
450 co = co + w3 - 1
451 mesh[mo] = 95 as u8; mo = mo + 1
452 png[po] = 95 as u8; po = po + 1
453 cpath[co] = 95 as u8; co = co + 1
454 i = i + 1
455 }
456 i = 0
457 while i < ncg[0] {
458 let w4: i64 = bf_num(mesh, mo, cg[i])
459 mo = mo + w4 - 1
460 let w5: i64 = bf_num(png, po, cg[i])
461 po = po + w5 - 1
462 let w6: i64 = bf_num(cpath, co, cg[i])
463 co = co + w6 - 1
464 mesh[mo] = 95 as u8; mo = mo + 1
465 png[po] = 95 as u8; po = po + 1
466 cpath[co] = 95 as u8; co = co + 1
467 i = i + 1
468 }
469 mo = bf_app(mesh, mo, ".nxmesh" as *u8); mesh[mo] = 0 as u8
470 po = bf_app(png, po, ".png" as *u8); png[po] = 0 as u8
471 co = bf_app(cpath, co, ".dat" as *u8); cpath[co] = 0 as u8
472
473 // ---- 0. CANON. Synthesise the canon this genome describes, then CONSTRAIN it. ----
474 var canonarg: *u8 = BF_CANON
475 var cviol: i64 = 0
476 if canonmode == 1 {
477 let pl: *i64 = sys_mmap(16) as *i64
478 let pb: *u8 = sys_read_file(BF_CANON_PATH, pl)
479 var canon_src: *u8 = BF_CANON_RINGS
480 if (pb as i64) != 0 { let pn: i64 = bf_trim_path(pb, pl[0]); if pn > 0 { canon_src = pb } }
481 let sl: *i64 = sys_mmap(16) as *i64
482 let sb: *u8 = sys_read_file(canon_src, sl)
483 if (sb as i64) == 0 {
484 bf_eputs("BODYFIT REFUSE: cannot read the ring canon\n" as *u8)
485 sys_exit(3)
486 return 3
487 }
488 let dcap: i64 = sl[0] * 3 + 4096
489 let db: *u8 = sys_mmap(dcap)
490 let dl: *i64 = sys_mmap(16) as *i64
491 let nparts: i64 = bf_canon_apply(sb, sl[0], cg, ncg[0], sset[BF_S_RLO], sset[BF_S_RHI], db, dcap, dl)
492 if nparts < 0 {
493 bf_eputs("BODYFIT REFUSE: canon rewrite overflowed -- refusing to emit a short canon\n" as *u8)
494 sys_exit(3)
495 return 3
496 }
497 // ★A GENE COUNT THAT DOES NOT MATCH THE PART COUNT IS A GUESSED MAPPING. Refuse rather than
498 // silently scaling the first N parts and leaving the rest at 1.0, which would still render.
499 if nparts != ncg[0] {
500 bf_eputs("BODYFIT REFUSE: genome carries " as *u8); bf_epn(ncg[0])
501 bf_eputs(" genes but the canon declares " as *u8); bf_epn(nparts)
502 bf_eputs(" parts -- one gene per part, or the mapping is a guess\n" as *u8)
503 sys_exit(3)
504 return 3
505 }
506 let fd: i64 = sys_openat_wr(cpath, MODE_0644)
507 if fd < 0 {
508 bf_eputs("BODYFIT REFUSE: cannot write the scratch canon\n" as *u8)
509 sys_exit(3)
510 return 3
511 }
512 sys_write(fd, db, dl[0])
513 sys_close(fd)
514 canonarg = cpath
515 // ★★★THE CONSTRAINT IS THE ESTATE'S OWN LINTER, not a rule invented here. nx_canon_solver projects
516 // each constrained part onto its feasible band and reports how far it had to move. That distance
517 // is a GRADED violation, which is what Deb's rule needs to rank infeasible candidates against
518 // each other instead of flattening them to one bad score.
519 // ★SCOPE, STATED: canon_face_rules constrains 2 of 16 parts (p15 eye, p3 foot). It is a
520 // REGRESSION GUARD on gate-verified anatomy, not a beauty band -- its own header says so. The
521 // other 14 parts are UNCONSTRAINED and this organ does not pretend otherwise.
522 // ★★★MEASURED 2026-08-14 AND IT MATTERS: UNDER THE RING-SCALE GENOME THIS TERM CANNOT FIRE.
523 // The rules bind part PLACEMENT (ox,oy,oz on the P rows); these genes scale ring RADII (a,b on
524 // the R rows). They are orthogonal, so cviol is 0 for EVERY genome -- verified at the extremes
525 // (all parts at 0.7x, and p3/p15 at 1.3x, both returned 0), not merely on the identity case.
526 // DO NOT READ canon_violation=0 AS THE CANON WAS VALIDATED: it means the constraint had
527 // nothing to say. A field that is always zero reads as evidence, which is why this is recorded
528 // here rather than left for the next reader to infer from a column of zeros.
529 // The term is KEPT, not deleted, because it goes live the moment the genome grows a placement
530 // gene -- and it is already bite-proven to fire when placement moves (p15 ox 19->40 gives
531 // LINT delta=15). Growing the genome is the work that makes it real.
532 let sout: *u8 = sys_mmap(BF_OUTCAP)
533 let solen: *i64 = sys_mmap(16) as *i64
534 let av0: *i64 = sys_mmap(64) as *i64
535 av0[0] = BF_SOLVER as i64
536 av0[1] = cpath as i64
537 av0[2] = BF_RULES as i64
538 av0[3] = 0
539 if bf_run(BF_SOLVER, av0, sout, solen) == 0 {
540 cviol = bf_sum_delta(sout, solen[0])
541 } else {
542 // ★"I COULD NOT LOOK" IS NOT "IT IS FEASIBLE". An unreadable constraint is treated as a
543 // violation so a candidate can never be admitted by the constraint checker failing.
544 cviol = 0 - 1
545 }
546 }
547
548 let nums: *u8 = sys_mmap(256)
549 var no: i64 = 0
550 let a_h: i64 = (nums as i64) + no; no = no + bf_num(nums, no, g[BF_G_HEIGHT])
551 let a_r: i64 = (nums as i64) + no; no = no + bf_num(nums, no, g[BF_G_RADIAL])
552 let a_s: i64 = (nums as i64) + no; no = no + bf_num(nums, no, g[BF_G_SUB])
553 let a_rl: i64 = (nums as i64) + no; no = no + bf_num(nums, no, g[BF_G_RELIEF])
554 let a_f: i64 = (nums as i64) + no; no = no + bf_num(nums, no, g[BF_G_FAT])
555 let a_w: i64 = (nums as i64) + no; no = no + bf_num(nums, no, sset[BF_S_W])
556 let a_ht: i64 = (nums as i64) + no; no = no + bf_num(nums, no, sset[BF_S_H])
557
558 let out: *u8 = sys_mmap(BF_OUTCAP)
559 let olen: *i64 = sys_mmap(16) as *i64
560
561 // ---- 1. GENERATE. nx_body_gen <out> <height> <radial> <sub> <relief> <canon> <fat> ----
562 let av1: *i64 = sys_mmap(128) as *i64
563 av1[0] = BF_BODYGEN as i64
564 av1[1] = mesh as i64
565 av1[2] = a_h
566 av1[3] = a_r
567 av1[4] = a_s
568 av1[5] = a_rl
569 av1[6] = canonarg as i64
570 av1[7] = a_f
571 av1[8] = 0
572 if bf_run(BF_BODYGEN, av1, out, olen) != 0 {
573 bf_eputs("BODYFIT: generate FAILED for genome " as *u8)
574 i = 0
575 while i < BF_NGENE { bf_epn(g[i]); bf_eputs(" " as *u8); i = i + 1 }
576 bf_eputs("\n" as *u8)
577 sys_exit(4)
578 return 4
579 }
580 // ---- 2. RENDER. nx_anat_sov <mesh> <mode> <eye> <W> <H> <out.png> ----
581 let av2: *i64 = sys_mmap(128) as *i64
582 av2[0] = BF_ANAT as i64
583 av2[1] = mesh as i64
584 av2[2] = "intact" as *u8 as i64
585 av2[3] = "C" as *u8 as i64
586 av2[4] = a_w
587 av2[5] = a_ht
588 av2[6] = png as i64
589 av2[7] = 0
590 if bf_run(BF_ANAT, av2, out, olen) != 0 {
591 bf_eputs("BODYFIT: render FAILED\n" as *u8)
592 sys_exit(5)
593 return 5
594 }
595 // ★★★THE MESH IS 6 MB AND A SEARCH IS THE CALLER. A 350-evaluation run leaves ~2.1 GB of scratch
596 // behind, and an evaluator that fills the disk is a defect even when its number is correct.
597 // ★THE PNG IS KEPT ON PURPOSE: nx_axisvar censuses the render population BY PREFIX, so deleting it
598 // would destroy the only durable record of what the search actually explored. The mesh is
599 // reproducible from the genome at any time, which makes it the one safe thing to drop.
600 sys_unlinkat(mesh)
601 // ---- 3. JUDGE. nx_charjudge <image.png> <label> ----
602 let av3: *i64 = sys_mmap(128) as *i64
603 av3[0] = BF_JUDGE as i64
604 av3[1] = png as i64
605 av3[2] = "bodyfit" as *u8 as i64
606 av3[3] = 0
607 if bf_run(BF_JUDGE, av3, out, olen) != 0 {
608 bf_eputs("BODYFIT: judge FAILED\n" as *u8)
609 sys_exit(6)
610 return 6
611 }
612 // ★★★THE JUDGE'S DECISION SCORE AND THE SEARCH'S FITNESS ARE DIFFERENT OBJECTS. CHARJUDGE is a VETO
613 // composite: MEASURED on a real render, composition 470 and contour 1000 still reported CHARJUDGE 0
614 // because the face axis was 0. That is exactly right for an ADMISSION decision and useless as a
615 // GRADIENT -- one zero axis flattens the entire landscape, and a GA cannot climb a cliff. It would
616 // still RUN, and it would still report a best-of-generation, and the whole search would be noise.
617 // Fitness therefore uses the MEAN OF THE AXES, which degrades smoothly; the veto score is reported
618 // alongside on stderr so a caller can still see whether the candidate would be ADMITTED.
619 let found: *i64 = sys_mmap(16) as *i64
620 let fc: *i64 = sys_mmap(16) as *i64
621 let comp: i64 = bf_field(out, olen[0], "composition" as *u8, found)
622 var nax: i64 = found[0]
623 let pal: i64 = bf_field(out, olen[0], "palette_axis" as *u8, fc)
624 nax = nax + fc[0]
625 let con: i64 = bf_field(out, olen[0], "contour_axis" as *u8, fc)
626 nax = nax + fc[0]
627 let fac: i64 = bf_field(out, olen[0], "face_axis" as *u8, fc)
628 nax = nax + fc[0]
629 let veto: i64 = bf_field(out, olen[0], "CHARJUDGE" as *u8, fc)
630 if nax != BF_NAXIS {
631 // ★AN ABSENT FIELD IS NOT A ZERO SCORE. Emitting a fitness here would make an unparsable judge
632 // look like an ugly body, and the search would happily optimise against noise.
633 bf_eputs("BODYFIT: judge output is missing axes -- refusing to invent a score\n" as *u8)
634 sys_exit(6)
635 return 6
636 }
637 // ★★★CHARJUDGE IS AN ADMISSION JUDGE, NOT A GRADIENT, AND THAT IS CORRECT. Its own header says
638 // HEADLINE = MIN(composition, palette, contour, face) and that each axis kills a named adversarial
639 // input; face is a CAPABILITY FLOOR -- 'a blob with no face is not 40pc of a character' -- so its
640 // 0/1000 shape is deliberate, not a defect. Measured over 218 renders: face takes 2 values and
641 // contour sits at mean 999/1000, so a MEAN of the four axes is ~32 points of gradient behind a
642 // 250-point cliff, and a GA loses to random search on it (proven, debt 1786731598).
643 // ★SO DO NOT BEND THE JUDGE TO SUIT THE SEARCH. Treat it as what it is -- a CONSTRAINT -- and
644 // optimise a continuous OBJECTIVE among the candidates it admits. That is Deb's feasibility rule,
645 // standard constrained-GA practice, not something invented here:
646 // every FEASIBLE candidate outranks every INFEASIBLE one, and
647 // inside each region the ordering is by the objective, so BOTH regions carry a gradient.
648 // composition is the objective because it is the measured continuous axis: range 97, 17 distinct
649 // values over 218 renders, versus palette's 33/18 and contour's ~0.
650 var admitted: i64 = 0
651 if veto > 0 { if cviol == 0 { admitted = 1 } }
652 // ★★THE OBJECTIVE IS THE CONF-WEIGHTED SUM OF THE CONTINUOUS AXES, and the ceiling SCALES with the
653 // weights so nx_evolve's "0 = optimum" convention holds however many axes are enabled. With the
654 // default weights (comp=1, rest 0) wsum is 1, ceilsum is 1000, and this is arithmetically identical
655 // to the shipped comp-only fitness -- so enabling an axis is a reviewable CONF change, not a code
656 // change, and the earlier runs stay comparable until someone deliberately rebases them.
657 let wsum: i64 = sset[BF_S_WCOMP] + sset[BF_S_WPAL] + sset[BF_S_WCON] + sset[BF_S_WFACE]
658 if wsum < 1 {
659 // ★AN ALL-ZERO OBJECTIVE SCORES EVERY CANDIDATE IDENTICALLY AND THE SEARCH STILL REPORTS A BEST.
660 // That is a flat landscape wearing the shape of a result, so refuse instead of running it.
661 bf_eputs("BODYFIT REFUSE: every objective weight is zero -- that scores all candidates alike\n" as *u8)
662 sys_exit(3)
663 return 3
664 }
665 let obj: i64 = comp*sset[BF_S_WCOMP] + pal*sset[BF_S_WPAL] + con*sset[BF_S_WCON] + fac*sset[BF_S_WFACE]
666 let ceilsum: i64 = BF_JUDGE_CEIL * wsum
667 var mean: i64 = 0
668 if admitted == 1 { mean = obj } else { mean = obj - ceilsum }
669 // ★CANON INFEASIBILITY IS A SECOND, GRADED CONSTRAINT stacked BELOW the veto floor, so an
670 // anatomically invalid canon can never outrank a valid one however well it happens to score.
671 // ★★KNOB MODE IS BIT-FOR-BIT UNCHANGED: cviol is 0 there, so both branches below are no-ops and the
672 // 272 renders already measured stay comparable. A fitness edit that silently rebased the earlier
673 // runs would make every prior number incomparable without announcing it.
674 if cviol > 0 { mean = mean - cviol }
675 if cviol < 0 { mean = mean - ceilsum }
676 bf_eputs("BODYFIT axes comp=" as *u8); bf_epn(comp)
677 bf_eputs(" palette=" as *u8); bf_epn(pal)
678 bf_eputs(" contour=" as *u8); bf_epn(con)
679 bf_eputs(" face=" as *u8); bf_epn(fac)
680 bf_eputs(" admitted=" as *u8); bf_epn(admitted)
681 bf_eputs(" objective=" as *u8); bf_epn(mean)
682 // ★WITHOUT THIS, TWO RUNS UNDER DIFFERENT WEIGHTS EMIT FITNESS NUMBERS THAT ARE NOT COMPARABLE AND
683 // NOTHING IN THE OUTPUT SAYS SO. The scale of the fitness is a property of the conf, so it travels
684 // with every evaluation rather than living only in the file that happened to be current.
685 bf_eputs(" wsum=" as *u8); bf_epn(wsum)
686 bf_eputs(" charjudge_veto=" as *u8); bf_epn(veto)
687 bf_eputs(" canon_violation=" as *u8); bf_epn(cviol)
688 // ★ANNOUNCE THE ARTIFACT PATH. A gate -- or an operator inspecting a winning genome -- would
689 // otherwise have to RECONSTRUCT this filename from bodyfit.conf's envelope midpoints, which breaks
690 // silently the moment any bound is edited. An organ that names what it wrote cannot be mis-addressed.
691 if canonmode == 1 { bf_eputs(" canon_path=" as *u8); bf_eputs(cpath) }
692 bf_eputs("\n" as *u8)
693 bf_puts("FITNESS=" as *u8)
694 let fit: i64 = mean - ceilsum
695 let fb: *u8 = sys_mmap(32)
696 let fw: i64 = bf_num(fb, 0, fit)
697 sys_write(1, fb, fw - 1)
698 bf_puts("\n" as *u8)
699 sys_exit(0)
700 return 0
701}