code wiki / (root) / nx_softjiggle.nx

nx_softjiggle.nx source

↩ module page · 164 lines · 6927 B

1// nx_softjiggle.nx -- JIGGLE-BONE soft dynamics (operator 2026-07-03: "we also want jiggle physics" -- the 2// VaM-class secondary-motion substrate). Industry-standard model: a jiggle NODE is a point mass tethered to 3// an ANCHOR (the skeleton/animation drives the anchor; the node lags, overshoots, decays = jiggle) by a 4// spring-damper, with gravity influence and a HARD max-stretch clamp (no explosion, by construction). 5// ALL INTEGER (pos fx256, vel fx65536, 60Hz -- the same fixed-point law as nx_phys3d) => deterministic, 6// byte-identical replays. Per-node stiffness/damping/stretch/gravity are DATA (the moddable-architecture law: 7// a body-part profile = 4 numbers a mod can ship). Chains (node anchored to another node) compose ponytails/ 8// cloth strips. Base-relative: runs native AND in-wasm. license_tier: ORIGINAL 9import "nx_syscalls.nx" 10 11// node record: 16 i64 slots 12// [0]x [1]y [2]z (fx256) [3]vx [4]vy [5]vz (fx65536) [6]ax [7]ay [8]az (anchor, fx256) 13// [9]k stiffness (dv per tick per unit dx, ~8..80) [10]damp (per-256 velocity bleed, ~20..80) 14// [11]maxstretch (fx256 hard cap) [12]gscale (extra vel per tick, hangs the node below its anchor) 15// [13]chain_parent (-1 = anchored to [6..8]; else node id whose POSITION is this node's anchor) [14,15]spare 16const SJ_MAXN: i64 = 128 17const SJ_MAXC: i64 = 32 18 19func sj_hdr(base: i64) -> *i64 { return base as *i64 } // [0]=node count [1]=capsule count 20func sj_node(base: i64, id: i64) -> *i64 { return (base + 64 + id * 128) as *i64 } 21// CAPSULES = the shared collision volume (character limbs, ROBOT links, machine tools, VR controllers): 22// 8 i64 each {p0x,p0y,p0z, p1x,p1y,p1z, r, spare} -- nodes are pushed OUT of capsules each step. 23func sj_capsule(base: i64, id: i64) -> *i64 { return (base + 64 + SJ_MAXN * 128 + id * 64) as *i64 } 24func sj_bytes() -> i64 { return 64 + SJ_MAXN * 128 + SJ_MAXC * 64 } 25 26func sj_init(base: i64) -> i64 { 27 let h: *i64 = sj_hdr(base) 28 h[0] = 0 29 h[1] = 0 30 return 0 31} 32 33func sj_add_capsule(base: i64, x0: i64, y0: i64, z0: i64, x1: i64, y1: i64, z1: i64, r: i64) -> i64 { 34 let h: *i64 = sj_hdr(base) 35 if h[1] >= SJ_MAXC { return 0 - 1 } 36 let c: *i64 = sj_capsule(base, h[1]) 37 c[0] = x0; c[1] = y0; c[2] = z0 38 c[3] = x1; c[4] = y1; c[5] = z1 39 c[6] = r 40 h[1] = h[1] + 1 41 return h[1] - 1 42} 43 44func sj_isqrt(v: i64) -> i64 { 45 if v <= 0 { return 0 } 46 var x: i64 = v 47 var y: i64 = (x + 1) / 2 48 while y < x { x = y; y = (x + v / x) / 2 } 49 return x 50} 51 52func sj_add(base: i64, x: i64, y: i64, z: i64, k: i64, damp: i64, maxstretch: i64, gscale: i64) -> i64 { 53 let h: *i64 = sj_hdr(base) 54 if h[0] >= SJ_MAXN { return 0 - 1 } 55 let id: i64 = h[0] 56 let n: *i64 = sj_node(base, id) 57 n[0] = x; n[1] = y; n[2] = z 58 n[3] = 0; n[4] = 0; n[5] = 0 59 n[6] = x; n[7] = y; n[8] = z 60 n[9] = k; n[10] = damp; n[11] = maxstretch; n[12] = gscale 61 n[13] = 0 - 1 62 h[0] = h[0] + 1 63 return id 64} 65 66func sj_anchor(base: i64, id: i64, x: i64, y: i64, z: i64) -> i64 { 67 let n: *i64 = sj_node(base, id) 68 n[6] = x; n[7] = y; n[8] = z 69 return 0 70} 71func sj_chain(base: i64, id: i64, parent: i64) -> i64 { 72 let n: *i64 = sj_node(base, id) 73 n[13] = parent 74 return 0 75} 76func sj_pos(base: i64, id: i64, axis: i64) -> i64 { 77 let n: *i64 = sj_node(base, id) 78 return n[axis] 79} 80 81func sj_iabs(v: i64) -> i64 { if v < 0 { return 0 - v } return v } 82 83// one axis of spring-damper: returns the new velocity component 84func sj_axis(x: i64, ax: i64, v: i64, k: i64, damp: i64) -> i64 { 85 var nv: i64 = v + (ax - x) * k // spring: dv = dx(fx256) * k -> fx65536-ish pull 86 nv = nv - nv * damp / 256 // damping bleed 87 return nv 88} 89 90func sj_step(base: i64) -> i64 { 91 let h: *i64 = sj_hdr(base) 92 let cnt: i64 = h[0] 93 var i: i64 = 0 94 while i < cnt { 95 let n: *i64 = sj_node(base, i) 96 // resolve the anchor: fixed point or the current position of a parent node (chain) 97 var axp: i64 = n[6] 98 var ayp: i64 = n[7] 99 var azp: i64 = n[8] 100 if n[13] >= 0 { 101 let p: *i64 = sj_node(base, n[13]) 102 axp = p[0]; ayp = p[1]; azp = p[2] 103 } 104 n[3] = sj_axis(n[0], axp, n[3], n[9], n[10]) 105 n[4] = sj_axis(n[1], ayp, n[4], n[9], n[10]) - n[12] 106 n[5] = sj_axis(n[2], azp, n[5], n[9], n[10]) 107 n[0] = n[0] + n[3] / 256 108 n[1] = n[1] + n[4] / 256 109 n[2] = n[2] + n[5] / 256 110 // HARD max-stretch clamp per axis (never-explode by construction; cheap and deterministic -- 111 // radial clamp needs isqrt and buys little at jiggle amplitudes) 112 let ms: i64 = n[11] 113 if n[0] - axp > ms { n[0] = axp + ms; if n[3] > 0 { n[3] = 0 } } 114 if axp - n[0] > ms { n[0] = axp - ms; if n[3] < 0 { n[3] = 0 } } 115 if n[1] - ayp > ms { n[1] = ayp + ms; if n[4] > 0 { n[4] = 0 } } 116 if ayp - n[1] > ms { n[1] = ayp - ms; if n[4] < 0 { n[4] = 0 } } 117 if n[2] - azp > ms { n[2] = azp + ms; if n[5] > 0 { n[5] = 0 } } 118 if azp - n[2] > ms { n[2] = azp - ms; if n[5] < 0 { n[5] = 0 } } 119 // capsule pushout: closest point on each capsule axis; if inside r, project to the surface and 120 // kill the inward radial velocity (slide, don't stick) 121 var ci: i64 = 0 122 while ci < h[1] { 123 let cp: *i64 = sj_capsule(base, ci) 124 let dx: i64 = cp[3] - cp[0] 125 let dy: i64 = cp[4] - cp[1] 126 let dz: i64 = cp[5] - cp[2] 127 let den: i64 = dx * dx + dy * dy + dz * dz 128 var t256: i64 = 0 129 if den > 0 { 130 let num: i64 = (n[0] - cp[0]) * dx + (n[1] - cp[1]) * dy + (n[2] - cp[2]) * dz 131 t256 = num * 256 / den 132 if t256 < 0 { t256 = 0 } 133 if t256 > 256 { t256 = 256 } 134 } 135 let cx: i64 = cp[0] + dx * t256 / 256 136 let cy: i64 = cp[1] + dy * t256 / 256 137 let cz: i64 = cp[2] + dz * t256 / 256 138 let ex: i64 = n[0] - cx 139 let ey: i64 = n[1] - cy 140 let ez: i64 = n[2] - cz 141 let d2: i64 = ex * ex + ey * ey + ez * ez 142 let r: i64 = cp[6] 143 if d2 < r * r { 144 var dist: i64 = sj_isqrt(d2) 145 var ux: i64 = 256 146 var uy: i64 = 0 147 var uz: i64 = 0 148 if dist > 0 { ux = ex * 256 / dist; uy = ey * 256 / dist; uz = ez * 256 / dist } 149 n[0] = cx + ux * r / 256 150 n[1] = cy + uy * r / 256 151 n[2] = cz + uz * r / 256 152 let vr: i64 = (n[3] * ux + n[4] * uy + n[5] * uz) / 256 153 if vr < 0 { 154 n[3] = n[3] - ux * vr / 256 155 n[4] = n[4] - uy * vr / 256 156 n[5] = n[5] - uz * vr / 256 157 } 158 } 159 ci = ci + 1 160 } 161 i = i + 1 162 } 163 return 0 164}