nx_autorig_pose_gate.nx
buildroot/runtime/nx_autorig_pose_gate.nx
about
nx_autorig_pose_gate.nx -- THE END-TO-END DONE-RULE GATE FOR DC16 (the BEND case): a BARE mesh goes through
nx_curveskel (skeleton) then nx_boneheat (weights), a 90 degree shoulder SWING is baked into the asset as a POSE
and PLAYED by the served nx_nxa_play, and the deformed skin is measured through md_measure -- this estate's ONE
mesh deviation ruler, never a second one -- plus a direct crease read across the shoulder.
THE POSE IS A BAKED WORLD DELTA, BECAUSE THAT IS THE PLAYER'S CONTRACT. nx_nxa_play applies, per joint,
M x = D(x - bind_t) + bind_t + dt with NO hierarchy walk at runtime (nxa_format_spec.md), so a single-joint
entry moves only that joint's skinned band -- measured 2026-09-05: one entry on joint 1 read md max=3 against a
10 mm bar while the ring crease read 5 mm, i.e. a local sleeve, not a swing. A real bend therefore carries one
entry per joint of the swung subtree: D = R and dt = (R - I)(bind_t_j - P), which is exactly the rigid swing
out = R(v - P) + P about the shoulder pivot P, while the rest of the body stays put.
UNITS, MEASURED NOT ASSUMED: the NXMESH handed to md_measure is in millimetres (NXA 0.01 mm -> tenths -> mm at
nm_put_tri), and md_measure reports in 0.1 mm: the 16-joint swing read max=259 (a ~26 mm tip), the twist
sleeve read 3 (0.3 mm). A bar written in 0.01 mm quanta here was 10x too high and failed a real swing.
WHAT IT PROVES AND WHAT IT DOES NOT. Smooth geodesic weights make the shoulder a SOFTER crease than the hard
nearest-joint boundary (the adjacent-ring displacement jump is smaller), and the pose really swings the limb
(md_measure posed-vs-rest reaches a derived floor). It does NOT prove the twist case: candy-wrapper collapse
under an axial twist is a DEFORMER property (nx_skeleton is linear-blend only, no dual-quaternion path), and
smoother weights make that collapse worse, so it is deferred to a dual-quaternion rung on the deformer's board.
SUBJECTS: the two libs in-process, the PLAYER forked as the deployed binary at its absolute path.
license_tier: ORIGINAL expect_exit: 0
dependencies 8 imports · 0 importers
imports: nx_syscalls.nxnx_gate_verdict.nxnx_nxa.nxnx_curveskel_lib.nxnx_tool_run.nxnx_nxmesh_lib.nxnx_mmdev_lib.nxnx_nxa_fk.nx
imported by: nobody (leaf or entry point)
call flow from main pre-order; caps 40 nodes / depth 6 declared; ↻ = already shown
structs
| none |
consts
| 30 | const AG_DIR: *u8 = "/tmp/nx_autorig_pose_gate" |
| 31 | const AG_MODE_DIR: i64 = 493 |
| 32 | const AG_PLAY: *u8 = "/volume1/homes/elderwesto/nishihost/nx_nxa_play.elf" |
| 33 | const AG_AROUND: i64 = 4 |
| 34 | const AG_LEN: i64 = 6000 // 60 mm prism, 0.01 mm units |
| 35 | const AG_RINGS: i64 = 61 // one ring per mm |
| 36 | const AG_RING_STEP: i64 = 100 // mm between rings in 0.01 mm units |
| 37 | const AG_R: i64 = 300 // 3 mm half-width |
| 38 | const AG_CELLS: i64 = 64 |
| 39 | const AG_VERT_CAP: i64 = 1024 |
| 40 | const AG_TRI_CAP: i64 = 4096 |
| 41 | const AG_POSE_ID: i64 = 1 |
| 42 | const AG_POSE_ID_ARG: *u8 = "1" |
| 43 | const AG_WFULL_ARG: *u8 = "1000" |
| 44 | const AG_Q12_SIN45: i64 = 2896 // round(4096 x sin 45): a 90 degree turn about x is (s,0,0,s) |
| 45 | const AG_POSE_HDR_WORDS: i64 = 3 // npose, pid, ne |
| 46 | const AG_POSE_ENTRY_WORDS: i64 = 8 // joint, qx qy qz qw, tx ty tz (the player's entry layout) |
| 47 | const AG_MIN_SUBTREE: i64 = 2 // a swing of fewer than two joints is a sleeve, not a limb |
| 48 | const AG_CAPTURE_CAP: i64 = 65536 |
| 49 | const AG_ARGV_SLOTS: i64 = 8 |
| 50 | const AG_WORD_BYTES: i64 = 8 |
| 51 | const AG_CWD_CAP: i64 = 4096 |
| 52 | const AG_NO_TIMEOUT: i64 = 0 |
| 53 | const AG_SLASH: i64 = 47 |
| 54 | const AG_NXA_PER_TENTH: i64 = 10 // NXA 0.01 mm -> NXMESH input tenths of a mm |
| 55 | const 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 |
| 56 | const AG_MAGIC: *u8 = "NXMSH2" // the ruler reads ntris at 12 and never the magic; written for readers that do |
| 57 | const AG_MAGIC_LEN: i64 = 6 |
| 58 | const AG_MAGIC_FIELD: i64 = 8 |
| 59 | const AG_ROOT: i64 = 0 - 1 |
functions
| 61 | func hw(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 } |
| 62 | func ag_ring_xy(k: i64, r: i64, out: *i64) -> i64 called by 1: ag_prism |
| 69 | func ag_tri(tris: *i64, st: *i64, a: i64, b: i64, c: i64) -> i64 called by 1: ag_prism |
| 75 | func ag_prism(vert: *i64, tris: *i64, st: *i64, r: i64, z0: i64, z1: i64, rings: i64) -> i64 |
| 120 | func ag_write2(path: *u8, vert: *i64, tris: *i64, st: *i64) -> i64 |
| 136 | func ag_append_swing(inpath: *u8, outpath: *u8, info: *i64) -> i64 |
| 220 | func ag_dirname(path: *u8, out: *u8) -> i64 called by 1: ag_play |
| 231 | func ag_play(inp: *u8, outp: *u8, cap: *u8, olen: *i64) -> i64 |
| 243 | func ag_find(b: *u8, n: i64, lit: *u8) -> i64 |
| 257 | func ag_has(b: *u8, n: i64, lit: *u8) -> i64 { if ag_find(b, n, lit) >= 0 { return 1 } return 0 } |
| 260 | func ag_num_after(b: *u8, n: i64, key: *u8) -> i64 |
| 280 | func ag_nxmesh(b: *u8, flen: i64) -> *u8 |
| 312 | func ag_disp(ra: *u8, fa: i64, pb: *u8, fb: i64, v: i64) -> i64 |
| 323 | func ag_max_ring_jump(ra: *u8, fa: i64, pb: *u8, fb: i64) -> i64 |
| 340 | func ag_open(path: *u8, lp: *i64) -> i64 |
| 345 | func ag_files_equal(pa: *u8, pb: *u8) -> i64 |
| 358 | func main() -> i64 |