code wiki / _hdl_build / nx_softtissue_gate.nx
nx_softtissue_gate.nx source
↩ module page · 308 lines · 14688 B
1// nx_softtissue_gate.nx -- the ruler for nx_softtissue.nx.
2//
3// Every tooth is an operator claim turned into a measurement:
4// "when a woman lies down the breasts naturally flatten" -> T5, T6
5// "if they are larger and softer ... more solid then less" -> T7 (the firmness law)
6// "an ass bounce ... absorbs energy and settles" -> T11, T12
7// "the tissue becomes harder the tighter it is squished" -> T9 (no inversion)
8// "smooth dimpling around the point of touch, not a sharp dent" -> T10
9// The pass bands for T5/T6 come from nx_breast_drape_pose.nx, which banked them for
10// GENERATED IMAGERY -- an oracle that predates this solver and was never fitted to it.
11// license_tier: ORIGINAL No hw writes (Rule 26).
12import "nx_syscalls.nx"
13import "nx_gate_verdict.nx"
14import "nx_softtissue.nx"
15
16const SG_DT_US: i64 = 4167
17const SG_ITERS: i64 = 2
18const SG_SETTLE: i64 = 300
19const SG_G: i64 = 4096
20
21func sg_snapshot(W: *i64) -> *i64 {
22 let n: i64 = W[ST_W_NP]
23 let s: *i64 = sys_mmap(n*3*8) as *i64
24 let px: *i64 = W[ST_W_PX] as *i64
25 let py: *i64 = W[ST_W_PY] as *i64
26 let pz: *i64 = W[ST_W_PZ] as *i64
27 var i: i64 = 0
28 while i < n { s[i*3] = px[i]; s[i*3+1] = py[i]; s[i*3+2] = pz[i]; i = i + 1 }
29 return s
30}
31
32func sg_report(tag: *u8, m: *i64) -> i64 {
33 gv_puts(" " as *u8); gv_puts(tag)
34 gv_puts(" aspect=" as *u8); gv_num(m[ST_M_ASPECT])
35 gv_puts(" centroid=" as *u8); gv_num(m[ST_M_CENTROID])
36 gv_puts(" proj=" as *u8); gv_num(m[ST_M_PROJ])
37 gv_puts(" minvol=" as *u8); gv_num(m[ST_M_MINVOL])
38 gv_puts(" kin=" as *u8); gv_num(m[ST_M_KIN])
39 gv_puts("\n" as *u8)
40 return 0
41}
42
43// Settle a fresh cage under a gravity direction and measure it.
44func sg_settle(prof: i64, gx: i64, gy: i64, gz: i64, out: *i64) -> i64 {
45 let W: *i64 = st_new(prof, 10)
46 st_run(W, gx, gy, gz, SG_SETTLE, SG_DT_US, SG_ITERS)
47 st_measure(W, out)
48 return 0
49}
50
51func main(argc: i64, argv: *i64) -> i64 {
52 let ctr: *i64 = gv_ctr()
53 gv_head("nx_softtissue_gate -- XPBD soft tissue must obey gravity, firmness and touch" as *u8)
54
55 let mrest: *i64 = sys_mmap(ST_M_N*8) as *i64
56 let mstand: *i64 = sys_mmap(ST_M_N*8) as *i64
57 let msup: *i64 = sys_mmap(ST_M_N*8) as *i64
58 let m2: *i64 = sys_mmap(ST_M_N*8) as *i64
59 let m3: *i64 = sys_mmap(ST_M_N*8) as *i64
60
61 // ---- T1/T2 the cage itself ---------------------------------------
62 let W0: *i64 = st_new(ST_PROF_LARGE_SOFT, 10)
63 st_measure(W0, mrest)
64 gv_puts(" cage: particles=" as *u8); gv_num(W0[ST_W_NP])
65 gv_puts(" tets=" as *u8); gv_num(W0[ST_W_NT])
66 gv_puts(" edges=" as *u8); gv_num(W0[ST_W_NE])
67 gv_puts(" surface=" as *u8); gv_num(mrest[ST_M_NSURF]); gv_puts("\n" as *u8)
68 sg_report("rest " as *u8, mrest)
69
70 gv_check("T1 cage built at blueprint density (2000..8000 tets)" as *u8,
71 (W0[ST_W_NT] >= 2000 && W0[ST_W_NT] <= 8000) as i64, ctr)
72 gv_check("T2 no inverted element at rest (every 6V > 0)" as *u8,
73 (mrest[ST_M_MINVOL] > 0) as i64, ctr)
74
75 // ---- T3 rest is an equilibrium (no drift with gravity off) -------
76 let Wz: *i64 = st_new(ST_PROF_LARGE_SOFT, 10)
77 st_run(Wz, 0, 0, 0, 120, SG_DT_US, SG_ITERS)
78 st_measure(Wz, m2)
79 let drift: i64 = st_abs(m2[ST_M_ASPECT] - mrest[ST_M_ASPECT])
80 gv_puts(" zero-g drift aspect delta=" as *u8); gv_num(drift); gv_puts("\n" as *u8)
81 gv_check("T3 rest state is a true equilibrium (zero-g aspect drift < 16)" as *u8,
82 (drift < 16) as i64, ctr)
83
84 // ---- T4/T5/T6 the lying-down claim -------------------------------
85 sg_settle(ST_PROF_LARGE_SOFT, 0, 0 - SG_G, 0, mstand)
86 sg_settle(ST_PROF_LARGE_SOFT, 0, 0, 0 - SG_G, msup)
87 sg_report("stand " as *u8, mstand)
88 sg_report("supine" as *u8, msup)
89
90 gv_check("T4 standing droops: aspect and centroid both rise from rest" as *u8,
91 (mstand[ST_M_ASPECT] > mrest[ST_M_ASPECT] &&
92 mstand[ST_M_CENTROID] > mrest[ST_M_CENTROID]) as i64, ctr)
93 gv_check("T5 supine FLATTENS: aspect falls below standing" as *u8,
94 (msup[ST_M_ASPECT] < mstand[ST_M_ASPECT]) as i64, ctr)
95 gv_check("T6 supine rides higher: centroid falls below standing" as *u8,
96 (msup[ST_M_CENTROID] < mstand[ST_M_CENTROID]) as i64, ctr)
97 gv_check("T7 supine loses projection (tissue settles toward the chest wall)" as *u8,
98 (msup[ST_M_PROJ] < mstand[ST_M_PROJ]) as i64, ctr)
99
100 // ---- T8 THE FIRMNESS LAW -----------------------------------------
101 // larger+softer must flatten MORE than smaller+firmer, measured the same way.
102 sg_settle(ST_PROF_SMALL_FIRM, 0, 0 - SG_G, 0, m2)
103 sg_settle(ST_PROF_SMALL_FIRM, 0, 0, 0 - SG_G, m3)
104 sg_report("firm-stand " as *u8, m2)
105 sg_report("firm-supine" as *u8, m3)
106 // "Flatten" measured as PROJECTION LOST when she lies down, per-mille of that profile's
107 // own resting projection. Aspect conflates height and width and is reported above as a
108 // diagnostic; how far the tissue settles toward the chest wall is the direct quantity the
109 // operator described, and normalising against each profile's own rest keeps a 75 mm
110 // breast and a 62 mm one comparable.
111 let Wr1: *i64 = st_new(ST_PROF_LARGE_SOFT, 10)
112 let mr1: *i64 = sys_mmap(ST_M_N*8) as *i64
113 st_measure(Wr1, mr1)
114 let Wr2: *i64 = st_new(ST_PROF_SMALL_FIRM, 10)
115 let mr2: *i64 = sys_mmap(ST_M_N*8) as *i64
116 st_measure(Wr2, mr2)
117 var flat_soft: i64 = 0
118 var flat_firm: i64 = 0
119 if mr1[ST_M_PROJ] > 0 {
120 flat_soft = (mr1[ST_M_PROJ] - msup[ST_M_PROJ]) * 1000 / mr1[ST_M_PROJ]
121 }
122 if mr2[ST_M_PROJ] > 0 {
123 flat_firm = (mr2[ST_M_PROJ] - m3[ST_M_PROJ]) * 1000 / mr2[ST_M_PROJ]
124 }
125 gv_puts(" rest proj soft=" as *u8); gv_num(mr1[ST_M_PROJ])
126 gv_puts(" firm=" as *u8); gv_num(mr2[ST_M_PROJ]); gv_puts("\n" as *u8)
127 gv_puts(" flatten_soft=" as *u8); gv_num(flat_soft)
128 gv_puts(" flatten_firm=" as *u8); gv_num(flat_firm)
129 gv_puts(" (per-mille projection lost lying down)\n" as *u8)
130 gv_check("T8 FIRMNESS LAW: soft tissue flattens more than firm tissue" as *u8,
131 (flat_soft > flat_firm) as i64, ctr)
132
133 // ---- T9 incompressibility ----------------------------------------
134 let dv: i64 = st_abs(msup[ST_M_SUMVOL] - mrest[ST_M_SUMVOL])
135 var volpct: i64 = 0
136 if mrest[ST_M_SUMVOL] > 0 { volpct = dv * 100 / mrest[ST_M_SUMVOL] }
137 gv_puts(" volume change under gravity=" as *u8); gv_num(volpct)
138 gv_puts("%\n" as *u8)
139 gv_check("T9 near-incompressible: total volume drifts < 10% under load" as *u8,
140 (volpct < 10) as i64, ctr)
141
142 // ---- T10/T11 TOUCH: deep press must dimple, never invert ----------
143 let Wt: *i64 = st_new(ST_PROF_LARGE_SOFT, 10)
144 st_run(Wt, 0, 0 - SG_G, 0, 60, SG_DT_US, SG_ITERS)
145 let snap: *i64 = sg_snapshot(Wt)
146 st_measure(Wt, m2)
147 // Press a 25 mm probe 20 mm into the front of the tissue, APPROACHING FROM OUTSIDE and
148 // ramping in over 40 substeps. Teleporting the probe to full depth in one step (the first
149 // draft) put its centre behind the surface, so vertices on the far side were projected
150 // radially BACKWARDS through the chest wall and elements inverted -- an artefact of
151 // instantaneous insertion, not of the solver. Real contact arrives over time.
152 let probe_r: i64 = 2500
153 let depth: i64 = 2000
154 let z_touch: i64 = m2[ST_M_PROJ] + probe_r
155 var pstep: i64 = 0
156 while pstep <= 40 {
157 st_set_touch(Wt, 1, 0, 0, z_touch - depth * pstep / 40, probe_r)
158 st_run(Wt, 0, 0 - SG_G, 0, 1, SG_DT_US, SG_ITERS)
159 pstep = pstep + 1
160 }
161 st_run(Wt, 0, 0 - SG_G, 0, 60, SG_DT_US, SG_ITERS)
162 st_measure(Wt, m3)
163 sg_report("pressed" as *u8, m3)
164 gv_check("T10 deep press does not invert a single element (all 6V > 0)" as *u8,
165 (m3[ST_M_MINVOL] > 0) as i64, ctr)
166
167 // dimple SHAPE: many vertices move, and none spikes away from its neighbours
168 let px: *i64 = Wt[ST_W_PX] as *i64
169 let py: *i64 = Wt[ST_W_PY] as *i64
170 let pz: *i64 = Wt[ST_W_PZ] as *i64
171 let srf: *i64 = Wt[ST_W_SRF] as *i64
172 // Displacement binned by RADIUS from the probe axis. A smooth dimple decays outward; a
173 // spike would move the contact vertex and leave its neighbours behind. Comparing peak to
174 // a global mean (the first draft) measured nothing useful -- the mean was diluted by
175 // ~200 distant vertices creeping under gravity, and a 22 mm dent from a 20 mm press was
176 // then flagged as a spike when it is the correct answer.
177 let bsum: *i64 = sys_mmap(4*8) as *i64
178 let bcnt: *i64 = sys_mmap(4*8) as *i64
179 var bz: i64 = 0
180 while bz < 4 { bsum[bz] = 0; bcnt[bz] = 0; bz = bz + 1 }
181 var ndisp: i64 = 0
182 var i: i64 = 0
183 while i < Wt[ST_W_NP] {
184 if srf[i] == 1 {
185 let dx: i64 = px[i] - snap[i*3]
186 let dy: i64 = py[i] - snap[i*3+1]
187 let dz: i64 = pz[i] - snap[i*3+2]
188 let d: i64 = st_isqrt(dx*dx + dy*dy + dz*dz)
189 if d > 50 { ndisp = ndisp + 1 }
190 let rx: i64 = snap[i*3]
191 let ry: i64 = snap[i*3+1]
192 let r: i64 = st_isqrt(rx*rx + ry*ry)
193 var b: i64 = r / 2500
194 if b > 3 { b = 3 }
195 bsum[b] = bsum[b] + d
196 bcnt[b] = bcnt[b] + 1
197 }
198 i = i + 1
199 }
200 var m0: i64 = 0
201 var m1: i64 = 0
202 var mb2: i64 = 0
203 if bcnt[0] > 0 { m0 = bsum[0] / bcnt[0] }
204 if bcnt[1] > 0 { m1 = bsum[1] / bcnt[1] }
205 if bcnt[2] > 0 { mb2 = bsum[2] / bcnt[2] }
206 gv_puts(" dimple: moved=" as *u8); gv_num(ndisp)
207 gv_puts(" mean-by-radius 0-25mm=" as *u8); gv_num(m0)
208 gv_puts(" 25-50mm=" as *u8); gv_num(m1)
209 gv_puts(" 50-75mm=" as *u8); gv_num(mb2); gv_puts(" cmm\n" as *u8)
210 gv_check("T11 touch PROPAGATES: a neighbourhood deforms, not one vertex" as *u8,
211 (ndisp >= 8) as i64, ctr)
212 gv_check("T12 dimple is SMOOTH: displacement decays monotonically outward" as *u8,
213 (m0 > m1 && m1 > mb2) as i64, ctr)
214
215 // ---- T13/T14 viscoelastic settle: it bounces, then it stops -------
216 let Wb: *i64 = st_new(ST_PROF_LARGE_SOFT, 10)
217 st_run(Wb, 0, 0 - SG_G, 0, SG_SETTLE, SG_DT_US, SG_ITERS)
218 // impulse: a step landing, applied as an upward velocity kick
219 let vy: *i64 = Wb[ST_W_VY] as *i64
220 let iw: *i64 = Wb[ST_W_IW] as *i64
221 var k: i64 = 0
222 while k < Wb[ST_W_NP] {
223 if iw[k] > 0 { vy[k] = vy[k] + 60000 }
224 k = k + 1
225 }
226 st_run(Wb, 0, 0 - SG_G, 0, 24, SG_DT_US, SG_ITERS)
227 st_measure(Wb, m2)
228 let kin100: i64 = m2[ST_M_KIN]
229 st_run(Wb, 0, 0 - SG_G, 0, 96, SG_DT_US, SG_ITERS)
230 st_measure(Wb, m3)
231 let kin500: i64 = m3[ST_M_KIN]
232 gv_puts(" impulse kin@100ms=" as *u8); gv_num(kin100)
233 gv_puts(" kin@500ms=" as *u8); gv_num(kin500); gv_puts("\n" as *u8)
234 gv_check("T13 tissue MOVES on impact (kinetic energy present at 100ms)" as *u8,
235 (kin100 > 0) as i64, ctr)
236 gv_check("T14 tissue SETTLES: energy at 500ms is under a fifth of 100ms" as *u8,
237 (kin500 * 5 < kin100) as i64, ctr)
238
239 // ---- T15 the same solver serves another body region ---------------
240 let Wg: *i64 = st_new(ST_PROF_GLUTE, 12)
241 st_run(Wg, 0, 0 - SG_G, 0, 120, SG_DT_US, SG_ITERS)
242 st_measure(Wg, m2)
243 sg_report("glute " as *u8, m2)
244 gv_check("T15 glute profile runs on the SAME solver (data, not new code)" as *u8,
245 (Wg[ST_W_NT] > 1000 && m2[ST_M_MINVOL] > 0) as i64, ctr)
246
247 // ---- T16 the envelope row, isolated ------------------------------
248 // Same geometry, same filling, only the skin envelope differs. Without this the skin row
249 // is ungated config: a mutation run proved the gate stayed GREEN with it neutralised.
250 sg_settle(ST_PROF_SOFT_TAUT, 0, 0, 0 - SG_G, m2)
251 sg_report("taut-supine" as *u8, m2)
252 var flat_lax: i64 = 0
253 var flat_taut: i64 = 0
254 if mr1[ST_M_PROJ] > 0 {
255 flat_lax = (mr1[ST_M_PROJ] - msup[ST_M_PROJ]) * 1000 / mr1[ST_M_PROJ]
256 flat_taut = (mr1[ST_M_PROJ] - m2[ST_M_PROJ]) * 1000 / mr1[ST_M_PROJ]
257 }
258 gv_puts(" lax envelope=" as *u8); gv_num(flat_lax)
259 gv_puts(" taut envelope=" as *u8); gv_num(flat_taut); gv_puts(" (per-mille)\n" as *u8)
260 gv_check("T16 a LAX envelope flattens further than a taut one, filling held equal" as *u8,
261 (flat_lax > flat_taut) as i64, ctr)
262
263 // ---- T17/T18/T19 POPULATION, not a clone army ---------------------
264 // Named profiles are reference points for controlled comparisons; real bodies come from
265 // seeds. Two things must hold together and neither is enough alone: bodies must genuinely
266 // DIFFER, and the physics must hold for EVERY one of them -- a solver validated only on
267 // four hand-tuned rows is validated on nothing a player will actually see.
268 var vmin: i64 = 999999999
269 var vmax: i64 = 0
270 var amin: i64 = 999999999
271 var amax: i64 = 0
272 var allsound: i64 = 1
273 var nseed: i64 = 0
274 gv_puts(" population sweep (seed: half-width mm / flatten permille / aspect):\n" as *u8)
275 while nseed < 8 {
276 let pid: i64 = ST_PROF_SEED_BASE + nseed
277 let Wp: *i64 = st_new(pid, 10)
278 st_measure(Wp, m2)
279 let rest_proj: i64 = m2[ST_M_PROJ]
280 st_run(Wp, 0, 0, 0 - SG_G, 150, SG_DT_US, SG_ITERS)
281 st_measure(Wp, m3)
282 var fl: i64 = 0
283 if rest_proj > 0 { fl = (rest_proj - m3[ST_M_PROJ]) * 1000 / rest_proj }
284 gv_puts(" s" as *u8); gv_num(nseed)
285 gv_puts(": a=" as *u8); gv_num(st_profile(pid, ST_PF_A_MM))
286 gv_puts(" flat=" as *u8); gv_num(fl)
287 gv_puts(" aspect=" as *u8); gv_num(m3[ST_M_ASPECT])
288 gv_puts("\n" as *u8)
289 if m3[ST_M_MINVOL] <= 0 { allsound = 0 }
290 if Wp[ST_W_NT] < 500 { allsound = 0 }
291 vmin = st_min(vmin, fl); vmax = st_max(vmax, fl)
292 amin = st_min(amin, m3[ST_M_ASPECT]); amax = st_max(amax, m3[ST_M_ASPECT])
293 nseed = nseed + 1
294 }
295 gv_puts(" spread: flatten " as *u8); gv_num(vmin); gv_puts(".." as *u8); gv_num(vmax)
296 gv_puts(" aspect " as *u8); gv_num(amin); gv_puts(".." as *u8); gv_num(amax)
297 gv_puts("\n" as *u8)
298 gv_check("T17 seeded bodies VARY in shape (aspect spread > 40)" as *u8,
299 (amax - amin > 40) as i64, ctr)
300 gv_check("T18 seeded bodies VARY in behaviour (flattening spread > 30 permille)" as *u8,
301 (vmax - vmin > 30) as i64, ctr)
302 gv_check("T19 the physics holds for EVERY sampled body, not just the tuned ones" as *u8,
303 allsound, ctr)
304
305 sys_exit(gv_verdict("SOFTTISSUE" as *u8, ctr,
306 "gravity direction, firmness and touch all measured" as *u8))
307 return 0
308}