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}