nx_autorig_pose_gate.nx source
↩ module page · 496 lines · 26551 B
1// nx_autorig_pose_gate.nx -- THE END-TO-END DONE-RULE GATE FOR DC16 (the BEND case): a BARE mesh goes through
2// nx_curveskel (skeleton) then nx_boneheat (weights), a 90 degree shoulder SWING is baked into the asset as a POSE
3// and PLAYED by the served nx_nxa_play, and the deformed skin is measured through md_measure -- this estate's ONE
4// mesh deviation ruler, never a second one -- plus a direct crease read across the shoulder.
5// THE POSE IS A BAKED WORLD DELTA, BECAUSE THAT IS THE PLAYER'S CONTRACT. nx_nxa_play applies, per joint,
6// M x = D(x - bind_t) + bind_t + dt with NO hierarchy walk at runtime (nxa_format_spec.md), so a single-joint
7// entry moves only that joint's skinned band -- measured 2026-09-05: one entry on joint 1 read md max=3 against a
8// 10 mm bar while the ring crease read 5 mm, i.e. a local sleeve, not a swing. A real bend therefore carries one
9// entry per joint of the swung subtree: D = R and dt = (R - I)(bind_t_j - P), which is exactly the rigid swing
10// out = R(v - P) + P about the shoulder pivot P, while the rest of the body stays put.
11// UNITS, MEASURED NOT ASSUMED: the NXMESH handed to md_measure is in millimetres (NXA 0.01 mm -> tenths -> mm at
12// nm_put_tri), and md_measure reports in 0.1 mm: the 16-joint swing read max=259 (a ~26 mm tip), the twist
13// sleeve read 3 (0.3 mm). A bar written in 0.01 mm quanta here was 10x too high and failed a real swing.
14// WHAT IT PROVES AND WHAT IT DOES NOT. Smooth geodesic weights make the shoulder a SOFTER crease than the hard
15// nearest-joint boundary (the adjacent-ring displacement jump is smaller), and the pose really swings the limb
16// (md_measure posed-vs-rest reaches a derived floor). It does NOT prove the twist case: candy-wrapper collapse
17// under an axial twist is a DEFORMER property (nx_skeleton is linear-blend only, no dual-quaternion path), and
18// smoother weights make that collapse worse, so it is deferred to a dual-quaternion rung on the deformer's board.
19// SUBJECTS: the two libs in-process, the PLAYER forked as the deployed binary at its absolute path.
20// license_tier: ORIGINAL expect_exit: 0
21import "nx_syscalls.nx"
22import "nx_gate_verdict.nx"
23import "nx_nxa.nx"
24import "nx_curveskel_lib.nx"
25import "nx_tool_run.nx"
26import "nx_nxmesh_lib.nx"
27import "nx_mmdev_lib.nx"
28import "nx_nxa_fk.nx"
29
30const AG_DIR: *u8 = "/tmp/nx_autorig_pose_gate"
31const AG_MODE_DIR: i64 = 493
32const AG_PLAY: *u8 = "/volume1/homes/elderwesto/nishihost/nx_nxa_play.elf"
33const AG_AROUND: i64 = 4
34const AG_LEN: i64 = 6000 // 60 mm prism, 0.01 mm units
35const AG_RINGS: i64 = 61 // one ring per mm
36const AG_RING_STEP: i64 = 100 // mm between rings in 0.01 mm units
37const AG_R: i64 = 300 // 3 mm half-width
38const AG_CELLS: i64 = 64
39const AG_VERT_CAP: i64 = 1024
40const AG_TRI_CAP: i64 = 4096
41const AG_POSE_ID: i64 = 1
42const AG_POSE_ID_ARG: *u8 = "1"
43const AG_WFULL_ARG: *u8 = "1000"
44const AG_Q12_SIN45: i64 = 2896 // round(4096 x sin 45): a 90 degree turn about x is (s,0,0,s)
45const AG_POSE_HDR_WORDS: i64 = 3 // npose, pid, ne
46const AG_POSE_ENTRY_WORDS: i64 = 8 // joint, qx qy qz qw, tx ty tz (the player's entry layout)
47const AG_MIN_SUBTREE: i64 = 2 // a swing of fewer than two joints is a sleeve, not a limb
48const AG_CAPTURE_CAP: i64 = 65536
49const AG_ARGV_SLOTS: i64 = 8
50const AG_WORD_BYTES: i64 = 8
51const AG_CWD_CAP: i64 = 4096
52const AG_NO_TIMEOUT: i64 = 0
53const AG_SLASH: i64 = 47
54const AG_NXA_PER_TENTH: i64 = 10 // NXA 0.01 mm -> NXMESH input tenths of a mm
55const AG_MD_MIN_SWING: i64 = 100 // 10 mm in md_measure's 0.1 mm units (see UNITS above): a third of the 30 mm limb must move
56const AG_MAGIC: *u8 = "NXMSH2" // the ruler reads ntris at 12 and never the magic; written for readers that do
57const AG_MAGIC_LEN: i64 = 6
58const AG_MAGIC_FIELD: i64 = 8
59const AG_ROOT: i64 = 0 - 1
60
61func hw(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 }
62func ag_ring_xy(k: i64, r: i64, out: *i64) -> i64 {
63 if k == 0 { out[0] = r; out[1] = 0 - r }
64 if k == 1 { out[0] = r; out[1] = r }
65 if k == 2 { out[0] = 0 - r; out[1] = r }
66 if k == 3 { out[0] = 0 - r; out[1] = 0 - r }
67 return 0
68}
69func ag_tri(tris: *i64, st: *i64, a: i64, b: i64, c: i64) -> i64 {
70 let t: i64 = st[1]
71 tris[1 + t * 3] = a; tris[1 + t * 3 + 1] = b; tris[1 + t * 3 + 2] = c
72 st[1] = t + 1
73 return 0
74}
75func ag_prism(vert: *i64, tris: *i64, st: *i64, r: i64, z0: i64, z1: i64, rings: i64) -> i64 {
76 let v0: i64 = st[0]
77 let xy: *i64 = sys_mmap(16) as *i64
78 var rr: i64 = 0
79 while rr < rings {
80 var k: i64 = 0
81 while k < AG_AROUND {
82 ag_ring_xy(k, r, xy)
83 let idx: i64 = v0 + rr * AG_AROUND + k
84 vert[1 + idx * 3] = xy[0]
85 vert[1 + idx * 3 + 1] = xy[1]
86 vert[1 + idx * 3 + 2] = z0 + (z1 - z0) * rr / (rings - 1)
87 k = k + 1
88 }
89 rr = rr + 1
90 }
91 st[0] = v0 + rings * AG_AROUND
92 rr = 0
93 while rr < rings - 1 {
94 var k: i64 = 0
95 while k < AG_AROUND {
96 let a: i64 = v0 + rr * AG_AROUND + k
97 let b: i64 = v0 + rr * AG_AROUND + (k + 1) % AG_AROUND
98 let c: i64 = v0 + (rr + 1) * AG_AROUND + (k + 1) % AG_AROUND
99 let d: i64 = v0 + (rr + 1) * AG_AROUND + k
100 ag_tri(tris, st, a, b, c)
101 ag_tri(tris, st, a, c, d)
102 k = k + 1
103 }
104 rr = rr + 1
105 }
106 let cb: i64 = st[0]
107 vert[1 + cb * 3] = 0; vert[1 + cb * 3 + 1] = 0; vert[1 + cb * 3 + 2] = z0
108 let ct: i64 = st[0] + 1
109 vert[1 + ct * 3] = 0; vert[1 + ct * 3 + 1] = 0; vert[1 + ct * 3 + 2] = z1
110 st[0] = st[0] + 2
111 var k: i64 = 0
112 while k < AG_AROUND {
113 ag_tri(tris, st, cb, v0 + (k + 1) % AG_AROUND, v0 + k)
114 let top: i64 = v0 + (rings - 1) * AG_AROUND
115 ag_tri(tris, st, ct, top + k, top + (k + 1) % AG_AROUND)
116 k = k + 1
117 }
118 return st[0] - v0
119}
120func ag_write2(path: *u8, vert: *i64, tris: *i64, st: *i64) -> i64 {
121 vert[0] = st[0]; tris[0] = st[1]
122 let tags: *i64 = sys_mmap(64) as *i64
123 let ptrs: *i64 = sys_mmap(64) as *i64
124 let wls: *i64 = sys_mmap(64) as *i64
125 tags[0] = nxa_tag4("VERT" as *u8); ptrs[0] = vert as i64; wls[0] = 1 + st[0] * 3
126 tags[1] = nxa_tag4("TRIS" as *u8); ptrs[1] = tris as i64; wls[1] = 1 + st[1] * 3
127 return bh_nxa_write(path, 2, tags, ptrs, wls)
128}
129// build a POSE that SWINGS one whole limb: the shoulder subtree rotated 90 degrees about x, baked as a WORLD
130// DELTA per joint -- exactly what nx_nxa_play consumes (its runtime does NO hierarchy walk; each joint carries
131// its own world delta, M x = D(x - bind_t) + bind_t + dt). For a rigid swing of the subtree about the shoulder
132// pivot P by rotation R, every subtree joint gets D = R and dt = (R - I)(bind_t_j - P), so out = R(v - P) + P.
133// R is composed from the SAME quaternion the player will apply (nf_qrotv over q12), so the swing this gate
134// encodes and the swing the player renders are one arithmetic, not two. info: [0]=subtree joints [1]=shoulder
135// [2]=root. Returns the subtree joint count, negative on refusal.
136func ag_append_swing(inpath: *u8, outpath: *u8, info: *i64) -> i64 {
137 let lp: *i64 = sys_mmap(16) as *i64
138 let b: *u8 = sys_read_file(inpath, lp)
139 if (b as i64) == 0 { return 0 - 1 }
140 let w: *i64 = b as *i64
141 let flen: i64 = lp[0]
142 let swo: i64 = nxa_find(b, flen, nxa_tag4("SKEL" as *u8))
143 if swo < 0 { return 0 - 2 }
144 let nj: i64 = w[swo]
145 if nj < AG_MIN_SUBTREE { return 0 - 3 }
146 // root = the joint whose parent is < 0; shoulder = the first joint whose parent is the root
147 var root: i64 = 0 - 1
148 var j: i64 = 0
149 while j < nj { if w[swo + 1 + j * CS_JOINT_WORDS] < 0 { if root < 0 { root = j } } j = j + 1 }
150 if root < 0 { return 0 - 4 }
151 var shoulder: i64 = 0 - 1
152 j = 0
153 while j < nj { if w[swo + 1 + j * CS_JOINT_WORDS] == root { if shoulder < 0 { shoulder = j } } j = j + 1 }
154 if shoulder < 0 { return 0 - 5 }
155 // subtree membership: a joint is in the swung limb if it IS the shoulder or its parent already is.
156 // cs parents a limb joint to an EARLIER joint, so one ascending pass closes the subtree.
157 let insub: *i64 = sys_mmap(nj * 8 + 64) as *i64
158 j = 0
159 while j < nj { insub[j] = 0; j = j + 1 }
160 insub[shoulder] = 1
161 j = 0
162 while j < nj {
163 if j != shoulder {
164 let par: i64 = w[swo + 1 + j * CS_JOINT_WORDS]
165 if par >= 0 { if insub[par] == 1 { insub[j] = 1 } }
166 }
167 j = j + 1
168 }
169 let px: i64 = w[swo + 1 + shoulder * CS_JOINT_WORDS + 1]
170 let py: i64 = w[swo + 1 + shoulder * CS_JOINT_WORDS + 2]
171 let pz: i64 = w[swo + 1 + shoulder * CS_JOINT_WORDS + 3]
172 let q: *i64 = sys_mmap(64) as *i64
173 q[0] = AG_Q12_SIN45; q[1] = 0; q[2] = 0; q[3] = AG_Q12_SIN45
174 let o3: *i64 = sys_mmap(64) as *i64
175 let scr: *i64 = sys_mmap(256) as *i64
176 var ne: i64 = 0
177 j = 0
178 while j < nj { if insub[j] == 1 { ne = ne + 1 } j = j + 1 }
179 let pose: *i64 = sys_mmap((AG_POSE_HDR_WORDS + ne * AG_POSE_ENTRY_WORDS) * 8 + 64) as *i64
180 pose[0] = 1; pose[1] = AG_POSE_ID; pose[2] = ne
181 var e: i64 = 0
182 j = 0
183 while j < nj {
184 if insub[j] == 1 {
185 let dx: i64 = w[swo + 1 + j * CS_JOINT_WORDS + 1] - px
186 let dy: i64 = w[swo + 1 + j * CS_JOINT_WORDS + 2] - py
187 let dz: i64 = w[swo + 1 + j * CS_JOINT_WORDS + 3] - pz
188 nf_qrotv(q, dx, dy, dz, o3, scr)
189 let eo: i64 = AG_POSE_HDR_WORDS + e * AG_POSE_ENTRY_WORDS
190 pose[eo] = j
191 pose[eo + 1] = q[0]; pose[eo + 2] = q[1]; pose[eo + 3] = q[2]; pose[eo + 4] = q[3]
192 pose[eo + 5] = o3[0] - dx; pose[eo + 6] = o3[1] - dy; pose[eo + 7] = o3[2] - dz
193 e = e + 1
194 }
195 j = j + 1
196 }
197 // splice the POSE section in, replacing any existing one
198 let ns0: i64 = w[2]
199 let tb: *i64 = ((b as i64) + BH_HDR_BYTES) as *i64
200 let tags: *i64 = sys_mmap((ns0 + 1) * 8 + 64) as *i64
201 let ptrs: *i64 = sys_mmap((ns0 + 1) * 8 + 64) as *i64
202 let wls: *i64 = sys_mmap((ns0 + 1) * 8 + 64) as *i64
203 let posetag: i64 = nxa_tag4("POSE" as *u8)
204 var ns: i64 = 0
205 var s0: i64 = 0
206 while s0 < ns0 {
207 if tb[s0 * BH_TOC_WORDS] != posetag {
208 tags[ns] = tb[s0 * BH_TOC_WORDS]
209 ptrs[ns] = (b as i64) + tb[s0 * BH_TOC_WORDS + 1]
210 wls[ns] = tb[s0 * BH_TOC_WORDS + 2]
211 ns = ns + 1
212 }
213 s0 = s0 + 1
214 }
215 tags[ns] = posetag; ptrs[ns] = pose as i64; wls[ns] = AG_POSE_HDR_WORDS + ne * AG_POSE_ENTRY_WORDS; ns = ns + 1
216 info[0] = ne; info[1] = shoulder; info[2] = root
217 if bh_nxa_write(outpath, ns, tags, ptrs, wls) <= 0 { return 0 - 6 }
218 return ne
219}
220func ag_dirname(path: *u8, out: *u8) -> i64 {
221 var last: i64 = 0 - 1
222 var i: i64 = 0
223 while path[i] != (0 as u8) { if path[i] == (AG_SLASH as u8) { last = i } i = i + 1 }
224 if last <= 0 { out[0] = AG_SLASH as u8; out[1] = 0 as u8; return 1 }
225 var k: i64 = 0
226 while k < last { if k < AG_CWD_CAP - 1 { out[k] = path[k] } k = k + 1 }
227 out[last] = 0 as u8
228 return last
229}
230// fork the served player: nx_nxa_play <in> 1 <out> 1000, in its own directory; returns its exit code
231func ag_play(inp: *u8, outp: *u8, cap: *u8, olen: *i64) -> i64 {
232 let av: *i64 = sys_mmap(AG_WORD_BYTES * AG_ARGV_SLOTS) as *i64
233 av[0] = AG_PLAY as i64
234 av[1] = inp as i64
235 av[2] = AG_POSE_ID_ARG as i64
236 av[3] = outp as i64
237 av[4] = AG_WFULL_ARG as i64
238 av[5] = 0
239 let cwd: *u8 = sys_mmap(AG_CWD_CAP)
240 ag_dirname(AG_PLAY, cwd)
241 return tr_run_capture_cwd(AG_PLAY, av, cap, AG_CAPTURE_CAP, olen, AG_NO_TIMEOUT, cwd)
242}
243func ag_find(b: *u8, n: i64, lit: *u8) -> i64 {
244 var ll: i64 = 0
245 while lit[ll] != (0 as u8) { ll = ll + 1 }
246 if ll == 0 { return 0 - 1 }
247 var i: i64 = 0
248 while i + ll <= n {
249 var k: i64 = 0
250 var ok: i64 = 1
251 while ok == 1 { if k >= ll { break } if b[i+k] != lit[k] { ok = 0 } k = k + 1 }
252 if ok == 1 { return i }
253 i = i + 1
254 }
255 return 0 - 1
256}
257func ag_has(b: *u8, n: i64, lit: *u8) -> i64 { if ag_find(b, n, lit) >= 0 { return 1 } return 0 }
258// the decimal integer that follows key= in a captured receipt, -1 when absent: the player's own numbers are the
259// third witness beside md_measure and the crease read, and a witness that is captured and not read is no witness
260func ag_num_after(b: *u8, n: i64, key: *u8) -> i64 {
261 let at: i64 = ag_find(b, n, key)
262 if at < 0 { return 0 - 1 }
263 var kl: i64 = 0
264 while key[kl] != (0 as u8) { kl = kl + 1 }
265 var i: i64 = at + kl
266 var v: i64 = 0
267 var any: i64 = 0
268 while i < n {
269 let c: i64 = b[i] as i64
270 if c < 48 { break }
271 if c > 57 { break }
272 v = v * 10 + (c - 48)
273 any = 1
274 i = i + 1
275 }
276 if any == 0 { return 0 - 1 }
277 return v
278}
279// an NXA's VERT/TRIS as an in-memory NXMESH buffer for md_measure: per-triangle coordinates in tenths of a mm
280func ag_nxmesh(b: *u8, flen: i64) -> *u8 {
281 let w: *i64 = b as *i64
282 let vwo: i64 = nxa_find(b, flen, nxa_tag4("VERT" as *u8))
283 let two: i64 = nxa_find(b, flen, nxa_tag4("TRIS" as *u8))
284 if vwo < 0 { return 0 as *u8 }
285 if two < 0 { return 0 as *u8 }
286 let nt: i64 = w[two]
287 let m: *u8 = sys_mmap(nm_file_bytes(0, nt) + 64)
288 var i: i64 = 0
289 while i < AG_MAGIC_LEN { m[i] = AG_MAGIC[i]; i = i + 1 }
290 while i < AG_MAGIC_FIELD { m[i] = 0 as u8; i = i + 1 }
291 nm_put_u32(m, NM_OFF_NLAYERS, 0)
292 nm_put_u32(m, NM_OFF_NTRIS, nt)
293 let tbase: i64 = nm_tri_base(m)
294 let xyz9: *i64 = sys_mmap(9 * 8) as *i64
295 var t: i64 = 0
296 while t < nt {
297 var v: i64 = 0
298 while v < 3 {
299 let vi: i64 = w[two + 1 + t * 3 + v]
300 let o: i64 = vwo + 1 + vi * 3
301 xyz9[v * 3] = w[o] / AG_NXA_PER_TENTH
302 xyz9[v * 3 + 1] = w[o + 1] / AG_NXA_PER_TENTH
303 xyz9[v * 3 + 2] = w[o + 2] / AG_NXA_PER_TENTH
304 v = v + 1
305 }
306 nm_put_tri(m, tbase, t, xyz9)
307 t = t + 1
308 }
309 return m
310}
311// displacement magnitude of vertex v between two NXAs sharing vertex order
312func ag_disp(ra: *u8, fa: i64, pb: *u8, fb: i64, v: i64) -> i64 {
313 let wa: *i64 = ra as *i64
314 let wb: *i64 = pb as *i64
315 let va: i64 = nxa_find(ra, fa, nxa_tag4("VERT" as *u8))
316 let vb: i64 = nxa_find(pb, fb, nxa_tag4("VERT" as *u8))
317 let oa: i64 = va + 1 + v * 3
318 let ob: i64 = vb + 1 + v * 3
319 let dx: i64 = wb[ob] - wa[oa]; let dy: i64 = wb[ob + 1] - wa[oa + 1]; let dz: i64 = wb[ob + 2] - wa[oa + 2]
320 return vm_isqrt(dx * dx + dy * dy + dz * dz)
321}
322// the crease read: the largest jump in displacement between corner k of ring r and corner k of ring r+1
323func ag_max_ring_jump(ra: *u8, fa: i64, pb: *u8, fb: i64) -> i64 {
324 var best: i64 = 0
325 var r: i64 = 0
326 while r < AG_RINGS - 1 {
327 var k: i64 = 0
328 while k < AG_AROUND {
329 let d0: i64 = ag_disp(ra, fa, pb, fb, r * AG_AROUND + k)
330 let d1: i64 = ag_disp(ra, fa, pb, fb, (r + 1) * AG_AROUND + k)
331 var dj: i64 = d1 - d0
332 if dj < 0 { dj = 0 - dj }
333 if dj > best { best = dj }
334 k = k + 1
335 }
336 r = r + 1
337 }
338 return best
339}
340func ag_open(path: *u8, lp: *i64) -> i64 {
341 let b: *u8 = sys_read_file(path, lp)
342 if (b as i64) == 0 { return 0 }
343 return b as i64
344}
345func ag_files_equal(pa: *u8, pb: *u8) -> i64 {
346 let la: *i64 = sys_mmap(16) as *i64
347 let lb: *i64 = sys_mmap(16) as *i64
348 let a: *u8 = sys_read_file(pa, la)
349 let b: *u8 = sys_read_file(pb, lb)
350 if (a as i64) == 0 { return 0 }
351 if (b as i64) == 0 { return 0 }
352 if la[0] != lb[0] { return 0 }
353 var i: i64 = 0
354 while i < la[0] { if a[i] != b[i] { return 0 } i = i + 1 }
355 return 1
356}
357
358func main() -> i64 {
359 let ctr: *i64 = gv_ctr()
360 gv_head("nx_autorig_pose_gate -- a bare mesh is skeletonised, weighted, its shoulder subtree swung 90 degrees by the served player as a baked world-delta pose, and measured through md_measure: the limb must swing, the geodesic crease must be softer than the hard-boundary one, and a skinless asset must be refused" as *u8)
361 sys_mkdir(AG_DIR, AG_MODE_DIR)
362 let vert: *i64 = sys_mmap((1 + AG_VERT_CAP * 3) * 8) as *i64
363 let tris: *i64 = sys_mmap((1 + AG_TRI_CAP * 3) * 8) as *i64
364 let st: *i64 = sys_mmap(16) as *i64
365 let crep: *i64 = sys_mmap(CR_WORDS * 8) as *i64
366 let brep: *i64 = sys_mmap(BR_WORDS * 8) as *i64
367 let lp: *i64 = sys_mmap(16) as *i64
368 let p_rest: *u8 = "/tmp/nx_autorig_pose_gate/rest.nxa"
369 let p_rig: *u8 = "/tmp/nx_autorig_pose_gate/rigged.nxa"
370 let p_geo: *u8 = "/tmp/nx_autorig_pose_gate/skin_geo.nxa"
371 let p_near: *u8 = "/tmp/nx_autorig_pose_gate/skin_near.nxa"
372 let p_geo_in: *u8 = "/tmp/nx_autorig_pose_gate/posein_geo.nxa"
373 let p_near_in: *u8 = "/tmp/nx_autorig_pose_gate/posein_near.nxa"
374 let p_geo_out: *u8 = "/tmp/nx_autorig_pose_gate/posed_geo.nxa"
375 let p_geo_out2: *u8 = "/tmp/nx_autorig_pose_gate/posed_geo2.nxa"
376 let p_near_out: *u8 = "/tmp/nx_autorig_pose_gate/posed_near.nxa"
377 let p_noskin_in: *u8 = "/tmp/nx_autorig_pose_gate/posein_noskin.nxa"
378 let p_noskin_out: *u8 = "/tmp/nx_autorig_pose_gate/posed_noskin.nxa"
379
380 // ---- 1. a bare prism -> skeleton -> weights (geodesic and the nearest control) ----
381 st[0] = 0; st[1] = 0
382 ag_prism(vert, tris, st, AG_R, 0, AG_LEN, AG_RINGS)
383 let wrote: i64 = ag_write2(p_rest, vert, tris, st)
384 let nv: i64 = st[0]
385 var k: i64 = 0
386 while k < CR_WORDS { crep[k] = 0; k = k + 1 }
387 let rcS: i64 = cs_run(p_rest, p_rig, crep)
388 cs_report(crep, p_rest, p_rig)
389 k = 0
390 while k < BR_WORDS { brep[k] = 0; k = k + 1 }
391 let rcG: i64 = bh_run(p_rig, p_geo, BH_MODE_GEODESIC, AG_CELLS, brep)
392 bh_report(BH_MODE_GEODESIC, brep, p_rig, p_geo)
393 let geoMixed: i64 = brep[BR_MIXED]
394 k = 0
395 while k < BR_WORDS { brep[k] = 0; k = k + 1 }
396 let rcN: i64 = bh_run(p_rig, p_near, BH_MODE_NEAREST, AG_CELLS, brep)
397 gv_check("T1 fixture-reached-the-condition: a bare 60 mm prism was skeletonised (two limbs) and weighted twice, geodesic (blends) and nearest (the hard control) -- both exit 0" as *u8, ((wrote > 0) as i64) * ((rcS == CS_EXIT_OK) as i64) * ((crep[CR_LIMBS] == 2) as i64) * ((rcG == BH_EXIT_OK) as i64) * ((rcN == BH_EXIT_OK) as i64) * ((geoMixed > 0) as i64), ctr)
398
399 // ---- 2. a WHOLE-LIMB swing pose (baked world deltas) is written INTO each asset and read back ----
400 let infoG: *i64 = sys_mmap(64) as *i64
401 let infoN: *i64 = sys_mmap(64) as *i64
402 let wp1: i64 = ag_append_swing(p_geo, p_geo_in, infoG)
403 let wp2: i64 = ag_append_swing(p_near, p_near_in, infoN)
404 hw(" swing subtree joints=" as *u8); gv_num(wp1); hw(" shoulder=" as *u8); gv_num(infoG[1]); hw(" root=" as *u8); gv_num(infoG[2]); hw("\n" as *u8)
405 let bgi: i64 = ag_open(p_geo_in, lp)
406 let fgi: i64 = lp[0]
407 let pwo: i64 = nxa_find(bgi as *u8, fgi, nxa_tag4("POSE" as *u8))
408 let wgi: *i64 = bgi as *i64
409 var npose: i64 = 0
410 var neFile: i64 = 0
411 if pwo >= 0 { npose = wgi[pwo]; neFile = wgi[pwo + 2] }
412 let skwo: i64 = nxa_find(bgi as *u8, fgi, nxa_tag4("SKEL" as *u8))
413 var shoulderParent: i64 = 0 - 2
414 var rootParent: i64 = 0
415 if skwo >= 0 {
416 shoulderParent = wgi[skwo + 1 + infoG[1] * CS_JOINT_WORDS]
417 rootParent = wgi[skwo + 1 + infoG[2] * CS_JOINT_WORDS]
418 }
419 gv_check("T2 a baked world-delta POSE swinging the whole shoulder subtree (at least two joints) about the shoulder is written to both skins and read back with the section check intact: one pose, its entry count equals the subtree, the shoulder is parented to the root, and the root has no parent" as *u8, ((wp1 >= AG_MIN_SUBTREE) as i64) * ((wp2 >= AG_MIN_SUBTREE) as i64) * ((wp1 == wp2) as i64) * ((pwo >= 0) as i64) * ((npose == 1) as i64) * ((neFile == infoG[0]) as i64) * ((shoulderParent == infoG[2]) as i64) * ((rootParent < 0) as i64), ctr)
420
421 // ---- 3. the SERVED player poses both; its receipt is printed whole, it is the third witness ----
422 let capG: *u8 = sys_mmap(AG_CAPTURE_CAP)
423 let lenG: *i64 = sys_mmap(16) as *i64
424 let prcG: i64 = ag_play(p_geo_in, p_geo_out, capG, lenG)
425 hw(" play(geodesic) exit=" as *u8); gv_num(prcG); hw(" captured=" as *u8); gv_num(lenG[0]); hw("\n " as *u8); sys_write(1, capG, lenG[0])
426 let capN: *u8 = sys_mmap(AG_CAPTURE_CAP)
427 let lenN: *i64 = sys_mmap(16) as *i64
428 let prcN: i64 = ag_play(p_near_in, p_near_out, capN, lenN)
429 hw(" play(nearest) exit=" as *u8); gv_num(prcN); hw(" captured=" as *u8); gv_num(lenN[0]); hw("\n " as *u8); sys_write(1, capN, lenN[0])
430 let playMaxG: i64 = ag_num_after(capG, lenG[0], "max_disp_umm=" as *u8)
431 let playMaxN: i64 = ag_num_after(capN, lenN[0], "max_disp_umm=" as *u8)
432 let playSpreadG: i64 = ag_num_after(capG, lenG[0], "disp_spread_umm=" as *u8)
433 let bGo: i64 = ag_open(p_geo_out, lp)
434 let fGo: i64 = lp[0]
435 let bNo: i64 = ag_open(p_near_out, lp)
436 let fNo: i64 = lp[0]
437 gv_check("T3 the DEPLOYED nx_nxa_play forked at its absolute path posed both assets: exit 0, its own receipt reports a nonzero joints_posed, and both posed files read back" as *u8, ((prcG == 0) as i64) * ((prcN == 0) as i64) * ((ag_has(capG, lenG[0], " joints_posed=0 " as *u8) == 0) as i64) * ((ag_has(capN, lenN[0], " joints_posed=0 " as *u8) == 0) as i64) * ag_has(capG, lenG[0], " joints_posed=" as *u8) * ((bGo != 0) as i64) * ((bNo != 0) as i64), ctr)
438 gv_check("T4 the player moved vertices in both (its receipt carries verts_moved above zero) and its own max displacement is nonzero -- the pose is not a no-op" as *u8, ((ag_has(capG, lenG[0], " verts_moved=0 " as *u8) == 0) as i64) * ((ag_has(capN, lenN[0], " verts_moved=0 " as *u8) == 0) as i64) * ((playMaxG > 0) as i64) * ((playMaxN > 0) as i64), ctr)
439
440 // ---- 4. THE ONE MESH RULER: md_measure posed-vs-rest (the limb swung) and posed-geo-vs-posed-near (weights matter) ----
441 let bR: i64 = ag_open(p_rest, lp)
442 let fR: i64 = lp[0]
443 let mRest: *u8 = ag_nxmesh(bR as *u8, fR)
444 let mGeo: *u8 = ag_nxmesh(bGo as *u8, fGo)
445 let mNear: *u8 = ag_nxmesh(bNo as *u8, fNo)
446 let resG: *i64 = md_res()
447 let mdG: i64 = md_measure(mGeo, mRest, resG)
448 let resN: *i64 = md_res()
449 let mdN: i64 = md_measure(mNear, mRest, resN)
450 let resGN: *i64 = md_res()
451 let mdGN: i64 = md_measure(mGeo, mNear, resGN)
452 hw(" md geo-vs-rest refused=" as *u8); gv_num(mdG); hw(" max=" as *u8); gv_num(resG[MD_R_MAX]); hw(" p95=" as *u8); gv_num(resG[MD_R_P95]); hw("\n" as *u8)
453 hw(" md near-vs-rest refused=" as *u8); gv_num(mdN); hw(" max=" as *u8); gv_num(resN[MD_R_MAX]); hw(" p95=" as *u8); gv_num(resN[MD_R_P95]); hw("\n" as *u8)
454 hw(" md geo-vs-near refused=" as *u8); gv_num(mdGN); hw(" max=" as *u8); gv_num(resGN[MD_R_MAX]); hw(" p95=" as *u8); gv_num(resGN[MD_R_P95]); hw("\n" as *u8)
455 gv_check("T5 md_measure (the one ruler, 0.1 mm units) reads the posed geodesic skin at least 10 mm from the rest surface at its farthest vertex -- the 90 degree swing really moved a third of the 30 mm limb -- and the ruler did not refuse" as *u8, ((mdG == 0) as i64) * ((resG[MD_R_MAX] >= AG_MD_MIN_SWING) as i64), ctr)
456 gv_check("T6 the same ruler reads the nearest-weighted pose swinging too (no refusal, max at least 10 mm) -- the control reached the same condition" as *u8, ((mdN == 0) as i64) * ((resN[MD_R_MAX] >= AG_MD_MIN_SWING) as i64), ctr)
457 gv_check("T7 WEIGHTS MATTER: the two posed skins measurably differ under md_measure (max deviation above zero) -- the same skeleton, the same pose, a different surface" as *u8, ((mdGN == 0) as i64) * ((resGN[MD_R_MAX] > 0) as i64), ctr)
458
459 // ---- 5. THE CREASE: adjacent-ring displacement jump across the shoulder ----
460 let jumpG: i64 = ag_max_ring_jump(bR as *u8, fR, bGo as *u8, fGo)
461 let jumpN: i64 = ag_max_ring_jump(bR as *u8, fR, bNo as *u8, fNo)
462 hw(" crease jump geodesic=" as *u8); gv_num(jumpG); hw(" nearest=" as *u8); gv_num(jumpN); hw("\n" as *u8)
463 gv_check("T8 SOFTER CREASE: the largest displacement jump between adjacent 1 mm rings is strictly SMALLER with geodesic weights than with the hard nearest boundary -- the blend spreads the shoulder over the medial radius instead of folding it between two rings" as *u8, ((jumpG < jumpN) as i64) * ((jumpN > 0) as i64), ctr)
464
465 // ---- 6. determinism and the named refusal ----
466 let cap2: *u8 = sys_mmap(AG_CAPTURE_CAP)
467 let len2: *i64 = sys_mmap(16) as *i64
468 ag_play(p_geo_in, p_geo_out2, cap2, len2)
469 gv_check("T9 posing the same asset twice through the served player is BYTE-IDENTICAL" as *u8, ag_files_equal(p_geo_out, p_geo_out2), ctr)
470 let infoK: *i64 = sys_mmap(64) as *i64
471 let wp3: i64 = ag_append_swing(p_rig, p_noskin_in, infoK)
472 let cap3: *u8 = sys_mmap(AG_CAPTURE_CAP)
473 let len3: *i64 = sys_mmap(16) as *i64
474 let prc3: i64 = ag_play(p_noskin_in, p_noskin_out, cap3, len3)
475 hw(" play(no SKIN) exit=" as *u8); gv_num(prc3); hw("\n" as *u8)
476 gv_check("T10 neg-control-refuses-skinless: the rigged-but-unweighted asset (SKEL, no SKIN) carries the same swing and is refused by the player with a nonzero exit -- the pipeline's weight half is load-bearing, not decoration" as *u8, ((wp3 >= AG_MIN_SUBTREE) as i64) * ((prc3 != 0) as i64), ctr)
477
478 gv_values_head()
479 gv_kv("verts" as *u8, nv)
480 gv_kv("geo_mixed_verts" as *u8, geoMixed)
481 gv_kv("swing_subtree_joints" as *u8, wp1)
482 gv_kv("shoulder_joint" as *u8, infoG[1])
483 gv_kv("player_max_disp_umm_geodesic" as *u8, playMaxG)
484 gv_kv("player_max_disp_umm_nearest" as *u8, playMaxN)
485 gv_kv("player_disp_spread_umm_geodesic" as *u8, playSpreadG)
486 gv_kv("md_geo_vs_rest_max_0p1mm" as *u8, resG[MD_R_MAX])
487 gv_kv("md_geo_vs_rest_p95_0p1mm" as *u8, resG[MD_R_P95])
488 gv_kv("md_near_vs_rest_max_0p1mm" as *u8, resN[MD_R_MAX])
489 gv_kv("md_geo_vs_near_max_0p1mm" as *u8, resGN[MD_R_MAX])
490 gv_kv("md_geo_vs_near_p95_0p1mm" as *u8, resGN[MD_R_P95])
491 gv_kv("crease_jump_geodesic_umm" as *u8, jumpG)
492 gv_kv("crease_jump_nearest_umm" as *u8, jumpN)
493 gv_kv("play_exit_geodesic" as *u8, prcG)
494 gv_kv("play_exit_noskin" as *u8, prc3)
495 return gv_verdict("nx_autorig_pose_gate" as *u8, ctr, "end-to-end auto-rig bend: skeleton, weights, a 90 degree shoulder swing baked as world deltas and played by the served player, measured through md_measure and a crease read; twist is the deformer's rung" as *u8)
496}