nx_vmd.nx
buildroot/runtime/nx_vmd.nx
about
nx_vmd.nx -- VMD (Vocaloid Motion Data) MOTION DECODER: the MMD dance corpus, read sovereignly.
WHY THIS EXISTS (measured 2026-08-07 against vrstormlab.com/dancexr/play, from their own served
bytes -- not their marketing copy): the free LW build ingests PMX + XPS models and VMD + BVH
motion. Our estate already owns the OTHER half of that pipeline -- nx_pose_retarget inverts the
sovereign FK, nx_gltf2mesh segments a donor into 74 named parts -- and owned ZERO of their four
ingest formats. A grep for "bvh" found nx_bvh and read as a match; nx_bvh is a BOUNDING VOLUME
HIERARCHY for the slicer, not BioVision Hierarchy motion capture.
*A NAME IS NOT A CAPABILITY. A GREP FINDS THE CLAIM; ONLY READING THE SOURCE SETTLES IT.
VMD is the keystone of the four because the dance corpus is overwhelmingly VMD, and because it
carries exactly what our retarget consumes: per-bone, per-frame rotations on a named skeleton.
FAIL-CLOSED BY CONSTRUCTION: every field is bounds-checked against the ACTUAL file length before
it is read. A count in a header is a CLAIM, never an authority. A truncated file is REFUSED, not
partially parsed into a plausible-looking track -- because a plausible segmentation is the most
dangerous kind, and a motion track that silently stops at frame 40 of 400 still animates.
UNITS ARE DECLARED, NEVER IMPLIED (a threshold is meaningless until its unit is stated):
positions = VMD/MMD model units x V_SCALE_POS (1000). NOT millimetres; MMD units are their own.
rotations = quaternion components x V_SCALE_ROT (4096), each in [-4096, 4096].
morph = weight x V_SCALE_POS (1000), nominally [0, 1000].
usage: nx_vmd info <file.vmd> version, model name, per-section counts, duration in frames
nx_vmd bones <file.vmd> per-bone keyframe census (raw name bytes + id)
nx_vmd dump <file.vmd> [n] first n bone keyframes as rows
nx_vmd --kat selftest: 7 teeth, 4 of them anti-vacuity
exit 0 ok | 2 unreadable | 3 usage | 4 REFUSED (bad magic / truncated / count exceeds bytes)
| 1 selftest RED
license_tier: ORIGINAL No hw writes (Rule 26). expect_exit: 0
dependencies 1 imports · 0 importers
imports: nx_syscalls.nx
imported by: nobody (leaf or entry point)
structs
| none |
consts
| 32 | const V_MAGIC_2000: i64 = 2000 |
| 35 | const V_HDR_SIG: i64 = 30 // signature block, NUL/garbage padded |
| 36 | const V_NAME_V2: i64 = 20 // model-name field, signature "...0002" |
| 37 | const V_NAME_V1: i64 = 10 // model-name field, older signature |
| 38 | const V_BONE_REC: i64 = 111 // name15 + frame4 + pos12 + quat16 + interp64 |
| 39 | const V_MORPH_REC: i64 = 23 // name15 + frame4 + weight4 |
| 40 | const V_CAM_REC: i64 = 61 // frame4 + dist4 + pos12 + rot12 + interp24 + fov4 + persp1 |
| 41 | const V_LIGHT_REC: i64 = 28 // frame4 + rgb12 + pos12 |
| 42 | const V_SHADOW_REC: i64 = 9 // frame4 + mode1 + dist4 |
| 43 | const V_NAME_BYTES: i64 = 15 // bone/morph name field width (Shift-JIS, NUL padded) |
| 46 | const V_SCALE_POS: i64 = 1000 |
| 47 | const V_SCALE_ROT: i64 = 4096 |
| 50 | const V_M_255: i64 = 255 |
| 51 | const V_M_8388607: i64 = 8388607 |
| 52 | const V_M_8388608: i64 = 8388608 |
| 53 | const V_M_EXPBIAS: i64 = 127 |
| 54 | const V_M_MANTBIT: i64 = 23 |
| 55 | const V_MAX_SH: i64 = 40 // refuse absurd exponents rather than overflow the shift |
| 56 | const V_MAX_RS: i64 = 62 // a shift wider than the word is undefined -- return 0, never garbage |
| 59 | const V_MAX_UNIQ: i64 = 512 // unique bone names held; exceeding this REFUSES (declared cap) |
| 60 | const V_DUMP_DEF: i64 = 8 |
| 63 | const V_L_VER: i64 = 0 |
| 64 | const V_L_NAMEOFF: i64 = 1 |
| 65 | const V_L_BONEOFF: i64 = 2 |
| 66 | const V_L_BONEN: i64 = 3 |
| 67 | const V_L_MORPHOFF:i64 = 4 |
| 68 | const V_L_MORPHN: i64 = 5 |
| 69 | const V_L_CAMOFF: i64 = 6 |
| 70 | const V_L_CAMN: i64 = 7 |
| 71 | const V_L_LIGHTOFF:i64 = 8 |
| 72 | const V_L_LIGHTN: i64 = 9 |
| 73 | const V_L_SHADOFF: i64 = 10 |
| 74 | const V_L_SHADN: i64 = 11 |
| 75 | const V_L_MAXFRAME:i64 = 12 |
| 76 | const V_L_IKOFF: i64 = 13 |
| 77 | const V_L_IKN: i64 = 14 |
| 78 | const V_L_END: i64 = 15 |
| 79 | const V_L_WORDS: i64 = 20 |
| 80 | const V_IK_NAME: i64 = 20 // per-IK-entry bone name field width |
| 81 | const V_IK_FIXED: i64 = 9 // frame4 + show1 + ikcount4 |
| 84 | const V_BUF: i64 = 4096 |
| 85 | const V_MODE_644: i64 = 420 |
| 86 | const V_F32_1_0: i64 = 1065353216 |
| 87 | const V_F32_2_0: i64 = 1073741824 |
| 88 | const V_F32_0_5: i64 = 1056964608 |
| 89 | const V_F32_N1_0: i64 = 3212836864 |
| 90 | const V_KAT_FRAME: i64 = 30 |
| 91 | const V_KAT_MORPHFRAME: i64 = 10 |
| 92 | const V_KAT_LIARCOUNT: i64 = 100000 |
functions
| 95 | func v_slen(s: *u8) -> i64 { var i: i64 = 0; while s[i] != (0 as u8) { i = i + 1 } return i } |
| 96 | func v_puts(s: *u8) -> i64 { sys_write(1, s, v_slen(s)); return 0 } |
| 98 | func v_num(v: i64) -> i64 |
| 114 | func v_u32(b: *u8, o: i64) -> i64 |
| 122 | func v_f32(b: *u8, o: i64, scale: i64) -> i64 |
| 145 | func v_version(b: *u8, len: i64) -> i64 |
| 164 | func v_name_width(ver: i64) -> i64 |
| 177 | func v_section(buf: *u8, len: i64, off: i64, rec: i64, L: *i64, slot_off: i64, slot_n: i64) -> i64 |
| 190 | func v_layout(buf: *u8, len: i64, L: *i64) -> i64 |
| 255 | func v_refuse(code: i64) -> i64 |
| 265 | func v_name_eq(buf: *u8, a: i64, tbl: *u8, slot: i64) -> i64 |
| 274 | func v_name_put(buf: *u8, a: i64, tbl: *u8, slot: i64) -> i64 |
| 282 | func v_name_print(tbl: *u8, slot: i64) -> i64 |
| 293 | func v_info(buf: *u8, len: i64, L: *i64) -> i64 |
| 324 | func v_bones(buf: *u8, L: *i64) -> i64 |
| 362 | func v_dump(buf: *u8, L: *i64, maxn: i64) -> i64 |
| 387 | func v_put_u32(b: *u8, o: i64, v: i64) -> i64 |
| 395 | func v_put_str(b: *u8, o: i64, s: *u8) -> i64 |
| 403 | func v_kat_build(b: *u8) -> i64 |
| 433 | func v_kat() -> i64 |
| 509 | func main(argc: i64, argv: *i64) -> i64 |