nx_nxa_posewrite_lib.nx source
↩ module page · 170 lines · 7463 B
1// nx_nxa_posewrite_lib.nx -- THE ONE POSE-SECTION WRITER for the NXA format (spec: knowledge/nxa_format_spec.md POSE).
2// WHY (measured 2026-09-05, /compare/modding MD3): the estate had exactly ONE writer of a POSE section -- inline inside
3// nx_nxa_pose freeze, an organ that is SOURCE-ONLY -- and nx_nxa_retarget, which computes a complete pose (a world-delta
4// quat and a dt per target joint, the exact POSE entry shape), published it by BAKING the posed VERT in place instead.
5// The baked donor is a statue with a different shape: nx_nxa_play reads poses_in_library=0 joints_posed=0 on every
6// retargeted output while the house rig reads 104/104, so nothing downstream can replay, hold, or sequence the pose.
7// A writer that lives in one organ and is re-typed in the next is the duplicate-ruler defect; this lib is that writer,
8// composed by both (nx_nxa_retarget emit=pose today; nx_nxa_pose freeze is the migration owed).
9// Layout written (nx_nxa.nx + the nx_nxa_pose incumbent): header 4 words [magic, NXA_VER, nsections, toc_check]; TOC 4
10// words per section [tag, byte_offset, word_len, payload_check]; payloads follow in TOC order. POSE payload: [count, then
11// per pose: pose_id, nentries, entries of NPW_WPE=8 words (joint, qx, qy, qz, qw q12, dtx, dty, dtz MODEL units)]. Every
12// existing section EXCEPT an old POSE is copied unchanged; the new POSE payload keeps every old pose whose id differs from
13// the one written (so re-emitting an id REPLACES, never DUPLICATES) and appends the new one LAST.
14// q and dt are indexed by JOINT id (q[joint*4], dt[joint*3]) and jl lists the joints to emit, so a dense per-joint table
15// (the retarget) and a tracked subset (the freeze) share one signature.
16// license_tier: ORIGINAL No hw writes (Rule 26).
17import "nx_syscalls.nx"
18import "nx_nxa.nx"
19
20const NPW_WPE: i64 = 8 // words per POSE entry (spec)
21const NPW_HDR_BYTES: i64 = 32 // 4 header words
22const NPW_TOC_BYTES: i64 = 32 // 4 TOC words per section
23const NPW_TOC_W: i64 = 4
24const NPW_TOC_MAX: i64 = 64 // nxa_find refuses ns > 64, so a writer must refuse before making such a file
25const NPW_MODE: i64 = 420 // 0644, every sibling NXA writer
26const NPW_ERR_NOTNXA: i64 = 0 - 1
27const NPW_ERR_TOCFULL: i64 = 0 - 2
28const NPW_ERR_OPEN: i64 = 0 - 3
29const NPW_ERR_SHORT: i64 = 0 - 4
30const NPW_ERR_PAYLOAD: i64 = 0 - 5 // built payload did not fill its predicted length: a partition that did not sum
31const NPW_ST_SECTIONS: i64 = 0 // st[] slots the writer fills for the caller's receipt
32const NPW_ST_KEPT: i64 = 1
33const NPW_ST_PLEN: i64 = 2
34const NPW_ST_N: i64 = 3
35const NPW_ST_W: i64 = 4
36
37// payload words a POSE section needs after writing pose `pid` with n entries over the file's existing library
38func npw_payload_words(b: *u8, flen: i64, pid: i64, n: i64, st: *i64) -> i64 {
39 let h: *i64 = b as *i64
40 var keptw: i64 = 0
41 var keptn: i64 = 0
42 let pwo: i64 = nxa_find(b, flen, nxa_tag4("POSE" as *u8))
43 if pwo >= 0 {
44 let np: i64 = h[pwo]
45 var rp: i64 = pwo + 1
46 var p: i64 = 0
47 while p < np {
48 let sz: i64 = 2 + h[rp+1] * NPW_WPE
49 if h[rp] != pid { keptw = keptw + sz; keptn = keptn + 1 }
50 rp = rp + sz
51 p = p + 1
52 }
53 }
54 st[NPW_ST_KEPT] = keptn
55 return 1 + keptw + 2 + n * NPW_WPE
56}
57
58// fill pp: kept poses first, the new pose LAST. Returns the words written (must equal npw_payload_words).
59func npw_build_payload(b: *u8, flen: i64, pid: i64, jl: *i64, q: *i64, dt: *i64, n: i64, pp: *i64) -> i64 {
60 let h: *i64 = b as *i64
61 let pwo: i64 = nxa_find(b, flen, nxa_tag4("POSE" as *u8))
62 var wp: i64 = 1
63 var keptn: i64 = 0
64 if pwo >= 0 {
65 let np: i64 = h[pwo]
66 var rp: i64 = pwo + 1
67 var p: i64 = 0
68 while p < np {
69 let sz: i64 = 2 + h[rp+1] * NPW_WPE
70 if h[rp] != pid {
71 var c: i64 = 0
72 while c < sz { pp[wp+c] = h[rp+c]; c = c + 1 }
73 wp = wp + sz
74 keptn = keptn + 1
75 }
76 rp = rp + sz
77 p = p + 1
78 }
79 }
80 pp[0] = keptn + 1
81 pp[wp] = pid
82 pp[wp+1] = n
83 wp = wp + 2
84 var e: i64 = 0
85 while e < n {
86 let jj: i64 = jl[e]
87 pp[wp] = jj
88 pp[wp+1] = q[jj*4]; pp[wp+2] = q[jj*4+1]; pp[wp+3] = q[jj*4+2]; pp[wp+4] = q[jj*4+3]
89 pp[wp+5] = dt[jj*3]; pp[wp+6] = dt[jj*3+1]; pp[wp+7] = dt[jj*3+2]
90 wp = wp + NPW_WPE
91 e = e + 1
92 }
93 return wp
94}
95
96// write every byte or report how many landed (a short write is a named refusal, never a truncated asset)
97func npw_put(fd: i64, p: *u8, n: i64) -> i64 {
98 var done: i64 = 0
99 while done < n {
100 let k: i64 = sys_write(fd, ((p as i64) + done) as *u8, n - done)
101 if k <= 0 { return done }
102 done = done + k
103 }
104 return done
105}
106
107// Rewrite the NXA at b[0..flen) to outp with pose `pid` (n entries over joints jl, quats q, deltas dt) in its POSE
108// library. Returns bytes written (> 0) or a negative NPW_ERR_*; st[NPW_ST_*] carries sections / kept poses / payload
109// words / entries for the caller's receipt.
110func npw_write(b: *u8, flen: i64, pid: i64, jl: *i64, q: *i64, dt: *i64, n: i64, outp: *u8, st: *i64) -> i64 {
111 let h: *i64 = b as *i64
112 if flen < NPW_HDR_BYTES { return NPW_ERR_NOTNXA }
113 if h[0] != nxa_magic() { return NPW_ERR_NOTNXA }
114 let plen: i64 = npw_payload_words(b, flen, pid, n, st)
115 let pp: *i64 = sys_mmap(plen * 8 + 64) as *i64
116 let got: i64 = npw_build_payload(b, flen, pid, jl, q, dt, n, pp)
117 if got != plen { return NPW_ERR_PAYLOAD }
118 let ptag: i64 = nxa_tag4("POSE" as *u8)
119 let ons: i64 = h[2]
120 let otoc: *i64 = ((b as i64) + NPW_HDR_BYTES) as *i64
121 var keep: i64 = 0
122 var ci: i64 = 0
123 while ci < ons { if otoc[ci*NPW_TOC_W] != ptag { keep = keep + 1 } ci = ci + 1 }
124 let ns2: i64 = keep + 1
125 if ns2 > NPW_TOC_MAX { return NPW_ERR_TOCFULL }
126 let hdr: *i64 = sys_mmap(64) as *i64
127 let toc: *i64 = sys_mmap(ns2 * NPW_TOC_BYTES + 64) as *i64
128 var o: i64 = NPW_HDR_BYTES + ns2 * NPW_TOC_BYTES
129 var wi: i64 = 0
130 ci = 0
131 while ci < ons {
132 if otoc[ci*NPW_TOC_W] != ptag {
133 toc[wi*NPW_TOC_W] = otoc[ci*NPW_TOC_W]
134 toc[wi*NPW_TOC_W+1] = o
135 toc[wi*NPW_TOC_W+2] = otoc[ci*NPW_TOC_W+2]
136 toc[wi*NPW_TOC_W+3] = otoc[ci*NPW_TOC_W+3]
137 o = o + otoc[ci*NPW_TOC_W+2] * 8
138 wi = wi + 1
139 }
140 ci = ci + 1
141 }
142 toc[wi*NPW_TOC_W] = ptag
143 toc[wi*NPW_TOC_W+1] = o
144 toc[wi*NPW_TOC_W+2] = plen
145 toc[wi*NPW_TOC_W+3] = nxa_check2(1, pp, plen)
146 let total: i64 = o + plen * 8
147 hdr[0] = nxa_magic()
148 hdr[1] = NXA_VER
149 hdr[2] = ns2
150 hdr[3] = nxa_check2(1, toc, ns2 * NPW_TOC_W)
151 let fd: i64 = sys_openat_wr(outp, NPW_MODE)
152 if fd < 0 { return NPW_ERR_OPEN }
153 var wrote: i64 = 0
154 wrote = wrote + npw_put(fd, hdr as *u8, NPW_HDR_BYTES)
155 wrote = wrote + npw_put(fd, toc as *u8, ns2 * NPW_TOC_BYTES)
156 ci = 0
157 while ci < ons {
158 if otoc[ci*NPW_TOC_W] != ptag {
159 wrote = wrote + npw_put(fd, ((b as i64) + otoc[ci*NPW_TOC_W+1]) as *u8, otoc[ci*NPW_TOC_W+2] * 8)
160 }
161 ci = ci + 1
162 }
163 wrote = wrote + npw_put(fd, pp as *u8, plen * 8)
164 sys_close(fd)
165 st[NPW_ST_SECTIONS] = ns2
166 st[NPW_ST_PLEN] = plen
167 st[NPW_ST_N] = n
168 if wrote != total { return NPW_ERR_SHORT }
169 return wrote
170}