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}