nx_nxa_texc_lib.nx source
↩ module page · 404 lines · 18760 B
1// nx_nxa_texc_lib.nx -- ★TEXC: UV TEXTURE COORDINATES AS A FORMAT CAPABILITY (debt 1785794796 rung 1).
2// The measured fact that forced this: ref9.nxa declares VERT TRIS CLUS SKEL SKIN ANIM HSTR GVRT GTRI
3// GPIN POSE -- NO TEXC, NO UV -- so sampling ANY texture map was IMPOSSIBLE, and 'we shade procedurally
4// from genome' was a CONSTRAINT wearing the costume of a choice. This lib computes a bone-anchored
5// cylindrical unwrap from sections the asset ALREADY carries (SKEL world bind positions + SKIN top-4
6// weights) and writes a TEXC section: every vertex gets a stable atlas coordinate AND a region tag.
7//
8// UNWRAP (integer, deterministic): per vertex take its PRIMARY bone (max SKIN weight slot). v = clamped
9// position along the parent->joint bind segment (Q12 -> Q16). u = angle around the bone's DOMINANT axis
10// (rt_atan2, full circle 25736 it-units -> Q16). Each joint owns one TILE of a g x g atlas grid
11// (g = ceil(sqrt(nj))), so charts from different bones are DISJOINT BY CONSTRUCTION -- a bake and a
12// sampler sharing this section can never bleed one body part into another. Seams sit at weight
13// boundaries (rigid ownership); bake+sample use the SAME mapping so sampling stays self-consistent.
14//
15// TEXC payload (i64 words): [0]=nv [1]=stride(3) [2]=atlas grid g [3]=reserved(0), then per vertex:
16// [u q16 0..65535][v q16 0..65535][owning joint]. The joint word IS the region tag (rung 2 seed):
17// finer than Face|Torso|Limbs and groupable through SKEL parents later.
18//
19// ADDITIVE AND IDEMPOTENT like its siblings (nx_nxa_dyna): the input is never modified; an existing
20// TEXC is REPLACED, so running twice is identical to running once. FAIL-CLOSED: refuses with a NAMED
21// reason rather than fabricate (no SKIN -> no invented ownership; count mismatch -> refuse).
22// license_tier: ORIGINAL No hw writes (Rule 26).
23import "nx_syscalls.nx"
24import "nx_nxa.nx"
25import "nx_pose_retarget.nx"
26
27const NT_HDR: i64 = 32
28const NT_TOCE: i64 = 32
29const NT_MAXSEC: i64 = 64
30const NT_MODE: i64 = 0x1a4
31const NT_ERRFD: i64 = 2
32const NT_Q12: i64 = 4096
33const NT_Q16: i64 = 65536
34const NT_Q16M: i64 = 65535
35const NT_HALFC: i64 = 12868
36const NT_FULLC: i64 = 25736
37const NT_PRESH: i64 = 16
38const NT_STRIDE: i64 = 3
39const NT_PHDR: i64 = 4
40const NT_PAD: i64 = 4096
41
42// ---- GE67b: AREA PER REGION, NOT AREA PER JOINT ---------------------------------------------
43// MEASURED DEFECT (nx_craft_emit cw_chart_adequacy, first live reading 2026-09-07): a uniform
44// g x g grid gave all 104 joints an EQUAL 186-texel tile while their bind spans differ by more
45// than tenfold -- head 95510um at 513um/texel carrying 1 of 4 declared skin bands, longest bone
46// 1010390um at 5432um/texel carrying 0 of 4. One tile size cannot serve both.
47// THE SOURCE FOR THE SPLIT IS A CORPUS, NOT A PREFERENCE: knowledge/asset_texture_floor.conf,
48// measured 2026-08-03 from 23 real shipped packages, records tex_region_count=4, tex_map_set=4
49// and tex_maps_total=16 -- four regions, an EQUAL map set each -- with tex_res_floor=4096 read
50// off a FACE map. Equal maps per region is equal AREA per region, so the face takes one quarter
51// of the atlas and the rest of the body shares the other three quarters. Nothing is tuned.
52// WHY NOT EQUAL TEXEL DENSITY, THE OBVIOUS ALTERNATIVE: density-equalisation makes tile side
53// proportional to span, which would SHRINK the head (a small region) to about half the texels it
54// has today. It is the right rule for a uniform surface and the wrong one for a face, and the
55// corpus is what says so.
56// SCOPE, DECLARED RATHER THAN GLOSSED: the face region here is the SINGLE highest-bound joint --
57// the same head the adequacy check already names, so there is one convention and no new
58// threshold constant. A multi-joint head (jaw, eyes, brow) is not yet split out and is the next
59// step; torso, limbs and gens are not yet separated from each other and share the three
60// remaining quadrants uniformly, which is why this is a quarter-and-the-rest and not yet 4x4.
61const NT_RECT_W: i64 = 4 // words per joint in the rect table: ox, oy, w, h (Q16)
62const NT_REST_Q: i64 = 3 // the three quadrants the non-face joints share
63const NT_MIN_CELL: i64 = 3 // a cell narrower than this has no interior after the 1px inset
64// the atlas quadrant a joint owns: q0 is the face, q1..q3 tile the rest
65func ntx_quad_ox(q: i64) -> i64 { if q == 1 { return NT_Q16/2 } if q == 3 { return NT_Q16/2 } return 0 }
66func ntx_quad_oy(q: i64) -> i64 { if q == 2 { return NT_Q16/2 } if q == 3 { return NT_Q16/2 } return 0 }
67
68const NT_OK: i64 = 0
69const NT_E_IO: i64 = 1
70const NT_E_BAD: i64 = 3
71const NT_E_NOSEC: i64 = 4
72const NT_E_MISMATCH: i64 = 5
73
74func nt_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n }
75func nt_err(s: *u8) -> i64 { sys_write(NT_ERRFD, s, nt_slen(s)); return 0 }
76func nt_outs(s: *u8) -> i64 { sys_write(1, s, nt_slen(s)); return 0 }
77func nt_outn(v: i64) -> i64 {
78 if v == 0 { sys_write(1, "0" as *u8, 1); return 0 }
79 var m: i64 = v
80 if m < 0 { sys_write(1, "-" as *u8, 1); m = 0 - m }
81 let t: *u8 = sys_mmap(32)
82 var k: i64 = 0
83 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 }
84 let o: *u8 = sys_mmap(32)
85 var i: i64 = 0
86 while i < k { o[i] = t[k - 1 - i]; i = i + 1 }
87 sys_write(1, o, k)
88 return 0
89}
90func nt_rd64(b: *u8, off: i64) -> i64 {
91 var v: i64 = 0
92 var i: i64 = 7
93 while i >= 0 { v = v*256 + ((b[off + i] & 0xff) as i64); i = i - 1 }
94 return v
95}
96func nt_wr64(b: *u8, off: i64, v: i64) -> i64 {
97 var m: i64 = v
98 var i: i64 = 0
99 while i < 8 { b[off + i] = (m & 0xff) as u8; m = m >> 8; i = i + 1 }
100 return 0
101}
102func nt_tageq(b: *u8, off: i64, t: *u8) -> i64 {
103 var i: i64 = 0
104 while i < 4 { if b[off + i] != t[i] { return 0 } i = i + 1 }
105 return 1
106}
107func nt_grid(n: i64) -> i64 {
108 var g: i64 = 1
109 while g*g < n { g = g + 1 }
110 return g
111}
112
113// ---- THE INVERSE OF THE ALLOCATION, OWNED BY THE ORGAN THAT DECIDES IT --------------------
114// GE67b changed the atlas layout and IMMEDIATELY broke its sibling: nx_nxa_texbake_lib maps a
115// texel to its joint at THREE sites with ty*g+tx, the uniform-grid arithmetic, which after a
116// per-region allocation names the wrong joint -- so the face quadrant took torso and limb tones
117// and texels past nj fell to the VOID grey. A FIX THAT LIVES IN ONE ORGAN AND NOT ITS SIBLING IS
118// HALF A FIX, and the missing half is invisible until something runs it.
119// The remedy is not a second copy of the layout in the baker: the organ that DECIDES the rects
120// exports the inverse, so the two cannot disagree. Rasterises each joint's rect into a res x res
121// joint-id map in ONE pass over the atlas (total work = sum of rect areas = res^2), and falls
122// back to the legacy uniform grid when word 3 is 0, which is exactly what an old asset carries.
123// An unowned texel reads nj -- outside every caller's existing [0,nj) range test, so it still
124// paints the VOID grey and a sampling leak stays VISIBLE, with no call site needing a new branch.
125func ntx_jmap(bb: *u8, flen: i64, res: i64, nj: i64, jm: *i64) -> i64 {
126 let xwo: i64 = nxa_find(bb, flen, nxa_tag4("TEXC" as *u8))
127 if xwo < 0 { return 0 - 1 }
128 let w: *i64 = bb as *i64
129 let g: i64 = w[xwo + 2]
130 let ro: i64 = w[xwo + 3]
131 if g < 1 { return 0 - 1 }
132 var i: i64 = 0
133 while i < res*res { jm[i] = nj; i = i + 1 }
134 // LEGACY: no rect table, so the uniform cell IS the layout and reproducing it here is not a
135 // duplicate convention -- it is the same arithmetic the same organ wrote.
136 if ro <= 0 {
137 var y: i64 = 0
138 while y < res {
139 let ty: i64 = y*g/res
140 var x: i64 = 0
141 while x < res {
142 jm[y*res + x] = ty*g + x*g/res
143 x = x + 1
144 }
145 y = y + 1
146 }
147 return 0
148 }
149 var j: i64 = 0
150 while j < nj {
151 let rb: i64 = xwo + ro + j*NT_RECT_W
152 let x0: i64 = w[rb]*res/NT_Q16
153 let y0: i64 = w[rb + 1]*res/NT_Q16
154 let x1: i64 = (w[rb] + w[rb + 2])*res/NT_Q16
155 let y1: i64 = (w[rb + 1] + w[rb + 3])*res/NT_Q16
156 var yy: i64 = y0
157 while yy < y1 {
158 if yy >= 0 { if yy < res {
159 var xx: i64 = x0
160 while xx < x1 {
161 if xx >= 0 { if xx < res { jm[yy*res + xx] = j } }
162 xx = xx + 1
163 }
164 } }
165 yy = yy + 1
166 }
167 j = j + 1
168 }
169 return 0
170}
171
172// core: read in.nxa, compute the unwrap, write out.nxa with a TEXC section. NT_OK or named refusal.
173func ntx_apply(inp: *u8, outp: *u8) -> i64 {
174 let lp: *i64 = sys_mmap(16) as *i64
175 let b: *u8 = sys_read_file(inp, lp)
176 if (b as i64) == 0 { nt_err("TEXC-REFUSE cannot read input NXA\n" as *u8); return NT_E_IO }
177 let flen: i64 = lp[0]
178 if flen < NT_HDR { nt_err("TEXC-REFUSE file too short to be an NXA\n" as *u8); return NT_E_BAD }
179 if nt_rd64(b, 0) != nxa_magic() { nt_err("TEXC-REFUSE not an NXA (bad magic)\n" as *u8); return NT_E_BAD }
180 if nt_rd64(b, 8) > NXA_VER { nt_err("TEXC-REFUSE future NXA version\n" as *u8); return NT_E_BAD }
181 let ns: i64 = nt_rd64(b, 16)
182 if ns < 1 { nt_err("TEXC-REFUSE section count invalid\n" as *u8); return NT_E_BAD }
183 if ns >= NT_MAXSEC { nt_err("TEXC-REFUSE section table full\n" as *u8); return NT_E_BAD }
184 if flen < NT_HDR + ns*NT_TOCE { nt_err("TEXC-REFUSE toc runs past EOF\n" as *u8); return NT_E_BAD }
185 var vo: i64 = 0 - 1
186 var ko: i64 = 0 - 1
187 var so: i64 = 0 - 1
188 var txi: i64 = 0 - 1
189 var s: i64 = 0
190 while s < ns {
191 let e: i64 = NT_HDR + s*NT_TOCE
192 if nt_tageq(b, e, "VERT" as *u8) == 1 { vo = nt_rd64(b, e + 8) }
193 if nt_tageq(b, e, "SKIN" as *u8) == 1 { ko = nt_rd64(b, e + 8) }
194 if nt_tageq(b, e, "SKEL" as *u8) == 1 { so = nt_rd64(b, e + 8) }
195 if nt_tageq(b, e, "TEXC" as *u8) == 1 { txi = s }
196 s = s + 1
197 }
198 if vo < 0 { nt_err("TEXC-REFUSE no VERT section\n" as *u8); return NT_E_NOSEC }
199 if so < 0 { nt_err("TEXC-REFUSE no SKEL section -- nothing anchors the unwrap\n" as *u8); return NT_E_NOSEC }
200 if ko < 0 { nt_err("TEXC-REFUSE no SKIN section -- refusing to fabricate bone ownership\n" as *u8); return NT_E_NOSEC }
201 let nv: i64 = nt_rd64(b, vo)
202 let nj: i64 = nt_rd64(b, so)
203 let nk: i64 = nt_rd64(b, ko)
204 if nv < 1 { nt_err("TEXC-REFUSE empty VERT\n" as *u8); return NT_E_BAD }
205 if nj < 1 { nt_err("TEXC-REFUSE empty SKEL\n" as *u8); return NT_E_BAD }
206 if nk != nv { nt_err("TEXC-REFUSE SKIN count does not match VERT count\n" as *u8); return NT_E_MISMATCH }
207 if vo + (1 + nv*3)*8 > flen { nt_err("TEXC-REFUSE VERT runs past EOF\n" as *u8); return NT_E_BAD }
208 if so + (1 + nj*8)*8 > flen { nt_err("TEXC-REFUSE SKEL runs past EOF\n" as *u8); return NT_E_BAD }
209 if ko + (1 + nv*8)*8 > flen { nt_err("TEXC-REFUSE SKIN runs past EOF\n" as *u8); return NT_E_BAD }
210 let g: i64 = nt_grid(nj)
211 let tw: i64 = NT_Q16 / g
212 // GE67b: the rect table rides the payload TAIL, so a reader that walks nv rows and stops is
213 // byte-for-byte unaffected; word 3 was reserved(0) and now carries the table's word offset,
214 // which is exactly the 0-means-legacy-uniform-grid signal an old asset already gives.
215 let rectoff: i64 = NT_PHDR + nv*NT_STRIDE
216 let uvw: *i64 = sys_mmap((rectoff + nj*NT_RECT_W)*8 + 64) as *i64
217 uvw[0] = nv
218 uvw[1] = NT_STRIDE
219 uvw[2] = g
220 uvw[3] = rectoff
221 // the face region is the highest-bound joint: the same head nx_craft_emit's adequacy check
222 // names, so the two organs cannot disagree about which chart the close-up reads.
223 var fj: i64 = 0
224 var fz: i64 = nt_rd64(b, so + 8 + 3*8)
225 var j0: i64 = 1
226 while j0 < nj {
227 let z0: i64 = nt_rd64(b, so + 8 + (j0*8 + 3)*8)
228 if z0 > fz { fz = z0; fj = j0 }
229 j0 = j0 + 1
230 }
231 let nrest: i64 = nj - 1
232 var per: i64 = (nrest + NT_REST_Q - 1) / NT_REST_Q
233 if per < 1 { per = 1 }
234 let gr: i64 = nt_grid(per)
235 let half: i64 = NT_Q16 / 2
236 let cell: i64 = half / gr
237 // A cell with no interior after the 1-unit inset would collapse every chart in it to a point.
238 // Refuse rather than emit an unwrap whose charts are empty -- fail closed, named.
239 if cell < NT_MIN_CELL { nt_err("TEXC-REFUSE too many joints for the atlas: a rest cell has no interior\n" as *u8); return NT_E_BAD }
240 var jr: i64 = 0
241 var ri: i64 = 0
242 while jr < nj {
243 let rb: i64 = rectoff + jr*NT_RECT_W
244 if jr == fj {
245 uvw[rb] = 0
246 uvw[rb + 1] = 0
247 uvw[rb + 2] = half
248 uvw[rb + 3] = half
249 }
250 if jr != fj {
251 let q: i64 = ri % NT_REST_Q + 1
252 let c: i64 = ri / NT_REST_Q
253 uvw[rb] = ntx_quad_ox(q) + (c % gr)*cell
254 uvw[rb + 1] = ntx_quad_oy(q) + (c / gr)*cell
255 uvw[rb + 2] = cell
256 uvw[rb + 3] = cell
257 ri = ri + 1
258 }
259 jr = jr + 1
260 }
261 let jused: *i64 = sys_mmap(nj*8 + 64) as *i64
262 var i: i64 = 0
263 while i < nv {
264 var pj: i64 = nt_rd64(b, ko + 8 + (i*8)*8)
265 var pw: i64 = nt_rd64(b, ko + 8 + (i*8 + 4)*8)
266 var sl: i64 = 1
267 while sl < 4 {
268 let jj: i64 = nt_rd64(b, ko + 8 + (i*8 + sl)*8)
269 let ww: i64 = nt_rd64(b, ko + 8 + (i*8 + 4 + sl)*8)
270 if ww > pw { pw = ww; pj = jj }
271 sl = sl + 1
272 }
273 if pj < 0 { nt_err("TEXC-REFUSE SKIN joint index negative\n" as *u8); return NT_E_MISMATCH }
274 if pj >= nj { nt_err("TEXC-REFUSE SKIN joint index out of SKEL range\n" as *u8); return NT_E_MISMATCH }
275 let par: i64 = nt_rd64(b, so + 8 + (pj*8)*8)
276 let jx: i64 = nt_rd64(b, so + 8 + (pj*8 + 1)*8)
277 let jy: i64 = nt_rd64(b, so + 8 + (pj*8 + 2)*8)
278 let jz: i64 = nt_rd64(b, so + 8 + (pj*8 + 3)*8)
279 var ox: i64 = jx
280 var oy: i64 = jy
281 var oz: i64 = jz
282 var ax: i64 = 0
283 var ay: i64 = 0
284 var az: i64 = 1000
285 if par >= 0 { if par < nj {
286 ox = nt_rd64(b, so + 8 + (par*8 + 1)*8)
287 oy = nt_rd64(b, so + 8 + (par*8 + 2)*8)
288 oz = nt_rd64(b, so + 8 + (par*8 + 3)*8)
289 ax = jx - ox
290 ay = jy - oy
291 az = jz - oz
292 } }
293 if ax == 0 { if ay == 0 { if az == 0 { az = 1000 } } }
294 let vx: i64 = nt_rd64(b, vo + 8 + (i*3)*8)
295 let vy: i64 = nt_rd64(b, vo + 8 + (i*3 + 1)*8)
296 let vz: i64 = nt_rd64(b, vo + 8 + (i*3 + 2)*8)
297 let dxp: i64 = (vx - ox) / NT_PRESH
298 let dyp: i64 = (vy - oy) / NT_PRESH
299 let dzp: i64 = (vz - oz) / NT_PRESH
300 let axp: i64 = ax / NT_PRESH
301 let ayp: i64 = ay / NT_PRESH
302 let azp: i64 = az / NT_PRESH
303 let den: i64 = axp*axp + ayp*ayp + azp*azp
304 var tq: i64 = 0
305 if den > 0 { tq = (dxp*axp + dyp*ayp + dzp*azp)*NT_Q12/den }
306 if tq < 0 { tq = 0 }
307 if tq > NT_Q12 { tq = NT_Q12 }
308 var aax: i64 = ax
309 if aax < 0 { aax = 0 - aax }
310 var aay: i64 = ay
311 if aay < 0 { aay = 0 - aay }
312 var aaz: i64 = az
313 if aaz < 0 { aaz = 0 - aaz }
314 var p1: i64 = dxp
315 var p2: i64 = dyp
316 if aax >= aay { if aax >= aaz { p1 = dyp; p2 = dzp } }
317 if aay > aax { if aay >= aaz { p1 = dzp; p2 = dxp } }
318 let ang: i64 = rt_atan2(p2, p1)
319 let u01: i64 = (ang + NT_HALFC) * NT_Q16M / NT_FULLC
320 let v01: i64 = tq * NT_Q16M / NT_Q12
321 // GE67b: the chart is the joint's ALLOCATED rectangle, not a cell of a uniform grid.
322 let rq: i64 = rectoff + pj*NT_RECT_W
323 let rx: i64 = uvw[rq]
324 let ry: i64 = uvw[rq + 1]
325 let rw: i64 = uvw[rq + 2]
326 let rh: i64 = uvw[rq + 3]
327 let uu: i64 = rx + 1 + u01*(rw - 2)/NT_Q16
328 let vv: i64 = ry + 1 + v01*(rh - 2)/NT_Q16
329 uvw[NT_PHDR + i*NT_STRIDE] = uu
330 uvw[NT_PHDR + i*NT_STRIDE + 1] = vv
331 uvw[NT_PHDR + i*NT_STRIDE + 2] = pj
332 jused[pj] = 1
333 i = i + 1
334 }
335 // ---- build the output: keep every section except an old TEXC, append ours, recompute checks ----
336 var nsec: i64 = ns
337 if txi < 0 { nsec = ns + 1 }
338 let twords: i64 = rectoff + nj*NT_RECT_W
339 let toclen: i64 = NT_HDR + nsec*NT_TOCE
340 var total: i64 = toclen
341 var s2: i64 = 0
342 while s2 < ns {
343 if s2 != txi {
344 let e2: i64 = NT_HDR + s2*NT_TOCE
345 total = total + nt_rd64(b, e2 + 16)*8
346 }
347 s2 = s2 + 1
348 }
349 total = total + twords*8
350 let nb: *u8 = sys_mmap(total + NT_PAD)
351 if (nb as i64) == 0 { nt_err("TEXC-REFUSE cannot allocate output\n" as *u8); return NT_E_IO }
352 nt_wr64(nb, 0, nxa_magic())
353 nt_wr64(nb, 8, NXA_VER)
354 nt_wr64(nb, 16, nsec)
355 var wo: i64 = toclen
356 var ti: i64 = 0
357 var s3: i64 = 0
358 while s3 < ns {
359 if s3 != txi {
360 let e3: i64 = NT_HDR + s3*NT_TOCE
361 let oldoff: i64 = nt_rd64(b, e3 + 8)
362 let wl: i64 = nt_rd64(b, e3 + 16)
363 let te: i64 = NT_HDR + ti*NT_TOCE
364 nt_wr64(nb, te, nt_rd64(b, e3))
365 nt_wr64(nb, te + 8, wo)
366 nt_wr64(nb, te + 16, wl)
367 var k2: i64 = 0
368 while k2 < wl*8 { nb[wo + k2] = b[oldoff + k2]; k2 = k2 + 1 }
369 // recompute rather than copy the old checksum (a copied checksum validates its own bug)
370 let pw2: *i64 = ((nb as i64) + wo) as *i64
371 nt_wr64(nb, te + 24, nxa_check2(1, pw2, wl))
372 wo = wo + wl*8
373 ti = ti + 1
374 }
375 s3 = s3 + 1
376 }
377 let te4: i64 = NT_HDR + ti*NT_TOCE
378 nt_wr64(nb, te4, nxa_tag4("TEXC" as *u8))
379 nt_wr64(nb, te4 + 8, wo)
380 nt_wr64(nb, te4 + 16, twords)
381 var k3: i64 = 0
382 while k3 < twords { nt_wr64(nb, wo + k3*8, uvw[k3]); k3 = k3 + 1 }
383 let pw3: *i64 = ((nb as i64) + wo) as *i64
384 nt_wr64(nb, te4 + 24, nxa_check2(1, pw3, twords))
385 wo = wo + twords*8
386 // toc checksum over the FINAL toc
387 let tb2: *i64 = ((nb as i64) + NT_HDR) as *i64
388 nt_wr64(nb, 24, nxa_check2(1, tb2, nsec*4))
389 let fd: i64 = sys_openat_wr(outp, NT_MODE)
390 if fd < 0 { nt_err("TEXC-REFUSE cannot open output\n" as *u8); return NT_E_IO }
391 sys_write(fd, nb, wo)
392 sys_close(fd)
393 var ju: i64 = 0
394 var j5: i64 = 0
395 while j5 < nj { if jused[j5] == 1 { ju = ju + 1 } j5 = j5 + 1 }
396 nt_outs("TEXC-OK nv=" as *u8); nt_outn(nv)
397 nt_outs(" nj=" as *u8); nt_outn(nj)
398 nt_outs(" grid=" as *u8); nt_outn(g)
399 nt_outs(" joints_used=" as *u8); nt_outn(ju)
400 nt_outs(" v0_uv=" as *u8); nt_outn(uvw[NT_PHDR]); nt_outs("," as *u8); nt_outn(uvw[NT_PHDR+1])
401 nt_outs(" bytes=" as *u8); nt_outn(wo)
402 nt_outs("\n" as *u8)
403 return NT_OK
404}