code wiki / (root) / nx_nxa_pose.nx

nx_nxa_pose.nx source

↩ module page · 379 lines · 13621 B

1// nx_nxa_pose.nx -- POSE section compiler: freezes ANIM channel-2 keyframes into the NXA 2// pose library (spec: knowledge/nxa_format_spec.md POSE). A pose entry is one frozen 3// channel-2 keyframe per joint -- [joint][qx qy qz qw q12][dtx dty dtz mm], world-delta 4// about bind, root horizontal motion removed -- so a player applies it with the exact LBS 5// the ANIM path already proves (M x = D(x-b)+b+dt), no FK reconstruction (which would 6// re-introduce the dead-chain-adoption shard class: adopted joints' dt is donor-relative, 7// not parent-relative). 8// freeze picks its frame BY MEASUREMENT when t=-1: the sample minimizing horizontal foot 9// separation (feet passing = legs together = reads as standing), never by taste. 10// usage: nx_nxa_pose freeze <in.nxa> <out.nxa> <pose_id> <t_ms|-1> 11// nx_nxa_pose list <in.nxa> 12// rc: 0 ok, 2 usage, 4 missing SKEL/ANIM/tracks, 5 corrupt, 6 future-version, 9 io 13// license_tier: ORIGINAL 14import "nx_syscalls.nx" 15import "nx_nxa.nx" 16import "nx_nxa_fk.nx" 17 18const NP_MAGIC_65535: i64 = 65535 19const NP_MAGIC_32767: i64 = 32767 20const NP_MAGIC_65536: i64 = 65536 21const NP_MAGIC_4096: i64 = 4096 22const NP_MAGIC_1000: i64 = 1000 23const NP_MAGIC_33: i64 = 33 24const NP_MAGIC_52: i64 = 52 25const NP_MAGIC_100: i64 = 100 26const NP_MAGIC_256: i64 = 256 27const NP_MAGIC_1000000000: i64 = 1000000000 28 29func npw(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 } 30func npn(v: i64) -> i64 { 31 let t: *u8 = sys_mmap(32) as *u8 32 var m: i64 = v; var w: i64 = 0 33 if m<0 { t[w]=45 as u8; w=w+1; m=0-m } 34 if m==0 { t[w]=48 as u8; sys_write(1,t,w+1); return 0 } 35 let d: *u8 = sys_mmap(32) as *u8 36 var k: i64=0 37 while m>0 { d[k]=(48+(m%10)) as u8; m=m/10; k=k+1 } 38 var j: i64=0 39 while j<k { t[w]=d[k-1-j]; w=w+1; j=j+1 } 40 sys_write(1,t,w); return 0 41} 42func np_atoi(s: *u8) -> i64 { 43 var v: i64 = 0 44 var i: i64 = 0 45 var neg: i64 = 0 46 if s[0]==(45 as u8) { neg=1; i=1 } 47 while s[i]>=(48 as u8) { 48 if s[i]>(57 as u8) { return v } 49 v = v*10 + ((s[i] as i64) - 48) 50 i = i + 1 51 } 52 if neg==1 { v = 0-v } 53 return v 54} 55func np_seq(a: *u8, b: *u8) -> i64 { 56 var i: i64 = 0 57 while a[i]!=(0 as u8) { 58 if a[i]!=b[i] { return 0 } 59 i = i + 1 60 } 61 if b[i]!=(0 as u8) { return 0 } 62 return 1 63} 64// sign-extend a 16-bit lane out of an i64 word (channel-2 packed keys) 65func np_i16(w: i64, sh: i64) -> i64 { 66 var v: i64 = (w >> sh) & NP_MAGIC_65535 67 if v > NP_MAGIC_32767 { v = v - NP_MAGIC_65536 } 68 return v 69} 70// sample one channel-2 track at t ms: shortest-path integer nlerp dq + lerp dt. 71// out7 = [qx qy qz qw dtx dty dtz]; koff = word offset of first key in h 72func np_sample(h: *i64, koff: i64, nk: i64, t: i64, out7: *i64) -> i64 { 73 var k: i64 = 0 74 var scan: i64 = 1 75 while scan == 1 { 76 scan = 0 77 if k+1 < nk { 78 let tn: i64 = h[koff+(k+1)*2] & NP_MAGIC_65535 79 if tn <= t { k = k + 1; scan = 1 } 80 } 81 } 82 var k1: i64 = k + 1 83 if k1 >= nk { k1 = k } 84 let w0a: i64 = h[koff+k*2] 85 let w1a: i64 = h[koff+k*2+1] 86 let w0b: i64 = h[koff+k1*2] 87 let w1b: i64 = h[koff+k1*2+1] 88 let t0: i64 = w0a & NP_MAGIC_65535 89 let t1: i64 = w0b & NP_MAGIC_65535 90 var u: i64 = 0 91 if t1 > t0 { u = (t - t0)*NP_MAGIC_1000/(t1 - t0) } 92 if u < 0 { u = 0 } 93 if u > NP_MAGIC_1000 { u = NP_MAGIC_1000 } 94 let ax: i64 = np_i16(w0a,16) 95 let ay: i64 = np_i16(w0a,32) 96 let az: i64 = np_i16(w0a,48) 97 let aq: i64 = np_i16(w1a,0) 98 var bx: i64 = np_i16(w0b,16) 99 var by: i64 = np_i16(w0b,32) 100 var bz: i64 = np_i16(w0b,48) 101 var bq: i64 = np_i16(w1b,0) 102 if ax*bx + ay*by + az*bz + aq*bq < 0 { bx=0-bx; by=0-by; bz=0-bz; bq=0-bq } 103 out7[0] = ax + (bx-ax)*u/NP_MAGIC_1000 104 out7[1] = ay + (by-ay)*u/NP_MAGIC_1000 105 out7[2] = az + (bz-az)*u/NP_MAGIC_1000 106 out7[3] = aq + (bq-aq)*u/NP_MAGIC_1000 107 nf_qnorm(out7) 108 let dxa: i64 = np_i16(w1a,16) 109 let dya: i64 = np_i16(w1a,32) 110 let dza: i64 = np_i16(w1a,48) 111 let dxb: i64 = np_i16(w1b,16) 112 let dyb: i64 = np_i16(w1b,32) 113 let dzb: i64 = np_i16(w1b,48) 114 out7[4] = dxa + (dxb-dxa)*u/NP_MAGIC_1000 115 out7[5] = dya + (dyb-dya)*u/NP_MAGIC_1000 116 out7[6] = dza + (dzb-dza)*u/NP_MAGIC_1000 117 return 0 118} 119 120func main(argc: i64, argv: *i64) -> i64 { 121 if argc < 3 { npw("usage: nx_nxa_pose freeze <in.nxa> <out.nxa> <pose_id> <t_ms|-1> | list <in.nxa>\n" as *u8); return 2 } 122 let verb: *u8 = argv[1] as *u8 123 let lp: *i64 = sys_mmap(16) as *i64 124 let b: *u8 = sys_map_file(argv[2] as *u8, lp) 125 let flen: i64 = lp[0] 126 if flen < 96 { npw("unreadable\n" as *u8); return 5 } 127 let h: *i64 = b as *i64 128 let swo: i64 = nxa_find(b, flen, nxa_tag4("SKEL" as *u8)) 129 if swo == 0 - 2 { npw("NXA-FUTURE-VERSION\n" as *u8); return 6 } 130 if swo == 0 - 3 { npw("NXA-CORRUPT skel\n" as *u8); return 5 } 131 if swo < 0 { npw("no SKEL\n" as *u8); return 4 } 132 let nj: i64 = h[swo] 133 let sk: *i64 = ((b as i64) + swo*8 + 8) as *i64 134 let awo: i64 = nxa_find(b, flen, nxa_tag4("ANIM" as *u8)) 135 if awo == 0 - 3 { npw("NXA-CORRUPT anim\n" as *u8); return 5 } 136 let pwo: i64 = nxa_find(b, flen, nxa_tag4("POSE" as *u8)) 137 if pwo == 0 - 3 { npw("NXA-CORRUPT pose\n" as *u8); return 5 } 138 139 if np_seq(verb, "list" as *u8) == 1 { 140 if pwo < 0 { npw("poses=0\n" as *u8); return 0 } 141 let npz: i64 = h[pwo] 142 npw("poses=" as *u8); npn(npz); npw("\n" as *u8) 143 var rp: i64 = pwo + 1 144 var pi: i64 = 0 145 while pi < npz { 146 npw(" pose id=" as *u8); npn(h[rp]) 147 npw(" entries=" as *u8); npn(h[rp+1]); npw("\n" as *u8) 148 rp = rp + 2 + h[rp+1]*8 149 pi = pi + 1 150 } 151 return 0 152 } 153 if np_seq(verb, "freeze" as *u8) == 0 { npw("unknown verb\n" as *u8); return 2 } 154 if argc < 6 { npw("usage: nx_nxa_pose freeze <in.nxa> <out.nxa> <pose_id> <t_ms|-1>\n" as *u8); return 2 } 155 if awo < 0 { npw("no ANIM\n" as *u8); return 4 } 156 let a4: *u8 = argv[4] as *u8 157 let a5: *u8 = argv[5] as *u8 158 let pid: i64 = np_atoi(a4) 159 var targ: i64 = np_atoi(a5) 160 161 // index channel-2 tracks by joint: koff/nk per joint (-1 = untracked) 162 let tko: *i64 = sys_mmap(nj*8 + 64) as *i64 163 let tkn: *i64 = sys_mmap(nj*8 + 64) as *i64 164 var j0: i64 = 0 165 while j0 < nj { tko[j0] = 0 - 1; tkn[j0] = 0; j0 = j0 + 1 } 166 let ntr: i64 = h[awo] 167 var rp2: i64 = awo + 1 168 var dur: i64 = 0 169 var ti: i64 = 0 170 var tracked: i64 = 0 171 while ti < ntr { 172 let jt: i64 = h[rp2] 173 let ch: i64 = h[rp2+1] 174 let nk: i64 = h[rp2+2] 175 if ch == 2 { 176 if jt >= 0 { if jt < nj { if nk > 0 { 177 tko[jt] = rp2 + 3 178 tkn[jt] = nk 179 tracked = tracked + 1 180 let lastt: i64 = h[rp2+3+(nk-1)*2] & NP_MAGIC_65535 181 if lastt > dur { dur = lastt } 182 } } } 183 rp2 = rp2 + 3 + nk*2 184 } 185 if ch == 0 { rp2 = rp2 + 3 + nk*4 } 186 if ch == 1 { rp2 = rp2 + 3 + nk*5 } 187 ti = ti + 1 188 } 189 if tracked == 0 { npw("no channel-2 tracks\n" as *u8); return 4 } 190 191 // structural picks from binds (no names in NXA): H = max bind z; 192 // root = nearest (0,0,.52H); feet = per side (bind x sign) the lowest-z TRACKED joint 193 var hmax: i64 = 0 194 var j1: i64 = 0 195 while j1 < nj { if sk[j1*8+3] > hmax { hmax = sk[j1*8+3] } j1 = j1 + 1 } 196 var rj: i64 = 0 - 1 197 var rsc: i64 = NP_MAGIC_1000000000 198 var fl: i64 = 0 - 1 199 var flz: i64 = NP_MAGIC_1000000000 200 var fr: i64 = 0 - 1 201 var frz: i64 = NP_MAGIC_1000000000 202 var j2: i64 = 0 203 while j2 < nj { 204 let bxj: i64 = sk[j2*8+1] 205 let bzj: i64 = sk[j2*8+3] 206 var ds: i64 = bzj - hmax*NP_MAGIC_52/NP_MAGIC_100 207 if ds < 0 { ds = 0-ds } 208 var axj: i64 = bxj 209 if axj < 0 { axj = 0-axj } 210 let sc: i64 = ds + 2*axj 211 if sc < rsc { rsc = sc; rj = j2 } 212 if tko[j2] >= 0 { 213 if bxj < 0-NP_MAGIC_1000 { if bzj < flz { flz = bzj; fl = j2 } } 214 if bxj > NP_MAGIC_1000 { if bzj < frz { frz = bzj; fr = j2 } } 215 } 216 j2 = j2 + 1 217 } 218 219 let ev: *i64 = sys_mmap(64) as *i64 220 let ev2: *i64 = sys_mmap(64) as *i64 221 // auto frame pick: minimize horizontal foot separation across the clip (33ms grid). 222 // dt lanes are MILLIMETERS (the bake divides by 100 to fit i16); binds are 0.01mm 223 // units -- scale dt x100 before mixing (the unit-mismatch class MEASURED 2026-07-29: 224 // consumers applying raw lanes rendered every baked translation at 1%) 225 if targ < 0 { 226 if fl < 0 { npw("no left foot track\n" as *u8); return 4 } 227 if fr < 0 { npw("no right foot track\n" as *u8); return 4 } 228 var bt: i64 = 0 229 var bsep: i64 = NP_MAGIC_1000000000*NP_MAGIC_1000 230 var t: i64 = 0 231 while t <= dur { 232 np_sample(h, tko[fl], tkn[fl], t, ev) 233 np_sample(h, tko[fr], tkn[fr], t, ev2) 234 let px: i64 = (sk[fl*8+1] + ev[4]*NP_MAGIC_100) - (sk[fr*8+1] + ev2[4]*NP_MAGIC_100) 235 let py: i64 = (sk[fl*8+2] + ev[5]*NP_MAGIC_100) - (sk[fr*8+2] + ev2[5]*NP_MAGIC_100) 236 let sep: i64 = px*px + py*py 237 if sep < bsep { bsep = sep; bt = t } 238 t = t + NP_MAGIC_33 239 } 240 targ = bt 241 npw("auto t_ms=" as *u8); npn(bt) 242 npw(" footsep_units=" as *u8); npn(nf_isqrt(bsep)); npw("\n" as *u8) 243 } 244 245 // sample every tracked joint at targ; POSE entries store dt in VERT units (mm lanes 246 // x100) so players compose them with binds directly, no scale knowledge needed 247 let pq: *i64 = sys_mmap(nj*32 + 64) as *i64 248 let pd: *i64 = sys_mmap(nj*24 + 64) as *i64 249 var j3: i64 = 0 250 while j3 < nj { 251 if tko[j3] >= 0 { 252 np_sample(h, tko[j3], tkn[j3], targ, ev) 253 pq[j3*4] = ev[0] 254 pq[j3*4+1] = ev[1] 255 pq[j3*4+2] = ev[2] 256 pq[j3*4+3] = ev[3] 257 pd[j3*3] = ev[4]*NP_MAGIC_100 258 pd[j3*3+1] = ev[5]*NP_MAGIC_100 259 pd[j3*3+2] = ev[6]*NP_MAGIC_100 260 } 261 j3 = j3 + 1 262 } 263 // root horizontal motion removal (same convention the runtime applies to the clip) 264 var rdx: i64 = 0 265 var rdy: i64 = 0 266 if rj >= 0 { if tko[rj] >= 0 { rdx = pd[rj*3]; rdy = pd[rj*3+1] } } 267 var j4: i64 = 0 268 while j4 < nj { 269 if tko[j4] >= 0 { pd[j4*3] = pd[j4*3] - rdx; pd[j4*3+1] = pd[j4*3+1] - rdy } 270 j4 = j4 + 1 271 } 272 273 // build the new POSE payload: kept old poses (id != pid) + the frozen one 274 var keptw: i64 = 0 275 var keptn: i64 = 0 276 if pwo >= 0 { 277 let npz0: i64 = h[pwo] 278 var rp3: i64 = pwo + 1 279 var pi2: i64 = 0 280 while pi2 < npz0 { 281 let sz: i64 = 2 + h[rp3+1]*8 282 if h[rp3] != pid { keptw = keptw + sz; keptn = keptn + 1 } 283 rp3 = rp3 + sz 284 pi2 = pi2 + 1 285 } 286 } 287 let newpw: i64 = 2 + tracked*8 288 let plen: i64 = 1 + keptw + newpw 289 let pp: *i64 = sys_mmap(plen*8 + 64) as *i64 290 pp[0] = keptn + 1 291 var wp: i64 = 1 292 if pwo >= 0 { 293 let npz1: i64 = h[pwo] 294 var rp4: i64 = pwo + 1 295 var pi3: i64 = 0 296 while pi3 < npz1 { 297 let sz2: i64 = 2 + h[rp4+1]*8 298 if h[rp4] != pid { 299 var cw: i64 = 0 300 while cw < sz2 { pp[wp+cw] = h[rp4+cw]; cw = cw + 1 } 301 wp = wp + sz2 302 } 303 rp4 = rp4 + sz2 304 pi3 = pi3 + 1 305 } 306 } 307 pp[wp] = pid 308 pp[wp+1] = tracked 309 wp = wp + 2 310 var j5: i64 = 0 311 while j5 < nj { 312 if tko[j5] >= 0 { 313 pp[wp] = j5 314 pp[wp+1] = pq[j5*4] 315 pp[wp+2] = pq[j5*4+1] 316 pp[wp+3] = pq[j5*4+2] 317 pp[wp+4] = pq[j5*4+3] 318 pp[wp+5] = pd[j5*3] 319 pp[wp+6] = pd[j5*3+1] 320 pp[wp+7] = pd[j5*3+2] 321 wp = wp + 8 322 } 323 j5 = j5 + 1 324 } 325 326 // write out: copy every section except any old POSE, append the new POSE 327 let ptag: i64 = nxa_tag4("POSE" as *u8) 328 let ons: i64 = h[2] 329 let otoc: *i64 = ((b as i64) + 32) as *i64 330 var keep: i64 = 0 331 var ci: i64 = 0 332 while ci < ons { 333 if otoc[ci*4] != ptag { keep = keep + 1 } 334 ci = ci + 1 335 } 336 let ns2: i64 = keep + 1 337 let hdr: *i64 = sys_mmap(64) as *i64 338 let toc: *i64 = sys_mmap(ns2*32 + 64) as *i64 339 var o: i64 = 32 + ns2*32 340 var wi: i64 = 0 341 var ci2: i64 = 0 342 while ci2 < ons { 343 if otoc[ci2*4] != ptag { 344 toc[wi*4] = otoc[ci2*4] 345 toc[wi*4+1] = o 346 toc[wi*4+2] = otoc[ci2*4+2] 347 toc[wi*4+3] = otoc[ci2*4+3] 348 o = o + otoc[ci2*4+2]*8 349 wi = wi + 1 350 } 351 ci2 = ci2 + 1 352 } 353 toc[wi*4] = ptag 354 toc[wi*4+1] = o 355 toc[wi*4+2] = plen 356 toc[wi*4+3] = nxa_check2(1, pp, plen) 357 hdr[0] = nxa_magic() 358 hdr[1] = NXA_VER 359 hdr[2] = ns2 360 hdr[3] = nxa_check2(1, toc, ns2*4) 361 let fd: i64 = sys_openat_wr(argv[3] as *u8, 0x1a4) 362 if fd < 0 { npw("open out failed\n" as *u8); return 9 } 363 sys_write(fd, hdr as *u8, 32) 364 sys_write(fd, toc as *u8, ns2*32) 365 var ci3: i64 = 0 366 while ci3 < ons { 367 if otoc[ci3*4] != ptag { 368 sys_write(fd, ((b as i64) + otoc[ci3*4+1]) as *u8, otoc[ci3*4+2]*8) 369 } 370 ci3 = ci3 + 1 371 } 372 sys_write(fd, pp as *u8, plen*8) 373 sys_close(fd) 374 npw("POSE written id=" as *u8); npn(pid) 375 npw(" t_ms=" as *u8); npn(targ) 376 npw(" entries=" as *u8); npn(tracked) 377 npw(" sections=" as *u8); npn(ns2); npw("\n" as *u8) 378 return 0 379}