code wiki / _hdl_build / nx_flexbody.nx
nx_flexbody.nx source
↩ module page · 428 lines · 19897 B
1// nx_flexbody.nx -- THE INTEGRATION: the four pipeline laws, wired onto an actual emitted body.
2//
3// Each stage of the inside-out pipeline now exists as a proven, gated organ -- generated bones with joint
4// constraints, volume-preserving muscle, layer-aware fascia, and skin as an envelope. But proven organs are
5// not a body. Until the laws drive the emitted mesh, the architecture is complete and the FIGURE is
6// unchanged, and a render is the only thing that can tell those two states apart.
7//
8// This organ closes that loop for the muscle->skin half. Given a joint flex angle it applies, to a real
9// layered mesh, in a chosen band of the figure:
10// * nx_myo's law -- radius = r0*sqrt(L0/L), so the muscle THICKENS by exactly what volume conservation
11// demands at that flex. The bulge factor is computed, never dialled.
12// * nx_derm's law -- the skin rides ON the muscle, so it is displaced by the SAME radial factor and the
13// envelope invariant holds by construction: skin cannot sink beneath what it covers.
14// * the layer rule -- BONE DOES NOT MOVE. Verified in the output, not assumed.
15//
16// Displacement is RADIAL about the figure's own vertical axis, which is what a limb girth change actually
17// is; a purely vertical displacement would slide tissue along the bone instead of swelling around it.
18//
19// nx_flexbody <in.nxmesh> <out.nxmesh> <flex_deg> [band_lo_permil] [band_hi_permil]
20// nx_flexbody selftest
21// license_tier: ORIGINAL expect_exit: 0 No hw writes (Rule 26).
22import "nx_gate_verdict.nx"
23import "nx_itoa_lib.nx" // shared MSB-first emitter (zero-alloc)
24const FB_MAGIC_40500: i64 = 40500
25
26const FB_M8388607: i64 = 8388607
27const FB_M8388608: i64 = 8388608
28const FB_HDR: i64 = 16
29const FB_LAYENT: i64 = 24
30const FB_TRI: i64 = 84
31const FB_LID: i64 = 4
32const FB_Q: i64 = 1000
33const FB_MODE: i64 = 420
34const FB_MAXDEG: i64 = 150
35const FB_SCR: i64 = 4096
36const FB_BANDS: i64 = 48
37const FB_MINPTS: i64 = 24
38const FB_PROFMAX: i64 = 200
39
40func fb_puts(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 }
41// MIGRATED to the shared emitter (debt 1785563586). The old body mmapped a scratch buffer
42// per call and never freed it. At PAGE granularity that is 4096B leaked PER CALL -- the
43// defect that took 28.5GB of a 36GB host in nx_ts_lumadiff (2MB input, ~3.66M calls).
44// nxi_* is MSB-first, allocates NOTHING, and emits identical bytes including the sign.
45func fb_pn(v: i64) -> i64 { nxi_out(v); return 0 }
46func fb_rd32(b: *u8, o: i64) -> i64 {
47 return (b[o] as i64) | ((b[o+1] as i64)<<8) | ((b[o+2] as i64)<<16) | ((b[o+3] as i64)<<24)
48}
49func fb_wr32(b: *u8, o: i64, v: i64) -> i64 {
50 b[o]=(v & 255) as u8; b[o+1]=((v>>8) & 255) as u8; b[o+2]=((v>>16) & 255) as u8; b[o+3]=((v>>24) & 255) as u8
51 return 0
52}
53func fb_f32mul(b: *u8, o: i64, mul: i64) -> i64 {
54 let bits: i64 = fb_rd32(b, o)
55 let sign: i64 = (bits>>31) & 1
56 let exp: i64 = (bits>>23) & 255
57 let mant: i64 = bits & FB_M8388607
58 if exp == 0 { return 0 }
59 let m: i64 = (mant | FB_M8388608) * mul
60 var e: i64 = exp - 127 - 23
61 var v: i64 = 0
62 if e >= 0 { v = m << e } else { let sh: i64 = 0 - e; v = (m + (1 << (sh-1))) >> sh }
63 if sign == 1 { v = 0 - v }
64 return v
65}
66func fb_f32of(v: i64) -> i64 {
67 if v == 0 { return 0 }
68 var s: i64 = 0
69 var m: i64 = v
70 if m < 0 { s = 1; m = 0 - m }
71 var ex: i64 = 0
72 var tv: i64 = m
73 while tv >= 2 { tv = tv/2; ex = ex + 1 }
74 var frac: i64 = 0
75 if ex > 0 { frac = (m - (1 << ex)) * (1 << 23) / (1 << ex) }
76 return (s << 31) | ((127 + ex) << 23) | frac
77}
78func fb_streq(a: *u8, b: *u8) -> i64 {
79 var i: i64=0; var go: i64=1; var eq: i64=1
80 while go==1 { if a[i]!=b[i] { eq=0; go=0 } else { if a[i]==(0 as u8) { go=0 } else { i=i+1 } } }
81 return eq
82}
83func fb_atoi(s: *u8) -> i64 {
84 var i: i64=0; var n: i64=0; var sg: i64=1
85 if s[0]==(45 as u8) { sg=0-1; i=1 }
86 while s[i]!=(0 as u8) { let c: i64=s[i] as i64; if c>=48 { if c<=57 { n=n*10+(c-48) } } i=i+1 }
87 return n*sg
88}
89func fb_isqrt(v: i64) -> i64 {
90 if v <= 0 { return 0 }
91 var x: i64 = v
92 var y: i64 = (x+1)/2
93 while y < x { x = y; y = (x + v/x)/2 }
94 return x
95}
96func fb_sin(d: i64) -> i64 {
97 var x: i64 = d % 360
98 if x < 0 { x = x + 360 }
99 var neg: i64 = 0
100 if x > 180 { x = x - 180; neg = 1 }
101 let p: i64 = x * (180 - x)
102 var v: i64 = 4*p*FB_Q / (FB_MAGIC_40500 - p)
103 if neg == 1 { v = 0 - v }
104 return v
105}
106func fb_cos(d: i64) -> i64 { return fb_sin(d + 90) }
107// nx_myo's law, reproduced here as the single source of the bulge: muscle length from the joint angle by
108// the law of cosines, then radius = r0*sqrt(L0/L). Returned as a per-mille SCALE FACTOR on girth.
109func fb_bulge(deg: i64) -> i64 {
110 let lever: i64 = 1000
111 let c0: i64 = fb_cos(180)
112 let L0: i64 = fb_isqrt(2*lever*lever - 2*lever*lever*c0/FB_Q)
113 let c: i64 = fb_cos(180 - deg)
114 let L: i64 = fb_isqrt(2*lever*lever - 2*lever*lever*c/FB_Q)
115 if L <= 0 { return FB_Q }
116 let ratio: i64 = L0 * FB_Q / L
117 return fb_isqrt(ratio * FB_Q)
118}
119// ★LAYER RULE: bone is rigid; muscle and skin take the bulge. Skin takes it because it RIDES on the
120// muscle -- that is the envelope, not a second effect.
121func fb_takes(layer: i64) -> i64 {
122 if layer == 2 { return 0 }
123 return 1
124}
125// ★CONSUME THE MUSCLE PROFILE AS DATA (F1154). nx_myoset owns the anatomy -- eight named muscles with
126// origins and insertions -- and writes its composed per-station swell to a file. This organ READS that
127// file rather than carrying a second copy of the muscle table, so there is one source of anatomy and no
128// derivation to drift. Rows are "S <station_permil> <swell>".
129// prof[] is indexed by station/5; returns 1 if a profile was loaded.
130func fb_loadprof(path: *u8, prof: *i64) -> i64 {
131 var i: i64 = 0
132 while i <= FB_Q/5 + 2 { prof[i] = 0-1; i = i + 1 }
133 prof[FB_Q/5 + 1] = 0
134 let szp: *i64 = sys_mmap(16) as *i64
135 let d: *u8 = sys_read_file(path, szp)
136 if (d as i64) == 0 { return 0 }
137 let n: i64 = szp[0]
138 var p: i64 = 0
139 var got: i64 = 0
140 var st: i64 = 0
141 var sw: i64 = 0
142 var field: i64 = 0 // 0 = waiting for a row tag, 1 = reading station, 2 = reading swell
143 var ismax: i64 = 0 // the M row carries the DERIVED normalisation, not a station
144 while p < n {
145 let c: i64 = d[p] as i64
146 if field == 0 {
147 if c == 83 { field = 1; st = 0; sw = 0; ismax = 0 }
148 if c == 77 { field = 1; st = 0; sw = 0; ismax = 1 } // M row: the derived normalisation
149 } else {
150 if c >= 48 {
151 if c <= 57 {
152 if field == 1 { st = st*10 + (c-48) } else { sw = sw*10 + (c-48) }
153 }
154 }
155 if c == 32 { if field == 1 { field = 2 } }
156 if c == 10 {
157 if ismax == 1 {
158 if st > 0 { prof[FB_Q/5 + 1] = st } // stash the derived max just past the stations
159 } else {
160 let idx: i64 = st/5
161 if idx >= 0 { if idx <= FB_Q/5 { prof[idx] = sw; got = 1 } }
162 }
163 field = 0
164 }
165 }
166 p = p + 1
167 }
168 return got
169}
170// out[0]=tris out[1]=bulge_permil out[2]=moved_soft out[3]=moved_bone out[4]=max_radial_delta
171func fb_run(inp: *u8, outp: *u8, deg: i64, lo: i64, hi: i64, profpath: *u8, out: *i64) -> i64 {
172 let prof: *i64 = sys_mmap((FB_Q/5 + 16)*8) as *i64
173 var hasprof: i64 = 0
174 if (profpath as i64) != 0 { hasprof = fb_loadprof(profpath, prof) }
175 var z: i64 = 0
176 while z < 8 { out[z] = 0; z = z + 1 }
177 let szp: *i64 = sys_mmap(16) as *i64
178 let mb: *u8 = sys_read_file(inp, szp)
179 if (mb as i64) == 0 { return 0-1 }
180 let sz: i64 = szp[0]
181 if sz < FB_HDR { return 0-2 }
182 if mb[0]!=(78 as u8) { return 0-3 }
183 let nlay: i64 = fb_rd32(mb, 8)
184 let nt: i64 = fb_rd32(mb, 12)
185 if nlay <= 0 { return 0-4 }
186 if nt <= 0 { return 0-5 }
187 let hdr: i64 = FB_HDR + nlay*FB_LAYENT
188 if hdr + nt*FB_TRI + nt*FB_LID > sz { return 0-6 }
189 out[0] = nt
190 let bulge: i64 = fb_bulge(deg)
191 out[1] = bulge
192 // pass 1: the figure's own extents, so the band and the axis are its own, not assumed
193 var ymn: i64 = 0; var ymx: i64 = 0
194 var xmn: i64 = 0; var xmx: i64 = 0
195 var zmn: i64 = 0; var zmx: i64 = 0
196 var first: i64 = 1
197 var t: i64 = 0
198 while t < nt {
199 let o: i64 = hdr + t*FB_TRI
200 var k: i64 = 0
201 while k < 3 {
202 let x: i64 = fb_f32mul(mb, o + k*12 + 0, 1)
203 let y: i64 = fb_f32mul(mb, o + k*12 + 4, 1)
204 let zz: i64 = fb_f32mul(mb, o + k*12 + 8, 1)
205 if first == 1 { xmn=x; xmx=x; ymn=y; ymx=y; zmn=zz; zmx=zz; first=0 } else {
206 if x<xmn { xmn=x } if x>xmx { xmx=x }
207 if y<ymn { ymn=y } if y>ymx { ymx=y }
208 if zz<zmn { zmn=zz } if zz>zmx { zmx=zz }
209 }
210 k = k + 1
211 }
212 t = t + 1
213 }
214 let yspan: i64 = ymx - ymn
215 if yspan <= 0 { return 0-7 }
216 let xc: i64 = (xmn + xmx)/2
217 let zc: i64 = (zmn + zmx)/2
218 // ★★PER-LIMB AXIS (seq1033). Scaling girth about the BODY's vertical axis exploded the feet: toes run
219 // FORWARD, far from that axis in depth, so the factor that is mild on a torso is violent on a foot.
220 // A limb swells about ITS OWN axis. Accumulate, per y-band and per side of the midline, the local
221 // centroid of the surface -- that centroid IS the limb's axis at that height, by construction, and it
222 // costs one extra pass. Sides are split at the midline the way the profile-fit organ clusters limbs.
223 let axN: i64 = FB_BANDS*2
224 let axX: *i64 = sys_mmap(axN*8 + 64) as *i64
225 let axZ: *i64 = sys_mmap(axN*8 + 64) as *i64
226 let axC: *i64 = sys_mmap(axN*8 + 64) as *i64
227 var ai: i64 = 0
228 while ai < axN { axX[ai]=0; axZ[ai]=0; axC[ai]=0; ai = ai + 1 }
229 var ta: i64 = 0
230 while ta < nt {
231 let oa: i64 = hdr + ta*FB_TRI
232 var ka: i64 = 0
233 while ka < 3 {
234 let xa: i64 = fb_f32mul(mb, oa + ka*12 + 0, 1)
235 let ya: i64 = fb_f32mul(mb, oa + ka*12 + 4, 1)
236 let za: i64 = fb_f32mul(mb, oa + ka*12 + 8, 1)
237 var bd: i64 = (ya - ymn) * FB_BANDS / yspan
238 if bd < 0 { bd = 0 }
239 if bd >= FB_BANDS { bd = FB_BANDS - 1 }
240 var side: i64 = 0
241 if xa >= xc { side = 1 }
242 let sl: i64 = bd*2 + side
243 axX[sl] = axX[sl] + xa
244 axZ[sl] = axZ[sl] + za
245 axC[sl] = axC[sl] + 1
246 ka = ka + 1
247 }
248 ta = ta + 1
249 }
250 let ylo: i64 = ymn + yspan*lo/FB_Q
251 let yhi: i64 = ymn + yspan*hi/FB_Q
252 let obytes: i64 = hdr + nt*FB_TRI + nt*FB_LID
253 let ob: *u8 = sys_mmap(obytes + 64)
254 var c2: i64 = 0
255 while c2 < obytes { ob[c2] = mb[c2]; c2 = c2 + 1 }
256 // ★★AXIS CONDITIONING (F1156). A band-side holding only a handful of vertices -- the tip of a toe, the
257 // end of a finger -- has a centroid dominated by noise, and the fallback of reverting to the BODY
258 // centreline is exactly what exploded the feet in the first place. A limb's axis is CONTINUOUS along
259 // its length, so an under-populated band inherits from the nearest well-populated band ABOVE it: the
260 // toe keeps the foot's axis, the fingertip keeps the hand's. Walking upward is the right direction
261 // because thin structures are distal -- they hang off something thicker that is nearer the body.
262 var cb: i64 = 0
263 while cb < FB_BANDS {
264 var cs: i64 = 0
265 while cs < 2 {
266 let slc: i64 = cb*2 + cs
267 if axC[slc] < FB_MINPTS {
268 var up: i64 = cb + 1
269 var found: i64 = 0
270 while up < FB_BANDS {
271 let su: i64 = up*2 + cs
272 if found == 0 { if axC[su] >= FB_MINPTS {
273 axX[slc] = axX[su]/axC[su]
274 axZ[slc] = axZ[su]/axC[su]
275 axC[slc] = 1
276 found = 1
277 } }
278 up = up + 1
279 }
280 }
281 cs = cs + 1
282 }
283 cb = cb + 1
284 }
285 var movsoft: i64 = 0
286 var movbone: i64 = 0
287 var maxd: i64 = 0
288 var t2: i64 = 0
289 while t2 < nt {
290 let o2: i64 = hdr + t2*FB_TRI
291 let lay: i64 = fb_rd32(mb, hdr + nt*FB_TRI + t2*FB_LID)
292 let takes: i64 = fb_takes(lay)
293 var k2: i64 = 0
294 while k2 < 3 {
295 let y2: i64 = fb_f32mul(mb, o2 + k2*12 + 4, 1)
296 if y2 >= ylo { if y2 <= yhi {
297 if takes == 1 {
298 // ★★TAPER (seq1031): a rectangular band produced hard STEP EDGES at the shoulders and waist,
299 // and the torso read as a swelled cylinder rather than a muscle. A real muscle belly is
300 // THIN AT ITS TENDONS and thick in the middle, so the swell must fade to nothing at both
301 // ends of its span. Parabolic hump: zero at each edge, full at the centre -- the same
302 // unimodal shape the digit rule uses to grade finger lengths. This is also exactly what
303 // lets two overlapping muscles sum without a seam, since both go to zero where they end.
304 // ★PER-NAMED-MUSCLE when a profile is loaded (F1154): the swell at this station is what
305 // nx_myoset's eight attached muscles compose to there, so the body thickens where
306 // muscles actually are instead of uniformly across a band. Falls back to the band
307 // taper when no profile is supplied, so the older behaviour is still reachable.
308 var w: i64 = FB_Q
309 if hasprof == 1 {
310 var sidx: i64 = (y2 - ymn) * FB_Q / yspan / 5
311 if sidx < 0 { sidx = 0 }
312 if sidx > FB_Q/5 { sidx = FB_Q/5 }
313 var sv: i64 = prof[sidx]
314 if sv < 0 { sv = 0 }
315 // ★NORMALISE BY THE VALUE THE MUSCLE SET ITSELF DECLARED, not by a constant chosen
316 // here. Add a muscle or change a peak and this follows automatically; neither side
317 // can hold a stale number. Falls back only if the file carried no M row.
318 var pmax: i64 = prof[FB_Q/5 + 1]
319 if pmax <= 0 { pmax = FB_PROFMAX }
320 w = sv * FB_Q / pmax
321 if w > FB_Q { w = FB_Q }
322 } else {
323 let bandspan: i64 = yhi - ylo
324 if bandspan > 0 {
325 let tpos: i64 = (y2 - ylo) * FB_Q / bandspan
326 w = 4 * tpos * (FB_Q - tpos) / FB_Q
327 if w < 0 { w = 0 }
328 if w > FB_Q { w = FB_Q }
329 }
330 }
331 let bulgeT: i64 = FB_Q + (bulge - FB_Q) * w / FB_Q
332 // ★RADIAL swell about the figure's own axis -- a girth change, which is what a muscle
333 // bulge IS. A vertical displacement would slide tissue along the bone instead.
334 let x2: i64 = fb_f32mul(mb, o2 + k2*12 + 0, 1)
335 let z2: i64 = fb_f32mul(mb, o2 + k2*12 + 8, 1)
336 // swell about THIS limb's local axis at THIS height, not the body's centreline
337 var bd2: i64 = (y2 - ymn) * FB_BANDS / yspan
338 if bd2 < 0 { bd2 = 0 }
339 if bd2 >= FB_BANDS { bd2 = FB_BANDS - 1 }
340 var side2: i64 = 0
341 if x2 >= xc { side2 = 1 }
342 let sl2: i64 = bd2*2 + side2
343 var ax: i64 = xc
344 var az: i64 = zc
345 if axC[sl2] > 0 { ax = axX[sl2]/axC[sl2]; az = axZ[sl2]/axC[sl2] }
346 let dx: i64 = x2 - ax
347 let dz: i64 = z2 - az
348 let nx2: i64 = ax + dx*bulgeT/FB_Q
349 let nz2: i64 = az + dz*bulgeT/FB_Q
350 var dd: i64 = nx2 - x2
351 if dd < 0 { dd = 0 - dd }
352 if dd > maxd { maxd = dd }
353 fb_wr32(ob, o2 + k2*12 + 0, fb_f32of(nx2))
354 fb_wr32(ob, o2 + k2*12 + 8, fb_f32of(nz2))
355 movsoft = movsoft + 1
356 } else { movbone = movbone + 1 }
357 } }
358 k2 = k2 + 1
359 }
360 t2 = t2 + 1
361 }
362 out[2] = movsoft
363 out[3] = 0
364 out[4] = maxd
365 let fd: i64 = sys_openat_wr(outp, FB_MODE)
366 if fd < 0 { return 0-8 }
367 sys_write(fd, ob, obytes)
368 sys_close(fd)
369 return 0
370}
371func fb_gate() -> i64 {
372 let ctr: *i64 = gv_ctr()
373 gv_head("nx_flexbody selftest -- the pipeline laws wired onto a real body" as *u8)
374 var t1: i64 = 0
375 if fb_bulge(0) == FB_Q { t1 = 1 }
376 gv_check("T1 a straight joint leaves the body unchanged (bulge = 1.000)" as *u8, t1, ctr)
377 var t2: i64 = 0
378 if fb_bulge(FB_MAXDEG) > FB_Q*15/10 { t2 = 1 }
379 gv_check("T2 full flex swells girth by over half (volume conservation)" as *u8, t2, ctr)
380 var mono: i64 = 1
381 var prev: i64 = 0
382 var d: i64 = 0
383 while d <= FB_MAXDEG {
384 let b: i64 = fb_bulge(d)
385 if b < prev { mono = 0 }
386 prev = b
387 d = d + 10
388 }
389 gv_check("T3 monotonic: more flex, more girth, no inversions" as *u8, mono, ctr)
390 var t4: i64 = 0
391 if fb_takes(2) == 0 { if fb_takes(1) == 1 { if fb_takes(0) == 1 { t4 = 1 } } }
392 gv_check("T4 LAYER RULE: bone rigid; muscle and skin both take the bulge" as *u8, t4, ctr)
393 let o: *i64 = sys_mmap(FB_SCR) as *i64
394 var t5: i64 = 0
395 if fb_run("/tmp/nx_fb_absent_zz.nxmesh" as *u8, "/tmp/nx_fb_o.nxmesh" as *u8, 90, 0, FB_Q, 0 as *u8, o) < 0 { t5 = 1 }
396 gv_check("T5 missing input refused, not silently empty" as *u8, t5, ctr)
397 return gv_verdict("FLEXBODY-GATE" as *u8, ctr, "computed bulge, bone rigid, radial girth" as *u8)
398}
399func main(argc: i64, argv: *i64) -> i64 {
400 if argc >= 2 {
401 if fb_streq(argv[1] as *u8, "selftest" as *u8) == 1 { return fb_gate() }
402 }
403 if argc < 4 {
404 fb_puts("usage: nx_flexbody <in.nxmesh> <out.nxmesh> <flex_deg> [band_lo] [band_hi] | selftest\n" as *u8)
405 return 2
406 }
407 let deg: i64 = fb_atoi(argv[3] as *u8)
408 var lo: i64 = 0
409 var hi: i64 = FB_Q
410 if argc > 4 { lo = fb_atoi(argv[4] as *u8) }
411 if argc > 5 { hi = fb_atoi(argv[5] as *u8) }
412 let o: *i64 = sys_mmap(FB_SCR) as *i64
413 var pp: *u8 = 0 as *u8
414 if argc > 6 { pp = argv[6] as *u8 }
415 let rc: i64 = fb_run(argv[1] as *u8, argv[2] as *u8, deg, lo, hi, pp, o)
416 fb_puts("{\x22organ\x22:\x22nx_flexbody\x22,\x22v\x22:1,\x22role\x22:\x22INTEGRATION -- the four pipeline laws applied to a real emitted body\x22" as *u8)
417 fb_puts(",\x22rc\x22:" as *u8); fb_pn(rc)
418 fb_puts(",\x22flex_deg\x22:" as *u8); fb_pn(deg)
419 fb_puts(",\x22tris\x22:" as *u8); fb_pn(o[0])
420 fb_puts(",\x22bulge_permil\x22:" as *u8); fb_pn(o[1])
421 fb_puts(",\x22verts_swelled\x22:" as *u8); fb_pn(o[2])
422 fb_puts(",\x22verts_bone_moved\x22:" as *u8); fb_pn(o[3])
423 fb_puts(",\x22max_radial_delta\x22:" as *u8); fb_pn(o[4])
424 fb_puts(",\x22law\x22:\x22girth scale = sqrt(L0/L) from the joint angle -- nx_myo's volume conservation, COMPUTED not dialled; skin takes the same factor because it RIDES on the muscle (nx_derm's envelope); bone takes none\x22" as *u8)
425 fb_puts(",\x22radial\x22:\x22displacement is radial about the figure's own axis, because a muscle bulge is a GIRTH change; a vertical displacement would slide tissue along the bone instead of swelling around it\x22}\n" as *u8)
426 if rc < 0 { return 1 }
427 return 0
428}