code wiki / (root) / nx_nxa_posewrite_lib.nx

nx_nxa_posewrite_lib.nx source

↩ module page · 170 lines · 7463 B

1// nx_nxa_posewrite_lib.nx -- THE ONE POSE-SECTION WRITER for the NXA format (spec: knowledge/nxa_format_spec.md POSE). 2// WHY (measured 2026-09-05, /compare/modding MD3): the estate had exactly ONE writer of a POSE section -- inline inside 3// nx_nxa_pose freeze, an organ that is SOURCE-ONLY -- and nx_nxa_retarget, which computes a complete pose (a world-delta 4// quat and a dt per target joint, the exact POSE entry shape), published it by BAKING the posed VERT in place instead. 5// The baked donor is a statue with a different shape: nx_nxa_play reads poses_in_library=0 joints_posed=0 on every 6// retargeted output while the house rig reads 104/104, so nothing downstream can replay, hold, or sequence the pose. 7// A writer that lives in one organ and is re-typed in the next is the duplicate-ruler defect; this lib is that writer, 8// composed by both (nx_nxa_retarget emit=pose today; nx_nxa_pose freeze is the migration owed). 9// Layout written (nx_nxa.nx + the nx_nxa_pose incumbent): header 4 words [magic, NXA_VER, nsections, toc_check]; TOC 4 10// words per section [tag, byte_offset, word_len, payload_check]; payloads follow in TOC order. POSE payload: [count, then 11// per pose: pose_id, nentries, entries of NPW_WPE=8 words (joint, qx, qy, qz, qw q12, dtx, dty, dtz MODEL units)]. Every 12// existing section EXCEPT an old POSE is copied unchanged; the new POSE payload keeps every old pose whose id differs from 13// the one written (so re-emitting an id REPLACES, never DUPLICATES) and appends the new one LAST. 14// q and dt are indexed by JOINT id (q[joint*4], dt[joint*3]) and jl lists the joints to emit, so a dense per-joint table 15// (the retarget) and a tracked subset (the freeze) share one signature. 16// license_tier: ORIGINAL No hw writes (Rule 26). 17import "nx_syscalls.nx" 18import "nx_nxa.nx" 19 20const NPW_WPE: i64 = 8 // words per POSE entry (spec) 21const NPW_HDR_BYTES: i64 = 32 // 4 header words 22const NPW_TOC_BYTES: i64 = 32 // 4 TOC words per section 23const NPW_TOC_W: i64 = 4 24const NPW_TOC_MAX: i64 = 64 // nxa_find refuses ns > 64, so a writer must refuse before making such a file 25const NPW_MODE: i64 = 420 // 0644, every sibling NXA writer 26const NPW_ERR_NOTNXA: i64 = 0 - 1 27const NPW_ERR_TOCFULL: i64 = 0 - 2 28const NPW_ERR_OPEN: i64 = 0 - 3 29const NPW_ERR_SHORT: i64 = 0 - 4 30const NPW_ERR_PAYLOAD: i64 = 0 - 5 // built payload did not fill its predicted length: a partition that did not sum 31const NPW_ST_SECTIONS: i64 = 0 // st[] slots the writer fills for the caller's receipt 32const NPW_ST_KEPT: i64 = 1 33const NPW_ST_PLEN: i64 = 2 34const NPW_ST_N: i64 = 3 35const NPW_ST_W: i64 = 4 36 37// payload words a POSE section needs after writing pose `pid` with n entries over the file's existing library 38func npw_payload_words(b: *u8, flen: i64, pid: i64, n: i64, st: *i64) -> i64 { 39 let h: *i64 = b as *i64 40 var keptw: i64 = 0 41 var keptn: i64 = 0 42 let pwo: i64 = nxa_find(b, flen, nxa_tag4("POSE" as *u8)) 43 if pwo >= 0 { 44 let np: i64 = h[pwo] 45 var rp: i64 = pwo + 1 46 var p: i64 = 0 47 while p < np { 48 let sz: i64 = 2 + h[rp+1] * NPW_WPE 49 if h[rp] != pid { keptw = keptw + sz; keptn = keptn + 1 } 50 rp = rp + sz 51 p = p + 1 52 } 53 } 54 st[NPW_ST_KEPT] = keptn 55 return 1 + keptw + 2 + n * NPW_WPE 56} 57 58// fill pp: kept poses first, the new pose LAST. Returns the words written (must equal npw_payload_words). 59func npw_build_payload(b: *u8, flen: i64, pid: i64, jl: *i64, q: *i64, dt: *i64, n: i64, pp: *i64) -> i64 { 60 let h: *i64 = b as *i64 61 let pwo: i64 = nxa_find(b, flen, nxa_tag4("POSE" as *u8)) 62 var wp: i64 = 1 63 var keptn: i64 = 0 64 if pwo >= 0 { 65 let np: i64 = h[pwo] 66 var rp: i64 = pwo + 1 67 var p: i64 = 0 68 while p < np { 69 let sz: i64 = 2 + h[rp+1] * NPW_WPE 70 if h[rp] != pid { 71 var c: i64 = 0 72 while c < sz { pp[wp+c] = h[rp+c]; c = c + 1 } 73 wp = wp + sz 74 keptn = keptn + 1 75 } 76 rp = rp + sz 77 p = p + 1 78 } 79 } 80 pp[0] = keptn + 1 81 pp[wp] = pid 82 pp[wp+1] = n 83 wp = wp + 2 84 var e: i64 = 0 85 while e < n { 86 let jj: i64 = jl[e] 87 pp[wp] = jj 88 pp[wp+1] = q[jj*4]; pp[wp+2] = q[jj*4+1]; pp[wp+3] = q[jj*4+2]; pp[wp+4] = q[jj*4+3] 89 pp[wp+5] = dt[jj*3]; pp[wp+6] = dt[jj*3+1]; pp[wp+7] = dt[jj*3+2] 90 wp = wp + NPW_WPE 91 e = e + 1 92 } 93 return wp 94} 95 96// write every byte or report how many landed (a short write is a named refusal, never a truncated asset) 97func npw_put(fd: i64, p: *u8, n: i64) -> i64 { 98 var done: i64 = 0 99 while done < n { 100 let k: i64 = sys_write(fd, ((p as i64) + done) as *u8, n - done) 101 if k <= 0 { return done } 102 done = done + k 103 } 104 return done 105} 106 107// Rewrite the NXA at b[0..flen) to outp with pose `pid` (n entries over joints jl, quats q, deltas dt) in its POSE 108// library. Returns bytes written (> 0) or a negative NPW_ERR_*; st[NPW_ST_*] carries sections / kept poses / payload 109// words / entries for the caller's receipt. 110func npw_write(b: *u8, flen: i64, pid: i64, jl: *i64, q: *i64, dt: *i64, n: i64, outp: *u8, st: *i64) -> i64 { 111 let h: *i64 = b as *i64 112 if flen < NPW_HDR_BYTES { return NPW_ERR_NOTNXA } 113 if h[0] != nxa_magic() { return NPW_ERR_NOTNXA } 114 let plen: i64 = npw_payload_words(b, flen, pid, n, st) 115 let pp: *i64 = sys_mmap(plen * 8 + 64) as *i64 116 let got: i64 = npw_build_payload(b, flen, pid, jl, q, dt, n, pp) 117 if got != plen { return NPW_ERR_PAYLOAD } 118 let ptag: i64 = nxa_tag4("POSE" as *u8) 119 let ons: i64 = h[2] 120 let otoc: *i64 = ((b as i64) + NPW_HDR_BYTES) as *i64 121 var keep: i64 = 0 122 var ci: i64 = 0 123 while ci < ons { if otoc[ci*NPW_TOC_W] != ptag { keep = keep + 1 } ci = ci + 1 } 124 let ns2: i64 = keep + 1 125 if ns2 > NPW_TOC_MAX { return NPW_ERR_TOCFULL } 126 let hdr: *i64 = sys_mmap(64) as *i64 127 let toc: *i64 = sys_mmap(ns2 * NPW_TOC_BYTES + 64) as *i64 128 var o: i64 = NPW_HDR_BYTES + ns2 * NPW_TOC_BYTES 129 var wi: i64 = 0 130 ci = 0 131 while ci < ons { 132 if otoc[ci*NPW_TOC_W] != ptag { 133 toc[wi*NPW_TOC_W] = otoc[ci*NPW_TOC_W] 134 toc[wi*NPW_TOC_W+1] = o 135 toc[wi*NPW_TOC_W+2] = otoc[ci*NPW_TOC_W+2] 136 toc[wi*NPW_TOC_W+3] = otoc[ci*NPW_TOC_W+3] 137 o = o + otoc[ci*NPW_TOC_W+2] * 8 138 wi = wi + 1 139 } 140 ci = ci + 1 141 } 142 toc[wi*NPW_TOC_W] = ptag 143 toc[wi*NPW_TOC_W+1] = o 144 toc[wi*NPW_TOC_W+2] = plen 145 toc[wi*NPW_TOC_W+3] = nxa_check2(1, pp, plen) 146 let total: i64 = o + plen * 8 147 hdr[0] = nxa_magic() 148 hdr[1] = NXA_VER 149 hdr[2] = ns2 150 hdr[3] = nxa_check2(1, toc, ns2 * NPW_TOC_W) 151 let fd: i64 = sys_openat_wr(outp, NPW_MODE) 152 if fd < 0 { return NPW_ERR_OPEN } 153 var wrote: i64 = 0 154 wrote = wrote + npw_put(fd, hdr as *u8, NPW_HDR_BYTES) 155 wrote = wrote + npw_put(fd, toc as *u8, ns2 * NPW_TOC_BYTES) 156 ci = 0 157 while ci < ons { 158 if otoc[ci*NPW_TOC_W] != ptag { 159 wrote = wrote + npw_put(fd, ((b as i64) + otoc[ci*NPW_TOC_W+1]) as *u8, otoc[ci*NPW_TOC_W+2] * 8) 160 } 161 ci = ci + 1 162 } 163 wrote = wrote + npw_put(fd, pp as *u8, plen * 8) 164 sys_close(fd) 165 st[NPW_ST_SECTIONS] = ns2 166 st[NPW_ST_PLEN] = plen 167 st[NPW_ST_N] = n 168 if wrote != total { return NPW_ERR_SHORT } 169 return wrote 170}