nx_nxa_skin.nx source
↩ module page · 1057 lines · 43728 B
1// nx_nxa_skin.nx -- RIG IMPORTER: fills the NXA's SKEL + SKIN sections from a binary FBX.
2// Walks the FBX id-graph (Model/LimbNode joints, Deformer Clusters with Indexes/Weights/
3// TransformLink bind matrices, the Connections table) and emits a 5-section NXA:
4// VERT/TRIS/CLUS copied from the mesh import, SKEL = joints [parent][t mm][quat q12=identity]
5// (delta-LBS convention: animation is applied RELATIVE to bind, so identity bind quats are
6// exact), SKIN = per-vertex top-4 joints + q12 weights normalized to sum 4096.
7// usage: nx_nxa_skin <in.fbx> <in.nxa> <out.nxa>
8// license_tier: ORIGINAL
9import "nx_syscalls.nx"
10import "nx_zlib_wrap.nx"
11import "nx_nxa.nx"
12import "nx_nxa_fk.nx"
13import "nx_fbx_core.nx"
14// G_USF: FBX GlobalSettings UnitScaleFactor in MICRO units (1000000 = factor 1.0), parsed once
15// in main via fbxc_unit_scale_micro and applied at every LENGTH site (Lcl Translation, the
16// TransformLink bind translations) -- rotations and weights are scale-free. Slot sits in the
17// spare G words past G_CLOV, same convention as the other G_ offsets.
18const G_USF: i64 = 357091
19const G_MAGIC_20000: i64 = 20000
20const G_MAGIC_2047: i64 = 2047
21const G_MAGIC_1048575: i64 = 1048575
22const G_MAGIC_4503599627370496: i64 = 4503599627370496
23const G_MAGIC_4090: i64 = 4090
24const G_MAGIC_99990: i64 = 99990
25const G_MAGIC_4096: i64 = 4096
26const G_MAGIC_7500: i64 = 7500
27const G_MAGIC_500000: i64 = 500000
28const G_MAGIC_4611686018427387903: i64 = 4611686018427387903
29
30// G layout (word offsets): 0=mcount 1=ccount 2=conncount 3=bestVertAlen 4=bestGeomId 5=skincount
31const G_MO: i64 = 16 // model ids x4096
32const G_LO: i64 = 4112 // limb flags x4096
33// CLUSTER TABLES x2048 (2026-08-23): the x512 era saturated SILENTLY on every multi-geometry
34// Fab donor -- all four measured donors reported exactly clusters=510 (the cap, not the count),
35// and every cluster past the cap dropped its authored weights without a word. 16 skins x ~110
36// joints can approach 2000 clusters; 2048 covers that and the count is now honest again.
37// CLUSTER TABLES x8192 (2026-08-23, second raise same day): x2048 ALSO saturated -- three
38// donors again read exactly clusters=2046, the cap wearing the shape of a measurement. A Fab
39// donor's cluster count is skins x joints (30 x 108 = 3240; toon 8 x 370 = 2960), so 8192
40// covers the class with headroom -- AND the walker now COUNTS overflow at G_CLOV and the
41// receipt prints it, so a future saturation announces itself instead of impersonating a count.
42const G_CID: i64 = 8208 // cluster ids x8192
43const G_CIX: i64 = 16400 // cluster Indexes meta (off,alen,enc,clen) x8192x4
44const G_CW: i64 = 49168 // cluster Weights meta x8192x4
45const G_CT: i64 = 81936 // cluster TransformLink meta x8192x4
46const G_CONN: i64 = 114704 // connection pairs (src,dst) x100000x2
47const G_SID: i64 = 314704 // Skin deformer ids x64
48const G_PR: i64 = 316000 // per-model PreRotation millideg x3
49const G_LR: i64 = 328288 // per-model Lcl Rotation default millideg x3
50const G_LT: i64 = 340576 // per-model Lcl Translation default units x3
51const G_PF: i64 = 352864 // per-model flags: 1=pre 2=lclrot 4=lclt
52const G_WORDS: i64 = 358000
53// MULTI-GEOMETRY (2026-08-23): table of every Vertices-array encounter in document order --
54// (geometry id, array len) pairs -- mirroring nx_fbx_import's merge order, so per-geometry
55// vertex bases in the merged in.nxa are prefix sums over this table. Count at G_GT, pairs after.
56// Cap 64 geometries; overflow COUNTED at G_GTOV and printed, never silent (a dropped geometry
57// would silently unskin its part). Table lives inside the G arena tail: 356960+129 < 358000.
58const G_GT: i64 = 356960
59const G_GTCAP: i64 = 64
60const G_GTOV: i64 = 357089
61// cluster-table admission cap: table is x8192, two slots of slack kept exactly as the x512 era
62// kept (512-2=510), so the walker can never write the final pair out of bounds mid-node.
63const G_CLUSTER_CAP: i64 = 8190
64// cluster overflow counter: clusters seen past the cap. Printed on the receipt -- a saturated
65// table must say so, never impersonate a count (this line exists because it happened twice today).
66const G_CLOV: i64 = 357090
67
68func skw(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 }
69func skn(v: i64) -> i64 {
70 let t: *u8 = sys_mmap(32) as *u8
71 var m: i64 = v; var w: i64 = 0
72 if m<0 { t[w]=45 as u8; w=w+1; m=0-m }
73 if m==0 { t[w]=48 as u8; sys_write(1,t,w+1); return 0 }
74 let d: *u8 = sys_mmap(32) as *u8
75 var k: i64=0
76 while m>0 { d[k]=(48+(m%10)) as u8; m=m/10; k=k+1 }
77 var j: i64=0
78 while j<k { t[w]=d[k-1-j]; w=w+1; j=j+1 }
79 sys_write(1,t,w); return 0
80}
81func sk_u32(b: *u8, o: i64) -> i64 {
82 return ((b[o] & 0xff) as i64) | (((b[o+1] & 0xff) as i64) << 8)
83 | (((b[o+2] & 0xff) as i64) << 16) | (((b[o+3] & 0xff) as i64) << 24)
84}
85func sk_u64(b: *u8, o: i64) -> i64 { return sk_u32(b, o) | (sk_u32(b, o + 4) << 32) }
86// IEEE754 double -> value x scale (scale=1000 for mm, 4096 for q12 weights). DRY debt vs
87// nx_fbx_measure's copy -- extract to a shared nx_f64.nx when the third user appears.
88func sk_f64(b: *u8, o: i64, scale: i64) -> i64 {
89 let lo: i64 = sk_u32(b, o)
90 let hi: i64 = sk_u32(b, o + 4)
91 let sign: i64 = (hi >> 31) & 1
92 let expo: i64 = (hi >> 20) & G_MAGIC_2047
93 if expo == 0 { return 0 }
94 var mant: i64 = ((hi & G_MAGIC_1048575) << 32) | lo
95 mant = mant | G_MAGIC_4503599627370496
96 let sh: i64 = expo - 1023
97 var v: i64 = 0
98 if sh >= 52 { v = mant * scale * (1 << (sh - 52)) }
99 if sh < 52 {
100 let rs: i64 = 52 - sh
101 if rs > 62 { return 0 }
102 v = (mant * scale) >> rs
103 }
104 if sign == 1 { return 0 - v }
105 return v
106}
107
108// recursive walk; ctx = current Cluster index (child arrays attach to it) or -1;
109// gctx = current Geometry node id (Vertices children identify OUR mesh's geometry) or 0
110func sk_walk(b: *u8, flen: i64, off0: i64, big: i64, G: *i64, ctx: i64, gctx: i64, mctx: i64) -> i64 {
111 let off: i64 = off0
112 var endo: i64 = 0
113 var nprops: i64 = 0
114 var plen: i64 = 0
115 var nlen: i64 = 0
116 var p: i64 = 0
117 if big == 1 {
118 endo = sk_u64(b, off)
119 nprops = sk_u64(b, off + 8)
120 plen = sk_u64(b, off + 16)
121 nlen = (b[off + 24] & 0xff) as i64
122 p = off + 25
123 }
124 if big == 0 {
125 endo = sk_u32(b, off)
126 nprops = sk_u32(b, off + 4)
127 plen = sk_u32(b, off + 8)
128 nlen = (b[off + 12] & 0xff) as i64
129 p = off + 13
130 }
131 if endo == 0 { return off }
132 if endo > flen { return flen }
133 var kind: i64 = 0
134 if nlen == 5 { if (b[p]&0xff)==77 { if (b[p+4]&0xff)==108 { kind = 1 } } } // Model
135 if nlen == 8 {
136 if (b[p]&0xff)==68 { if (b[p+7]&0xff)==114 { kind = 2 } } // Deformer
137 if (b[p]&0xff)==71 { if (b[p+7]&0xff)==121 { kind = 7 } } // Geometry
138 if (b[p]&0xff)==86 { if (b[p+7]&0xff)==115 { kind = 8 } } // Vertices
139 }
140 if nlen == 7 {
141 if (b[p]&0xff)==73 { if (b[p+6]&0xff)==115 { kind = 3 } } // Indexes
142 if (b[p]&0xff)==87 { if (b[p+6]&0xff)==115 { kind = 4 } } // Weights
143 }
144 if nlen == 13 { if (b[p]&0xff)==84 { if (b[p+12]&0xff)==107 { kind = 5 } } } // TransformLink
145 if nlen == 1 { if (b[p]&0xff)==67 { kind = 6 } } // C
146 if nlen == 1 { if (b[p]&0xff)==80 { kind = 9 } } // P (Properties70)
147 p = p + nlen
148 let pstart: i64 = p
149 var id1: i64 = 0
150 var id2: i64 = 0
151 var lseen: i64 = 0
152 var s1l: i64 = 0
153 var s1o: i64 = 0
154 var s2l: i64 = 0
155 var s2o: i64 = 0
156 var sseen: i64 = 0
157 var dseen: i64 = 0
158 var dv1: i64 = 0
159 var dv2: i64 = 0
160 var dv3: i64 = 0
161 var arro: i64 = 0
162 var arrl: i64 = 0
163 var arre: i64 = 0
164 var arrc: i64 = 0
165 var pi: i64 = 0
166 var stop: i64 = 0
167 while pi < nprops {
168 if stop == 0 {
169 let t: i64 = (b[p] & 0xff) as i64
170 p = p + 1
171 var adv: i64 = 0 - 1
172 if t == 83 {
173 let l: i64 = sk_u32(b, p)
174 sseen = sseen + 1
175 if sseen == 1 { s1o = p + 4; s1l = l }
176 if sseen == 2 { s2o = p + 4; s2l = l }
177 adv = 4 + l
178 }
179 if t == 82 { adv = 4 + sk_u32(b, p) }
180 if t == 89 { adv = 2 }
181 if t == 67 { adv = 1 }
182 if t == 73 { adv = 4 }
183 if t == 70 { adv = 4 }
184 if t == 68 {
185 if dseen == 0 { dv1 = p }
186 if dseen == 1 { dv2 = p }
187 if dseen == 2 { dv3 = p }
188 dseen = dseen + 1
189 adv = 8
190 }
191 if t == 76 {
192 lseen = lseen + 1
193 if lseen == 1 { id1 = sk_u64(b, p) }
194 if lseen == 2 { id2 = sk_u64(b, p) }
195 adv = 8
196 }
197 var isarr: i64 = 0
198 if t == 100 { isarr = 1 }
199 if t == 102 { isarr = 1 }
200 if t == 108 { isarr = 1 }
201 if t == 105 { isarr = 1 }
202 if t == 98 { isarr = 1 }
203 if isarr == 1 {
204 arrl = sk_u32(b, p)
205 arre = sk_u32(b, p + 4)
206 arrc = sk_u32(b, p + 8)
207 arro = p + 12
208 adv = 12 + arrc
209 }
210 if adv < 0 { stop = 1 }
211 if adv >= 0 { p = p + adv }
212 }
213 pi = pi + 1
214 }
215 var childctx: i64 = ctx
216 var childg: i64 = gctx
217 var childm: i64 = mctx
218 if kind == 1 { if G[0] < G_MAGIC_4090 {
219 G[G_MO + G[0]] = id1
220 var lf: i64 = 0
221 if s2l == 8 { if (b[s2o]&0xff)==76 { if (b[s2o+7]&0xff)==101 { lf = 1 } } }
222 G[G_LO + G[0]] = lf
223 childm = G[0]
224 G[0] = G[0] + 1
225 } }
226 if kind == 2 { if s2l == 7 {
227 var ctf: i64 = 0
228 if G[1] < G_CLUSTER_CAP { ctf = 1 }
229 if ctf == 1 {
230 G[G_CID + G[1]] = id1
231 childctx = G[1]
232 G[1] = G[1] + 1
233 }
234 if ctf == 0 { G[G_CLOV] = G[G_CLOV] + 1 }
235 } }
236 if kind == 2 { if s2l == 4 { if G[5] < 60 {
237 G[G_SID + G[5]] = id1
238 G[5] = G[5] + 1
239 } } }
240 if kind == 7 { childg = id1 }
241 if kind == 8 {
242 if arrl > G[3] { G[3] = arrl; G[4] = gctx }
243 var gtf: i64 = 0
244 if G[G_GT] < G_GTCAP { gtf = 1 }
245 if gtf == 1 {
246 G[G_GT + 1 + G[G_GT]*2] = gctx
247 G[G_GT + 2 + G[G_GT]*2] = arrl
248 G[G_GT] = G[G_GT] + 1
249 }
250 if gtf == 0 { G[G_GTOV] = G[G_GTOV] + 1 }
251 }
252 if kind == 3 { if ctx >= 0 {
253 G[G_CIX + ctx*4] = arro
254 G[G_CIX + ctx*4 + 1] = arrl
255 G[G_CIX + ctx*4 + 2] = arre
256 G[G_CIX + ctx*4 + 3] = arrc
257 } }
258 if kind == 4 { if ctx >= 0 {
259 G[G_CW + ctx*4] = arro
260 G[G_CW + ctx*4 + 1] = arrl
261 G[G_CW + ctx*4 + 2] = arre
262 G[G_CW + ctx*4 + 3] = arrc
263 } }
264 if kind == 5 { if ctx >= 0 {
265 G[G_CT + ctx*4] = arro
266 G[G_CT + ctx*4 + 1] = arrl
267 G[G_CT + ctx*4 + 2] = arre
268 G[G_CT + ctx*4 + 3] = arrc
269 } }
270 if kind == 6 { if G[2] < G_MAGIC_99990 {
271 G[G_CONN + G[2]*2] = id1
272 G[G_CONN + G[2]*2 + 1] = id2
273 G[2] = G[2] + 1
274 } }
275 // Properties70 P entry under a Model: name = FIRST S prop; vector value = 3 D props.
276 // Matched by (len, first, last): PreRotation(11 P..n), Lcl Rotation(12 L..n),
277 // Lcl Translation(15 L..n). Angles degrees->millideg, translation units x1000.
278 if kind == 9 { if mctx >= 0 { if dseen >= 3 {
279 var pk: i64 = 0
280 if s1l == 11 { if (b[s1o]&0xff)==80 { if (b[s1o+10]&0xff)==110 { pk = 1 } } }
281 if s1l == 12 { if (b[s1o]&0xff)==76 { if (b[s1o+11]&0xff)==110 { pk = 2 } } }
282 if s1l == 15 { if (b[s1o]&0xff)==76 { if (b[s1o+14]&0xff)==110 { pk = 3 } } }
283 if pk == 1 {
284 G[G_PR + mctx*3] = sk_f64(b, dv1, 1000)
285 G[G_PR + mctx*3 + 1] = sk_f64(b, dv2, 1000)
286 G[G_PR + mctx*3 + 2] = sk_f64(b, dv3, 1000)
287 G[G_PF + mctx] = G[G_PF + mctx] | 1
288 }
289 if pk == 2 {
290 G[G_LR + mctx*3] = sk_f64(b, dv1, 1000)
291 G[G_LR + mctx*3 + 1] = sk_f64(b, dv2, 1000)
292 G[G_LR + mctx*3 + 2] = sk_f64(b, dv3, 1000)
293 G[G_PF + mctx] = G[G_PF + mctx] | 2
294 }
295 if pk == 3 {
296 // LENGTH site: Lcl Translation carries the unit factor (identity at 1.0).
297 G[G_LT + mctx*3] = sk_f64(b, dv1, 1000) * G[G_USF] / FBXC_MICRO
298 G[G_LT + mctx*3 + 1] = sk_f64(b, dv2, 1000) * G[G_USF] / FBXC_MICRO
299 G[G_LT + mctx*3 + 2] = sk_f64(b, dv3, 1000) * G[G_USF] / FBXC_MICRO
300 G[G_PF + mctx] = G[G_PF + mctx] | 4
301 }
302 } } }
303 p = pstart + plen
304 var sent: i64 = 13
305 if big == 1 { sent = 25 }
306 while p < endo - sent {
307 p = sk_walk(b, flen, p, big, G, childctx, childg, childm)
308 if p >= flen { return flen }
309 }
310 return endo
311}
312
313// inflate-or-raw helper for FBX arrays
314func sk_arr(b: *u8, off: i64, alen: i64, enc: i64, clen: i64, esz: i64) -> *u8 {
315 if enc == 0 { return ((b as i64) + off) as *u8 }
316 let zr: *NxZlibResult = nx_zlib_inflate(((b as i64) + off) as *u8, clen, alen*esz + G_MAGIC_4096)
317 if zr.error_code != 0 { return 0 as *u8 }
318 return zr.output_data
319}
320
321func main(argc: i64, argv: *i64) -> i64 {
322 if argc < 4 { skw("usage: nx_nxa_skin <in.fbx> <in.nxa> <out.nxa>\n" as *u8); return 2 }
323 let lpf: *i64 = sys_mmap(16) as *i64
324 let fb: *u8 = sys_map_file(argv[1] as *u8, lpf)
325 let flen: i64 = lpf[0]
326 if flen < 64 { skw("fbx unreadable\n" as *u8); return 3 }
327 let ver: i64 = sk_u32(fb, 23)
328 var big: i64 = 0
329 if ver >= G_MAGIC_7500 { big = 1 }
330 let G: *i64 = sys_mmap(G_WORDS*8) as *i64
331 // unit scale: parse once, announce, stash for every length site (see G_USF decl). -2 refuses
332 // (forged/nonpositive factor); -1 (absent) assumes the FBX template default 1.0, announced.
333 var usf9: i64 = fbxc_unit_scale_micro(fb, flen)
334 if usf9 == 0 - 2 { skw("NXA-SKIN REFUSED: UnitScaleFactor present but not a positive double\n" as *u8); return 3 }
335 if usf9 == 0 - 1 {
336 skw("unit_scale=ABSENT assumed=1000000 micro (FBX template default)\n" as *u8)
337 usf9 = FBXC_MICRO
338 } else {
339 skw("unit_scale_micro=" as *u8); skn(usf9); skw(" source=GlobalSettings\n" as *u8)
340 }
341 G[G_USF] = usf9
342 var pos: i64 = 27
343 var guard: i64 = 0
344 while pos < flen - 200 {
345 if guard > G_MAGIC_500000 { break }
346 let e: i64 = sk_walk(fb, flen, pos, big, G, 0 - 1, 0, 0 - 1)
347 if e <= pos { break }
348 pos = e
349 guard = guard + 1
350 }
351 let mc: i64 = G[0]
352 let cc: i64 = G[1]
353 let nc: i64 = G[2]
354 let gid: i64 = G[4]
355 skw("models=" as *u8); skn(mc)
356 skw(" clusters=" as *u8); skn(cc)
357 skw(" conns=" as *u8); skn(nc)
358 skw(" skins=" as *u8); skn(G[5])
359 skw(" cluster_overflow=" as *u8); skn(G[G_CLOV]); skw("\n" as *u8)
360 // MULTI-GEOMETRY (2026-08-23): a Skin is valid iff it connects to ANY geometry in the walk
361 // table, and we RECORD WHICH -- each cluster's Indexes are local to its own geometry's
362 // vertex space, so the stamping loop offsets by that geometry's base in the merged mesh.
363 // (The old rule -- only the LARGEST geometry's skin counts -- was the single-geometry era:
364 // on a 16-part Fab donor the largest part need not be the skinned body, and it filtered
365 // every cluster out: measured valid_clusters=0 skinned=0 on dark_witch.)
366 let sval: *i64 = sys_mmap(64*8 + 64) as *i64
367 let sgeo: *i64 = sys_mmap(64*8 + 64) as *i64
368 var sv0: i64 = 0
369 while sv0 < G[5] {
370 sval[sv0] = 0
371 sgeo[sv0] = 0 - 1
372 var k9: i64 = 0
373 while k9 < nc {
374 if G[G_CONN + k9*2] == G[G_SID + sv0] {
375 let dg9: i64 = G[G_CONN + k9*2 + 1]
376 var g9: i64 = 0
377 while g9 < G[G_GT] {
378 if G[G_GT + 1 + g9*2] == dg9 {
379 sval[sv0] = 1
380 sgeo[sv0] = g9
381 g9 = G[G_GT]
382 k9 = nc
383 }
384 g9 = g9 + 1
385 }
386 }
387 k9 = k9 + 1
388 }
389 sv0 = sv0 + 1
390 }
391 // cluster validity: cluster -> (valid Skin), RECORDING the skin's geometry for base offsets
392 let cval: *i64 = sys_mmap(cc*8 + 64) as *i64
393 let cgeo: *i64 = sys_mmap(cc*8 + 64) as *i64
394 var cv0: i64 = 0
395 while cv0 < cc {
396 cval[cv0] = 0
397 cgeo[cv0] = 0 - 1
398 var k8: i64 = 0
399 while k8 < nc {
400 if G[G_CONN + k8*2] == G[G_CID + cv0] {
401 let d8: i64 = G[G_CONN + k8*2 + 1]
402 var s8: i64 = 0
403 while s8 < G[5] {
404 if G[G_SID + s8] == d8 { if sval[s8] == 1 {
405 cval[cv0] = 1
406 cgeo[cv0] = sgeo[s8]
407 s8 = 64
408 k8 = nc
409 } }
410 s8 = s8 + 1
411 }
412 }
413 k8 = k8 + 1
414 }
415 cv0 = cv0 + 1
416 }
417 // joints = limb models in encounter order
418 let jmap: *i64 = sys_mmap(mc*8 + 64) as *i64
419 var nj: i64 = 0
420 var m0: i64 = 0
421 while m0 < mc {
422 jmap[m0] = 0 - 1
423 if G[G_LO + m0] == 1 { jmap[m0] = nj; nj = nj + 1 }
424 m0 = m0 + 1
425 }
426 // parent per joint via Connections (child -> parent)
427 let jpar: *i64 = sys_mmap(nj*8 + 64) as *i64
428 var j0: i64 = 0
429 while j0 < nj { jpar[j0] = 0 - 1; j0 = j0 + 1 }
430 var m1: i64 = 0
431 while m1 < mc {
432 if jmap[m1] >= 0 {
433 let myid: i64 = G[G_MO + m1]
434 var k: i64 = 0
435 while k < nc {
436 if G[G_CONN + k*2] == myid {
437 let dst: i64 = G[G_CONN + k*2 + 1]
438 var m2: i64 = 0
439 while m2 < mc {
440 if G[G_MO + m2] == dst { if jmap[m2] >= 0 {
441 jpar[jmap[m1]] = jmap[m2]
442 m2 = mc
443 k = nc
444 } }
445 m2 = m2 + 1
446 }
447 }
448 k = k + 1
449 }
450 }
451 m1 = m1 + 1
452 }
453 // cluster -> joint (Model(limb) --conn--> Cluster) + bind translation from TransformLink
454 let cjoint: *i64 = sys_mmap(cc*8 + 64) as *i64
455 let jbind: *i64 = sys_mmap(nj*24 + 64) as *i64
456 let jreal: *i64 = sys_mmap(nj*8 + 64) as *i64
457 var c0: i64 = 0
458 while c0 < cc { cjoint[c0] = 0 - 1; c0 = c0 + 1 }
459 var c1: i64 = 0
460 while c1 < cc {
461 let cid: i64 = G[G_CID + c1]
462 var k2: i64 = 0
463 while k2 < nc {
464 if G[G_CONN + k2*2 + 1] == cid {
465 let src: i64 = G[G_CONN + k2*2]
466 var m3: i64 = 0
467 while m3 < mc {
468 if G[G_MO + m3] == src { if jmap[m3] >= 0 {
469 cjoint[c1] = jmap[m3]
470 m3 = mc
471 k2 = nc
472 } }
473 m3 = m3 + 1
474 }
475 }
476 k2 = k2 + 1
477 }
478 // bind translation: TransformLink col-major elems 12,13,14 (joint->world at bind)
479 if cjoint[c1] >= 0 { if G[G_CT + c1*4 + 1] == 16 {
480 let tl: *u8 = sk_arr(fb, G[G_CT + c1*4], 16, G[G_CT + c1*4 + 2], G[G_CT + c1*4 + 3], 8)
481 if (tl as i64) != 0 {
482 let jj: i64 = cjoint[c1]
483 // LENGTH site: TransformLink bind translation carries the unit factor (identity
484 // at 1.0) -- must scale WITH the mesh or the rig detaches from the skin.
485 jbind[jj*3] = sk_f64(tl, 12*8, 1000) * G[G_USF] / FBXC_MICRO
486 jbind[jj*3+1] = sk_f64(tl, 13*8, 1000) * G[G_USF] / FBXC_MICRO
487 jbind[jj*3+2] = sk_f64(tl, 14*8, 1000) * G[G_USF] / FBXC_MICRO
488 jreal[jj] = 1
489 }
490 } }
491 c1 = c1 + 1
492 }
493 // v2: STATIC FK -> TRUE bind positions for ALL joints, replacing the old placeholder
494 // propagation (which parked unclustered joints on an ancestor; auto-skin against those
495 // degenerate segments mis-bound flesh -- shoulder shards under real per-joint mocap).
496 // Local = PreRotation x LclRotation chained down parents; non-limb ancestor Models
497 // compose into a per-root prefix. VALIDATED against the real cluster binds before use.
498 let mpar: *i64 = sys_mmap(mc*8 + 64) as *i64
499 var mp0: i64 = 0
500 while mp0 < mc { mpar[mp0] = 0 - 1; mp0 = mp0 + 1 }
501 var mp1: i64 = 0
502 while mp1 < mc {
503 let myid2: i64 = G[G_MO + mp1]
504 var kp: i64 = 0
505 while kp < nc {
506 if G[G_CONN + kp*2] == myid2 {
507 let dstp: i64 = G[G_CONN + kp*2 + 1]
508 var mp2: i64 = 0
509 while mp2 < mc {
510 if G[G_MO + mp2] == dstp { mpar[mp1] = mp2; mp2 = mc; kp = nc }
511 mp2 = mp2 + 1
512 }
513 }
514 kp = kp + 1
515 }
516 mp1 = mp1 + 1
517 }
518 let jmi: *i64 = sys_mmap(G_MAGIC_4096*8 + 64) as *i64
519 var mj0: i64 = 0
520 while mj0 < mc {
521 if jmap[mj0] >= 0 { jmi[jmap[mj0]] = mj0 }
522 mj0 = mj0 + 1
523 }
524 let scr: *i64 = sys_mmap(256) as *i64
525 let pq: *i64 = sys_mmap(nj*32 + 64) as *i64
526 let sq: *i64 = sys_mmap(nj*32 + 64) as *i64
527 var jq0: i64 = 0
528 while jq0 < nj {
529 let mi: i64 = jmi[jq0]
530 nf_eul2q(G[G_PR + mi*3], G[G_PR + mi*3 + 1], G[G_PR + mi*3 + 2], ((pq as i64) + jq0*32) as *i64, scr)
531 nf_eul2q(G[G_LR + mi*3], G[G_LR + mi*3 + 1], G[G_LR + mi*3 + 2], ((sq as i64) + jq0*32) as *i64, scr)
532 jq0 = jq0 + 1
533 }
534 let topo: *i64 = sys_mmap(nj*8 + 64) as *i64
535 let done: *i64 = sys_mmap(nj*8 + 64) as *i64
536 var tn: i64 = 0
537 var pass2: i64 = 0
538 while pass2 < nj {
539 if tn < nj {
540 var jp0: i64 = 0
541 while jp0 < nj {
542 if done[jp0] == 0 {
543 var ok: i64 = 0
544 if jpar[jp0] < 0 { ok = 1 }
545 if jpar[jp0] >= 0 { if done[jpar[jp0]] == 1 { ok = 1 } }
546 if ok == 1 { topo[tn] = jp0; tn = tn + 1; done[jp0] = 1 }
547 }
548 jp0 = jp0 + 1
549 }
550 }
551 pass2 = pass2 + 1
552 }
553 if tn != nj { skw("PARENT-CYCLE -- refusing\n" as *u8); return 7 }
554 let gqs: *i64 = sys_mmap(nj*32 + 64) as *i64
555 let gts: *i64 = sys_mmap(nj*24 + 64) as *i64
556 let lq: *i64 = sys_mmap(64) as *i64
557 let rv: *i64 = sys_mmap(64) as *i64
558 let prefq: *i64 = sys_mmap(nj*32 + 64) as *i64
559 let preft: *i64 = sys_mmap(nj*24 + 64) as *i64
560 let anc: *i64 = sys_mmap(32*8 + 64) as *i64
561 let aqq: *i64 = sys_mmap(64) as *i64
562 let bqq: *i64 = sys_mmap(64) as *i64
563 let tq2: *i64 = sys_mmap(64) as *i64
564 var jr0: i64 = 0
565 while jr0 < nj {
566 prefq[jr0*4] = 0
567 prefq[jr0*4+1] = 0
568 prefq[jr0*4+2] = 0
569 prefq[jr0*4+3] = G_MAGIC_4096
570 if jpar[jr0] < 0 {
571 var nanc: i64 = 0
572 var cm: i64 = mpar[jmi[jr0]]
573 var hops: i64 = 0
574 while cm >= 0 {
575 if nanc < 30 { anc[nanc] = cm; nanc = nanc + 1 }
576 cm = mpar[cm]
577 hops = hops + 1
578 if hops > 30 { cm = 0 - 1 }
579 }
580 var pi2: i64 = nanc - 1
581 while pi2 >= 0 {
582 let am: i64 = anc[pi2]
583 nf_eul2q(G[G_PR + am*3], G[G_PR + am*3 + 1], G[G_PR + am*3 + 2], aqq, scr)
584 nf_eul2q(G[G_LR + am*3], G[G_LR + am*3 + 1], G[G_LR + am*3 + 2], bqq, scr)
585 nf_qmul(aqq, bqq, tq2)
586 nf_qnorm(tq2)
587 nf_qrotv(((prefq as i64) + jr0*32) as *i64, G[G_LT + am*3], G[G_LT + am*3 + 1], G[G_LT + am*3 + 2], rv, scr)
588 preft[jr0*3] = preft[jr0*3] + rv[0]
589 preft[jr0*3+1] = preft[jr0*3+1] + rv[1]
590 preft[jr0*3+2] = preft[jr0*3+2] + rv[2]
591 nf_qmul(((prefq as i64) + jr0*32) as *i64, tq2, aqq)
592 nf_qnorm(aqq)
593 prefq[jr0*4] = aqq[0]
594 prefq[jr0*4+1] = aqq[1]
595 prefq[jr0*4+2] = aqq[2]
596 prefq[jr0*4+3] = aqq[3]
597 pi2 = pi2 - 1
598 }
599 }
600 jr0 = jr0 + 1
601 }
602 var ti9: i64 = 0
603 while ti9 < nj {
604 let j9: i64 = topo[ti9]
605 let mi9: i64 = jmi[j9]
606 nf_qmul(((pq as i64) + j9*32) as *i64, ((sq as i64) + j9*32) as *i64, lq)
607 nf_qnorm(lq)
608 let pj9: i64 = jpar[j9]
609 if pj9 < 0 {
610 nf_qmul(((prefq as i64) + j9*32) as *i64, lq, ((gqs as i64) + j9*32) as *i64)
611 nf_qnorm(((gqs as i64) + j9*32) as *i64)
612 nf_qrotv(((prefq as i64) + j9*32) as *i64, G[G_LT + mi9*3], G[G_LT + mi9*3 + 1], G[G_LT + mi9*3 + 2], rv, scr)
613 gts[j9*3] = preft[j9*3] + rv[0]
614 gts[j9*3+1] = preft[j9*3+1] + rv[1]
615 gts[j9*3+2] = preft[j9*3+2] + rv[2]
616 }
617 if pj9 >= 0 {
618 nf_qmul(((gqs as i64) + pj9*32) as *i64, lq, ((gqs as i64) + j9*32) as *i64)
619 nf_qnorm(((gqs as i64) + j9*32) as *i64)
620 nf_qrotv(((gqs as i64) + pj9*32) as *i64, G[G_LT + mi9*3], G[G_LT + mi9*3 + 1], G[G_LT + mi9*3 + 2], rv, scr)
621 gts[j9*3] = gts[pj9*3] + rv[0]
622 gts[j9*3+1] = gts[pj9*3+1] + rv[1]
623 gts[j9*3+2] = gts[pj9*3+2] + rv[2]
624 }
625 ti9 = ti9 + 1
626 }
627 var fkmax: i64 = 0
628 var fkvb: i64 = 0
629 var jv0: i64 = 0
630 while jv0 < nj {
631 if jreal[jv0] == 1 {
632 fkvb = fkvb + 1
633 var a9: i64 = 0
634 while a9 < 3 {
635 var d9: i64 = gts[jv0*3+a9] - jbind[jv0*3+a9]
636 if d9 < 0 { d9 = 0 - d9 }
637 if d9 > fkmax { fkmax = d9 }
638 a9 = a9 + 1
639 }
640 }
641 jv0 = jv0 + 1
642 }
643 skw("fk_validated=" as *u8); skn(fkvb)
644 skw(" fk_maxerr_units=" as *u8); skn(fkmax); skw("\n" as *u8)
645 if fkvb < 8 { skw("TOO-FEW-REAL-BINDS -- refusing FK bind replacement\n" as *u8); return 7 }
646 // FK-vs-cluster divergence DOWNGRADED from refusal to announced-keep (2026-08-23): TransformLink
647 // cluster binds ARE the rig's own declared bind pose; the FK recomposition is a cross-check whose
648 // pre/post-rotation model does not fit every exporter (measured: a Fab donor diverged by 63% of
649 // its own height -- structural, not tolerance noise). When they disagree the DECLARED binds win
650 // and the divergence stays announced above; when they agree, FK replacement proceeds as before.
651 if fkmax > G_MAGIC_20000 { skw("FK-DIVERGES from cluster binds -- KEEPING declared cluster binds; fk replacement skipped\n" as *u8) }
652 if fkmax <= G_MAGIC_20000 {
653 var jb9: i64 = 0
654 while jb9 < nj {
655 jbind[jb9*3] = gts[jb9*3]
656 jbind[jb9*3+1] = gts[jb9*3+1]
657 jbind[jb9*3+2] = gts[jb9*3+2]
658 jb9 = jb9 + 1
659 }
660 }
661 // SKIN accumulation: top-4 (joint, weight q12) per vertex of the main mesh
662 let lpn: *i64 = sys_mmap(16) as *i64
663 let nb: *u8 = sys_map_file(argv[2] as *u8, lpn)
664 let nlen2: i64 = lpn[0]
665 let vwo: i64 = nxa_find(nb, nlen2, nxa_tag4("VERT" as *u8))
666 if vwo < 0 { skw("in.nxa bad\n" as *u8); return 5 }
667 let two: i64 = nxa_find(nb, nlen2, nxa_tag4("TRIS" as *u8))
668 if two < 0 { skw("in.nxa bad\n" as *u8); return 5 }
669 let cwo: i64 = nxa_find(nb, nlen2, nxa_tag4("CLUS" as *u8))
670 let nh: *i64 = nb as *i64
671 let nv: i64 = nh[vwo]
672 let nt: i64 = nh[two]
673 var nclw: i64 = 0
674 if cwo >= 0 { nclw = 1 + nh[cwo]*10 }
675 let j4: *i64 = sys_mmap(nv*32 + 64) as *i64
676 let w4: *i64 = sys_mmap(nv*32 + 64) as *i64
677 let japp: *i64 = sys_mmap(nj*8 + 64) as *i64
678 var cvn: i64 = 0
679 var cvi: i64 = 0
680 var dbg: i64 = 0
681 while dbg < cc {
682 if cval[dbg] == 1 { cvn = cvn + 1; cvi = cvi + G[G_CIX + dbg*4 + 1] }
683 dbg = dbg + 1
684 }
685 skw("valid_clusters=" as *u8); skn(cvn)
686 skw(" their_index_elems=" as *u8); skn(cvi)
687 skw(" gid_vert_alen=" as *u8); skn(G[3]); skw("\n" as *u8)
688 // MULTI-GEOMETRY (2026-08-23): each cluster's Indexes are LOCAL to its own geometry's
689 // vertex space; offset by that geometry's base (prefix sum over the walk table, same
690 // document order nx_fbx_import merges in). DESYNC GUARD: if the table's vertex total
691 // disagrees with the in.nxa's, the pairing is unproven and weights would land on wrong
692 // vertices -- refuse authored-weight application BY NAME (joints still emit, autoskin
693 // covers) rather than stamp a plausible-looking wrong skin.
694 var tblnv: i64 = 0
695 var gtb0: i64 = 0
696 while gtb0 < G[G_GT] { tblnv = tblnv + G[G_GT + 2 + gtb0*2]/3; gtb0 = gtb0 + 1 }
697 var geomsync: i64 = 1
698 if tblnv != nv { geomsync = 0 }
699 skw("geom_table=" as *u8); skn(G[G_GT])
700 skw(" geom_table_overflow=" as *u8); skn(G[G_GTOV])
701 skw(" tbl_nv=" as *u8); skn(tblnv)
702 skw(" mesh_nv=" as *u8); skn(nv)
703 skw(" sync=" as *u8); skn(geomsync); skw("\n" as *u8)
704 if geomsync == 0 { skw("GEOM-TABLE-DESYNC -- refusing authored-weight application (autoskin only)\n" as *u8) }
705 var c2: i64 = 0
706 var applied: i64 = 0
707 while c2 < cc {
708 if geomsync == 1 { if cval[c2] == 1 { if cjoint[c2] >= 0 { if G[G_CIX + c2*4 + 1] > 0 { if G[G_CIX + c2*4 + 1] == G[G_CW + c2*4 + 1] {
709 let ni: i64 = G[G_CIX + c2*4 + 1]
710 let cg: i64 = cgeo[c2]
711 var vb2: i64 = 0
712 var gpre: i64 = 0
713 while gpre < cg { vb2 = vb2 + G[G_GT + 2 + gpre*2]/3; gpre = gpre + 1 }
714 var gnv: i64 = 0
715 if cg >= 0 { gnv = G[G_GT + 2 + cg*2]/3 }
716 let ia: *u8 = sk_arr(fb, G[G_CIX + c2*4], ni, G[G_CIX + c2*4 + 2], G[G_CIX + c2*4 + 3], 4)
717 let wa: *u8 = sk_arr(fb, G[G_CW + c2*4], ni, G[G_CW + c2*4 + 2], G[G_CW + c2*4 + 3], 8)
718 if (ia as i64) != 0 { if (wa as i64) != 0 {
719 let jj2: i64 = cjoint[c2]
720 var k3: i64 = 0
721 while k3 < ni {
722 let vi0: i64 = sk_u32(ia, k3*4)
723 let vi: i64 = vb2 + vi0
724 if vi0 < gnv { if vi < nv {
725 let wq: i64 = sk_f64(wa, k3*8, G_MAGIC_4096)
726 if wq > 0 {
727 // replace the smallest of the vertex's 4 slots if we beat it
728 var mslot: i64 = 0
729 var s: i64 = 1
730 while s < 4 { if w4[vi*4+s] < w4[vi*4+mslot] { mslot = s } s = s + 1 }
731 if wq > w4[vi*4+mslot] {
732 w4[vi*4+mslot] = wq
733 j4[vi*4+mslot] = jj2
734 japp[jj2] = japp[jj2] + 1
735 applied = applied + 1
736 }
737 }
738 } }
739 k3 = k3 + 1
740 }
741 } }
742 } } } } }
743 c2 = c2 + 1
744 }
745 // normalize each vertex to exact sum 4096; unskinned verts -> AUTO-SKIN to the nearest
746 // two clustered joints by inverse distance (authored weights only cover skin sub-regions
747 // in CC exports, and unrigged sculpts have none at all -- auto-fill makes every mesh posable)
748 let vx5: *i64 = ((nb as i64) + vwo*8 + 8) as *i64
749 // auto-skin candidates = ONLY bones that received AUTHORED cluster weight on OUR mesh.
750 // v2 regression caught by the name instrument: FK-true binds gave EYE/TONGUE bones real
751 // positions and nearest-segment auto-skin bound ~1600 FACE verts to an eyeball rotating
752 // 260deg in the mocap -- the "scapula shards" were the face. Organ bones whose clusters
753 // live on foreign meshes (eyes/tongue/teeth) drop out of japp by construction.
754 let jok: *i64 = sys_mmap(nj*8 + 64) as *i64
755 var jo0: i64 = 0
756 while jo0 < nj {
757 jok[jo0] = 0
758 if japp[jo0] > 0 { jok[jo0] = 1 }
759 jo0 = jo0 + 1
760 }
761 var skinned: i64 = 0
762 var autos: i64 = 0
763 var v0: i64 = 0
764 while v0 < nv {
765 let s0: i64 = w4[v0*4] + w4[v0*4+1] + w4[v0*4+2] + w4[v0*4+3]
766 if s0 == 0 {
767 var b1: i64 = 0 - 1
768 var b2j: i64 = 0 - 1
769 var d1: i64 = G_MAGIC_4611686018427387903
770 var d2: i64 = d1
771 var jn: i64 = 0
772 while jn < nj {
773 if jok[jn] == 1 {
774 // distance to the BONE SEGMENT (parent->joint), not the joint point --
775 // point-distance bound mid-limb verts to the wrong bone = walk clipping
776 var ax: i64 = jbind[jn*3]
777 var ay: i64 = jbind[jn*3+1]
778 var az: i64 = jbind[jn*3+2]
779 let pj: i64 = jpar[jn]
780 var bxs: i64 = ax
781 var bys: i64 = ay
782 var bzs: i64 = az
783 if pj >= 0 { if jok[pj] == 1 {
784 bxs = jbind[pj*3]
785 bys = jbind[pj*3+1]
786 bzs = jbind[pj*3+2]
787 } }
788 let abx: i64 = (bxs - ax)/16
789 let aby: i64 = (bys - ay)/16
790 let abz: i64 = (bzs - az)/16
791 let vax: i64 = (vx5[v0*3] - ax)/16
792 let vay: i64 = (vx5[v0*3+1] - ay)/16
793 let vaz: i64 = (vx5[v0*3+2] - az)/16
794 let denom: i64 = abx*abx + aby*aby + abz*abz
795 var tq: i64 = 0
796 if denom > 0 { tq = (vax*abx + vay*aby + vaz*abz)*G_MAGIC_4096/denom }
797 if tq < 0 { tq = 0 }
798 if tq > G_MAGIC_4096 { tq = G_MAGIC_4096 }
799 let dx: i64 = vax - abx*tq/G_MAGIC_4096
800 let dy: i64 = vay - aby*tq/G_MAGIC_4096
801 let dz: i64 = vaz - abz*tq/G_MAGIC_4096
802 let dd: i64 = dx*dx + dy*dy + dz*dz
803 if dd < d1 { d2 = d1; b2j = b1; d1 = dd; b1 = jn }
804 if dd >= d1 { if dd < d2 { d2 = dd; b2j = jn } }
805 }
806 jn = jn + 1
807 }
808 if b1 >= 0 {
809 if b2j >= 0 {
810 // inverse-distance split over the two nearest bones (d in squared space)
811 let wsum: i64 = d1 + d2
812 var wa: i64 = G_MAGIC_4096*d2/wsum
813 if wa > G_MAGIC_4096 { wa = G_MAGIC_4096 }
814 j4[v0*4] = b1
815 w4[v0*4] = wa
816 j4[v0*4+1] = b2j
817 w4[v0*4+1] = G_MAGIC_4096 - wa
818 }
819 if b2j < 0 {
820 j4[v0*4] = b1
821 w4[v0*4] = G_MAGIC_4096
822 }
823 autos = autos + 1
824 }
825 if b1 < 0 {
826 j4[v0*4] = 0
827 w4[v0*4] = G_MAGIC_4096
828 }
829 }
830 if s0 > 0 {
831 skinned = skinned + 1
832 var acc: i64 = 0
833 var s1: i64 = 0
834 while s1 < 3 {
835 let nw: i64 = w4[v0*4+s1]*G_MAGIC_4096/s0
836 w4[v0*4+s1] = nw
837 acc = acc + nw
838 s1 = s1 + 1
839 }
840 w4[v0*4+3] = G_MAGIC_4096 - acc
841 }
842 v0 = v0 + 1
843 }
844 // WEIGHT SMOOTHING (2 Laplacian passes over the mesh graph): the stretch instrument
845 // measured hard 100pct-to-100pct weight steps tearing under real per-joint motion --
846 // armpit Spine02<>UpperarmTwist01 128 torn edges, hip crease Pelvis<>ThighTwist01 118,
847 // shoulder top <>Clavicle 112. 60/40 own/neighbor blend, top-4 retruncate, sum=4096.
848 let tr5: *i64 = ((nb as i64) + two*8 + 8) as *i64
849 let deg: *i64 = sys_mmap(nv*8 + 64) as *i64
850 var te0: i64 = 0
851 while te0 < nt {
852 deg[tr5[te0*3]] = deg[tr5[te0*3]] + 2
853 deg[tr5[te0*3+1]] = deg[tr5[te0*3+1]] + 2
854 deg[tr5[te0*3+2]] = deg[tr5[te0*3+2]] + 2
855 te0 = te0 + 1
856 }
857 let adjo: *i64 = sys_mmap(nv*8 + 72) as *i64
858 var ao0: i64 = 0
859 var acc0: i64 = 0
860 while ao0 < nv { adjo[ao0] = acc0; acc0 = acc0 + deg[ao0]; ao0 = ao0 + 1 }
861 adjo[nv] = acc0
862 let adjl: *i64 = sys_mmap(acc0*8 + 64) as *i64
863 let cur: *i64 = sys_mmap(nv*8 + 64) as *i64
864 var te1: i64 = 0
865 while te1 < nt {
866 var ec0: i64 = 0
867 while ec0 < 3 {
868 let a5: i64 = tr5[te1*3 + ec0]
869 var b5: i64 = tr5[te1*3]
870 var c5: i64 = tr5[te1*3 + 1]
871 if ec0 == 0 { b5 = tr5[te1*3+1]; c5 = tr5[te1*3+2] }
872 if ec0 == 1 { b5 = tr5[te1*3]; c5 = tr5[te1*3+2] }
873 if ec0 == 2 { b5 = tr5[te1*3]; c5 = tr5[te1*3+1] }
874 adjl[adjo[a5] + cur[a5]] = b5
875 cur[a5] = cur[a5] + 1
876 adjl[adjo[a5] + cur[a5]] = c5
877 cur[a5] = cur[a5] + 1
878 ec0 = ec0 + 1
879 }
880 te1 = te1 + 1
881 }
882 let j4b: *i64 = sys_mmap(nv*32 + 64) as *i64
883 let w4b: *i64 = sys_mmap(nv*32 + 64) as *i64
884 let mj: *i64 = sys_mmap(64*8 + 64) as *i64
885 let mw: *i64 = sys_mmap(64*8 + 64) as *i64
886 var it0: i64 = 0
887 while it0 < 3 {
888 var vs0: i64 = 0
889 while vs0 < nv {
890 let dv5: i64 = deg[vs0]
891 if dv5 == 0 {
892 var sc0: i64 = 0
893 while sc0 < 4 { j4b[vs0*4+sc0] = j4[vs0*4+sc0]; w4b[vs0*4+sc0] = w4[vs0*4+sc0]; sc0 = sc0 + 1 }
894 }
895 if dv5 > 0 {
896 var mn: i64 = 0
897 var so0: i64 = 0
898 while so0 < 4 {
899 if w4[vs0*4+so0] > 0 {
900 let jj5: i64 = j4[vs0*4+so0]
901 let ww5: i64 = w4[vs0*4+so0]*6*dv5
902 var f0: i64 = 0 - 1
903 var m0s: i64 = 0
904 while m0s < mn { if mj[m0s] == jj5 { f0 = m0s; m0s = mn } m0s = m0s + 1 }
905 if f0 >= 0 { mw[f0] = mw[f0] + ww5 }
906 if f0 < 0 { if mn < 60 { mj[mn] = jj5; mw[mn] = ww5; mn = mn + 1 } }
907 }
908 so0 = so0 + 1
909 }
910 var nb0: i64 = 0
911 while nb0 < dv5 {
912 let vn5: i64 = adjl[adjo[vs0] + nb0]
913 var sn0: i64 = 0
914 while sn0 < 4 {
915 if w4[vn5*4+sn0] > 0 {
916 let jj6: i64 = j4[vn5*4+sn0]
917 let ww6: i64 = w4[vn5*4+sn0]*4
918 var f1: i64 = 0 - 1
919 var m1s: i64 = 0
920 while m1s < mn { if mj[m1s] == jj6 { f1 = m1s; m1s = mn } m1s = m1s + 1 }
921 if f1 >= 0 { mw[f1] = mw[f1] + ww6 }
922 if f1 < 0 { if mn < 60 { mj[mn] = jj6; mw[mn] = ww6; mn = mn + 1 } }
923 }
924 sn0 = sn0 + 1
925 }
926 nb0 = nb0 + 1
927 }
928 // top-4 of the merged map -> renormalize to exact 4096
929 var sl0: i64 = 0
930 while sl0 < 4 {
931 var bi5: i64 = 0 - 1
932 var bw5: i64 = 0
933 var m2s: i64 = 0
934 while m2s < mn {
935 if mw[m2s] > bw5 { bw5 = mw[m2s]; bi5 = m2s }
936 m2s = m2s + 1
937 }
938 if bi5 >= 0 {
939 j4b[vs0*4+sl0] = mj[bi5]
940 w4b[vs0*4+sl0] = mw[bi5]
941 mw[bi5] = 0
942 }
943 if bi5 < 0 { j4b[vs0*4+sl0] = 0; w4b[vs0*4+sl0] = 0 }
944 sl0 = sl0 + 1
945 }
946 let ts5: i64 = w4b[vs0*4] + w4b[vs0*4+1] + w4b[vs0*4+2] + w4b[vs0*4+3]
947 if ts5 > 0 {
948 var an5: i64 = 0
949 var sr0: i64 = 0
950 while sr0 < 3 {
951 let nw5: i64 = w4b[vs0*4+sr0]*G_MAGIC_4096/ts5
952 w4b[vs0*4+sr0] = nw5
953 an5 = an5 + nw5
954 sr0 = sr0 + 1
955 }
956 w4b[vs0*4+3] = G_MAGIC_4096 - an5
957 }
958 if ts5 == 0 { j4b[vs0*4] = 0; w4b[vs0*4] = G_MAGIC_4096; w4b[vs0*4+1] = 0; w4b[vs0*4+2] = 0; w4b[vs0*4+3] = 0 }
959 }
960 vs0 = vs0 + 1
961 }
962 var cp0: i64 = 0
963 while cp0 < nv*4 { j4[cp0] = j4b[cp0]; w4[cp0] = w4b[cp0]; cp0 = cp0 + 1 }
964 it0 = it0 + 1
965 }
966 skw("weights_smoothed=2passes\n" as *u8)
967 skw("joints=" as *u8); skn(nj)
968 skw(" skinned=" as *u8); skn(skinned)
969 skw(" autoskinned=" as *u8); skn(autos)
970 skw("/" as *u8); skn(nv)
971 skw(" applied=" as *u8); skn(applied); skw("\n" as *u8)
972 // build SKEL + SKIN payloads
973 let skel: *i64 = sys_mmap((1 + nj*8)*8 + 64) as *i64
974 skel[0] = nj
975 var j1: i64 = 0
976 while j1 < nj {
977 skel[1 + j1*8] = jpar[j1]
978 skel[1 + j1*8 + 1] = jbind[j1*3]
979 skel[1 + j1*8 + 2] = jbind[j1*3 + 1]
980 skel[1 + j1*8 + 3] = jbind[j1*3 + 2]
981 skel[1 + j1*8 + 4] = 0
982 skel[1 + j1*8 + 5] = 0
983 skel[1 + j1*8 + 6] = 0
984 skel[1 + j1*8 + 7] = G_MAGIC_4096
985 j1 = j1 + 1
986 }
987 let skin: *i64 = sys_mmap((1 + nv*8)*8 + 64) as *i64
988 skin[0] = nv
989 var v1: i64 = 0
990 while v1 < nv {
991 var s2: i64 = 0
992 while s2 < 4 {
993 skin[1 + v1*8 + s2] = j4[v1*4 + s2]
994 skin[1 + v1*8 + 4 + s2] = w4[v1*4 + s2]
995 s2 = s2 + 1
996 }
997 v1 = v1 + 1
998 }
999 // emit the 5-section NXA
1000 let fd: i64 = sys_openat_wr(argv[3] as *u8, 0x1a4)
1001 if fd < 0 { skw("open out failed\n" as *u8); return 9 }
1002 let vwl: i64 = 1 + nv*3
1003 let twl: i64 = 1 + nt*3
1004 let swl: i64 = 1 + nj*8
1005 let kwl: i64 = 1 + nv*8
1006 var ns: i64 = 4
1007 if nclw > 0 { ns = 5 }
1008 let hdr: *i64 = sys_mmap(64) as *i64
1009 let toc: *i64 = sys_mmap(512) as *i64
1010 var o: i64 = 32 + ns*32
1011 var ti: i64 = 0
1012 toc[ti*4] = nxa_tag4("VERT" as *u8)
1013 toc[ti*4+1] = o
1014 toc[ti*4+2] = vwl
1015 toc[ti*4+3] = nxa_check2(1, ((nb as i64) + vwo*8) as *i64, vwl)
1016 o = o + vwl*8
1017 ti = ti + 1
1018 toc[ti*4] = nxa_tag4("TRIS" as *u8)
1019 toc[ti*4+1] = o
1020 toc[ti*4+2] = twl
1021 toc[ti*4+3] = nxa_check2(1, ((nb as i64) + two*8) as *i64, twl)
1022 o = o + twl*8
1023 ti = ti + 1
1024 if nclw > 0 {
1025 toc[ti*4] = nxa_tag4("CLUS" as *u8)
1026 toc[ti*4+1] = o
1027 toc[ti*4+2] = nclw
1028 toc[ti*4+3] = nxa_check2(1, ((nb as i64) + cwo*8) as *i64, nclw)
1029 o = o + nclw*8
1030 ti = ti + 1
1031 }
1032 toc[ti*4] = nxa_tag4("SKEL" as *u8)
1033 toc[ti*4+1] = o
1034 toc[ti*4+2] = swl
1035 toc[ti*4+3] = nxa_check2(1, skel, swl)
1036 o = o + swl*8
1037 ti = ti + 1
1038 toc[ti*4] = nxa_tag4("SKIN" as *u8)
1039 toc[ti*4+1] = o
1040 toc[ti*4+2] = kwl
1041 toc[ti*4+3] = nxa_check2(1, skin, kwl)
1042 hdr[0] = nxa_magic()
1043 hdr[1] = NXA_VER
1044 hdr[2] = ns
1045 hdr[3] = nxa_check2(1, toc, ns*4)
1046 sys_write(fd, hdr as *u8, 32)
1047 sys_write(fd, toc as *u8, ns*32)
1048 sys_write(fd, ((nb as i64) + vwo*8) as *u8, vwl*8)
1049 sys_write(fd, ((nb as i64) + two*8) as *u8, twl*8)
1050 if nclw > 0 { sys_write(fd, ((nb as i64) + cwo*8) as *u8, nclw*8) }
1051 sys_write(fd, skel as *u8, swl*8)
1052 sys_write(fd, skin as *u8, kwl*8)
1053 sys_close(fd)
1054 skw("NXA5 written joints=" as *u8); skn(nj)
1055 skw(" skin_verts=" as *u8); skn(nv); skw("\n" as *u8)
1056 return 0
1057}