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}