code wiki / _hdl_build / nx_meshprofile.nx

nx_meshprofile.nx source

↩ module page · 296 lines · 14368 B

1// nx_meshprofile.nx -- ★WHERE DOES THE SURFACE STEP? The instrument that should have been built before 2// the third hypothesis (seq1472). 3// 4// Three guesses at the profile-prior banding were each built and rendered and each refuted: axial C0 5// creasing, layer superposition, part-edge divergence. That is three build-and-look cycles spent proposing 6// causes instead of one measurement locating one. This organ locates it: it reduces a mesh to its RADIUS 7// PROFILE about the vertical axis -- max radius per y band -- and reports the bands where that profile 8// JUMPS. A visible ring IS a step in this curve, so the largest jumps NAME the stations to go look at. 9// 10// Output is deliberately a SHORTLIST, not a dump: the top jumps with their per-mille stations, so two runs 11// can be compared by eye in a dozen lines instead of two hundred. 12// 13// nx_meshprofile <in.nxmesh> [layer -1|0|1|2] 14// license_tier: ORIGINAL expect_exit: 0 No hw writes (Rule 26). 15import "nx_syscalls.nx" 16import "nx_itoa_lib.nx" // shared MSB-first emitter (zero-alloc) 17const MP_MAGIC_8388607: i64 = 8388607 18const MP_MAGIC_8388608: i64 = 8388608 19const MP_MAGIC_1152921504606846976: i64 = 1152921504606846976 20 21const MP_TS: i64 = 84 22const MP_NB: i64 = 128 // y bands over the mesh's own height 23const MP_TOP: i64 = 12 // jumps reported 24const MP_FAR: i64 = 2000000000 25const MP_SCALE: i64 = 1000 // read vertices at micrometre precision, as nx_meshview does 26 27func mp_puts(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 } 28// MIGRATED to the shared emitter (debt 1785563586). The old body mmapped a scratch buffer 29// per call and never freed it. At PAGE granularity that is 4096B leaked PER CALL -- the 30// defect that took 28.5GB of a 36GB host in nx_ts_lumadiff (2MB input, ~3.66M calls). 31// nxi_* is MSB-first, allocates NOTHING, and emits identical bytes including the sign. 32func mp_num(v: i64) -> i64 { nxi_out(v); return 0 } 33func mp_rd32(b: *u8, o: i64) -> i64 { 34 return (b[o] as i64) | ((b[o+1] as i64)<<8) | ((b[o+2] as i64)<<16) | ((b[o+3] as i64)<<24) 35} 36// IEEE-754 single -> integer scaled by `scale` (same convention nx_meshview reads with) 37func mp_f32(bits: i64, scale: i64) -> i64 { 38 let sgn: i64 = (bits>>31) & 1 39 let exp: i64 = (bits>>23) & 255 40 var man: i64 = bits & MP_MAGIC_8388607 41 if exp == 0 { return 0 } 42 man = man | MP_MAGIC_8388608 43 let e: i64 = exp - 127 - 23 44 var v: i64 = man * scale 45 if e > 0 { v = v << e } else { v = v >> (0 - e) } 46 if sgn == 1 { v = 0 - v } 47 return v 48} 49func mp_isqrt(v: i64) -> i64 { 50 if v <= 0 { return 0 } 51 var r: i64 = 0 52 var b: i64 = MP_MAGIC_1152921504606846976 // ★4^30: the largest power of FOUR an i64 argument can need. The 53 // seed MUST be >= n or the algorithm starts below its own leading 54 // digit; 2^30 (=4^15) was too small for x*x+z*z, which reaches ~1.8e11. 55 var n: i64 = v 56 while b > n { b = b>>2 } 57 while b > 0 { 58 if n >= r + b { n = n - r - b; r = (r>>1) + b } else { r = r>>1 } 59 b = b>>2 60 } 61 return r 62} 63 64// `layers` verb (debt 1785968584): the layer names became machine-usable but OPERATOR-INVISIBLE -- 65// JSON transports mangle the raw 16-byte name fields. Print them as text, one row per layer. 66func mp_layers(path: *u8) -> i64 { 67 let ln2: *i64 = sys_mmap(16) as *i64 68 let b: *u8 = sys_read_file(path, ln2) 69 if (b as i64) == 0 { mp_puts("{\x22error\x22:\x22cannot read mesh\x22}\n" as *u8); return 3 } 70 if b[0] != (78 as u8) { mp_puts("{\x22error\x22:\x22not NXMSH2\x22}\n" as *u8); return 4 } 71 let nl: i64 = mp_rd32(b, 8) 72 let nt2: i64 = mp_rd32(b, 12) 73 mp_puts("LAYERS n=" as *u8); mp_num(nl); mp_puts(" tris=" as *u8); mp_num(nt2); mp_puts("\n" as *u8) 74 var L: i64 = 0 75 while L < nl { 76 let lb: i64 = 16 + L*24 77 mp_puts(" " as *u8); mp_num(L); mp_puts("\t" as *u8) 78 var k: i64 = 0 79 var shown: i64 = 0 80 while k < 16 { 81 let ch: i64 = b[lb+k] as i64 82 if ch >= 32 { if ch < 127 { sys_write(1, ((b as i64)+lb+k) as *u8, 1); shown = shown + 1 } } 83 k = k + 1 84 } 85 if shown == 0 { mp_puts("(unnamed)" as *u8) } 86 mp_puts("\toff=" as *u8); mp_num(mp_rd32(b, lb+16)) 87 mp_puts("\tcount=" as *u8); mp_num(mp_rd32(b, lb+20)) 88 mp_puts("\n" as *u8) 89 L = L + 1 90 } 91 return 0 92} 93 94func main(argc: i64, argv: *i64) -> i64 { 95 if argc < 2 { mp_puts("usage: nx_meshprofile <in.nxmesh> [layer -1|0|1|2] | layers <in.nxmesh>\n" as *u8); return 2 } 96 let a1v: *u8 = argv[1] as *u8 97 if a1v[0] == (108 as u8) { if a1v[1] == (97 as u8) { if a1v[2] == (121 as u8) { if a1v[3] == (101 as u8) { if a1v[4] == (114 as u8) { if a1v[5] == (115 as u8) { if a1v[6] == (0 as u8) { 98 if argc < 3 { mp_puts("usage: nx_meshprofile layers <in.nxmesh>\n" as *u8); return 2 } 99 let rcl: i64 = mp_layers(argv[2] as *u8) 100 sys_exit(rcl) 101 return rcl 102 } } } } } } } 103 var want: i64 = 0 - 1 104 if argc > 2 { 105 var a: i64 = 0 106 let s: *u8 = argv[2] as *u8 107 var i2: i64 = 0 108 var neg: i64 = 0 109 if s[0] == (45 as u8) { neg = 1; i2 = 1 } 110 while s[i2] != (0 as u8) { a = a*10 + ((s[i2] as i64) - 48); i2 = i2 + 1 } 111 if neg == 1 { a = 0 - a } 112 want = a 113 } 114 // ★LANE (argv[3]): 0 front(+z) 1 right(+x) 2 back(-z) 3 left(-x). A SINGLE midline lane is blind to 115 // anything that does not live on it -- nx_facemark learned this the expensive way (seq873/F1091: its 116 // midline judge rated the best brow WORST because a brow lives laterally). Four sectors need no trig. 117 var lane: i64 = 0 118 if argc > 3 { 119 var la: i64 = 0 120 let ls: *u8 = argv[3] as *u8 121 var li: i64 = 0 122 while ls[li] != (0 as u8) { la = la*10 + ((ls[li] as i64) - 48); li = li + 1 } 123 lane = la 124 } 125 let ln: *i64 = sys_mmap(16) as *i64 126 let buf: *u8 = sys_read_file(argv[1] as *u8, ln) 127 if (buf as i64) == 0 { mp_puts("{\x22error\x22:\x22cannot read mesh\x22}\n" as *u8); return 3 } 128 if buf[0] != (78 as u8) { mp_puts("{\x22error\x22:\x22not NXMSH2\x22}\n" as *u8); return 4 } 129 let nlayer: i64 = mp_rd32(buf, 8) 130 let nt: i64 = mp_rd32(buf, 12) 131 if nt <= 0 { mp_puts("{\x22error\x22:\x22no triangles\x22}\n" as *u8); return 5 } 132 let hdr: i64 = 16 + nlayer*24 133 let lay: i64 = hdr + nt*MP_TS 134 135 // pass 1: bounds over the selected layer 136 var lox: i64 = MP_FAR; var hix: i64 = 0-MP_FAR 137 var loy: i64 = MP_FAR; var hiy: i64 = 0-MP_FAR 138 var loz: i64 = MP_FAR; var hiz: i64 = 0-MP_FAR 139 var kept: i64 = 0 140 var t: i64 = 0 141 while t < nt { 142 var take: i64 = 1 143 if want >= 0 { if mp_rd32(buf, lay + t*4) != want { take = 0 } } 144 if take == 1 { 145 kept = kept + 1 146 var j: i64 = 0 147 while j < 3 { 148 let o: i64 = hdr + t*MP_TS + j*12 149 let x: i64 = mp_f32(mp_rd32(buf,o), MP_SCALE) 150 let y: i64 = mp_f32(mp_rd32(buf,o+4), MP_SCALE) 151 let z: i64 = mp_f32(mp_rd32(buf,o+8), MP_SCALE) 152 if x < lox { lox = x } 153 if x > hix { hix = x } 154 if y < loy { loy = y } 155 if y > hiy { hiy = y } 156 if z < loz { loz = z } 157 if z > hiz { hiz = z } 158 j = j + 1 159 } 160 } 161 t = t + 1 162 } 163 if kept == 0 { mp_puts("{\x22error\x22:\x22layer empty\x22}\n" as *u8); return 6 } 164 let cx: i64 = (lox+hix)/2 165 let cz: i64 = (loz+hiz)/2 166 var hy: i64 = hiy - loy 167 if hy < 1 { hy = 1 } 168 169 // pass 2: max radius about the vertical axis, per y band 170 let R: *i64 = sys_mmap(MP_NB*8) as *i64 171 var b: i64 = 0 172 while b < MP_NB { R[b] = 0; b = b + 1 } 173 t = 0 174 while t < nt { 175 var take2: i64 = 1 176 if want >= 0 { if mp_rd32(buf, lay + t*4) != want { take2 = 0 } } 177 if take2 == 1 { 178 var j: i64 = 0 179 while j < 3 { 180 let o: i64 = hdr + t*MP_TS + j*12 181 let x: i64 = mp_f32(mp_rd32(buf,o), MP_SCALE) - cx 182 let y: i64 = mp_f32(mp_rd32(buf,o+4), MP_SCALE) 183 let z: i64 = mp_f32(mp_rd32(buf,o+8), MP_SCALE) - cz 184 var bi: i64 = (y - loy)*MP_NB/hy 185 if bi < 0 { bi = 0 } 186 if bi >= MP_NB { bi = MP_NB - 1 } 187 let r: i64 = mp_isqrt(x*x + z*z) 188 // ★★FIXED-THETA LANE, and the first version's failure is why. Max-radius-over-ALL-theta is 189 // CONFOUNDED BY LIMBS: at hip and shoulder stations the arms enter and leave the band and 190 // the max jumps for reasons that have nothing to do with a surface step. MEASURED: the jump 191 // stations came back IDENTICAL for PROF=0 and PROF=1000 (539/554/593/609/632/648/671/718/ 192 // 750/773/820/828 both times) -- the signature of a metric reading ANATOMY, not a defect. 193 // seq1472 asked for radius-vs-y at FIXED THETA and this is now that: a narrow sector about 194 // the FRONT midline, where the torso and the arms do not overlap. 195 var ax: i64 = x 196 if ax < 0 { ax = 0 - ax } 197 var az: i64 = z 198 if az < 0 { az = 0 - az } 199 var keep: i64 = 0 200 if lane == 0 { if z > 0 { if ax*4 < r { keep = 1 } } } 201 if lane == 1 { if x > 0 { if az*4 < r { keep = 1 } } } 202 if lane == 2 { if z < 0 { if ax*4 < r { keep = 1 } } } 203 if lane == 3 { if x < 0 { if az*4 < r { keep = 1 } } } 204 if keep == 1 { if r > R[bi] { R[bi] = r } } 205 j = j + 1 206 } 207 } 208 t = t + 1 209 } 210 211 // ★THE SHORTLIST: the bands where the radius profile JUMPS. A visible ring is a step in this curve, 212 // so the largest jumps name the stations. Empty bands are skipped -- a gap is not a step. 213 mp_puts("{\x22organ\x22:\x22nx_meshprofile\x22,\x22layer\x22:" as *u8); mp_num(want) 214 mp_puts(",\x22tris\x22:" as *u8); mp_num(kept) 215 mp_puts(",\x22bands\x22:" as *u8); mp_num(MP_NB) 216 mp_puts(",\x22height_um\x22:" as *u8); mp_num(hy) 217 // ★DIAGNOSTIC BEFORE THE VERDICT: an empty shortlist could mean 'no steps' or 'the array never filled', 218 // and those are opposite conclusions. Print the population so the output can never be read as the wrong 219 // one. This is the instrumentation the empty first run needed, added instead of a fourth hypothesis. 220 var nz: i64 = 0 221 var rmax: i64 = 0 222 var rmin: i64 = MP_FAR 223 // ★WHERE, NOT JUST HOW MUCH. radius_min alone said our skull pinches to 2.5-9mm against the oracle's 224 // 37.7mm floor -- a real defect, but unactionable without the station. A number with no location cannot 225 // be fixed; it can only be worried about. 226 var rminat: i64 = 0-1 227 var rmaxat: i64 = 0-1 228 b = 0 229 while b < MP_NB { 230 if R[b] > 0 { 231 nz = nz + 1 232 if R[b] > rmax { rmax = R[b]; rmaxat = b*1000/MP_NB } 233 if R[b] < rmin { rmin = R[b]; rminat = b*1000/MP_NB } 234 } 235 b = b + 1 236 } 237 mp_puts("}\n DIAG nonzero_bands " as *u8); mp_num(nz) 238 mp_puts(" of " as *u8); mp_num(MP_NB) 239 mp_puts(" radius_min " as *u8); mp_num(rmin) 240 mp_puts(" @station " as *u8); mp_num(rminat) 241 mp_puts(" radius_max " as *u8); mp_num(rmax) 242 mp_puts(" @station " as *u8); mp_num(rmaxat) 243 mp_puts(" mid_band_R " as *u8); mp_num(R[MP_NB/2]) 244 mp_puts("\n station_permille radius_um jump_from_previous (largest first)\n" as *u8) 245 // ★★★CONSUME WITHOUT MUTATING THE MEASUREMENT. The consume step used to be `R[best] = R[best-1]`, which 246 // overwrites the band's measured radius to erase the jump -- and that CASCADES: rewriting band N changes 247 // band N-1's delta, so later passes invent jumps at radii the mesh never had, and the SAME station can be 248 // reported twice with two different radii (observed live: stations 203/210/218 each appeared twice, once 249 // at 67500/68529/69564 and again at a flat 48500). Only the first few entries were ever trustworthy. 250 // A mark array erases the jump from the SEARCH without editing the data the report prints. 251 let USED: *i64 = sys_mmap(MP_NB*8) as *i64 252 b = 0 253 while b < MP_NB { USED[b] = 0; b = b + 1 } 254 var shown: i64 = 0 255 var guard: i64 = 0 256 while guard < MP_TOP { 257 var best: i64 = 0 - 1 258 var bestv: i64 = 0 259 b = 1 260 while b < MP_NB { 261 if USED[b] == 0 { if R[b] > 0 { if R[b-1] > 0 { 262 var d: i64 = R[b] - R[b-1] 263 if d < 0 { d = 0 - d } 264 if d > bestv { bestv = d; best = b } 265 } } } 266 b = b + 1 267 } 268 if best < 0 { guard = MP_TOP } else { 269 mp_puts(" " as *u8); mp_num((loy + best*hy/MP_NB - loy)*1000/hy) 270 mp_puts(" " as *u8); mp_num(R[best]) 271 mp_puts(" " as *u8); mp_num(bestv) 272 mp_puts("\n" as *u8) 273 USED[best] = 1 // consume the jump from the SEARCH, never from the DATA 274 shown = shown + 1 275 guard = guard + 1 276 } 277 } 278 mp_puts(" jumps_shown " as *u8); mp_num(shown) 279 mp_puts(" of cap " as *u8); mp_num(MP_TOP) 280 mp_puts(" DECLARED\n" as *u8) 281 // ★★★THE CLOSURE PROFILE: radius as per-mille of THIS mesh's OWN max, per band. Dimensionless, so two 282 // meshes of different size compare directly and it transfers as a SHAPE rule with SIZE left procedural -- 283 // the same discipline nx_profile_fit already uses for sections. The top-12 shortlist structurally CANNOT 284 // carry this: the crown defect (ours 9000um @station 960 vs the oracle's 37716 @984) is a property of the 285 // whole tail of the curve, not of any single jump, so it was invisible until the curve itself was printed. 286 // -1 marks an empty band, which is itself the signal for the 10 height bands we are missing vs the oracle. 287 mp_puts(" closure_permille_of_max band0..127 (-1 = empty)\n " as *u8) 288 b = 0 289 while b < MP_NB { 290 mp_puts(" " as *u8) 291 if R[b] > 0 { mp_num(R[b]*1000/rmax) } else { mp_num(0-1) } 292 b = b + 1 293 } 294 mp_puts("\n" as *u8) 295 return 0 296}