nx_motion_rig.nx source
↩ module page · 298 lines · 17244 B
1// nx_motion_rig.nx -- THE JOB BEHIND /motion (2026-09-06): one upload directory in; a rigged, rendered, exported result out,
2// every composed organ's own report kept beside it, a sha256 of the rig, and a done marker written LAST.
3// nx_motion_rig rig <jobdir> [cells]
4// nx_motion_rig anim <jobdir> <donor.nxa> <pose_id> <clipkey>
5// The jobdir holds in.<kind> and kind (written by the door, nx_motion_serve). Nothing here re-implements anything: the
6// converters, the auto-rig, the UV unwrap, the rasteriser, the exporter, the retarget and the player are the estate's
7// PROMOTED organs, forked with their stdout kept as log_<stage>.txt. A stage that refuses leaves its exit code in the
8// status rows and the job still finishes with `done`, so the page reads FAILED at a named stage -- never green, never hung.
9// MEASURED ON THE FIRST REAL JOB (house mesh, 2026-09-06, 16.9 s): the textured rasteriser REFUSES an asset without a TEXC
10// section ("run nx_nxa_texc first"), so the render stage now composes nx_nxa_texc (a bone-anchored UV unwrap from the fresh
11// SKEL+SKIN) before nx_nxa_texbake view; and nx_filehash lives at the serving root, not in _offc, so every fork resolves
12// _offc/<name>.elf first and ./<name>.elf second -- a 127 from an absent twin is not a property of the organ.
13// HONEST LIMIT, stated here and on the page: the promoted nx_autorig_mesh applies [cells] to the WEIGHT solve; its skeleton
14// runs at the estate's conf resolution until the one-call organ takes the ladder for both stages (motion.plan MO12).
15// license_tier: ORIGINAL expect_exit: 0
16import "nx_syscalls.nx"
17import "nx_motion_lib.nx"
18
19const MR_EXIT_OK: i64 = 0
20const MR_EXIT_USAGE: i64 = 2
21const MR_EXIT_REFUSE: i64 = 3
22const MR_CELLS_DEFAULT: i64 = 192 // the referee-measured rung on the house rig (923 permil); a caller's cells wins
23const MR_STATUS_CAP: i64 = 8192
24const MR_RC_CONVERT: i64 = 0
25const MR_RC_RIG: i64 = 1
26const MR_RC_TEXC: i64 = 2
27const MR_RC_VIEW: i64 = 3
28const MR_RC_GLB: i64 = 4
29const MR_RC_HASH: i64 = 5
30const MR_RC_WORDS: i64 = 6
31const MR_N_VERTS: i64 = 0
32const MR_N_TRIS: i64 = 1
33const MR_N_LIMBS: i64 = 2
34const MR_N_JOINTS: i64 = 3
35const MR_N_BRANCHES: i64 = 4
36const MR_N_MIXED: i64 = 5
37const MR_N_WORDS: i64 = 6
38const MR_SHA_LEN: i64 = 64
39const MR_RC_KIND_REFUSED: i64 = 415 // the HTTP status the door would answer for the same refusal, kept as one vocabulary
40
41func mrw(s: *u8) -> i64 { sys_write(1, s, mo_slen(s)); return 0 }
42// the organ's elf: the _offc twin when it exists (the runners' root), else the serving root (where registered tools live)
43func mr_elf(name: *u8) -> *u8 {
44 let p: *u8 = sys_mmap(MO_PATH_CAP)
45 var o: i64 = mo_cat(p, 0, "_offc/" as *u8); o = mo_cat(p, o, name); o = mo_cat(p, o, ".elf" as *u8); p[o] = 0 as u8
46 if mo_exists(p) == 1 { return p }
47 o = mo_cat(p, 0, "./" as *u8); o = mo_cat(p, o, name); o = mo_cat(p, o, ".elf" as *u8); p[o] = 0 as u8
48 return p
49}
50func mr_kv(b: *u8, o: i64, k: *u8, v: i64) -> i64 { var p: i64 = mo_cat(b, o, k); b[p] = 61 as u8; p = mo_num(b, p + 1, v); b[p] = 10 as u8; return p + 1 }
51func mr_ks(b: *u8, o: i64, k: *u8, s: *u8) -> i64 { var p: i64 = mo_cat(b, o, k); b[p] = 61 as u8; p = mo_cat(b, p + 1, s); b[p] = 10 as u8; return p + 1 }
52// the status rows, truncate-written at EVERY stage so a job that dies leaves its last stage named
53func mr_status(jobdir: *u8, kind: *u8, cells: i64, stage: *u8, ok: i64, rcs: *i64, nums: *i64, sha: *u8, ms: i64) -> i64 {
54 let b: *u8 = sys_mmap(MR_STATUS_CAP)
55 var o: i64 = 0
56 o = mr_ks(b, o, "stage" as *u8, stage)
57 o = mr_kv(b, o, "ok" as *u8, ok)
58 o = mr_ks(b, o, "kind" as *u8, kind)
59 o = mr_kv(b, o, "cells" as *u8, cells)
60 o = mr_kv(b, o, "rc_convert" as *u8, rcs[MR_RC_CONVERT])
61 o = mr_kv(b, o, "rc_rig" as *u8, rcs[MR_RC_RIG])
62 o = mr_kv(b, o, "rc_texc" as *u8, rcs[MR_RC_TEXC])
63 o = mr_kv(b, o, "rc_view" as *u8, rcs[MR_RC_VIEW])
64 o = mr_kv(b, o, "rc_glb" as *u8, rcs[MR_RC_GLB])
65 o = mr_kv(b, o, "rc_hash" as *u8, rcs[MR_RC_HASH])
66 o = mr_kv(b, o, "verts" as *u8, nums[MR_N_VERTS])
67 o = mr_kv(b, o, "tris" as *u8, nums[MR_N_TRIS])
68 o = mr_kv(b, o, "limbs" as *u8, nums[MR_N_LIMBS])
69 o = mr_kv(b, o, "joints" as *u8, nums[MR_N_JOINTS])
70 o = mr_kv(b, o, "branches" as *u8, nums[MR_N_BRANCHES])
71 o = mr_kv(b, o, "mixed_verts" as *u8, nums[MR_N_MIXED])
72 o = mr_ks(b, o, "rig_sha256" as *u8, sha)
73 o = mr_kv(b, o, "ms" as *u8, ms)
74 let r: i64 = mo_write_all(mo_join(jobdir, "status" as *u8), b, o)
75 sys_munmap(b, MR_STATUS_CAP)
76 return r
77}
78func mr_done(jobdir: *u8) -> i64 { return mo_write_all(mo_join(jobdir, "done" as *u8), "1\n" as *u8, 2) }
79// the log's reported integer for key, or -1
80func mr_logv(path: *u8, key: *u8) -> i64 {
81 let lp: *i64 = sys_mmap(16) as *i64
82 let b: *u8 = mo_read(path, lp)
83 if (b as i64) == 0 { return 0 - 1 }
84 return mo_log_field(b, lp[0], key)
85}
86// nx_filehash prints {"organ":"nx_filehash","sha256":"<64 hex>",...}; copy the 64 hex into out (NUL-terminated), or "-"
87func mr_sha_from_log(path: *u8, out: *u8) -> i64 {
88 let lp: *i64 = sys_mmap(16) as *i64
89 let b: *u8 = mo_read(path, lp)
90 out[0] = 45 as u8; out[1] = 0 as u8
91 if (b as i64) == 0 { return 0 }
92 let p: i64 = mo_find(b, lp[0], "\"sha256\":\"" as *u8)
93 if p < 0 { return 0 }
94 let s: i64 = p + 10
95 if s + MR_SHA_LEN > lp[0] { return 0 }
96 var i: i64 = 0
97 while i < MR_SHA_LEN { out[i] = b[s + i]; i = i + 1 }
98 out[MR_SHA_LEN] = 0 as u8
99 return MR_SHA_LEN
100}
101// render an asset from three sides: TEXC first (the rasteriser refuses without it), then the textured view. Returns the
102// view's exit code and stores the unwrap's in rc_texc; a render failure never fails the rig, the page shows the codes.
103func mr_render(jobdir: *u8, src: *u8, texp: *u8, png: *u8, logt: *u8, logv: *u8, rc_texc: *i64) -> i64 {
104 let words: *i64 = sys_mmap(MO_ARGV_CAP * 8) as *i64
105 words[0] = src as i64; words[1] = texp as i64
106 rc_texc[0] = mo_run(mr_elf("nx_nxa_texc" as *u8), words, 2, logt)
107 var viewsrc: *u8 = texp
108 if rc_texc[0] != 0 { viewsrc = src }
109 let vview: *u8 = "view" as *u8
110 words[0] = vview as i64; words[1] = viewsrc as i64; words[2] = png as i64
111 return mo_run(mr_elf("nx_nxa_texbake" as *u8), words, 3, logv)
112}
113func mr_rig(jobdir: *u8, cells: i64) -> i64 {
114 let t0: i64 = sys_clock_now_us()
115 let lp: *i64 = sys_mmap(16) as *i64
116 let rcs: *i64 = sys_mmap(MR_RC_WORDS * 8 + 8) as *i64
117 let nums: *i64 = sys_mmap(MR_N_WORDS * 8 + 8) as *i64
118 var k: i64 = 0
119 while k < MR_RC_WORDS { rcs[k] = 0 - 1; k = k + 1 }
120 k = 0
121 while k < MR_N_WORDS { nums[k] = 0 - 1; k = k + 1 }
122 let sha: *u8 = sys_mmap(MR_SHA_LEN + 8)
123 sha[0] = 45 as u8; sha[1] = 0 as u8
124 // the kind the door sniffed, one word on one line
125 let kb: *u8 = mo_read(mo_join(jobdir, "kind" as *u8), lp)
126 let kind: *u8 = sys_mmap(16)
127 var kn: i64 = 0
128 if (kb as i64) != 0 { while kn < lp[0] { if kn < 12 { let c: i64 = kb[kn] as i64; if c == 10 { break } if c == 13 { break } kind[kn] = kb[kn]; kn = kn + 1 } else { break } } }
129 kind[kn] = 0 as u8
130 let kk: i64 = mo_kind_from_name(kind)
131 mr_status(jobdir, kind, cells, "convert" as *u8, 0, rcs, nums, sha, 0)
132 // convert: in.<kind> -> bare.nxa through the estate's own readers, or a byte copy for an NXA
133 let inname: *u8 = sys_mmap(32)
134 var io: i64 = mo_cat(inname, 0, "in." as *u8)
135 io = mo_cat(inname, io, kind)
136 inname[io] = 0 as u8
137 let inpath: *u8 = mo_join(jobdir, inname)
138 let bare: *u8 = mo_join(jobdir, "bare.nxa" as *u8)
139 let logc: *u8 = mo_join(jobdir, "log_convert.txt" as *u8)
140 let words: *i64 = sys_mmap(MO_ARGV_CAP * 8) as *i64
141 var rc: i64 = 0 - 1
142 if kk == MO_KIND_GLB { words[0] = inpath as i64; words[1] = bare as i64; rc = mo_run(mr_elf("nx_gltf2mesh" as *u8), words, 2, logc) }
143 if kk == MO_KIND_FBX { words[0] = inpath as i64; words[1] = bare as i64; rc = mo_run(mr_elf("nx_fbx2nxa" as *u8), words, 2, logc) }
144 if kk == MO_KIND_NXA {
145 let ib: *u8 = mo_read(inpath, lp)
146 if (ib as i64) == 0 { rc = 3; mo_write_all(logc, "COPY REFUSED: the uploaded NXA could not be read back\n" as *u8, 54) } else {
147 let w: i64 = mo_write_all(bare, ib, lp[0])
148 if w == lp[0] { rc = 0 } else { rc = 6 }
149 let lb: *u8 = sys_mmap(128)
150 var lo: i64 = mo_cat(lb, 0, "COPY bytes=" as *u8); lo = mo_num(lb, lo, lp[0]); lo = mo_cat(lb, lo, " (an NXA needs no reader: the container is already ours)\n" as *u8)
151 mo_write_all(logc, lb, lo)
152 }
153 }
154 if rc == (0 - 1) {
155 rc = MR_RC_KIND_REFUSED
156 let lb: *u8 = sys_mmap(256)
157 var lo: i64 = mo_cat(lb, 0, "CONVERT REFUSED BY NAME: kind=" as *u8); lo = mo_cat(lb, lo, kind); lo = mo_cat(lb, lo, " has no sovereign reader composed on this door (OBJ and unknown bytes are refused, never guessed)\n" as *u8)
158 mo_write_all(logc, lb, lo)
159 }
160 rcs[MR_RC_CONVERT] = rc
161 if rc != 0 {
162 mr_status(jobdir, kind, cells, "convert" as *u8, 0, rcs, nums, sha, (sys_clock_now_us() - t0) / 1000)
163 mr_done(jobdir)
164 mrw("MOTION-RIG FAILED stage=convert rc=" as *u8); let nb: *u8 = sys_mmap(32); let nn: i64 = mo_num(nb, 0, rc); sys_write(1, nb, nn); mrw("\n" as *u8)
165 return MR_EXIT_REFUSE
166 }
167 // rig: the one-call auto-rig, skeleton then weights
168 mr_status(jobdir, kind, cells, "rig" as *u8, 0, rcs, nums, sha, (sys_clock_now_us() - t0) / 1000)
169 let skel: *u8 = mo_join(jobdir, "skel.nxa" as *u8)
170 let rig: *u8 = mo_join(jobdir, "rig.nxa" as *u8)
171 let logr: *u8 = mo_join(jobdir, "log_rig.txt" as *u8)
172 let cellstr: *u8 = sys_mmap(32)
173 let cl: i64 = mo_num(cellstr, 0, cells)
174 cellstr[cl] = 0 as u8
175 words[0] = bare as i64; words[1] = skel as i64; words[2] = rig as i64; words[3] = cellstr as i64
176 rc = mo_run(mr_elf("nx_autorig_mesh" as *u8), words, 4, logr)
177 rcs[MR_RC_RIG] = rc
178 nums[MR_N_VERTS] = mr_logv(logr, "verts" as *u8)
179 nums[MR_N_TRIS] = mr_logv(logr, "tris" as *u8)
180 nums[MR_N_LIMBS] = mr_logv(logr, "limbs" as *u8)
181 nums[MR_N_JOINTS] = mr_logv(logr, "joints" as *u8)
182 nums[MR_N_BRANCHES] = mr_logv(logr, "branches" as *u8)
183 nums[MR_N_MIXED] = mr_logv(logr, "mixed_verts" as *u8)
184 if rc != 0 {
185 mr_status(jobdir, kind, cells, "rig" as *u8, 0, rcs, nums, sha, (sys_clock_now_us() - t0) / 1000)
186 mr_done(jobdir)
187 mrw("MOTION-RIG FAILED stage=rig\n" as *u8)
188 return MR_EXIT_REFUSE
189 }
190 // view: UV unwrap then the sovereign three-view render (a failure here does not fail the rig; the page shows the codes)
191 mr_status(jobdir, kind, cells, "view" as *u8, 0, rcs, nums, sha, (sys_clock_now_us() - t0) / 1000)
192 let rct: *i64 = sys_mmap(16) as *i64
193 rcs[MR_RC_VIEW] = mr_render(jobdir, rig, mo_join(jobdir, "rig_tex.nxa" as *u8), mo_join(jobdir, "rig_view.png" as *u8), mo_join(jobdir, "log_texc.txt" as *u8), mo_join(jobdir, "log_view.txt" as *u8), rct)
194 rcs[MR_RC_TEXC] = rct[0]
195 // glb: the engine-ready export with its skin
196 mr_status(jobdir, kind, cells, "glb" as *u8, 0, rcs, nums, sha, (sys_clock_now_us() - t0) / 1000)
197 let glb: *u8 = mo_join(jobdir, "rig.glb" as *u8)
198 words[0] = rig as i64; words[1] = glb as i64
199 rcs[MR_RC_GLB] = mo_run(mr_elf("nx_mesh2glb" as *u8), words, 2, mo_join(jobdir, "log_glb.txt" as *u8))
200 // hash: the determinism witness
201 let logh: *u8 = mo_join(jobdir, "log_hash.txt" as *u8)
202 words[0] = rig as i64
203 rcs[MR_RC_HASH] = mo_run(mr_elf("nx_filehash" as *u8), words, 1, logh)
204 mr_sha_from_log(logh, sha)
205 let ms: i64 = (sys_clock_now_us() - t0) / 1000
206 mr_status(jobdir, kind, cells, "done" as *u8, 1, rcs, nums, sha, ms)
207 mr_done(jobdir)
208 mrw("MOTION-RIG OK limbs=" as *u8)
209 let nb2: *u8 = sys_mmap(256)
210 var no: i64 = mo_num(nb2, 0, nums[MR_N_LIMBS]); no = mo_cat(nb2, no, " joints=" as *u8); no = mo_num(nb2, no, nums[MR_N_JOINTS]); no = mo_cat(nb2, no, " sha256=" as *u8); no = mo_cat(nb2, no, sha); no = mo_cat(nb2, no, " ms=" as *u8); no = mo_num(nb2, no, ms); no = mo_cat(nb2, no, "\n" as *u8)
211 sys_write(1, nb2, no)
212 return MR_EXIT_OK
213}
214func mr_anim(jobdir: *u8, donor: *u8, pose: *u8, key: *u8) -> i64 {
215 if mo_safe_name(key) == 0 { mrw("MOTION-ANIM REFUSED: clip key is not a safe name\n" as *u8); return MR_EXIT_REFUSE }
216 if mo_digits_only(pose) == 0 { mrw("MOTION-ANIM REFUSED: pose id is not a number\n" as *u8); return MR_EXIT_REFUSE }
217 let rig: *u8 = mo_join(jobdir, "rig.nxa" as *u8)
218 if mo_exists(rig) == 0 { mrw("MOTION-ANIM REFUSED: the job has no rig.nxa yet\n" as *u8); return MR_EXIT_REFUSE }
219 if mo_exists(donor) == 0 { mrw("MOTION-ANIM REFUSED: donor absent\n" as *u8); return MR_EXIT_REFUSE }
220 let nb: *u8 = sys_mmap(MO_PATH_CAP)
221 var o: i64 = mo_cat(nb, 0, "anim_" as *u8); o = mo_cat(nb, o, key); o = mo_cat(nb, o, ".nxa" as *u8); nb[o] = 0 as u8
222 let anim: *u8 = mo_join(jobdir, nb)
223 o = mo_cat(nb, 0, "posed_" as *u8); o = mo_cat(nb, o, key); o = mo_cat(nb, o, ".nxa" as *u8); nb[o] = 0 as u8
224 let posed: *u8 = mo_join(jobdir, nb)
225 o = mo_cat(nb, 0, "posed_" as *u8); o = mo_cat(nb, o, key); o = mo_cat(nb, o, "_tex.nxa" as *u8); nb[o] = 0 as u8
226 let posedtex: *u8 = mo_join(jobdir, nb)
227 o = mo_cat(nb, 0, "posed_" as *u8); o = mo_cat(nb, o, key); o = mo_cat(nb, o, ".png" as *u8); nb[o] = 0 as u8
228 let png: *u8 = mo_join(jobdir, nb)
229 o = mo_cat(nb, 0, "posed_" as *u8); o = mo_cat(nb, o, key); o = mo_cat(nb, o, ".glb" as *u8); nb[o] = 0 as u8
230 let glb: *u8 = mo_join(jobdir, nb)
231 o = mo_cat(nb, 0, "log_ret_" as *u8); o = mo_cat(nb, o, key); o = mo_cat(nb, o, ".txt" as *u8); nb[o] = 0 as u8
232 let logr: *u8 = mo_join(jobdir, nb)
233 o = mo_cat(nb, 0, "log_play_" as *u8); o = mo_cat(nb, o, key); o = mo_cat(nb, o, ".txt" as *u8); nb[o] = 0 as u8
234 let logp: *u8 = mo_join(jobdir, nb)
235 o = mo_cat(nb, 0, "log_texc_" as *u8); o = mo_cat(nb, o, key); o = mo_cat(nb, o, ".txt" as *u8); nb[o] = 0 as u8
236 let logt: *u8 = mo_join(jobdir, nb)
237 o = mo_cat(nb, 0, "log_view_" as *u8); o = mo_cat(nb, o, key); o = mo_cat(nb, o, ".txt" as *u8); nb[o] = 0 as u8
238 let logv: *u8 = mo_join(jobdir, nb)
239 o = mo_cat(nb, 0, "log_glb_" as *u8); o = mo_cat(nb, o, key); o = mo_cat(nb, o, ".txt" as *u8); nb[o] = 0 as u8
240 let logg: *u8 = mo_join(jobdir, nb)
241 let words: *i64 = sys_mmap(MO_ARGV_CAP * 8) as *i64
242 let vret: *u8 = "retarget" as *u8
243 let vemit: *u8 = "emit=pose" as *u8
244 words[0] = vret as i64; words[1] = donor as i64; words[2] = pose as i64; words[3] = rig as i64; words[4] = anim as i64; words[5] = vemit as i64
245 let rc1: i64 = mo_run(mr_elf("nx_nxa_retarget" as *u8), words, 6, logr)
246 let vfull: *u8 = "1000" as *u8
247 words[0] = anim as i64; words[1] = pose as i64; words[2] = posed as i64; words[3] = vfull as i64
248 var rc2: i64 = 0 - 1
249 if rc1 == 0 { rc2 = mo_run(mr_elf("nx_nxa_play" as *u8), words, 4, logp) }
250 let jp: i64 = mr_logv(logp, "joints_posed" as *u8)
251 let vm: i64 = mr_logv(logp, "verts_moved" as *u8)
252 let md: i64 = mr_logv(logp, "max_disp_umm" as *u8)
253 var rc3: i64 = 0 - 1
254 var rc4: i64 = 0 - 1
255 let rct: *i64 = sys_mmap(16) as *i64
256 rct[0] = 0 - 1
257 if rc2 == 0 {
258 rc3 = mr_render(jobdir, posed, posedtex, png, logt, logv, rct)
259 words[0] = posed as i64; words[1] = glb as i64
260 rc4 = mo_run(mr_elf("nx_mesh2glb" as *u8), words, 2, logg)
261 }
262 // the applied-clip row: key|rc_retarget|rc_play|joints_posed|verts_moved|max_disp_umm|rc_view|rc_glb|rc_texc
263 let row: *u8 = sys_mmap(MO_VAL_CAP)
264 var r: i64 = mo_cat(row, 0, key)
265 row[r] = 124 as u8; r = mo_num(row, r + 1, rc1)
266 row[r] = 124 as u8; r = mo_num(row, r + 1, rc2)
267 row[r] = 124 as u8; r = mo_num(row, r + 1, jp)
268 row[r] = 124 as u8; r = mo_num(row, r + 1, vm)
269 row[r] = 124 as u8; r = mo_num(row, r + 1, md)
270 row[r] = 124 as u8; r = mo_num(row, r + 1, rc3)
271 row[r] = 124 as u8; r = mo_num(row, r + 1, rc4)
272 row[r] = 124 as u8; r = mo_num(row, r + 1, rct[0])
273 row[r] = 10 as u8; r = r + 1
274 mo_append(mo_join(jobdir, "clips" as *u8), row, r)
275 mrw("MOTION-ANIM " as *u8); sys_write(1, row, r)
276 if rc1 != 0 { return MR_EXIT_REFUSE }
277 if rc2 != 0 { return MR_EXIT_REFUSE }
278 return MR_EXIT_OK
279}
280func main(argc: i64, argv: *i64) -> i64 {
281 if argc < 3 {
282 mrw("usage: nx_motion_rig rig <jobdir> [cells] | anim <jobdir> <donor.nxa> <pose_id> <clipkey>\n" as *u8)
283 return MR_EXIT_USAGE
284 }
285 let verb: *u8 = argv[1] as *u8
286 if mo_streq(verb, "rig" as *u8) == 1 {
287 var cells: i64 = MR_CELLS_DEFAULT
288 if argc >= 4 { let a: *u8 = argv[3] as *u8; cells = mo_atoi(a, mo_slen(a)) }
289 if cells < 1 { cells = MR_CELLS_DEFAULT }
290 return mr_rig(argv[2] as *u8, cells)
291 }
292 if mo_streq(verb, "anim" as *u8) == 1 {
293 if argc < 6 { mrw("usage: nx_motion_rig anim <jobdir> <donor.nxa> <pose_id> <clipkey>\n" as *u8); return MR_EXIT_USAGE }
294 return mr_anim(argv[2] as *u8, argv[3] as *u8, argv[4] as *u8, argv[5] as *u8)
295 }
296 mrw("usage: nx_motion_rig rig <jobdir> [cells] | anim <jobdir> <donor.nxa> <pose_id> <clipkey>\n" as *u8)
297 return MR_EXIT_USAGE
298}