code wiki / (root) / nx_autorig_pose_gate.nx

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}