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}