nx_vmd.nx source
↩ module page · 552 lines · 22549 B
1// nx_vmd.nx -- VMD (Vocaloid Motion Data) MOTION DECODER: the MMD dance corpus, read sovereignly.
2//
3// WHY THIS EXISTS (measured 2026-08-07 against vrstormlab.com/dancexr/play, from their own served
4// bytes -- not their marketing copy): the free LW build ingests PMX + XPS models and VMD + BVH
5// motion. Our estate already owns the OTHER half of that pipeline -- nx_pose_retarget inverts the
6// sovereign FK, nx_gltf2mesh segments a donor into 74 named parts -- and owned ZERO of their four
7// ingest formats. A grep for "bvh" found nx_bvh and read as a match; nx_bvh is a BOUNDING VOLUME
8// HIERARCHY for the slicer, not BioVision Hierarchy motion capture.
9// *A NAME IS NOT A CAPABILITY. A GREP FINDS THE CLAIM; ONLY READING THE SOURCE SETTLES IT.
10//
11// VMD is the keystone of the four because the dance corpus is overwhelmingly VMD, and because it
12// carries exactly what our retarget consumes: per-bone, per-frame rotations on a named skeleton.
13//
14// FAIL-CLOSED BY CONSTRUCTION: every field is bounds-checked against the ACTUAL file length before
15// it is read. A count in a header is a CLAIM, never an authority. A truncated file is REFUSED, not
16// partially parsed into a plausible-looking track -- because a plausible segmentation is the most
17// dangerous kind, and a motion track that silently stops at frame 40 of 400 still animates.
18//
19// UNITS ARE DECLARED, NEVER IMPLIED (a threshold is meaningless until its unit is stated):
20// positions = VMD/MMD model units x V_SCALE_POS (1000). NOT millimetres; MMD units are their own.
21// rotations = quaternion components x V_SCALE_ROT (4096), each in [-4096, 4096].
22// morph = weight x V_SCALE_POS (1000), nominally [0, 1000].
23//
24// usage: nx_vmd info <file.vmd> version, model name, per-section counts, duration in frames
25// nx_vmd bones <file.vmd> per-bone keyframe census (raw name bytes + id)
26// nx_vmd dump <file.vmd> [n] first n bone keyframes as rows
27// nx_vmd --kat selftest: 7 teeth, 4 of them anti-vacuity
28// exit 0 ok | 2 unreadable | 3 usage | 4 REFUSED (bad magic / truncated / count exceeds bytes)
29// | 1 selftest RED
30// license_tier: ORIGINAL No hw writes (Rule 26). expect_exit: 0
31import "nx_syscalls.nx"
32const V_MAGIC_2000: i64 = 2000
33
34// ---- format constants (VMD is a fixed-record binary format; every record size is exact) ----
35const V_HDR_SIG: i64 = 30 // signature block, NUL/garbage padded
36const V_NAME_V2: i64 = 20 // model-name field, signature "...0002"
37const V_NAME_V1: i64 = 10 // model-name field, older signature
38const V_BONE_REC: i64 = 111 // name15 + frame4 + pos12 + quat16 + interp64
39const V_MORPH_REC: i64 = 23 // name15 + frame4 + weight4
40const V_CAM_REC: i64 = 61 // frame4 + dist4 + pos12 + rot12 + interp24 + fov4 + persp1
41const V_LIGHT_REC: i64 = 28 // frame4 + rgb12 + pos12
42const V_SHADOW_REC: i64 = 9 // frame4 + mode1 + dist4
43const V_NAME_BYTES: i64 = 15 // bone/morph name field width (Shift-JIS, NUL padded)
44
45// ---- scales (declared above; never inferred at a call site) ----
46const V_SCALE_POS: i64 = 1000
47const V_SCALE_ROT: i64 = 4096
48
49// ---- f32 decode ----
50const V_M_255: i64 = 255
51const V_M_8388607: i64 = 8388607
52const V_M_8388608: i64 = 8388608
53const V_M_EXPBIAS: i64 = 127
54const V_M_MANTBIT: i64 = 23
55const V_MAX_SH: i64 = 40 // refuse absurd exponents rather than overflow the shift
56const V_MAX_RS: i64 = 62 // a shift wider than the word is undefined -- return 0, never garbage
57
58// ---- census ----
59const V_MAX_UNIQ: i64 = 512 // unique bone names held; exceeding this REFUSES (declared cap)
60const V_DUMP_DEF: i64 = 8
61
62// ---- layout slots (L array) ----
63const V_L_VER: i64 = 0
64const V_L_NAMEOFF: i64 = 1
65const V_L_BONEOFF: i64 = 2
66const V_L_BONEN: i64 = 3
67const V_L_MORPHOFF:i64 = 4
68const V_L_MORPHN: i64 = 5
69const V_L_CAMOFF: i64 = 6
70const V_L_CAMN: i64 = 7
71const V_L_LIGHTOFF:i64 = 8
72const V_L_LIGHTN: i64 = 9
73const V_L_SHADOFF: i64 = 10
74const V_L_SHADN: i64 = 11
75const V_L_MAXFRAME:i64 = 12
76const V_L_IKOFF: i64 = 13
77const V_L_IKN: i64 = 14
78const V_L_END: i64 = 15
79const V_L_WORDS: i64 = 20
80const V_IK_NAME: i64 = 20 // per-IK-entry bone name field width
81const V_IK_FIXED: i64 = 9 // frame4 + show1 + ikcount4
82
83// ---- KAT fixture ----
84const V_BUF: i64 = 4096
85const V_MODE_644: i64 = 420
86const V_F32_1_0: i64 = 1065353216
87const V_F32_2_0: i64 = 1073741824
88const V_F32_0_5: i64 = 1056964608
89const V_F32_N1_0: i64 = 3212836864
90const V_KAT_FRAME: i64 = 30
91const V_KAT_MORPHFRAME: i64 = 10
92const V_KAT_LIARCOUNT: i64 = 100000
93
94// ===== tiny io helpers ============================================
95func v_slen(s: *u8) -> i64 { var i: i64 = 0; while s[i] != (0 as u8) { i = i + 1 } return i }
96func v_puts(s: *u8) -> i64 { sys_write(1, s, v_slen(s)); return 0 }
97
98func v_num(v: i64) -> i64 {
99 let t: *u8 = sys_mmap(32)
100 var m: i64 = v
101 var w: i64 = 0
102 if m < 0 { t[0] = 45 as u8; sys_write(1, t, 1); m = 0 - m }
103 if m == 0 { t[0] = 48 as u8; sys_write(1, t, 1); return 0 }
104 let d: *u8 = sys_mmap(32)
105 var k: i64 = 0
106 while m > 0 { d[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 }
107 var j: i64 = 0
108 while j < k { t[w] = d[k - 1 - j]; w = w + 1; j = j + 1 }
109 sys_write(1, t, w)
110 return 0
111}
112
113// ===== byte readers ===============================================
114func v_u32(b: *u8, o: i64) -> i64 {
115 return ((b[o] & 0xff) as i64) | (((b[o+1] & 0xff) as i64) << 8)
116 | (((b[o+2] & 0xff) as i64) << 16) | (((b[o+3] & 0xff) as i64) << 24)
117}
118
119// IEEE-754 binary32 -> integer x scale. Guards BOTH shift directions: an exponent that would
120// shift wider than the word is returned as 0 rather than as undefined bits. That guard is the
121// banked a_f64 lesson applied before it can bite, not after.
122func v_f32(b: *u8, o: i64, scale: i64) -> i64 {
123 let w: i64 = v_u32(b, o)
124 let sign: i64 = (w >> 31) & 1
125 let expo: i64 = (w >> V_M_MANTBIT) & V_M_255
126 if expo == 0 { return 0 }
127 let mant: i64 = (w & V_M_8388607) | V_M_8388608
128 let sh: i64 = expo - V_M_EXPBIAS
129 if sh > V_MAX_SH { return 0 }
130 var v: i64 = 0
131 if sh >= V_M_MANTBIT {
132 v = mant * scale * (1 << (sh - V_M_MANTBIT))
133 } else {
134 let rs: i64 = V_M_MANTBIT - sh
135 if rs > V_MAX_RS { return 0 }
136 v = (mant * scale) >> rs
137 }
138 if sign == 1 { return 0 - v }
139 return v
140}
141
142// ===== signature ==================================================
143// Both VMD generations begin "Vocaloid Motion Data"; only the tail distinguishes them.
144// Returns 2 (name field 20B), 1 (name field 10B), or 0 = NOT a VMD.
145func v_version(b: *u8, len: i64) -> i64 {
146 if len < V_HDR_SIG { return 0 }
147 let sig: *u8 = "Vocaloid Motion Data" as *u8
148 let n: i64 = v_slen(sig)
149 var i: i64 = 0
150 while i < n {
151 if b[i] != sig[i] { return 0 }
152 i = i + 1
153 }
154 if b[21] == (48 as u8) {
155 if b[22] == (48 as u8) {
156 if b[23] == (48 as u8) {
157 if b[24] == (50 as u8) { return 2 }
158 }
159 }
160 }
161 return 1
162}
163
164func v_name_width(ver: i64) -> i64 {
165 if ver == 2 { return V_NAME_V2 }
166 return V_NAME_V1
167}
168
169// ===== layout walk (THE fail-closed core) =========================
170// Fills L. Returns 0 ok, or a negative refusal code:
171// -1 bad signature -2 truncated before a count -3 a count exceeds the bytes actually present
172//
173// The third case is the one that matters: a VMD header states its own record counts, and a
174// corrupt or deliberately-crafted file states counts far larger than its body. Trusting that
175// number is how a parser walks off the end of its buffer. Every section therefore proves
176// off + n*rec <= len BEFORE the first record is touched.
177func v_section(buf: *u8, len: i64, off: i64, rec: i64, L: *i64, slot_off: i64, slot_n: i64) -> i64 {
178 if off + 4 > len { return 0 - 2 }
179 let n: i64 = v_u32(buf, off)
180 let body: i64 = off + 4
181 if n < 0 { return 0 - 3 }
182 if rec > 0 {
183 if n > (len - body) / rec { return 0 - 3 }
184 }
185 L[slot_off] = body
186 L[slot_n] = n
187 return body + n * rec
188}
189
190func v_layout(buf: *u8, len: i64, L: *i64) -> i64 {
191 let ver: i64 = v_version(buf, len)
192 if ver == 0 { return 0 - 1 }
193 L[V_L_VER] = ver
194 L[V_L_NAMEOFF] = V_HDR_SIG
195
196 var off: i64 = V_HDR_SIG + v_name_width(ver)
197 off = v_section(buf, len, off, V_BONE_REC, L, V_L_BONEOFF, V_L_BONEN)
198 if off < 0 { return off }
199 off = v_section(buf, len, off, V_MORPH_REC, L, V_L_MORPHOFF, V_L_MORPHN)
200 if off < 0 { return off }
201 off = v_section(buf, len, off, V_CAM_REC, L, V_L_CAMOFF, V_L_CAMN)
202 if off < 0 { return off }
203 off = v_section(buf, len, off, V_LIGHT_REC, L, V_L_LIGHTOFF, V_L_LIGHTN)
204 if off < 0 { return off }
205 off = v_section(buf, len, off, V_SHADOW_REC, L, V_L_SHADOFF, V_L_SHADN)
206 if off < 0 { return off }
207
208 // ---- IK/show section: VARIABLE length, so it cannot be skipped by arithmetic ----
209 // Each record is frame4 + show1 + ikcount4, then ikcount entries of (name20 + enabled1).
210 // Walking it is what lets the chain reconcile to EOF, and that reconciliation is the
211 // structural proof: if any earlier record size were wrong by a single byte, the chain
212 // would not land on the end of the file. *THE FILE CARRIES ITS OWN ANSWER KEY.
213 // Older VMD files legitimately END before this section -- absence is not corruption.
214 L[V_L_IKOFF] = off
215 L[V_L_IKN] = 0
216 if off + 4 <= len {
217 let ikn: i64 = v_u32(buf, off)
218 var p: i64 = off + 4
219 if ikn >= 0 {
220 var i2: i64 = 0
221 var bad: i64 = 0
222 while i2 < ikn {
223 if bad == 0 {
224 if p + V_IK_FIXED > len { bad = 1 }
225 else {
226 let nik: i64 = v_u32(buf, p + 5)
227 p = p + V_IK_FIXED
228 if nik < 0 { bad = 1 }
229 else {
230 if p + nik * (V_IK_NAME + 1) > len { bad = 1 }
231 else { p = p + nik * (V_IK_NAME + 1) }
232 }
233 }
234 }
235 i2 = i2 + 1
236 }
237 if bad == 0 { L[V_L_IKN] = ikn; off = p }
238 }
239 }
240 L[V_L_END] = off
241
242 var mx: i64 = 0
243 let bo: i64 = L[V_L_BONEOFF]
244 let bn: i64 = L[V_L_BONEN]
245 var i: i64 = 0
246 while i < bn {
247 let f: i64 = v_u32(buf, bo + i * V_BONE_REC + V_NAME_BYTES)
248 if f > mx { mx = f }
249 i = i + 1
250 }
251 L[V_L_MAXFRAME] = mx
252 return 0
253}
254
255func v_refuse(code: i64) -> i64 {
256 v_puts("nx_vmd REFUSED code=" as *u8); v_num(0 - code)
257 if code == 0 - 1 { v_puts(" not-a-VMD (signature)" as *u8) }
258 if code == 0 - 2 { v_puts(" truncated-before-count" as *u8) }
259 if code == 0 - 3 { v_puts(" count-exceeds-bytes" as *u8) }
260 v_puts("\n" as *u8)
261 return 4
262}
263
264// ===== name helpers ===============================================
265func v_name_eq(buf: *u8, a: i64, tbl: *u8, slot: i64) -> i64 {
266 var i: i64 = 0
267 while i < V_NAME_BYTES {
268 if buf[a + i] != tbl[slot * V_NAME_BYTES + i] { return 0 }
269 i = i + 1
270 }
271 return 1
272}
273
274func v_name_put(buf: *u8, a: i64, tbl: *u8, slot: i64) -> i64 {
275 var i: i64 = 0
276 while i < V_NAME_BYTES { tbl[slot * V_NAME_BYTES + i] = buf[a + i]; i = i + 1 }
277 return 0
278}
279
280// print the raw name bytes up to the NUL pad (Shift-JIS: emitted as bytes, NOT transcoded --
281// an untranscoded byte string is honest; a mojibake guess would be a fabricated fact)
282func v_name_print(tbl: *u8, slot: i64) -> i64 {
283 var n: i64 = 0
284 while n < V_NAME_BYTES {
285 if tbl[slot * V_NAME_BYTES + n] == (0 as u8) { n = V_NAME_BYTES + 9 } else { n = n + 1 }
286 }
287 if n > V_NAME_BYTES { n = n - V_NAME_BYTES - 9 }
288 sys_write(1, ((tbl as i64) + slot * V_NAME_BYTES) as *u8, n)
289 return 0
290}
291
292// ===== verbs ======================================================
293func v_info(buf: *u8, len: i64, L: *i64) -> i64 {
294 v_puts("VMD version=" as *u8); v_num(L[V_L_VER])
295 v_puts(" bytes=" as *u8); v_num(len)
296 v_puts("\nmodel=" as *u8)
297 var i: i64 = 0
298 let w: i64 = v_name_width(L[V_L_VER])
299 while i < w {
300 if buf[V_HDR_SIG + i] == (0 as u8) { i = w + 9 } else { i = i + 1 }
301 }
302 var nn: i64 = i
303 if nn > w { nn = nn - w - 9 }
304 sys_write(1, ((buf as i64) + V_HDR_SIG) as *u8, nn)
305 v_puts("\nbone_frames=" as *u8); v_num(L[V_L_BONEN])
306 v_puts("\nmorph_frames=" as *u8); v_num(L[V_L_MORPHN])
307 v_puts("\ncamera_frames=" as *u8); v_num(L[V_L_CAMN])
308 v_puts("\nlight_frames=" as *u8); v_num(L[V_L_LIGHTN])
309 v_puts("\nshadow_frames=" as *u8); v_num(L[V_L_SHADN])
310 v_puts("\nik_frames=" as *u8); v_num(L[V_L_IKN])
311 v_puts("\nduration_frames=" as *u8); v_num(L[V_L_MAXFRAME])
312 // THE STRUCTURAL PROOF: every section size is fixed and declared, so the walked chain must
313 // land on the end of the file. A mismatch means one of the record sizes is wrong -- which is
314 // exactly the failure a synthetic fixture written by the same author CANNOT reveal.
315 v_puts("\nchain_end=" as *u8); v_num(L[V_L_END])
316 v_puts(" bytes=" as *u8); v_num(len)
317 v_puts(" trailing=" as *u8); v_num(len - L[V_L_END])
318 if L[V_L_END] == len { v_puts(" reconcile=EXACT" as *u8) }
319 else { v_puts(" reconcile=RESIDUAL-inspect-record-sizes" as *u8) }
320 v_puts("\nunits pos_x1000 rot_x4096 fps30\n" as *u8)
321 return 0
322}
323
324func v_bones(buf: *u8, L: *i64) -> i64 {
325 let tbl: *u8 = sys_mmap(V_MAX_UNIQ * V_NAME_BYTES)
326 let cnt: *i64 = sys_mmap(V_MAX_UNIQ * 8) as *i64
327 var uniq: i64 = 0
328 let bo: i64 = L[V_L_BONEOFF]
329 let bn: i64 = L[V_L_BONEN]
330 var i: i64 = 0
331 while i < bn {
332 let a: i64 = bo + i * V_BONE_REC
333 var s: i64 = 0
334 var hit: i64 = 0 - 1
335 while s < uniq {
336 if v_name_eq(buf, a, tbl, s) == 1 { hit = s; s = uniq } else { s = s + 1 }
337 }
338 if hit < 0 {
339 if uniq >= V_MAX_UNIQ {
340 v_puts("nx_vmd REFUSED unique-bone-cap " as *u8); v_num(V_MAX_UNIQ); v_puts("\n" as *u8)
341 return 4
342 }
343 v_name_put(buf, a, tbl, uniq)
344 cnt[uniq] = 1
345 uniq = uniq + 1
346 } else {
347 cnt[hit] = cnt[hit] + 1
348 }
349 i = i + 1
350 }
351 v_puts("unique_bones=" as *u8); v_num(uniq); v_puts("\n" as *u8)
352 var k: i64 = 0
353 while k < uniq {
354 v_num(k); v_puts("\t" as *u8); v_num(cnt[k]); v_puts("\t" as *u8)
355 v_name_print(tbl, k)
356 v_puts("\n" as *u8)
357 k = k + 1
358 }
359 return 0
360}
361
362func v_dump(buf: *u8, L: *i64, maxn: i64) -> i64 {
363 let bo: i64 = L[V_L_BONEOFF]
364 var bn: i64 = L[V_L_BONEN]
365 if maxn < bn { bn = maxn }
366 v_puts("idx\tframe\tpx\tpy\tpz\tqx\tqy\tqz\tqw\tname\n" as *u8)
367 var i: i64 = 0
368 while i < bn {
369 let a: i64 = bo + i * V_BONE_REC
370 v_num(i); v_puts("\t" as *u8)
371 v_num(v_u32(buf, a + V_NAME_BYTES)); v_puts("\t" as *u8)
372 v_num(v_f32(buf, a + V_NAME_BYTES + 4, V_SCALE_POS)); v_puts("\t" as *u8)
373 v_num(v_f32(buf, a + V_NAME_BYTES + 8, V_SCALE_POS)); v_puts("\t" as *u8)
374 v_num(v_f32(buf, a + V_NAME_BYTES + 12, V_SCALE_POS)); v_puts("\t" as *u8)
375 v_num(v_f32(buf, a + V_NAME_BYTES + 16, V_SCALE_ROT)); v_puts("\t" as *u8)
376 v_num(v_f32(buf, a + V_NAME_BYTES + 20, V_SCALE_ROT)); v_puts("\t" as *u8)
377 v_num(v_f32(buf, a + V_NAME_BYTES + 24, V_SCALE_ROT)); v_puts("\t" as *u8)
378 v_num(v_f32(buf, a + V_NAME_BYTES + 28, V_SCALE_ROT)); v_puts("\t" as *u8)
379 sys_write(1, ((buf as i64) + a) as *u8, V_NAME_BYTES)
380 v_puts("\n" as *u8)
381 i = i + 1
382 }
383 return 0
384}
385
386// ===== KAT ========================================================
387func v_put_u32(b: *u8, o: i64, v: i64) -> i64 {
388 b[o] = (v & 0xff) as u8
389 b[o+1] = ((v >> 8) & 0xff) as u8
390 b[o+2] = ((v >> 16) & 0xff) as u8
391 b[o+3] = ((v >> 24) & 0xff) as u8
392 return 0
393}
394
395func v_put_str(b: *u8, o: i64, s: *u8) -> i64 {
396 let n: i64 = v_slen(s)
397 var i: i64 = 0
398 while i < n { b[o + i] = s[i]; i = i + 1 }
399 return n
400}
401
402// Build a VALID 2-bone-frame + 1-morph-frame VMD. Returns total byte length.
403func v_kat_build(b: *u8) -> i64 {
404 var i: i64 = 0
405 while i < V_BUF { b[i] = 0 as u8; i = i + 1 }
406 v_put_str(b, 0, "Vocaloid Motion Data 0002" as *u8)
407 v_put_str(b, V_HDR_SIG, "KAT" as *u8)
408 var o: i64 = V_HDR_SIG + V_NAME_V2
409 v_put_u32(b, o, 2); o = o + 4
410 v_put_str(b, o, "center" as *u8)
411 v_put_u32(b, o + V_NAME_BYTES, 0)
412 v_put_u32(b, o + V_NAME_BYTES + 4, V_F32_1_0)
413 v_put_u32(b, o + V_NAME_BYTES + 8, V_F32_2_0)
414 v_put_u32(b, o + V_NAME_BYTES + 12, V_F32_N1_0)
415 v_put_u32(b, o + V_NAME_BYTES + 28, V_F32_1_0)
416 o = o + V_BONE_REC
417 v_put_str(b, o, "center" as *u8)
418 v_put_u32(b, o + V_NAME_BYTES, V_KAT_FRAME)
419 v_put_u32(b, o + V_NAME_BYTES + 4, V_F32_0_5)
420 v_put_u32(b, o + V_NAME_BYTES + 28, V_F32_1_0)
421 o = o + V_BONE_REC
422 v_put_u32(b, o, 1); o = o + 4
423 v_put_str(b, o, "a" as *u8)
424 v_put_u32(b, o + V_NAME_BYTES, V_KAT_MORPHFRAME)
425 v_put_u32(b, o + V_NAME_BYTES + 4, V_F32_0_5)
426 o = o + V_MORPH_REC
427 v_put_u32(b, o, 0); o = o + 4
428 v_put_u32(b, o, 0); o = o + 4
429 v_put_u32(b, o, 0); o = o + 4
430 return o
431}
432
433func v_kat() -> i64 {
434 let b: *u8 = sys_mmap(V_BUF)
435 let L: *i64 = sys_mmap(V_L_WORDS * 8) as *i64
436 let n: i64 = v_kat_build(b)
437 var red: i64 = 0
438
439 if v_layout(b, n, L) != 0 { v_puts("T1 RED layout-refused-valid\n" as *u8); red = red + 1 }
440 else {
441 if L[V_L_VER] != 2 { v_puts("T1 RED version\n" as *u8); red = red + 1 }
442 if L[V_L_BONEN] != 2 { v_puts("T1 RED bonecount\n" as *u8); red = red + 1 }
443 if L[V_L_MORPHN] != 1{ v_puts("T1 RED morphcount\n" as *u8); red = red + 1 }
444 }
445 if red == 0 { v_puts("T1 GREEN counts\n" as *u8) }
446
447 let a0: i64 = L[V_L_BONEOFF] + V_NAME_BYTES + 4
448 let px: i64 = v_f32(b, a0, V_SCALE_POS)
449 let py: i64 = v_f32(b, a0 + 4, V_SCALE_POS)
450 let pz: i64 = v_f32(b, a0 + 8, V_SCALE_POS)
451 let qw: i64 = v_f32(b, L[V_L_BONEOFF] + V_NAME_BYTES + 28, V_SCALE_ROT)
452 if px != 1000 { v_puts("T2 RED px=" as *u8); v_num(px); v_puts("\n" as *u8); red = red + 1 }
453 if py != V_MAGIC_2000 { v_puts("T2 RED py=" as *u8); v_num(py); v_puts("\n" as *u8); red = red + 1 }
454 if pz != 0 - 1000 { v_puts("T2 RED pz=" as *u8); v_num(pz); v_puts("\n" as *u8); red = red + 1 }
455 if qw != V_SCALE_ROT { v_puts("T2 RED qw=" as *u8); v_num(qw); v_puts("\n" as *u8); red = red + 1 }
456 v_puts("T2 f32-exact px=" as *u8); v_num(px); v_puts(" py=" as *u8); v_num(py)
457 v_puts(" pz=" as *u8); v_num(pz); v_puts(" qw=" as *u8); v_num(qw); v_puts("\n" as *u8)
458
459 if L[V_L_MAXFRAME] != V_KAT_FRAME {
460 v_puts("T3 RED duration=" as *u8); v_num(L[V_L_MAXFRAME]); v_puts("\n" as *u8); red = red + 1
461 } else { v_puts("T3 GREEN duration=30\n" as *u8) }
462
463 let L2: *i64 = sys_mmap(V_L_WORDS * 8) as *i64
464 if v_layout(b, V_HDR_SIG + V_NAME_V2 + 4 + V_BONE_REC, L2) >= 0 {
465 v_puts("T4 RED truncated-file-accepted\n" as *u8); red = red + 1
466 } else { v_puts("T4 GREEN truncation-refused\n" as *u8) }
467
468 let sav: u8 = b[0]
469 b[0] = 88 as u8
470 let L3: *i64 = sys_mmap(V_L_WORDS * 8) as *i64
471 if v_layout(b, n, L3) != 0 - 1 {
472 v_puts("T5 RED bad-signature-accepted\n" as *u8); red = red + 1
473 } else { v_puts("T5 GREEN signature-refused\n" as *u8) }
474 b[0] = sav
475
476 let bc_off: i64 = V_HDR_SIG + V_NAME_V2
477 v_put_u32(b, bc_off, V_KAT_LIARCOUNT)
478 let L4: *i64 = sys_mmap(V_L_WORDS * 8) as *i64
479 if v_layout(b, n, L4) != 0 - 3 {
480 v_puts("T6 RED lying-count-accepted\n" as *u8); red = red + 1
481 } else { v_puts("T6 GREEN lying-count-refused\n" as *u8) }
482 v_put_u32(b, bc_off, 2)
483
484 let L5: *i64 = sys_mmap(V_L_WORDS * 8) as *i64
485 let r5: i64 = v_layout(b, n, L5)
486 if r5 != 0 { v_puts("T7 RED fixture-not-restored\n" as *u8); red = red + 1 }
487 else { v_puts("T7 GREEN fixture-idempotent\n" as *u8) }
488
489 // T8 THE STRUCTURAL PROOF: the walked section chain must land EXACTLY on the end of the file.
490 // Every VMD record size is fixed and declared, so if V_BONE_REC were 110 or 112 instead of 111
491 // the chain would overshoot or undershoot -- and no count check would notice, because the
492 // counts would still be self-consistent. This is the tooth that a real file makes decisive:
493 // hand this organ any genuine VMD and the reconciliation validates every record size at once.
494 if r5 == 0 {
495 if L5[V_L_END] != n {
496 v_puts("T8 RED chain_end=" as *u8); v_num(L5[V_L_END])
497 v_puts(" bytes=" as *u8); v_num(n); v_puts("\n" as *u8); red = red + 1
498 } else {
499 v_puts("T8 GREEN chain reconciles to EOF at " as *u8); v_num(n); v_puts("\n" as *u8)
500 }
501 }
502
503 if red > 0 { v_puts("nx_vmd KAT RED teeth_failed=" as *u8); v_num(red); v_puts("\n" as *u8); return 1 }
504 v_puts("nx_vmd KAT GREEN 8/8\n" as *u8)
505 return 0
506}
507
508// ===== main =======================================================
509func main(argc: i64, argv: *i64) -> i64 {
510 if argc < 2 {
511 v_puts("usage: nx_vmd info|bones|dump <file.vmd> [n] | --kat\n" as *u8)
512 return 3
513 }
514 let verb: *u8 = argv[1] as *u8
515
516 if v_slen(verb) == 5 {
517 if verb[0] == (45 as u8) { return v_kat() }
518 }
519 if argc < 3 {
520 v_puts("usage: nx_vmd info|bones|dump <file.vmd> [n] | --kat\n" as *u8)
521 return 3
522 }
523
524 let path: *u8 = argv[2] as *u8
525 let lenp: *i64 = sys_mmap(16) as *i64
526 let buf: *u8 = sys_read_file(path, lenp)
527 if (buf as i64) == 0 {
528 v_puts("nx_vmd unreadable: " as *u8); v_puts(path); v_puts("\n" as *u8)
529 return 2
530 }
531 let len: i64 = lenp[0]
532 let L: *i64 = sys_mmap(V_L_WORDS * 8) as *i64
533 let rc: i64 = v_layout(buf, len, L)
534 if rc < 0 { return v_refuse(rc) }
535
536 if verb[0] == (105 as u8) { return v_info(buf, len, L) }
537 if verb[0] == (98 as u8) { return v_bones(buf, L) }
538 if verb[0] == (100 as u8) {
539 var n: i64 = V_DUMP_DEF
540 if argc > 3 {
541 let s: *u8 = argv[3] as *u8
542 var acc: i64 = 0
543 var i: i64 = 0
544 let sl: i64 = v_slen(s)
545 while i < sl { acc = acc * 10 + ((s[i] & 0xff) as i64) - 48; i = i + 1 }
546 if acc > 0 { n = acc }
547 }
548 return v_dump(buf, L, n)
549 }
550 v_puts("nx_vmd unknown verb\n" as *u8)
551 return 3
552}