code wiki / (root) / nx_softbind.nx

nx_softbind.nx source

↩ module page · 128 lines · 6519 B

1// nx_softbind.nx -- LIB: BINDS nx_softdyn to character geometry (roadmap rung A4, 2026-08-01). 2// The solver was proven 7/7 in isolation but bound to NOTHING -- no body part consumed it, so 3// nothing in the game visibly jiggled. This lib is the missing joint-anchor contract: each mob 4// owns SB_PTS spring points (hair / chest / skirt class), each seated at a fixed local offset 5// from the mob ROOT, stepped once per sim tick from root motion ONLY (organ truth -- the same 6// source as the mobdx/mobdz/mobdy doors), yielding signed Q8 offsets a renderer ADDS to its 7// designated joints on top of the animated pose (spring-decomposed-skinning pattern: helper 8// offsets over the animation, never replacing it). Pure function of the root trajectory => 9// bit-deterministic, so replay / netsync / save-transparency hold by construction. 10// license_tier: ORIGINAL No hw writes (Rule 26). 11import "nx_syscalls.nx" 12import "nx_softdyn.nx" 13const SB_MAGIC_5381: i64 = 5381 14const SB_MAGIC_1000000007: i64 = 1000000007 15 16const SB_PTS: i64 = 3 17const SB_HAIR: i64 = 0 18const SB_CHEST: i64 = 1 19const SB_SKIRT: i64 = 2 20// per-kind spring params (per-1024). ALL underdamped -- discrete envelope: underdamped while 21// C < 64*sqrt(K) (zeta = C / (2*sqrt(1024*K))), so every row below VISIBLY oscillates: 22// hair K=200 -> bound ~905, C=60 23// chest K=500 -> bound ~1431 (capped by SD_CAPC), C=120 24// skirt K=300 -> bound ~1108, C=80 25const SB_K_HAIR: i64 = 200 26const SB_C_HAIR: i64 = 60 27const SB_K_CHEST: i64 = 85 28const SB_C_CHEST: i64 = 110 29const SB_K_SKIRT: i64 = 300 30const SB_C_SKIRT: i64 = 80 31// PER-BODY RANGE (published gait biomechanics: unsupported chest displacement ~2-5cm at 32// ~2-3Hz during walking, phase-lagged from the thorax; the OLD K=500 was ~6.7Hz -- it tracked 33// the anchor rigidly, which is why nothing visibly moved): firmness 0..3 spans K 60/85/110/135 34// (natural freq ~2.3-3.4Hz at 60Hz ticks) with C holding zeta ~0.17-0.20. Firmness is BRED -- 35// genome bits 16-17, field-crossed at birth like every other trait: a population of bodies. 36const SB_K_CHEST_BASE: i64 = 60 37const SB_K_CHEST_STEP: i64 = 25 38const SB_C_CHEST_BASE: i64 = 100 39const SB_C_CHEST_STEP: i64 = 16 40// hard displacement clamps (model units) -- tissue can never leave the body 41const SB_MAXD_HAIR: i64 = 6 42const SB_MAXD_CHEST: i64 = 2 // +-3.1cm: tissue-vs-bone p2p <= 6.2cm, the cited band 43const SB_MAXD_SKIRT: i64 = 5 44// local anchor heights above the mob root (model units): hair tip / chest / skirt hem class 45const SB_OFFY_HAIR: i64 = 17 46const SB_OFFY_CHEST: i64 = 12 47const SB_OFFY_SKIRT: i64 = 7 48 49func sb_k(kind: i64) -> i64 { if kind == SB_HAIR { return SB_K_HAIR } if kind == SB_CHEST { return SB_K_CHEST } return SB_K_SKIRT } 50func sb_c(kind: i64) -> i64 { if kind == SB_HAIR { return SB_C_HAIR } if kind == SB_CHEST { return SB_C_CHEST } return SB_C_SKIRT } 51func sb_maxd(kind: i64) -> i64 { if kind == SB_HAIR { return SB_MAXD_HAIR } if kind == SB_CHEST { return SB_MAXD_CHEST } return SB_MAXD_SKIRT } 52func sb_offy(kind: i64) -> i64 { if kind == SB_HAIR { return SB_OFFY_HAIR } if kind == SB_CHEST { return SB_OFFY_CHEST } return SB_OFFY_SKIRT } 53 54// state for n mobs (n*SB_PTS softdyn points, softdyn owns the layout) 55func sb_alloc(nmobs: i64) -> *i64 { return sd_alloc(nmobs*SB_PTS) } 56 57// seat every point of one mob exactly on its anchor (no startup transient) 58func sb_seat_mob(st: *i64, mobi: i64, rx: i64, ry: i64, rz: i64) -> i64 { 59 var k: i64 = 0 60 while k < SB_PTS { sd_seat(st, mobi*SB_PTS + k, rx, ry + sb_offy(k), rz); k = k + 1 } 61 return 0 62} 63 64// one sim tick for one mob against its CURRENT root position (organ truth) 65func sb_tick_mob(st: *i64, mobi: i64, rx: i64, ry: i64, rz: i64) -> i64 { 66 var k: i64 = 0 67 while k < SB_PTS { 68 sd_step(st, mobi*SB_PTS + k, rx, ry + sb_offy(k), rz, sb_k(k), sb_c(k), sb_maxd(k)) 69 k = k + 1 70 } 71 return 0 72} 73// single point with explicit params (the per-body range's primitive) 74func sb_tick_pt(st: *i64, mobi: i64, kind: i64, rx: i64, ry: i64, rz: i64, K: i64, C: i64, maxd: i64) -> i64 { 75 sd_step(st, mobi*SB_PTS + kind, rx, ry + sb_offy(kind), rz, K, C, maxd) 76 return 0 77} 78// one tick with a BRED body: firmness 0..3 selects the chest band; hair/skirt ride the defaults 79func sb_tick_mob_firm(st: *i64, mobi: i64, rx: i64, ry: i64, rz: i64, firm: i64) -> i64 { 80 var f: i64 = firm 81 if f < 0 { f = 0 } 82 if f > 3 { f = 3 } 83 sb_tick_pt(st, mobi, SB_HAIR, rx, ry, rz, SB_K_HAIR, SB_C_HAIR, SB_MAXD_HAIR) 84 sb_tick_pt(st, mobi, SB_CHEST, rx, ry, rz, 85 SB_K_CHEST_BASE + f*SB_K_CHEST_STEP, SB_C_CHEST_BASE + f*SB_C_CHEST_STEP, SB_MAXD_CHEST) 86 sb_tick_pt(st, mobi, SB_SKIRT, rx, ry, rz, SB_K_SKIRT, SB_C_SKIRT, SB_MAXD_SKIRT) 87 return 0 88} 89 90// a hit / footfall / landing kicks every point of the mob (recoil inheritance, same solver as firearms) 91func sb_impulse_mob(st: *i64, mobi: i64, ix: i64, iy: i64, iz: i64) -> i64 { 92 var k: i64 = 0 93 while k < SB_PTS { sd_impulse(st, mobi*SB_PTS + k, ix, iy, iz); k = k + 1 } 94 return 0 95} 96 97// ---- renderer-facing accessors: signed Q8 offsets FROM the anchor, per axis -------------------- 98// The consumer adds these to its designated joint translations. Q8 sub-unit resolution is 99// deliberate: jiggle amplitude is often a fraction of a model unit (softdyn gate T2 lesson). 100func sb_off_x_q8(st: *i64, mobi: i64, kind: i64, rx: i64) -> i64 { 101 return st[(mobi*SB_PTS + kind)*SD_STRIDE] - rx*SD_Q8 102} 103func sb_off_y_q8(st: *i64, mobi: i64, kind: i64, ry: i64) -> i64 { 104 return st[(mobi*SB_PTS + kind)*SD_STRIDE + 1] - (ry + sb_offy(kind))*SD_Q8 105} 106func sb_off_z_q8(st: *i64, mobi: i64, kind: i64, rz: i64) -> i64 { 107 return st[(mobi*SB_PTS + kind)*SD_STRIDE + 2] - rz*SD_Q8 108} 109// magnitude of displacement from anchor in Q8 (clamp verification) 110func sb_disp_q8(st: *i64, mobi: i64, kind: i64, rx: i64, ry: i64, rz: i64) -> i64 { 111 return sd_disp_q8(st, mobi*SB_PTS + kind, rx, ry + sb_offy(kind), rz) 112} 113// kinetic energy proxy of one point (decay proof) 114func sb_energy(st: *i64, mobi: i64, kind: i64) -> i64 { return sd_energy(st, mobi*SB_PTS + kind) } 115 116// deterministic rolling checksum over all state words of n mobs (replay / netsync proof) 117func sb_ck(st: *i64, nmobs: i64) -> i64 { 118 var h: i64 = SB_MAGIC_5381 119 var i: i64 = 0 120 let n: i64 = nmobs*SB_PTS*SD_STRIDE 121 while i < n { 122 var w: i64 = st[i] 123 if w < 0 { w = (0 - w)*2 + 1 } else { w = w*2 } 124 h = (h*33 + w) % SB_MAGIC_1000000007 125 i = i + 1 126 } 127 return h 128}