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}