code wiki / (root) / nx_softjiggle.nx

nx_softjiggle.nx source

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