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}