code wiki / (root) / nx_nxa_pose.nx

nx_nxa_pose.nx source

↩ module page · 296 lines · 11805 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" 17import "nx_nxa_posewrite_lib.nx" 18 19const NP_MAGIC_65535: i64 = 65535 20const NP_MAGIC_32767: i64 = 32767 21const NP_MAGIC_65536: i64 = 65536 22const NP_MAGIC_4096: i64 = 4096 23const NP_MAGIC_1000: i64 = 1000 24const NP_MAGIC_33: i64 = 33 25const NP_MAGIC_52: i64 = 52 26const NP_MAGIC_100: i64 = 100 27const NP_MAGIC_256: i64 = 256 28const NP_MAGIC_1000000000: i64 = 1000000000 29 30func npw(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 } 31func npn(v: i64) -> i64 { 32 let t: *u8 = sys_mmap(32) as *u8 33 var m: i64 = v; var w: i64 = 0 34 if m<0 { t[w]=45 as u8; w=w+1; m=0-m } 35 if m==0 { t[w]=48 as u8; sys_write(1,t,w+1); return 0 } 36 let d: *u8 = sys_mmap(32) as *u8 37 var k: i64=0 38 while m>0 { d[k]=(48+(m%10)) as u8; m=m/10; k=k+1 } 39 var j: i64=0 40 while j<k { t[w]=d[k-1-j]; w=w+1; j=j+1 } 41 sys_write(1,t,w); return 0 42} 43func np_atoi(s: *u8) -> i64 { 44 var v: i64 = 0 45 var i: i64 = 0 46 var neg: i64 = 0 47 if s[0]==(45 as u8) { neg=1; i=1 } 48 while s[i]>=(48 as u8) { 49 if s[i]>(57 as u8) { return v } 50 v = v*10 + ((s[i] as i64) - 48) 51 i = i + 1 52 } 53 if neg==1 { v = 0-v } 54 return v 55} 56func np_seq(a: *u8, b: *u8) -> i64 { 57 var i: i64 = 0 58 while a[i]!=(0 as u8) { 59 if a[i]!=b[i] { return 0 } 60 i = i + 1 61 } 62 if b[i]!=(0 as u8) { return 0 } 63 return 1 64} 65// sign-extend a 16-bit lane out of an i64 word (channel-2 packed keys) 66func np_i16(w: i64, sh: i64) -> i64 { 67 var v: i64 = (w >> sh) & NP_MAGIC_65535 68 if v > NP_MAGIC_32767 { v = v - NP_MAGIC_65536 } 69 return v 70} 71// sample one channel-2 track at t ms: shortest-path integer nlerp dq + lerp dt. 72// out7 = [qx qy qz qw dtx dty dtz]; koff = word offset of first key in h 73func np_sample(h: *i64, koff: i64, nk: i64, t: i64, out7: *i64) -> i64 { 74 var k: i64 = 0 75 var scan: i64 = 1 76 while scan == 1 { 77 scan = 0 78 if k+1 < nk { 79 let tn: i64 = h[koff+(k+1)*2] & NP_MAGIC_65535 80 if tn <= t { k = k + 1; scan = 1 } 81 } 82 } 83 var k1: i64 = k + 1 84 if k1 >= nk { k1 = k } 85 let w0a: i64 = h[koff+k*2] 86 let w1a: i64 = h[koff+k*2+1] 87 let w0b: i64 = h[koff+k1*2] 88 let w1b: i64 = h[koff+k1*2+1] 89 let t0: i64 = w0a & NP_MAGIC_65535 90 let t1: i64 = w0b & NP_MAGIC_65535 91 var u: i64 = 0 92 if t1 > t0 { u = (t - t0)*NP_MAGIC_1000/(t1 - t0) } 93 if u < 0 { u = 0 } 94 if u > NP_MAGIC_1000 { u = NP_MAGIC_1000 } 95 let ax: i64 = np_i16(w0a,16) 96 let ay: i64 = np_i16(w0a,32) 97 let az: i64 = np_i16(w0a,48) 98 let aq: i64 = np_i16(w1a,0) 99 var bx: i64 = np_i16(w0b,16) 100 var by: i64 = np_i16(w0b,32) 101 var bz: i64 = np_i16(w0b,48) 102 var bq: i64 = np_i16(w1b,0) 103 if ax*bx + ay*by + az*bz + aq*bq < 0 { bx=0-bx; by=0-by; bz=0-bz; bq=0-bq } 104 out7[0] = ax + (bx-ax)*u/NP_MAGIC_1000 105 out7[1] = ay + (by-ay)*u/NP_MAGIC_1000 106 out7[2] = az + (bz-az)*u/NP_MAGIC_1000 107 out7[3] = aq + (bq-aq)*u/NP_MAGIC_1000 108 nf_qnorm(out7) 109 let dxa: i64 = np_i16(w1a,16) 110 let dya: i64 = np_i16(w1a,32) 111 let dza: i64 = np_i16(w1a,48) 112 let dxb: i64 = np_i16(w1b,16) 113 let dyb: i64 = np_i16(w1b,32) 114 let dzb: i64 = np_i16(w1b,48) 115 out7[4] = dxa + (dxb-dxa)*u/NP_MAGIC_1000 116 out7[5] = dya + (dyb-dya)*u/NP_MAGIC_1000 117 out7[6] = dza + (dzb-dza)*u/NP_MAGIC_1000 118 return 0 119} 120 121func main(argc: i64, argv: *i64) -> i64 { 122 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 } 123 let verb: *u8 = argv[1] as *u8 124 let lp: *i64 = sys_mmap(16) as *i64 125 let b: *u8 = sys_map_file(argv[2] as *u8, lp) 126 let flen: i64 = lp[0] 127 if flen < 96 { npw("unreadable\n" as *u8); return 5 } 128 let h: *i64 = b as *i64 129 let swo: i64 = nxa_find(b, flen, nxa_tag4("SKEL" as *u8)) 130 if swo == 0 - 2 { npw("NXA-FUTURE-VERSION\n" as *u8); return 6 } 131 if swo == 0 - 3 { npw("NXA-CORRUPT skel\n" as *u8); return 5 } 132 if swo < 0 { npw("no SKEL\n" as *u8); return 4 } 133 let nj: i64 = h[swo] 134 let sk: *i64 = ((b as i64) + swo*8 + 8) as *i64 135 let awo: i64 = nxa_find(b, flen, nxa_tag4("ANIM" as *u8)) 136 if awo == 0 - 3 { npw("NXA-CORRUPT anim\n" as *u8); return 5 } 137 let pwo: i64 = nxa_find(b, flen, nxa_tag4("POSE" as *u8)) 138 if pwo == 0 - 3 { npw("NXA-CORRUPT pose\n" as *u8); return 5 } 139 140 if np_seq(verb, "list" as *u8) == 1 { 141 if pwo < 0 { npw("poses=0\n" as *u8); return 0 } 142 let npz: i64 = h[pwo] 143 npw("poses=" as *u8); npn(npz); npw("\n" as *u8) 144 var rp: i64 = pwo + 1 145 var pi: i64 = 0 146 while pi < npz { 147 npw(" pose id=" as *u8); npn(h[rp]) 148 npw(" entries=" as *u8); npn(h[rp+1]); npw("\n" as *u8) 149 rp = rp + 2 + h[rp+1]*8 150 pi = pi + 1 151 } 152 return 0 153 } 154 if np_seq(verb, "freeze" as *u8) == 0 { npw("unknown verb\n" as *u8); return 2 } 155 if argc < 6 { npw("usage: nx_nxa_pose freeze <in.nxa> <out.nxa> <pose_id> <t_ms|-1>\n" as *u8); return 2 } 156 if awo < 0 { npw("no ANIM\n" as *u8); return 4 } 157 let a4: *u8 = argv[4] as *u8 158 let a5: *u8 = argv[5] as *u8 159 let pid: i64 = np_atoi(a4) 160 var targ: i64 = np_atoi(a5) 161 162 // index channel-2 tracks by joint: koff/nk per joint (-1 = untracked) 163 let tko: *i64 = sys_mmap(nj*8 + 64) as *i64 164 let tkn: *i64 = sys_mmap(nj*8 + 64) as *i64 165 var j0: i64 = 0 166 while j0 < nj { tko[j0] = 0 - 1; tkn[j0] = 0; j0 = j0 + 1 } 167 let ntr: i64 = h[awo] 168 var rp2: i64 = awo + 1 169 var dur: i64 = 0 170 var ti: i64 = 0 171 var tracked: i64 = 0 172 while ti < ntr { 173 let jt: i64 = h[rp2] 174 let ch: i64 = h[rp2+1] 175 let nk: i64 = h[rp2+2] 176 if ch == 2 { 177 if jt >= 0 { if jt < nj { if nk > 0 { 178 tko[jt] = rp2 + 3 179 tkn[jt] = nk 180 tracked = tracked + 1 181 let lastt: i64 = h[rp2+3+(nk-1)*2] & NP_MAGIC_65535 182 if lastt > dur { dur = lastt } 183 } } } 184 rp2 = rp2 + 3 + nk*2 185 } 186 if ch == 0 { rp2 = rp2 + 3 + nk*4 } 187 if ch == 1 { rp2 = rp2 + 3 + nk*5 } 188 ti = ti + 1 189 } 190 if tracked == 0 { npw("no channel-2 tracks\n" as *u8); return 4 } 191 192 // structural picks from binds (no names in NXA): H = max bind z; 193 // root = nearest (0,0,.52H); feet = per side (bind x sign) the lowest-z TRACKED joint 194 var hmax: i64 = 0 195 var j1: i64 = 0 196 while j1 < nj { if sk[j1*8+3] > hmax { hmax = sk[j1*8+3] } j1 = j1 + 1 } 197 var rj: i64 = 0 - 1 198 var rsc: i64 = NP_MAGIC_1000000000 199 var fl: i64 = 0 - 1 200 var flz: i64 = NP_MAGIC_1000000000 201 var fr: i64 = 0 - 1 202 var frz: i64 = NP_MAGIC_1000000000 203 var j2: i64 = 0 204 while j2 < nj { 205 let bxj: i64 = sk[j2*8+1] 206 let bzj: i64 = sk[j2*8+3] 207 var ds: i64 = bzj - hmax*NP_MAGIC_52/NP_MAGIC_100 208 if ds < 0 { ds = 0-ds } 209 var axj: i64 = bxj 210 if axj < 0 { axj = 0-axj } 211 let sc: i64 = ds + 2*axj 212 if sc < rsc { rsc = sc; rj = j2 } 213 if tko[j2] >= 0 { 214 if bxj < 0-NP_MAGIC_1000 { if bzj < flz { flz = bzj; fl = j2 } } 215 if bxj > NP_MAGIC_1000 { if bzj < frz { frz = bzj; fr = j2 } } 216 } 217 j2 = j2 + 1 218 } 219 220 let ev: *i64 = sys_mmap(64) as *i64 221 let ev2: *i64 = sys_mmap(64) as *i64 222 // auto frame pick: minimize horizontal foot separation across the clip (33ms grid). 223 // dt lanes are MILLIMETERS (the bake divides by 100 to fit i16); binds are 0.01mm 224 // units -- scale dt x100 before mixing (the unit-mismatch class MEASURED 2026-07-29: 225 // consumers applying raw lanes rendered every baked translation at 1%) 226 if targ < 0 { 227 if fl < 0 { npw("no left foot track\n" as *u8); return 4 } 228 if fr < 0 { npw("no right foot track\n" as *u8); return 4 } 229 var bt: i64 = 0 230 var bsep: i64 = NP_MAGIC_1000000000*NP_MAGIC_1000 231 var t: i64 = 0 232 while t <= dur { 233 np_sample(h, tko[fl], tkn[fl], t, ev) 234 np_sample(h, tko[fr], tkn[fr], t, ev2) 235 let px: i64 = (sk[fl*8+1] + ev[4]*NP_MAGIC_100) - (sk[fr*8+1] + ev2[4]*NP_MAGIC_100) 236 let py: i64 = (sk[fl*8+2] + ev[5]*NP_MAGIC_100) - (sk[fr*8+2] + ev2[5]*NP_MAGIC_100) 237 let sep: i64 = px*px + py*py 238 if sep < bsep { bsep = sep; bt = t } 239 t = t + NP_MAGIC_33 240 } 241 targ = bt 242 npw("auto t_ms=" as *u8); npn(bt) 243 npw(" footsep_units=" as *u8); npn(nf_isqrt(bsep)); npw("\n" as *u8) 244 } 245 246 // sample every tracked joint at targ; POSE entries store dt in VERT units (mm lanes 247 // x100) so players compose them with binds directly, no scale knowledge needed 248 let pq: *i64 = sys_mmap(nj*32 + 64) as *i64 249 let pd: *i64 = sys_mmap(nj*24 + 64) as *i64 250 var j3: i64 = 0 251 while j3 < nj { 252 if tko[j3] >= 0 { 253 np_sample(h, tko[j3], tkn[j3], targ, ev) 254 pq[j3*4] = ev[0] 255 pq[j3*4+1] = ev[1] 256 pq[j3*4+2] = ev[2] 257 pq[j3*4+3] = ev[3] 258 pd[j3*3] = ev[4]*NP_MAGIC_100 259 pd[j3*3+1] = ev[5]*NP_MAGIC_100 260 pd[j3*3+2] = ev[6]*NP_MAGIC_100 261 } 262 j3 = j3 + 1 263 } 264 // root horizontal motion removal (same convention the runtime applies to the clip) 265 var rdx: i64 = 0 266 var rdy: i64 = 0 267 if rj >= 0 { if tko[rj] >= 0 { rdx = pd[rj*3]; rdy = pd[rj*3+1] } } 268 var j4: i64 = 0 269 while j4 < nj { 270 if tko[j4] >= 0 { pd[j4*3] = pd[j4*3] - rdx; pd[j4*3+1] = pd[j4*3+1] - rdy } 271 j4 = j4 + 1 272 } 273 274 // POSE emission goes through the estate's ONE POSE-section writer (nx_nxa_posewrite_lib, 2026-09-05): kept poses 275 // first, the frozen one LAST, the same id REPLACES, TOC rebuilt. This organ used to carry its own inline copy of 276 // that writer beside nx_nxa_retarget's -- the duplicate-ruler defect; now there is exactly one. 277 let jl: *i64 = sys_mmap(nj*8 + 64) as *i64 278 var ntr: i64 = 0 279 var j5: i64 = 0 280 while j5 < nj { 281 if tko[j5] >= 0 { jl[ntr] = j5; ntr = ntr + 1 } 282 j5 = j5 + 1 283 } 284 // a partition that does not sum is a leak: the joint list must hold exactly the tracked count 285 if ntr != tracked { npw("NXA-POSE-REFUSE tracked joint list does not sum to the tracked count\n" as *u8); return 5 } 286 let pst: *i64 = sys_mmap(64) as *i64 287 let wrote: i64 = npw_write(b, flen, pid, jl, pq, pd, ntr, argv[3] as *u8, pst) 288 if wrote < 0 { npw("open out failed posewrite_code=" as *u8); npn(wrote); npw("\n" as *u8); return 9 } 289 npw("POSE written id=" as *u8); npn(pid) 290 npw(" t_ms=" as *u8); npn(targ) 291 npw(" entries=" as *u8); npn(ntr) 292 npw(" kept=" as *u8); npn(pst[NPW_ST_KEPT]) 293 npw(" sections=" as *u8); npn(pst[NPW_ST_SECTIONS]) 294 npw(" bytes=" as *u8); npn(wrote); npw("\n" as *u8) 295 return 0 296}