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}