code wiki / (root) / nx_bodyfit.nx

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}