code wiki / (root) / nx_manga_script.nx

nx_manga_script.nx source

↩ module page · 178 lines · 8156 B

1// nx_manga_script.nx -- R3 of the mangagen lane (comparewatch- contract mangagen_ms_beats): 2// a SCRIPT becomes PANEL BEATS. Rows in, rows out, no model in the loop -- the deterministic half of what 3// DiffSensei does with an MLLM, so the assignment is reproducible and auditable before any art exists. 4// script rows: beat|<action or caption> say|<speaker>|<line of dialogue> 5// beatplan out: panel|<idx>|beat|<text> panel|<idx>|say|<speaker>|<line> 6// THE DONE-RULE, pre-declared on /compare/mangagen before this organ existed: 7// "Every script line lands in exactly one panel; the partition is printed." 8// So the partition is not a comment -- it is COMPUTED and PRINTED, and a plan whose per-panel counts do 9// not sum back to the rows read is a REFUSAL, never a quietly-dropped line. 10// usage: nx_manga_script <script> <panels> [out.beatplan] 11// license_tier: ORIGINAL expect_exit: 0 12import "nx_str.nx" 13import "nx_syscalls.nx" 14const MS_CAP: i64 = 262144 15const MS_MAXROW: i64 = 2048 16const MS_MAXPANEL: i64 = 64 17func ms_puts(s: *u8) -> i64 { sys_write(1, s, nx_str_len(s)); return 0 } 18func ms_pi(v: i64) -> i64 { 19 let t: *u8 = sys_mmap(32) 20 let o: *u8 = sys_mmap(32) 21 var m: i64 = v 22 var k: i64 = 0 23 if m < 0 { m = 0 - m; sys_write(1, "-" as *u8, 1) } 24 if m == 0 { t[0] = 48; k = 1 } 25 while m > 0 { t[k] = 48 + (m % 10); m = m / 10; k = k + 1 } 26 var i: i64 = 0 27 while i < k { o[i] = t[k - 1 - i]; i = i + 1 } 28 sys_write(1, o, k) 29 return 0 30} 31func ms_atoi(s: *u8) -> i64 { 32 var i: i64 = 0 33 var v: i64 = 0 34 var go: i64 = 1 35 while go == 1 { 36 let c: i64 = s[i] as i64 37 if c < 48 { go = 0 } else { if c > 57 { go = 0 } else { v = v * 10 + (c - 48); i = i + 1 } } 38 } 39 return v 40} 41func ms_read(path: *u8, buf: *u8, cap: i64) -> i64 { 42 let fd: i64 = sys_openat_rd(path) 43 if fd < 0 { return 0 - 1 } 44 var tot: i64 = 0 45 while tot < cap { let r: i64 = sys_read(fd, (buf as i64 + tot) as *u8, cap - tot); if r <= 0 { break } tot = tot + r } 46 sys_close(fd) 47 return tot 48} 49// THE CONTRACT (watched as mangagen_ms_beats): assign every script row to exactly one panel. 50// Rows are kept in SCRIPT ORDER and split into `panels` contiguous runs -- a beat sheet is a sequence, 51// so reordering it would be a different story, not a different layout. The split is balanced: the first 52// (rows mod panels) panels take one extra row, which is the only distribution that is both contiguous 53// and never leaves a panel empty when rows >= panels. 54// Fills first[i]/count[i] and returns the number of rows assigned, or a NEGATIVE refusal code: 55// -1 no rows -2 no panels -3 fewer rows than panels (a panel would be empty) 56func ms_beats(nrows: i64, panels: i64, first: *i64, count: *i64) -> i64 { 57 if nrows <= 0 { return 0 - 1 } 58 if panels <= 0 { return 0 - 2 } 59 if nrows < panels { return 0 - 3 } 60 let base: i64 = nrows / panels 61 let extra: i64 = nrows % panels 62 var assigned: i64 = 0 63 var p: i64 = 0 64 while p < panels { 65 var c: i64 = base 66 if p < extra { c = c + 1 } 67 first[p] = assigned 68 count[p] = c 69 assigned = assigned + c 70 p = p + 1 71 } 72 return assigned 73} 74func main(argc: i64, argv: *i64) -> i64 { 75 if argc < 3 { 76 ms_puts("usage: nx_manga_script <script> <panels> [out.beatplan]\n" as *u8) 77 ms_puts(" script rows: beat|<action> or say|<speaker>|<line>\n" as *u8) 78 ms_puts(" every row lands in exactly one panel and the partition is printed; it never drops a line.\n" as *u8) 79 sys_exit(2); return 2 80 } 81 let buf: *u8 = sys_mmap(MS_CAP) 82 let n: i64 = ms_read(argv[1] as *u8, buf, MS_CAP - 2) 83 if n <= 0 { 84 ms_puts("MANGA-SCRIPT-REFUSE cannot read script: " as *u8); ms_puts(argv[1] as *u8); ms_puts("\n" as *u8) 85 sys_exit(3); return 3 86 } 87 buf[n] = 0 as u8 88 // index the rows: keep offsets, skip blanks and comments, NUL-terminate each in place 89 let roff: *i64 = sys_mmap(8 * MS_MAXROW) as *i64 90 var nrows: i64 = 0 91 var p: i64 = 0 92 var overflow: i64 = 0 93 while p < n { 94 var e: i64 = p 95 while e < n { if buf[e] == (10 as u8) { break } e = e + 1 } 96 buf[e] = 0 as u8 97 let line: *u8 = (buf as i64 + p) as *u8 98 p = e + 1 99 var skip: i64 = 0 100 if line[0] == (0 as u8) { skip = 1 } 101 if line[0] == (35 as u8) { skip = 1 } 102 if line[0] == (13 as u8) { skip = 1 } 103 if skip == 0 { 104 if nrows >= MS_MAXROW { overflow = 1 } else { roff[nrows] = line as i64; nrows = nrows + 1 } 105 } 106 } 107 if overflow == 1 { 108 ms_puts("MANGA-SCRIPT-REFUSE script exceeds " as *u8); ms_pi(MS_MAXROW); ms_puts(" rows -- split the chapter rather than truncating it\n" as *u8) 109 sys_exit(4); return 4 110 } 111 let panels: i64 = ms_atoi(argv[2] as *u8) 112 if panels > MS_MAXPANEL { 113 ms_puts("MANGA-SCRIPT-REFUSE more panels than a page can hold\n" as *u8) 114 sys_exit(4); return 4 115 } 116 let first: *i64 = sys_mmap(8 * MS_MAXPANEL) as *i64 117 let count: *i64 = sys_mmap(8 * MS_MAXPANEL) as *i64 118 let assigned: i64 = ms_beats(nrows, panels, first, count) 119 if assigned == 0 - 1 { ms_puts("MANGA-SCRIPT-REFUSE the script has no rows to place\n" as *u8); sys_exit(5); return 5 } 120 if assigned == 0 - 2 { ms_puts("MANGA-SCRIPT-REFUSE a page needs at least one panel\n" as *u8); sys_exit(5); return 5 } 121 if assigned == 0 - 3 { 122 ms_puts("MANGA-SCRIPT-REFUSE fewer script rows (" as *u8); ms_pi(nrows) 123 ms_puts(") than panels (" as *u8); ms_pi(panels) 124 ms_puts(") -- a panel would be empty. Write more beats or choose a smaller layout.\n" as *u8) 125 sys_exit(5); return 5 126 } 127 ms_puts("=== nx_manga_script rows=" as *u8); ms_pi(nrows); ms_puts(" panels=" as *u8); ms_pi(panels); ms_puts("\n" as *u8) 128 // ---- emit the beatplan, and PROVE the partition while emitting it ------------------------- 129 let out: *u8 = sys_mmap(MS_CAP) 130 var oo: i64 = 0 131 var sum: i64 = 0 132 var pi2: i64 = 0 133 while pi2 < panels { 134 var r: i64 = 0 135 while r < count[pi2] { 136 let idx: i64 = first[pi2] + r 137 let row: *u8 = roff[idx] as *u8 138 // panel|<idx>|<the row verbatim> 139 var k: i64 = 0 140 let pre: *u8 = "panel|" as *u8 141 while pre[k] != (0 as u8) { out[oo] = pre[k]; oo = oo + 1; k = k + 1 } 142 var m: i64 = pi2 143 let tb: *u8 = sys_mmap(24) 144 var tk: i64 = 0 145 if m == 0 { tb[0] = 48 as u8; tk = 1 } 146 while m > 0 { tb[tk] = (48 + (m % 10)) as u8; m = m / 10; tk = tk + 1 } 147 var z: i64 = 0 148 while z < tk { out[oo] = tb[tk - 1 - z]; oo = oo + 1; z = z + 1 } 149 out[oo] = 124 as u8; oo = oo + 1 150 var q: i64 = 0 151 while row[q] != (0 as u8) { out[oo] = row[q]; oo = oo + 1; q = q + 1 } 152 out[oo] = 10 as u8; oo = oo + 1 153 sum = sum + 1 154 r = r + 1 155 } 156 ms_puts(" panel " as *u8); ms_pi(pi2); ms_puts(" <- rows " as *u8); ms_pi(first[pi2]) 157 ms_puts(".." as *u8); ms_pi(first[pi2] + count[pi2] - 1); ms_puts(" (" as *u8); ms_pi(count[pi2]); ms_puts(")\n" as *u8) 158 pi2 = pi2 + 1 159 } 160 ms_puts(" partition: rows_in=" as *u8); ms_pi(nrows); ms_puts(" placed=" as *u8); ms_pi(sum) 161 ms_puts(" across " as *u8); ms_pi(panels); ms_puts(" panels" as *u8) 162 if sum != nrows { 163 ms_puts(" PARTITION-LEAK -- refusing\n" as *u8) 164 sys_exit(6); return 6 165 } 166 ms_puts(" (parts sum)\n" as *u8) 167 if argc >= 4 { 168 let fd: i64 = sys_openat_wr(argv[3] as *u8, 420) 169 if fd < 0 { ms_puts("MANGA-SCRIPT-REFUSE cannot write beatplan\n" as *u8); sys_exit(7); return 7 } 170 var w2: i64 = 0 171 while w2 < oo { let r2: i64 = sys_write(fd, (out as i64 + w2) as *u8, oo - w2); if r2 <= 0 { break } w2 = w2 + r2 } 172 sys_close(fd) 173 ms_puts(" beatplan -> " as *u8); ms_puts(argv[3] as *u8); ms_puts(" (" as *u8); ms_pi(oo); ms_puts(" bytes)\n" as *u8) 174 } 175 ms_puts("MANGA-SCRIPT-OK\n" as *u8) 176 sys_exit(0) 177 return 0 178}