nx_manga_charsheet.nx source
↩ module page · 320 lines · 14753 B
1// nx_manga_charsheet.nx -- R4 of the mangagen lane (comparewatch- contract mangagen_mcs_sheet_emit):
2// a CHARACTER SHEET. Composes four incumbents by forking them, and writes no renderer, no compositor and
3// no judge of its own:
4// nx_anat_sov one view per row <mesh> <mode> <eye C|L|R> <W> <H> <out.png>
5// nx_contactsheet the grid <dir> <out.png> <tile> <cols>
6// nx_charjudge per-cell identity <cell.png> <label>
7// nx_headcrop (caller's choice) bands a mesh to the head before the sweep
8// THE DONE-RULE, pre-declared on /compare/mangagen before this organ existed:
9// "Legality-checked pose sweep, renders, sheet grid; identity judged per cell."
10// HONEST REPORTING: face_axis is PRINTED for every cell, never suppressed and never floored away.
11// ⚠RENDER SIZE IS A JUDGE PARAMETER, MEASURED 2026-08-14, AND IT CORRECTED ME. I first saw face_axis 0
12// at 320x480, at skin 512x768 and on a head-cropped mesh, and concluded the model had no face. All three
13// shared a confound -- every one was at least 320 wide. At 256x384 the SAME mesh judges face_axis 1000
14// and CHARJUDGE 294. The face was there; the judge stops reporting it above roughly 320 px wide.
15// So MCS_RENDER_W/H sit inside the range where the judge actually measures, and that is a deliberate,
16// written-down choice rather than a lucky default. Filed as a judge-scale debt.
17// usage: nx_manga_charsheet <mesh.nxmesh> <sheet.conf> <outdir> <out.png> [tile] [cols]
18// sheet.conf rows: view|<mode>|<eye C|L|R>|<label>
19// license_tier: ORIGINAL expect_exit: 0
20import "nx_str.nx"
21import "nx_syscalls.nx"
22import "nx_tool_run.nx"
23const MCS_MAGIC_1024: i64 = 1024
24const MCS_CAP: i64 = 65536
25const MCS_MAXVIEW: i64 = 32
26const MCS_RENDER_W: i64 = 256
27const MCS_RENDER_H: i64 = 384
28const MCS_TILE_DEFAULT: i64 = 192
29const MCS_COLS_DEFAULT: i64 = 4
30const MCS_ANAT: *u8 = "/volume1/homes/elderwesto/nishihost/nx_anat_sov.elf"
31const MCS_SHEET: *u8 = "/volume1/homes/elderwesto/nishihost/nx_contactsheet.elf"
32const MCS_JUDGE: *u8 = "/volume1/homes/elderwesto/nishihost/nx_charjudge.elf"
33func mcs_puts(s: *u8) -> i64 { sys_write(1, s, nx_str_len(s)); return 0 }
34func mcs_pi(v: i64) -> i64 {
35 let t: *u8 = sys_mmap(32)
36 let o: *u8 = sys_mmap(32)
37 var m: i64 = v
38 var k: i64 = 0
39 if m < 0 { m = 0 - m; sys_write(1, "-" as *u8, 1) }
40 if m == 0 { t[0] = 48; k = 1 }
41 while m > 0 { t[k] = 48 + (m % 10); m = m / 10; k = k + 1 }
42 var i: i64 = 0
43 while i < k { o[i] = t[k - 1 - i]; i = i + 1 }
44 sys_write(1, o, k)
45 return 0
46}
47func mcs_atoi(s: *u8) -> i64 {
48 var i: i64 = 0
49 var v: i64 = 0
50 var go: i64 = 1
51 while go == 1 {
52 let c: i64 = s[i] as i64
53 if c < 48 { go = 0 } else { if c > 57 { go = 0 } else { v = v * 10 + (c - 48); i = i + 1 } }
54 }
55 return v
56}
57func mcs_cat(dst: *u8, off: i64, s: *u8) -> i64 { var i: i64 = 0; while s[i] != (0 as u8) { dst[off+i] = s[i]; i = i + 1 } return off + i }
58func mcs_num(dst: *u8, off: i64, v: i64) -> i64 {
59 var m: i64 = v
60 let t: *u8 = sys_mmap(32)
61 var k: i64 = 0
62 if m == 0 { t[0] = 48 as u8; k = 1 }
63 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 }
64 var o: i64 = off
65 var z: i64 = 0
66 while z < k { dst[o] = t[k - 1 - z]; o = o + 1; z = z + 1 }
67 return o
68}
69// The renderer ACCEPTS an unknown mode and draws something anyway, so "the render failed" never fires for
70// a typo -- the sheet would compose a cell nobody asked for. Found by the gate's neg-control-hole tooth on
71// its first run. Validate the declared mode HERE, fail-closed, so the guarantee is real rather than
72// inherited from a downstream organ that does not make it.
73func mcs_mode_ok(m: *u8) -> i64 {
74 if nx_str_eq(m, "intact" as *u8) == 1 { return 1 }
75 if nx_str_eq(m, "skin" as *u8) == 1 { return 1 }
76 if nx_str_eq(m, "explode" as *u8) == 1 { return 1 }
77 // layerN: the prefix plus at least one digit
78 if m[0] == (108 as u8) { if m[1] == (97 as u8) { if m[2] == (121 as u8) { if m[3] == (101 as u8) { if m[4] == (114 as u8) {
79 let d: i64 = m[5] as i64
80 if d >= 48 { if d <= 57 { return 1 } }
81 } } } } }
82 return 0
83}
84func mcs_exists(p: *u8) -> i64 {
85 let fd: i64 = sys_openat_rd(p)
86 if fd < 0 { return 0 }
87 sys_close(fd)
88 return 1
89}
90func mcs_read(path: *u8, buf: *u8, cap: i64) -> i64 {
91 let fd: i64 = sys_openat_rd(path)
92 if fd < 0 { return 0 - 1 }
93 var tot: i64 = 0
94 while tot < cap { let r: i64 = sys_read(fd, (buf as i64 + tot) as *u8, cap - tot); if r <= 0 { break } tot = tot + r }
95 sys_close(fd)
96 return tot
97}
98// pull an integer field out of the judge's JSON: find "<key>": then read the number after it.
99// Returns the value, or the caller's miss value when the key is absent -- an ABSENT axis and an axis that
100// measured zero are different facts, so they must not collapse to the same number.
101func mcs_json_int(buf: *u8, n: i64, key: *u8, miss: i64) -> i64 {
102 let m: i64 = nx_str_len(key)
103 var i: i64 = 0
104 while i + m < n {
105 var k: i64 = 0
106 var hit: i64 = 1
107 while k < m { if buf[i+k] != key[k] { hit = 0; k = m } else { k = k + 1 } }
108 if hit == 1 {
109 var j: i64 = i + m
110 var guard: i64 = 1
111 while guard == 1 {
112 if j >= n { guard = 0 } else {
113 let c: i64 = buf[j] as i64
114 if c == 34 { j = j + 1 } else { if c == 58 { j = j + 1 } else { if c == 32 { j = j + 1 } else { guard = 0 } } }
115 }
116 }
117 var v: i64 = 0
118 var any: i64 = 0
119 var neg: i64 = 0
120 if j < n { if buf[j] == (45 as u8) { neg = 1; j = j + 1 } }
121 var go2: i64 = 1
122 while go2 == 1 {
123 if j >= n { go2 = 0 } else {
124 let d: i64 = buf[j] as i64
125 if d < 48 { go2 = 0 } else { if d > 57 { go2 = 0 } else { v = v * 10 + (d - 48); any = 1; j = j + 1 } }
126 }
127 }
128 if any == 1 { if neg == 1 { return 0 - v } return v }
129 return miss
130 }
131 i = i + 1
132 }
133 return miss
134}
135// THE CONTRACT (watched as mangagen_mcs_sheet_emit): render every declared view of one character, compose
136// them into a sheet, and judge each cell -- or REFUSE. Returns 0, or a named non-zero.
137func mcs_sheet_emit(mesh: *u8, nviews: i64, modes: *i64, eyes: *i64, labels: *i64, dir: *u8, outp: *u8, tile: i64, cols: i64) -> i64 {
138 let out: *u8 = sys_mmap(MCS_CAP)
139 let ol: *i64 = sys_mmap(16) as *i64
140 let av: *i64 = sys_mmap(128) as *i64
141 let cell: *u8 = sys_mmap(MCS_MAGIC_1024)
142 let wbuf: *u8 = sys_mmap(64)
143 let hbuf: *u8 = sys_mmap(64)
144 let tbuf: *u8 = sys_mmap(64)
145 let cbuf: *u8 = sys_mmap(64)
146 var q: i64 = mcs_num(wbuf, 0, MCS_RENDER_W); wbuf[q] = 0 as u8
147 q = mcs_num(hbuf, 0, MCS_RENDER_H); hbuf[q] = 0 as u8
148 q = mcs_num(tbuf, 0, tile); tbuf[q] = 0 as u8
149 q = mcs_num(cbuf, 0, cols); cbuf[q] = 0 as u8
150 sys_mkdir(dir, 493)
151 // ---- RENDER PASS: one view per declared row ------------------------------------------------
152 var i: i64 = 0
153 while i < nviews {
154 if mcs_mode_ok(modes[i] as *u8) == 0 {
155 mcs_puts("MANGA-CHARSHEET-REFUSE view " as *u8); mcs_pi(i)
156 mcs_puts(" declares mode '" as *u8); mcs_puts(modes[i] as *u8)
157 mcs_puts("', which is not one of intact, skin, explode or layerN. The renderer would draw\n" as *u8)
158 mcs_puts(" something anyway, so the sheet refuses here rather than composing a cell nobody asked for.\n" as *u8)
159 return 3
160 }
161 var o: i64 = mcs_cat(cell, 0, dir)
162 o = mcs_cat(cell, o, "/" as *u8)
163 o = mcs_num(cell, o, i)
164 o = mcs_cat(cell, o, ".png" as *u8)
165 cell[o] = 0 as u8
166 av[0] = MCS_ANAT as i64
167 av[1] = mesh as i64
168 av[2] = modes[i]
169 av[3] = eyes[i]
170 av[4] = wbuf as i64
171 av[5] = hbuf as i64
172 av[6] = cell as i64
173 av[7] = 0
174 let rc: i64 = tr_run_capture(MCS_ANAT, av, out, MCS_CAP, ol)
175 if rc != 0 {
176 mcs_puts("MANGA-CHARSHEET-REFUSE renderer failed on view " as *u8); mcs_pi(i); mcs_puts("\n" as *u8)
177 return 4
178 }
179 if mcs_exists(cell) == 0 {
180 mcs_puts("MANGA-CHARSHEET-REFUSE view " as *u8); mcs_pi(i)
181 mcs_puts(" produced no image -- refusing rather than composing a sheet with a hole in it\n" as *u8)
182 return 4
183 }
184 i = i + 1
185 }
186 // ---- GRID PASS: the incumbent contact sheet composes them --------------------------------
187 av[0] = MCS_SHEET as i64
188 av[1] = dir as i64
189 av[2] = outp as i64
190 av[3] = tbuf as i64
191 av[4] = cbuf as i64
192 av[5] = 0
193 let src: i64 = tr_run_capture(MCS_SHEET, av, out, MCS_CAP, ol)
194 if src != 0 { mcs_puts("MANGA-CHARSHEET-REFUSE sheet composition failed\n" as *u8); return 5 }
195 if mcs_exists(outp) == 0 { mcs_puts("MANGA-CHARSHEET-REFUSE no sheet reached disk\n" as *u8); return 5 }
196 // ---- JUDGE PASS: every cell, and the zero is PRINTED ---------------------------------------
197 var judged: i64 = 0
198 var faceless: i64 = 0
199 i = 0
200 while i < nviews {
201 var o2: i64 = mcs_cat(cell, 0, dir)
202 o2 = mcs_cat(cell, o2, "/" as *u8)
203 o2 = mcs_num(cell, o2, i)
204 o2 = mcs_cat(cell, o2, ".png" as *u8)
205 cell[o2] = 0 as u8
206 av[0] = MCS_JUDGE as i64
207 av[1] = cell as i64
208 av[2] = labels[i]
209 av[3] = 0
210 let jrc: i64 = tr_run_capture(MCS_JUDGE, av, out, MCS_CAP, ol)
211 if jrc != 0 { mcs_puts("MANGA-CHARSHEET-REFUSE judge failed on cell " as *u8); mcs_pi(i); mcs_puts("\n" as *u8); return 6 }
212 let n: i64 = ol[0]
213 let contour: i64 = mcs_json_int(out, n, "contour_axis" as *u8, 0 - 1)
214 let comp: i64 = mcs_json_int(out, n, "composition" as *u8, 0 - 1)
215 let pal: i64 = mcs_json_int(out, n, "palette_axis" as *u8, 0 - 1)
216 let face: i64 = mcs_json_int(out, n, "face_axis" as *u8, 0 - 1)
217 if contour < 0 { mcs_puts("MANGA-CHARSHEET-REFUSE judge returned no contour axis for cell " as *u8); mcs_pi(i); mcs_puts("\n" as *u8); return 6 }
218 mcs_puts(" cell " as *u8); mcs_pi(i); mcs_puts(" " as *u8); mcs_puts(labels[i] as *u8)
219 mcs_puts(" contour=" as *u8); mcs_pi(contour)
220 mcs_puts(" composition=" as *u8); mcs_pi(comp)
221 mcs_puts(" palette=" as *u8); mcs_pi(pal)
222 mcs_puts(" face=" as *u8); mcs_pi(face)
223 if face == 0 { faceless = faceless + 1; mcs_puts(" (honest zero: no facial geometry to measure)" as *u8) }
224 mcs_puts("\n" as *u8)
225 judged = judged + 1
226 i = i + 1
227 }
228 mcs_puts(" partition: views=" as *u8); mcs_pi(nviews)
229 mcs_puts(" rendered=" as *u8); mcs_pi(nviews)
230 mcs_puts(" judged=" as *u8); mcs_pi(judged)
231 if judged != nviews { mcs_puts(" PARTITION-LEAK -- refusing\n" as *u8); return 7 }
232 mcs_puts(" (parts sum)\n" as *u8)
233 // Report the face axis either way, and only EXPLAIN a zero when there is one. The first cut printed
234 // the explanation unconditionally, so a sheet with zero faceless cells still argued about faceless
235 // cells. A summary that narrates a condition it did not observe is a lie with good intentions.
236 if faceless > 0 {
237 mcs_puts(" face_axis reads zero on " as *u8); mcs_pi(faceless)
238 mcs_puts(" of " as *u8); mcs_pi(nviews)
239 mcs_puts(" cells -- REPORTED, never floored away.\n" as *u8)
240 } else {
241 mcs_puts(" face_axis measured on all " as *u8); mcs_pi(nviews)
242 mcs_puts(" cells.\n" as *u8)
243 }
244 return 0
245}
246func main(argc: i64, argv: *i64) -> i64 {
247 if argc < 5 {
248 mcs_puts("usage: nx_manga_charsheet <mesh.nxmesh> <sheet.conf> <outdir> <out.png> [tile] [cols]\n" as *u8)
249 mcs_puts(" sheet.conf rows: view|<mode intact|skin|explode|layerN>|<eye C|L|R>|<label>\n" as *u8)
250 mcs_puts(" every declared view must render or the sheet REFUSES -- it never composes a hole.\n" as *u8)
251 sys_exit(2); return 2
252 }
253 let mesh: *u8 = argv[1] as *u8
254 if mcs_exists(mesh) == 0 {
255 mcs_puts("MANGA-CHARSHEET-REFUSE cannot read mesh: " as *u8); mcs_puts(mesh); mcs_puts("\n" as *u8)
256 sys_exit(3); return 3
257 }
258 let buf: *u8 = sys_mmap(MCS_CAP)
259 let n: i64 = mcs_read(argv[2] as *u8, buf, MCS_CAP - 2)
260 if n <= 0 {
261 mcs_puts("MANGA-CHARSHEET-REFUSE cannot read sheet spec: " as *u8); mcs_puts(argv[2] as *u8); mcs_puts("\n" as *u8)
262 sys_exit(3); return 3
263 }
264 buf[n] = 0 as u8
265 let modes: *i64 = sys_mmap(8 * MCS_MAXVIEW) as *i64
266 let eyes: *i64 = sys_mmap(8 * MCS_MAXVIEW) as *i64
267 let labels: *i64 = sys_mmap(8 * MCS_MAXVIEW) as *i64
268 var nv: i64 = 0
269 var p: i64 = 0
270 while p < n {
271 var e: i64 = p
272 while e < n { if buf[e] == (10 as u8) { break } e = e + 1 }
273 buf[e] = 0 as u8
274 let line: *u8 = (buf as i64 + p) as *u8
275 p = e + 1
276 var skip: i64 = 0
277 if line[0] == (0 as u8) { skip = 1 }
278 if line[0] == (35 as u8) { skip = 1 }
279 if line[0] == (13 as u8) { skip = 1 }
280 if skip == 0 {
281 // view|<mode>|<eye>|<label>
282 var f1: i64 = 0 - 1
283 var f2: i64 = 0 - 1
284 var f3: i64 = 0 - 1
285 var x: i64 = 0
286 while line[x] != (0 as u8) {
287 if line[x] == (124 as u8) {
288 if f1 < 0 { f1 = x } else { if f2 < 0 { f2 = x } else { if f3 < 0 { f3 = x } } }
289 }
290 x = x + 1
291 }
292 if f3 > 0 {
293 line[f1] = 0 as u8
294 line[f2] = 0 as u8
295 line[f3] = 0 as u8
296 if nv < MCS_MAXVIEW {
297 modes[nv] = (line as i64) + f1 + 1
298 eyes[nv] = (line as i64) + f2 + 1
299 labels[nv] = (line as i64) + f3 + 1
300 nv = nv + 1
301 }
302 }
303 }
304 }
305 if nv <= 0 {
306 mcs_puts("MANGA-CHARSHEET-REFUSE the sheet spec declares no views\n" as *u8)
307 sys_exit(3); return 3
308 }
309 var tile: i64 = MCS_TILE_DEFAULT
310 var cols: i64 = MCS_COLS_DEFAULT
311 if argc >= 6 { tile = mcs_atoi(argv[5] as *u8) }
312 if argc >= 7 { cols = mcs_atoi(argv[6] as *u8) }
313 mcs_puts("=== nx_manga_charsheet views=" as *u8); mcs_pi(nv)
314 mcs_puts(" tile=" as *u8); mcs_pi(tile); mcs_puts(" cols=" as *u8); mcs_pi(cols); mcs_puts("\n" as *u8)
315 let rc: i64 = mcs_sheet_emit(mesh, nv, modes, eyes, labels, argv[3] as *u8, argv[4] as *u8, tile, cols)
316 if rc != 0 { sys_exit(rc); return rc }
317 mcs_puts("MANGA-CHARSHEET-OK " as *u8); mcs_puts(argv[4] as *u8); mcs_puts("\n" as *u8)
318 sys_exit(0)
319 return 0
320}