code wiki / _hdl_build / nx_selfcontact_gate.nx
nx_selfcontact_gate.nx source
↩ module page · 253 lines · 12487 B
1// nx_selfcontact_gate.nx -- TWO TISSUE SURFACES CANNOT PASS THROUGH EACH OTHER (softbody SB10).
2//
3// THE ORACLE IS A MEASUREMENT, NOT THE ROUTINE'S OPINION. st_self_contact returns how many pairs it
4// found in contact, and grading it on its own count would be circular -- a routine that resolved
5// nothing and reported zero scores identically to one that worked. Every outcome tooth here is graded
6// on st_sc_min_sep, an INDEPENDENT all-pairs scan of the closest surface-to-surface approach.
7//
8// SCOPE. Non-interpenetration only. Friction at self-contact is deliberately NOT in this rung -- see
9// st_self_contact's note: bundling them is the compound bar that kept the old SB3 from ever closing,
10// and mu is UNSET estate-wide for want of a citation, so a friction term would provably do nothing
11// here while making the rung look bigger than it is.
12//
13// THREE RED RUNS BUILT THIS GATE, AND ALL THREE WERE ONE MISTAKE: ASKING A QUESTION ABOUT SURFACES WITH
14// AN INSTRUMENT THAT ANSWERS ABOUT BOXES OR AXES.
15// 1. Placement by bounding-box overlap left the nearest surfaces 397 cmm apart -- overlapping boxes
16// say the bodies occupy nearby space, never that any two points of them are close. T1 caught it.
17// 2. "Did it move" by bounding extreme read zero, because these cages meet mid-body and the particle
18// at max_x is nowhere near the contact.
19// 3. "Did it move" by x displacement ALSO read zero -- correctly. sc_place leaves the cages
20// overlapping in x, so the nearest surfaces meet on their flanks and the contact normal is mostly
21// y and z. The instrument was right; the AXIS was an assumption nobody had checked.
22// So every tooth below is axis-free, and the displacement instrument carries a planted-shift control:
23// a zero from it can never again be confused with a correction that did not happen.
24// license_tier: ORIGINAL No hw writes (Rule 26). expect_exit: 0
25import "nx_syscalls.nx"
26import "nx_softtissue.nx"
27import "nx_gate_verdict.nx"
28
29const SC_G: i64 = 9800
30const SC_ITERS: i64 = 8
31const SC_SETTLE: i64 = 40
32const SC_H_MM: i64 = 10
33// DERIVED, never chosen: the contact distance is the mesh's own mean surface-particle spacing. Picking
34// a number below it silently reduces every contact to a single point and lets surfaces slip between
35// particles. Measured by st_surface_spacing at run time, so a mesh change moves it automatically.
36const SC_DIST_FALLBACK: i64 = 300 // only if the mesh reports no spacing at all, which is a REFUSAL case
37const SC_PASSES: i64 = 24 // projection passes
38const SC_FAR: i64 = 100000 // 1 m apart: the broad phase must reject this for free
39const SC_SNAP: i64 = 4096 // >= any profile's particle count (largest measured is 1636)
40const SC_PLANT: i64 = 7 // planted displacement for the instrument's own control, cmm
41// Integer division truncates toward zero, so each correction is very slightly UNDER-applied and the
42// converged separation can sit a hair under SC_DIST. DERIVED from that, not picked: at most one cmm of
43// loss per correction, over the passes that actually run.
44const SC_TOL: i64 = SC_PASSES
45// The mesh must actually report a spacing. Zero means no surface, which is a refusal, not a default.
46
47// Fixture helpers are gate-local ON PURPOSE: placing and measuring bodies is a test concern, and adding
48// them to the solver API to serve a gate would be production surface no shipping caller asked for.
49func sc_translate(W: *i64, dx: i64, dy: i64, dz: i64) -> i64 {
50 let px: *i64 = W[ST_W_PX] as *i64
51 let py: *i64 = W[ST_W_PY] as *i64
52 let pz: *i64 = W[ST_W_PZ] as *i64
53 var i: i64 = 0
54 while i < W[ST_W_NP] {
55 px[i] = px[i] + dx
56 py[i] = py[i] + dy
57 pz[i] = pz[i] + dz
58 i = i + 1
59 }
60 return 0
61}
62
63func sc_nsurf(W: *i64) -> i64 {
64 let srf: *i64 = W[ST_W_SRF] as *i64
65 var n: i64 = 0
66 var i: i64 = 0
67 while i < W[ST_W_NP] {
68 if srf[i] == 1 { n = n + 1 }
69 i = i + 1
70 }
71 return n
72}
73
74func sc_snap3(W: *i64, sx: *i64, sy: *i64, sz: *i64) -> i64 {
75 let px: *i64 = W[ST_W_PX] as *i64
76 let py: *i64 = W[ST_W_PY] as *i64
77 let pz: *i64 = W[ST_W_PZ] as *i64
78 var i: i64 = 0
79 while i < W[ST_W_NP] {
80 sx[i] = px[i]
81 sy[i] = py[i]
82 sz[i] = pz[i]
83 i = i + 1
84 }
85 return 0
86}
87
88// How many SURFACE particles moved at all, on ANY axis. Axis-free by construction: the contact normal
89// is a property of where two curved bodies happen to meet, and no tooth may assume it.
90func sc_moved(W: *i64, sx: *i64, sy: *i64, sz: *i64) -> i64 {
91 let px: *i64 = W[ST_W_PX] as *i64
92 let py: *i64 = W[ST_W_PY] as *i64
93 let pz: *i64 = W[ST_W_PZ] as *i64
94 let srf: *i64 = W[ST_W_SRF] as *i64
95 var n: i64 = 0
96 var i: i64 = 0
97 while i < W[ST_W_NP] {
98 if srf[i] == 1 {
99 var d: i64 = 0
100 if px[i] != sx[i] { d = 1 }
101 if py[i] != sy[i] { d = 1 }
102 if pz[i] != sz[i] { d = 1 }
103 n = n + d
104 }
105 i = i + 1
106 }
107 return n
108}
109
110// Bring B toward A until their nearest SURFACE PARTICLES are within `target`.
111func sc_place(A: *i64, B: *i64, target: i64) -> i64 {
112 // st_new builds both cages at the SAME position, so they start fully coincident. Move B clear of A
113 // first: without this, min_sep is 0 before anything happens, the walk-in loop exits on its first
114 // test, and the "two bodies in contact" fixture is really one volume counted twice.
115 let ba: *i64 = sys_mmap(8*8) as *i64
116 let bb: *i64 = sys_mmap(8*8) as *i64
117 st_sc_bounds(A, 0, ba)
118 st_sc_bounds(B, 0, bb)
119 sc_translate(B, (ba[1] + target*4) - bb[0], 0, 0)
120 var k: i64 = 0
121 var done: i64 = 0
122 while k < 8 {
123 if done == 0 {
124 let sep: i64 = st_sc_min_sep(A, B)
125 if sep < target { done = 1 }
126 if done == 0 { sc_translate(B, 0 - (sep - target/2), 0, 0) }
127 }
128 k = k + 1
129 }
130 return done
131}
132
133func sc_settled(W: *i64) -> i64 {
134 st_run(W, 0, 0 - SC_G, 0, SC_SETTLE, ST_DT_REF_US, SC_ITERS)
135 return 0
136}
137
138func main() -> i64 {
139 let ctr: *i64 = gv_ctr()
140 gv_head("nx_selfcontact_gate -- two cages in contact resolve, and a separated pair costs nothing" as *u8)
141
142 let A: *i64 = st_new(ST_PROF_LARGE_SOFT, SC_H_MM)
143 let B: *i64 = st_new(ST_PROF_LARGE_SOFT, SC_H_MM)
144 sc_settled(A)
145 sc_settled(B)
146 let SC_DIST: i64 = st_surface_spacing(A)
147 gv_puts(" derived contact distance from mesh spacing = " as *u8); gv_num(SC_DIST)
148 gv_puts(" cmm (fallback would be " as *u8); gv_num(SC_DIST_FALLBACK); gv_puts(")\n" as *u8)
149 let placed: i64 = sc_place(A, B, SC_DIST)
150 let sep0: i64 = st_sc_min_sep(A, B)
151 gv_puts(" placed: converged=" as *u8); gv_num(placed)
152 gv_puts(" min_sep=" as *u8); gv_num(sep0)
153 gv_puts(" contact_dist=" as *u8); gv_num(SC_DIST); gv_puts("\n" as *u8)
154
155 // T1 ANTI-VACUITY, AND IT RUNS FIRST. If the fixture never comes within contact distance, every
156 // outcome tooth below passes on a pair of bodies that were never touching.
157 var t1: i64 = 0
158 if sep0 < SC_DIST { if placed == 1 { t1 = 1 } }
159 gv_check("T1 FIXTURE REACHED THE CONDITION: the pair really is closer than the contact distance" as *u8, t1, ctr)
160
161 let sxA: *i64 = sys_mmap(SC_SNAP*8) as *i64
162 let syA: *i64 = sys_mmap(SC_SNAP*8) as *i64
163 let szA: *i64 = sys_mmap(SC_SNAP*8) as *i64
164 let sxB: *i64 = sys_mmap(SC_SNAP*8) as *i64
165 let syB: *i64 = sys_mmap(SC_SNAP*8) as *i64
166 let szB: *i64 = sys_mmap(SC_SNAP*8) as *i64
167 sc_snap3(A, sxA, syA, szA)
168 sc_snap3(B, sxB, syB, szB)
169
170 let hits: i64 = st_self_contact(A, B, SC_DIST, SC_PASSES, ST_DT_REF_US)
171 let sep1: i64 = st_sc_min_sep(A, B)
172 let movedA: i64 = sc_moved(A, sxA, syA, szA)
173 let movedB: i64 = sc_moved(B, sxB, syB, szB)
174 gv_puts(" resolved: min_sep=" as *u8); gv_num(sep1)
175 gv_puts(" residual_pairs=" as *u8); gv_num(hits)
176 gv_puts(" pairs_tested=" as *u8); gv_num(st_sc_tested(A))
177 gv_puts(" peak_pairs=" as *u8); gv_num(st_sc_peak(A))
178 gv_puts(" moved A/B=" as *u8); gv_num(movedA); gv_puts("/" as *u8); gv_num(movedB); gv_puts("\n" as *u8)
179
180 // T2 THE CLAIM ITSELF, graded by the independent ruler.
181 var t2: i64 = 0
182 if sep1 >= SC_DIST - SC_TOL { t2 = 1 }
183 gv_check("T2 RESOLVED: closest approach reaches the contact distance, measured by an independent scan" as *u8, t2, ctr)
184
185 // T3 it moved in the right DIRECTION by a real amount. T2 alone passes for a fixture that happened
186 // to start almost resolved, so the improvement is asserted separately.
187 var t3: i64 = 0
188 if sep1 > sep0 { t3 = 1 }
189 gv_check("T3 SEPARATION INCREASED: the pass pushed the surfaces apart rather than merely not crashing" as *u8, t3, ctr)
190
191 // T4 BOTH bodies were corrected. A one-sided push resolves the overlap just as well and is wrong:
192 // it shoves whichever cage the loop happened to name first, which no pair of equal masses would do.
193 var t4: i64 = 0
194 if movedA > 0 { if movedB > 0 { t4 = 1 } }
195 // ...and the contact must have been a PATCH, not a point. With the distance derived from mesh
196 // spacing, neighbouring particles fall in range too; a peak of one would mean the derivation did
197 // not take, and every claim about shared correction would rest on a single pair.
198 if st_sc_peak(A) < 2 { t4 = 0 }
199 gv_check("T4 MASS-SPLIT: BOTH cages are corrected, so the push is shared rather than shoved onto one" as *u8, t4, ctr)
200
201 // T5 the broad phase is REAL. Two bodies a metre apart must cost zero pair tests; if this reads
202 // non-zero the quadratic term is paid on every call regardless of proximity, and the cost figure
203 // published on the board is a fiction.
204 let C: *i64 = st_new(ST_PROF_LARGE_SOFT, SC_H_MM)
205 let D: *i64 = st_new(ST_PROF_LARGE_SOFT, SC_H_MM)
206 sc_settled(C)
207 sc_settled(D)
208 sc_translate(D, SC_FAR, 0, 0)
209 st_self_contact(C, D, SC_DIST, SC_PASSES, ST_DT_REF_US)
210 var t5: i64 = 0
211 if st_sc_tested(C) == 0 { if st_sc_hits(C) == 0 { t5 = 1 } }
212 gv_puts(" far pair: pairs_tested=" as *u8); gv_num(st_sc_tested(C)); gv_puts("\n" as *u8)
213 gv_check("T5 BROAD PHASE: cages a metre apart cost ZERO pair tests, not a silent quadratic sweep" as *u8, t5, ctr)
214
215 // neg-control: a nonsensical contact distance or timestep must DECLINE to act rather than act
216 // arbitrarily. A guard that has only ever seen sane input has not been shown to refuse anything.
217 let E: *i64 = st_new(ST_PROF_LARGE_SOFT, SC_H_MM)
218 let F: *i64 = st_new(ST_PROF_LARGE_SOFT, SC_H_MM)
219 sc_settled(E)
220 sc_settled(F)
221 let before: i64 = st_sc_min_sep(E, F)
222 var t6: i64 = 0
223 if st_self_contact(E, F, 0, SC_PASSES, ST_DT_REF_US) == 0 {
224 if st_self_contact(E, F, SC_DIST, SC_PASSES, 0) == 0 {
225 if st_sc_min_sep(E, F) == before { t6 = 1 }
226 }
227 }
228 gv_check("neg-control-a-zero-distance-or-zero-dt-REFUSES-and-moves-nothing" as *u8, t6, ctr)
229
230 // neg-control: the cost readback must be NON-ZERO for a pair that IS in contact, or T5's zero
231 // proves nothing -- a counter wired to a constant zero passes T5 and says nothing about anything.
232 var t7: i64 = 0
233 if st_sc_tested(A) > 0 { t7 = 1 }
234 gv_check("neg-control-the-pair-counter-reads-NON-ZERO-for-a-touching-pair-so-T5s-zero-means-something" as *u8, t7, ctr)
235
236 // neg-control: THE DISPLACEMENT INSTRUMENT MUST DETECT A PLANTED SHIFT. T4 rests entirely on
237 // sc_moved, and a broken sc_moved reports zero for a correction that happened -- which is exactly
238 // what the earlier axis-bound version did. Plant SC_PLANT cmm, require EVERY surface particle to be
239 // seen moving, then put it back.
240 sc_snap3(C, sxA, syA, szA)
241 sc_translate(C, 0 - SC_PLANT, 0, 0)
242 let seen: i64 = sc_moved(C, sxA, syA, szA)
243 sc_translate(C, SC_PLANT, 0, 0)
244 let nsc: i64 = sc_nsurf(C)
245 var t8: i64 = 0
246 if seen == nsc { if nsc > 0 { t8 = 1 } }
247 gv_puts(" planted " as *u8); gv_num(SC_PLANT); gv_puts(" cmm: instrument saw " as *u8); gv_num(seen)
248 gv_puts(" of " as *u8); gv_num(nsc); gv_puts(" surface particles\n" as *u8)
249 gv_check("neg-control-the-displacement-instrument-DETECTS-a-planted-shift-so-T4s-count-is-trustworthy" as *u8, t8, ctr)
250
251 return gv_verdict("SELF-CONTACT" as *u8, ctr,
252 "two cages resolve to a stated separation, graded by an independent closest-approach scan; broad phase proven free; displacement instrument planted-shift proven" as *u8)
253}