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}