nx_nxa_texbake_region_lib.nx source
↩ module page · 1638 lines · 78432 B
1// nx_nxa_texbake_region_lib.nx -- PER-REGION SKIN ATLAS + PERIODIC MICRO-RELIEF TILE, written INTO the
2// asset as a TEXR section (2026-08-30, /compare/graphics GR29 gpe_skin_microsurface -- the ASSET half).
3//
4// THE OPERATOR'S BAR, VERBATIM: "for per region we need texture ... make sure we aren't just doing more
5// smooth surface painting." So this organ bakes RELIEF -- a 16-bit height channel in MICRONS and a normal
6// DERIVED from that height by finite differences -- and it MEASURES, per region, what the chart footprint
7// can actually carry before it prints carried=1 for anything.
8//
9// WHAT WAS MEASURED BEFORE A BYTE WAS BAKED. The estate's adequacy ruler (nx_craft_emit cw_atlas_adequate)
10// is span-based: texel = 1.7m / res, 830um at the shipped 2048. But TEXC (nx_nxa_texc_lib) is a per-JOINT
11// cylindrical unwrap: each bone owns one tile of a g x g grid, u runs the FULL circumference, v the bone
12// length. The physical footprint of a texel is therefore (bone circumference / tile px) on u, and a chest
13// bone's circumference is a metre. The `measure` verb below solves the UV->3D Jacobian on every triangle
14// and reports, per region, the area-weighted footprint and the FRACTION OF SURFACE AREA that carries each
15// band -- the honest number. A per-region UNIQUE atlas at 2048 raises the tile px by ~g/gR and carries
16// the primary band on more of the body; it CANNOT carry the 250um secondary band around a torso at any
17// resolution this estate ships. That is arithmetic, and it is why the three finer bands ride a PERIODIC
18// MICRO TILE (nx_relief_lib rlf_relief_um2_tile): a texel pitch that is physical by construction (two per
19// pore orifice), repeated by the shader per chart at (chart extent / tile repeat) using the per-joint
20// circumference and length this section publishes. This is the field's practice for exactly this case:
21// Epic's skin guidance keeps wrinkles in the unique normal and puts pore detail in a tiling texture
22// [@ue-skin], MetaHuman carries a separate Micro Skin Detail normal layer [@mh-materials], the scan vendors
23// ship the micro band as its own displacement channel [@txyz-vface], Disney's procedural micro-wrinkle
24// synthesis places pores first and grows the furrow graph between them [@disney23], and Nagano et al.
25// measured that microstructure is a DISPLACEMENT that smooths under stretch and roughens under compression
26// [@nagano15] -- the next contract this file names (ntr_tension_attenuate) and does not fake.
27//
28// THE AREOLA IS ANATOMY, NOT A DISC. The torso region's albedo and height carry the nipple-areola complex
29// at the MESH-DERIVED bust apex (nx_nxa_dyna's lateral anchors, then the most anterior vertex near each):
30// nipple radius and projection from published anthropometry and the areola from the published 1:3
31// nipple-to-areola ratio [@nac-review21], the areola darker than the surrounding skin by a lightness step
32// DERIVED from the estate's own referee band against a published breast-skin tone [@nac-tattoo24],
33// Montgomery glands as a discrete dome field. Every one of those numbers is a ROW in
34// knowledge/areola_relief.conf with its source; this organ READS them (nx_lane_conf, the shared getter)
35// and REFUSES to bake anatomy when a row is missing -- an unsourced default is the magic number rule 11
36// forbids. The registry row nx_anatomy an_add(AF_MONTGOMERY ...) is the vocabulary; the ledger is the
37// provenance.
38//
39// TEXR PAYLOAD (i64 words). ADDITIVE: TEXM (map set 0) is carried byte-for-byte, never touched -- the
40// shipped page reads TEXM's blob at a hard-coded +144, so growing TEXM would break the live sampler.
41// hdr[16]: nsets res model seed hue blob_words nmaps_per_set micro_px micro_repeat_um disp_range_um
42// nj set_words joint_words version 0 0
43// joints[nj*4]: region rank circ_um len_um (rank -1 = joint owns no tile; circ/len feed the page's
44// per-chart micro repeat counts: circ/repeat, len/repeat)
45// sets[nsets*set_words]: region gR njr tile_px foot_mean_um foot_worst_um bands_unique bands_micro
46// pore_period_um apexLx apexLy apexRx apexRy areola_r_px areola_dl_e3 carried_area_permil
47// then 5 map records [id off len]: 0 albedo 1 disp16 2 normal 3 tile_disp16 4 tile_normal
48// blob: PNGs. disp16 is RGB8 PNG with R=hi G=lo of (h_um*2 + 32768) and B=255 on mesh-covered texels.
49// license_tier: ORIGINAL No hw writes (Rule 26).
50import "nx_syscalls.nx"
51import "nx_nxa.nx"
52import "nx_nxa_texc_lib.nx"
53import "nx_nxa_texbake_lib.nx"
54import "nx_relief_lib.nx"
55import "nx_png.nx"
56import "nx_skin_ita.nx"
57import "nx_lane_conf.nx"
58import "nx_fsops_lib.nx"
59
60// ---- section geometry ----
61const NTR_HDR_WORDS: i64 = 16
62const NTR_JOINT_WORDS: i64 = 4
63const NTR_SET_META: i64 = 16
64const NTR_NMAPS: i64 = 5
65const NTR_MAPREC: i64 = 3
66const NTR_VERSION: i64 = 1
67const NTR_REGIONS: i64 = 4
68const NTR_MAP_ALBEDO: i64 = 0
69const NTR_MAP_DISP: i64 = 1
70const NTR_MAP_NORMAL: i64 = 2
71const NTR_MAP_TDISP: i64 = 3
72const NTR_MAP_TNORMAL: i64 = 4
73// header word slots
74const NTR_W_NSETS: i64 = 0
75const NTR_W_RES: i64 = 1
76const NTR_W_MODEL: i64 = 2
77const NTR_W_SEED: i64 = 3
78const NTR_W_HUE: i64 = 4
79const NTR_W_BLOBW: i64 = 5
80const NTR_W_NMAPS: i64 = 6
81const NTR_W_MPX: i64 = 7
82const NTR_W_MREP: i64 = 8
83const NTR_W_RANGE: i64 = 9
84const NTR_W_NJ: i64 = 10
85const NTR_W_SETW: i64 = 11
86const NTR_W_JW: i64 = 12
87const NTR_W_VER: i64 = 13
88// set meta slots
89const NTR_S_REGION: i64 = 0
90const NTR_S_GR: i64 = 1
91const NTR_S_NJR: i64 = 2
92const NTR_S_TPX: i64 = 3
93const NTR_S_FMEAN: i64 = 4
94const NTR_S_FWORST: i64 = 5
95const NTR_S_BANDS_U: i64 = 6
96const NTR_S_BANDS_M: i64 = 7
97const NTR_S_PERIOD: i64 = 8
98const NTR_S_ALX: i64 = 9
99const NTR_S_ALY: i64 = 10
100const NTR_S_ARX: i64 = 11
101const NTR_S_ARY: i64 = 12
102const NTR_S_ARPX: i64 = 13
103const NTR_S_DL: i64 = 14
104const NTR_S_CARRIED: i64 = 15
105// the PBR model word TEXM uses (spec-gloss), restated so the two sections agree by value
106const NTR_MODEL_SPECGLOSS: i64 = 1
107// ---- encoding ----
108// signed range of the 16-bit height channel, microns. DERIVED: the smallest power of two above the
109// cited nipple projection (9000um, nac-review21 via the ledger), so nipple dome, gland dome and pore pit
110// share ONE channel at 0.5um per LSB (65536 levels over 32768um). A pit of 30um is 60 levels.
111const NTR_DISP_RANGE_UM: i64 = 16384
112const NTR_Q16: i64 = 65536
113const NTR_U16_HALF: i64 = 32768
114const NTR_U16_MAX: i64 = 65535
115const NTR_BYTE: i64 = 256
116const NTR_BYTE_MAX: i64 = 255
117const NTR_RGB: i64 = 3
118const NTR_WORD: i64 = 8
119const NTR_SUB: i64 = 16 // rasterizer sub-pixel scale
120const NTR_PI_E3: i64 = 3142
121const NTR_E3: i64 = 1000
122const NTR_E6: i64 = 1000000
123const NTR_MISS: i64 = 0 - 1
124// the two-texel bar every band is judged by (rlf_band_resolvable), restated as the divisor it is
125const NTR_TEXELS_PER_BAND: i64 = 2
126// the 16-gon used to place gland centres: within 2 percent of a circle at these radii, no trig table
127const NTR_NGON: i64 = 16
128// ---- control bits (the gate's controls differ from the subject ONLY in these) ----
129const NTR_CTL_RELIEF: i64 = 1 // unique-atlas micro-relief bands
130const NTR_CTL_ANATOMY: i64 = 2 // nipple dome + Montgomery glands in the height channel
131const NTR_CTL_PIGMENT: i64 = 4 // areola lightness step in the albedo
132const NTR_CTL_MICRO: i64 = 8 // micro tile carries secondary+pore (else primary only = smooth control)
133const NTR_CTL_SETS: i64 = 16 // write the region sets at all (0 = nsets 0, the adequacy neg-control)
134const NTR_CTL_ALL: i64 = 31
135const NTR_CTL_ONLY_TORSO: i64 = 32 // bake only the torso set (the gate's 2048 probe run); NOT in ALL
136// ---- refusals ----
137const NTR_OK: i64 = 0
138const NTR_E_IO: i64 = 1
139const NTR_E_BAD: i64 = 3
140const NTR_E_NOSEC: i64 = 4
141const NTR_E_MISMATCH: i64 = 5
142const NTR_E_CONF: i64 = 6
143// ---- the provenance ledger (rule 11: numbers live in conf with their source) ----
144const NTR_CONF_REL: *u8 = "knowledge/areola_relief.conf"
145const NTR_CONF_ABS: *u8 = "/volume1/homes/elderwesto/nishihost/knowledge/areola_relief.conf"
146// anatomy parameter slots (the ledger row order)
147const NTR_AN_RA: i64 = 0
148const NTR_AN_RN: i64 = 1
149const NTR_AN_HN: i64 = 2
150const NTR_AN_DL: i64 = 3
151const NTR_AN_NMONT: i64 = 4
152const NTR_AN_RG: i64 = 5
153const NTR_AN_HG: i64 = 6
154const NTR_AN_SEED: i64 = 7
155const NTR_AN_N: i64 = 8
156// box-mean window pad for the pore-period measurement: 2 x pit radius + this, in texels
157const NTR_HP_PAD: i64 = 1
158// ---- the run context (one array instead of twenty parameters) ----
159const CX_W: i64 = 0
160const CX_VWO: i64 = 1
161const CX_TWO: i64 = 2
162const CX_XWO: i64 = 3
163const CX_SWO: i64 = 4
164const CX_NV: i64 = 5
165const CX_NTR: i64 = 6
166const CX_NJ: i64 = 7
167const CX_G: i64 = 8
168const CX_RES: i64 = 9
169const CX_UNUM: i64 = 10
170const CX_UDEN: i64 = 11
171const CX_REGS: i64 = 12
172const CX_JRANK: i64 = 13
173const CX_AX: i64 = 14
174const CX_APEX: i64 = 15
175const CX_N: i64 = 16
176
177// ---- PROBES for the gate: when NTR_PROBE_ON is 1 the torso set's raw maps and the micro tile are copied
178// out before PNG encoding, so the gate measures the SAME arrays the PNGs were made from (lossless), in
179// process, without a decoder. Zero when off; the CLI never sets them.
180static NTR_PROBE_ON: i64
181static NTR_PROBE_RES: i64
182static NTR_PROBE_RGB: i64
183static NTR_PROBE_NRM: i64
184static NTR_PROBE_HQ: i64
185static NTR_PROBE_COV: i64
186static NTR_PROBE_FA: i64
187static NTR_PROBE_FB: i64
188static NTR_PROBE_META: i64
189static NTR_PROBE_SETW: i64
190static NTR_PROBE_THQ: i64
191static NTR_PROBE_TNRM: i64
192static NTR_PROBE_MPX: i64
193
194// ------------------------------------------------------------------------------------------------
195// small helpers
196func ntr_abs(v: i64) -> i64 { if v < 0 { return 0 - v } return v }
197func ntr_max(a: i64, b: i64) -> i64 { if a > b { return a } return b }
198func ntr_min(a: i64, b: i64) -> i64 { if a < b { return a } return b }
199func ntr_clamp(v: i64, lo: i64, hi: i64) -> i64 { if v < lo { return lo } if v > hi { return hi } return v }
200func ntr_copy(dst: *u8, src: *u8, n: i64) -> i64 { var i: i64 = 0; while i < n { dst[i] = src[i]; i = i + 1 } return 0 }
201func ntr_dup(src: *u8, n: i64) -> i64 { let d: *u8 = sys_mmap(n + 64); ntr_copy(d, src, n); return d as i64 }
202// ledger read: MISS when the file or the row is absent -- the caller REFUSES, never defaults
203func ntr_conf(key: *u8) -> i64 { return lc_geti(NTR_CONF_REL, NTR_CONF_ABS, key, NTR_MISS) }
204// region name for receipts
205func ntr_region_name(r: i64) -> *u8 {
206 if r == NTB_FACE { return "face" as *u8 }
207 if r == NTB_TORSO { return "torso" as *u8 }
208 if r == NTB_LIMBS { return "limbs" as *u8 }
209 return "gens" as *u8
210}
211// bands carried at a footprint of f um per texel: same predicate as rlf_band_resolvable_span, applied to
212// a MEASURED footprint instead of a span/res pair
213func ntr_bands_at_footprint(f_um: i64) -> i64 {
214 var m: i64 = 0
215 if f_um <= 0 { return 0 }
216 if RLF_RELIEF_LAMBDA_UM / f_um >= NTR_TEXELS_PER_BAND { m = m + RLF_BAND_PRIMARY }
217 if RLF_SEC_LAMBDA_UM / f_um >= NTR_TEXELS_PER_BAND { m = m + RLF_BAND_SECONDARY }
218 if RLF_PORE_PITCH_UM / f_um >= NTR_TEXELS_PER_BAND { m = m + RLF_BAND_POREGRID }
219 if RLF_PORE_DIA_UM / f_um >= NTR_TEXELS_PER_BAND { m = m + RLF_BAND_ORIFICE }
220 return m
221}
222// 16-bit height encode/decode, microns <-> unsigned 16
223// The caller owns a validated complete chart and a quantized signed-height buffer.
224// Adjacent atlas charts have unrelated coordinates: their heights cannot be derivative neighbors.
225// Retain the centered stencil inside a chart; at an edge use a one-sided difference with its actual spacing.
226func ntr_normal_chart(hq:*i64,res:i64,tp:i64,x:i64,y:i64,footprint_um:i64,out3:*i64)->i64 {
227 let x0:i64=(x/tp)*tp;let y0:i64=(y/tp)*tp
228 let xp:i64=ntr_min(x+1,x0+tp-1);let xm:i64=ntr_max(x-1,x0)
229 let yp:i64=ntr_min(y+1,y0+tp-1);let ym:i64=ntr_max(y-1,y0)
230 var dx:i64=hq[y*res+xp]-hq[y*res+xm]
231 var dy:i64=hq[yp*res+x]-hq[ym*res+x]
232 if xp-xm==1{dx=dx*2}
233 if yp-ym==1{dy=dy*2}
234 return ntb_encode_normal_um(dx,dy,2*footprint_um,out3)
235}
236func ntr_h_enc(h_um: i64) -> i64 {
237 return ntr_clamp(h_um * NTR_U16_HALF / NTR_DISP_RANGE_UM + NTR_U16_HALF, 0, NTR_U16_MAX)
238}
239func ntr_h_dec(v: i64) -> i64 { return (v - NTR_U16_HALF) * NTR_DISP_RANGE_UM / NTR_U16_HALF }
240// pack an RGB8 map into write_png_buf's pixel words and encode; returns PNG bytes or <=0
241// Validate source section extents before any per-vertex or per-joint access.
242// This boundary does not establish physical units, calibrated material values, or visual quality.
243func ntr_source_safe(b:*u8,flen:i64)->i64 {
244 if (b as i64)<=0 || flen<NT_HDR{return 0}
245 let w:*i64=b as *i64
246 let vo:i64=nxa_counted_section(b,flen,nxa_tag4("VERT"),3)
247 let tr:i64=nxa_counted_section(b,flen,nxa_tag4("TRIS"),3)
248 let sk:i64=nxa_counted_section(b,flen,nxa_tag4("SKEL"),8)
249 if vo<0 || tr<0 || sk<0{return 0}
250 let nv:i64=w[vo];let nj:i64=w[sk];let nt:i64=w[tr]
251 if nv<=0 || nj<=0 || nj>NTB_MAGIC_4095{return 0}
252 let xe:i64=nxa_section_entry(b,flen,nxa_tag4("TEXC"));if xe<0{return 0}
253 let xo:i64=w[xe+1]/NTR_WORD;let xwords:i64=w[xe+2]
254 if xwords<4 || nv>(xwords-4)/3{return 0}
255 if w[xo]!=nv || w[xo+1]!=3 || w[xo+2]<=0{return 0}
256 var j:i64=0
257 while j<nj{let parent:i64=w[sk+1+j*8];if parent<(0-1) || parent>=nj{return 0};j=j+1}
258 var v:i64=0
259 while v<nv{
260 let at:i64=xo+4+v*3
261 if w[at]<0 || w[at]>NTR_Q16 || w[at+1]<0 || w[at+1]>NTR_Q16{return 0}
262 if w[at+2]<0 || w[at+2]>=nj{return 0}
263 v=v+1
264 }
265 var t:i64=0;while t<nt*3{if w[tr+1+t]<0 || w[tr+1+t]>=nv{return 0};t=t+1}
266 let de:i64=nxa_section_entry(b,flen,nxa_tag4("DYNA"))
267 if de>=0{
268 let d:i64=w[de+1]/NTR_WORD;let dw:i64=w[de+2]
269 if dw<2{return 0};let count:i64=w[d];let stride:i64=w[d+1]
270 if count<0 || stride<8{return 0};if count>(dw-2)/stride{return 0}
271 } else {if de!=(0-1){return 0}}
272 let me:i64=nxa_section_entry(b,flen,nxa_tag4("TEXM"))
273 if me>=0{if w[me+2]<2{return 0}}else{if me!=(0-1){return 0}}
274 let ns:i64=w[2];var s:i64=0
275 while s<ns{
276 let tag:i64=w[4+s*4];if nxa_section_entry(b,flen,tag)<0{return 0}
277 var k:i64=0;while k<s{if w[4+k*4]==tag{return 0};k=k+1}
278 s=s+1
279 }
280 return 1
281}
282func ntr_blob_capacity(res:i64,mpx:i64,nsets:i64)->i64 {
283 let a:i64=png_buf_cap(res,res);let b:i64=png_buf_cap(mpx,mpx)
284 let imax:i64=9223372036854775807
285 if a<0 || b<0 || nsets<0 || nsets>NTR_REGIONS{return 0-1}
286 if a>imax/3 || b>imax/2{return 0-1}
287 if 3*a>imax-2*b{return 0-1}
288 let per:i64=3*a+2*b
289 if nsets>0{if per>(imax-NT_PAD)/nsets{return 0-1}}
290 return nsets*per+NT_PAD
291}
292func ntr_png_checked(rgb:*u8,w:i64,h:i64,fbp:*i64,fb_pixels:i64,out:*u8,capacity:i64)->i64 {
293 if (rgb as i64)<=0 || (fbp as i64)<=0 || (out as i64)<=0{return 0-1}
294 let needed:i64=png_buf_cap(w,h)
295 if needed<0 || capacity<needed || fb_pixels<w*h{return 0-1}
296 var p:i64=0;let n:i64=w*h
297 while p<n{
298 fbp[p]=(rgb[p*NTR_RGB] as i64)+(rgb[p*NTR_RGB+1] as i64)*NTR_BYTE+(rgb[p*NTR_RGB+2] as i64)*NTR_Q16;p=p+1
299 }
300 return write_png_buf_checked(fbp,fb_pixels,w,h,out,capacity)
301}
302
303func ntr_png(rgb: *u8, w: i64, h: i64, fbp: *i64, out: *u8) -> i64 {
304 var p: i64 = 0
305 let n: i64 = w*h
306 while p < n {
307 fbp[p] = (rgb[p*NTR_RGB] as i64) + (rgb[p*NTR_RGB+1] as i64)*NTR_BYTE + (rgb[p*NTR_RGB+2] as i64)*NTR_Q16
308 p = p + 1
309 }
310 return write_png_buf(fbp, w, h, out)
311}
312// 16-gon unit vectors x1000
313func ntr_cos16(s: i64) -> i64 {
314 if s == 0 { return 1000 }
315 if s == 1 { return 924 }
316 if s == 2 { return 707 }
317 if s == 3 { return 383 }
318 if s == 4 { return 0 }
319 if s == 5 { return 0 - 383 }
320 if s == 6 { return 0 - 707 }
321 if s == 7 { return 0 - 924 }
322 if s == 8 { return 0 - 1000 }
323 if s == 9 { return 0 - 924 }
324 if s == 10 { return 0 - 707 }
325 if s == 11 { return 0 - 383 }
326 if s == 12 { return 0 }
327 if s == 13 { return 383 }
328 if s == 14 { return 707 }
329 return 924
330}
331func ntr_sin16(s: i64) -> i64 { return ntr_cos16((s + 12) % NTR_NGON) }
332
333// ------------------------------------------------------------------------------------------------
334// BODY AXES, derived from the mesh's own extents (the nx_nxa_dyna derivation, restated so the apex search
335// below reads the SAME lateral/anterior/stature axes the DYNA anchors were measured on).
336// out: [0]=sa [1]=la [2]=aa [3]=min_s [4]=span_s [5]=mid_a
337func ntr_axes(w: *i64, vwo: i64, nv: i64, out: *i64) -> i64 {
338 let e0: i64 = ntb_axspan(w, vwo, nv, 0)
339 let e1: i64 = ntb_axspan(w, vwo, nv, 1)
340 let e2: i64 = ntb_axspan(w, vwo, nv, 2)
341 var sa: i64 = 2
342 if e0 >= e1 { if e0 >= e2 { sa = 0 } }
343 if e1 > e0 { if e1 >= e2 { sa = 1 } }
344 var la: i64 = 0
345 var aa: i64 = 1
346 if sa == 0 { la = 1; aa = 2; if e2 > e1 { la = 2; aa = 1 } }
347 if sa == 1 { la = 0; aa = 2; if e2 > e0 { la = 2; aa = 0 } }
348 if sa == 2 { la = 0; aa = 1; if e1 > e0 { la = 1; aa = 0 } }
349 var mns: i64 = w[vwo + 1 + sa]
350 var mxs: i64 = mns
351 var mna: i64 = w[vwo + 1 + aa]
352 var mxa: i64 = mna
353 var i: i64 = 0
354 while i < nv {
355 let s: i64 = w[vwo + 1 + i*3 + sa]
356 let a: i64 = w[vwo + 1 + i*3 + aa]
357 if s < mns { mns = s }
358 if s > mxs { mxs = s }
359 if a < mna { mna = a }
360 if a > mxa { mxa = a }
361 i = i + 1
362 }
363 out[0] = sa
364 out[1] = la
365 out[2] = aa
366 out[3] = mns
367 out[4] = mxs - mns
368 out[5] = (mna + mxa) / 2
369 if out[4] <= 0 { return 0 }
370 return 1
371}
372
373// ------------------------------------------------------------------------------------------------
374// REGION LAYOUT: joint -> (region, rank). gR[r] = grid edge for region r, njr[r] = tiles in region r,
375// rj[r*nj + rank] = joint owning that tile. Returns nj or a negative refusal. Regions come from
376// ntb_regions, the SAME classifier the whole-body bake uses -- one owner of the grouping.
377func ntr_layout(b: *u8, flen: i64, regs: *i64, jrank: *i64, gR: *i64, njr: *i64, rj: *i64) -> i64 {
378 let perms: *i64 = sys_mmap(NTB_MAGIC_4096*NTR_WORD) as *i64
379 let nj: i64 = ntb_regions(b, flen, regs, perms)
380 if nj < 0 { return nj }
381 if nj > NTB_MAGIC_4095 { nt_err("TEXR-REFUSE joint count over classifier capacity\n" as *u8); return 0 - NTR_E_BAD }
382 var r: i64 = 0
383 while r < NTR_REGIONS { njr[r] = 0; r = r + 1 }
384 var j: i64 = 0
385 while j < nj {
386 let rg: i64 = regs[j]
387 jrank[j] = NTR_MISS
388 if rg >= 0 { if rg < NTR_REGIONS {
389 jrank[j] = njr[rg]
390 rj[rg*nj + njr[rg]] = j
391 njr[rg] = njr[rg] + 1
392 } }
393 j = j + 1
394 }
395 r = 0
396 while r < NTR_REGIONS { gR[r] = nt_grid(njr[r]); if njr[r] == 0 { gR[r] = 0 } r = r + 1 }
397 return nj
398}
399
400// ------------------------------------------------------------------------------------------------
401// PER-JOINT PHYSICAL EXTENTS, microns: circumference (2 pi x mean radial distance of the joint's own
402// TEXC-owned vertices from its bind bone axis) and bone length. These are what the page divides by the
403// tile repeat to get per-chart micro repeat counts on u and v -- the anisotropy of the unwrap is corrected
404// there, not hidden. unit: pos_um = pos_units * unum / uden (stature declared = RLF_BODY_UM, the same
405// assumption the whole-body ruler already makes).
406func ntr_joint_extents(cx: *i64, upax: i64, circ: *i64, blen: *i64, nown: *i64) -> i64 {
407 let w: *i64 = cx[CX_W] as *i64
408 let vwo: i64 = cx[CX_VWO]
409 let xwo: i64 = cx[CX_XWO]
410 let swo: i64 = cx[CX_SWO]
411 let nv: i64 = cx[CX_NV]
412 let nj: i64 = cx[CX_NJ]
413 let unum: i64 = cx[CX_UNUM]
414 let uden: i64 = cx[CX_UDEN]
415 let sumr: *i64 = sys_mmap(nj*NTR_WORD + 64) as *i64
416 var j: i64 = 0
417 while j < nj { sumr[j] = 0; nown[j] = 0; circ[j] = 0; blen[j] = 0; j = j + 1 }
418 var i: i64 = 0
419 while i < nv {
420 let pj: i64 = w[xwo + 4 + i*3 + 2]
421 if pj >= 0 { if pj < nj {
422 let par: i64 = w[swo + 1 + pj*8]
423 let jx: i64 = w[swo + 1 + pj*8 + 1]
424 let jy: i64 = w[swo + 1 + pj*8 + 2]
425 let jz: i64 = w[swo + 1 + pj*8 + 3]
426 var ax: i64 = 0
427 var ay: i64 = 0
428 var az: i64 = 0
429 if upax == 1 { ay = NTR_E3 } else { az = NTR_E3 }
430 if par >= 0 { if par < nj {
431 ax = jx - w[swo + 1 + par*8 + 1]
432 ay = jy - w[swo + 1 + par*8 + 2]
433 az = jz - w[swo + 1 + par*8 + 3]
434 } }
435 if ax == 0 { if ay == 0 { if az == 0 { if upax == 1 { ay = NTR_E3 } else { az = NTR_E3 } } } }
436 let dx: i64 = w[vwo + 1 + i*3] - jx
437 let dy: i64 = w[vwo + 1 + i*3 + 1] - jy
438 let dz: i64 = w[vwo + 1 + i*3 + 2] - jz
439 let sh: i64 = NT_PRESH
440 let dxp: i64 = dx / sh
441 let dyp: i64 = dy / sh
442 let dzp: i64 = dz / sh
443 let axp: i64 = ax / sh
444 let ayp: i64 = ay / sh
445 let azp: i64 = az / sh
446 let a2: i64 = axp*axp + ayp*ayp + azp*azp
447 let d2: i64 = dxp*dxp + dyp*dyp + dzp*dzp
448 var perp2: i64 = d2
449 if a2 > 0 {
450 let dot: i64 = dxp*axp + dyp*ayp + dzp*azp
451 perp2 = d2 - dot*dot/a2
452 if perp2 < 0 { perp2 = 0 }
453 }
454 sumr[pj] = sumr[pj] + tm_isqrt(perp2) * sh
455 nown[pj] = nown[pj] + 1
456 } }
457 i = i + 1
458 }
459 j = 0
460 while j < nj {
461 let par: i64 = w[swo + 1 + j*8]
462 var lenu: i64 = 0
463 if par >= 0 { if par < nj {
464 let ax: i64 = w[swo + 1 + j*8 + 1] - w[swo + 1 + par*8 + 1]
465 let ay: i64 = w[swo + 1 + j*8 + 2] - w[swo + 1 + par*8 + 2]
466 let az: i64 = w[swo + 1 + j*8 + 3] - w[swo + 1 + par*8 + 3]
467 let sh: i64 = NT_PRESH
468 lenu = tm_isqrt((ax/sh)*(ax/sh) + (ay/sh)*(ay/sh) + (az/sh)*(az/sh)) * sh
469 } }
470 blen[j] = lenu * unum / uden
471 if nown[j] > 0 {
472 let rmean: i64 = sumr[j] / nown[j]
473 circ[j] = 2 * NTR_PI_E3 * rmean / NTR_E3 * unum / uden
474 }
475 j = j + 1
476 }
477 sys_munmap(sumr as *u8, nj*NTR_WORD + 64)
478 return 0
479}
480
481// ------------------------------------------------------------------------------------------------
482// TEXC uv -> atlas pixel (SUB-pixel units) in one of two layouts. mode 0 = the whole-body layout the
483// incumbent samples (px = u * res / Q16); mode 1 = the per-region layout (the joint's tile moved to its
484// rank slot in a gR grid at tile_px per tile). axis 0 = u/x, 1 = v/y.
485func ntr_px_sub(uv: i64, j: i64, axis: i64, mode: i64, g: i64, gR: i64, tpR: i64, rank: i64, res: i64) -> i64 {
486 if mode == 0 { return uv * res * NTR_SUB / NTR_Q16 }
487 let tw: i64 = NTR_Q16 / g
488 var t: i64 = j % g
489 if axis == 1 { t = j / g }
490 var rk: i64 = rank % gR
491 if axis == 1 { rk = rank / gR }
492 let local: i64 = uv - t*tw
493 return rk*tpR*NTR_SUB + local*tpR*NTR_SUB/tw
494}
495
496// ------------------------------------------------------------------------------------------------
497// THE HONEST RULER: per-triangle UV->3D Jacobian in a layout. For every triangle whose three vertices
498// share one joint (a chart-interior triangle), solve dP/du and dP/dv in um per PIXEL and take the worse
499// axis as that triangle's footprint. Accumulates per JOINT (area-weighted mean, worst) and per REGION the
500// surface-area fraction carrying each band. Straddling triangles (verts in different charts) are counted
501// and printed -- never silently folded into either bucket. Areas in sub-px^2 of the layout.
502func ntr_footprint(cx: *i64, mode: i64, gRt: *i64, tpRt: *i64, fsum: *i64, asum: *i64, fmax: *i64, carea: *i64, rarea: *i64, straddle: *i64) -> i64 {
503 let w: *i64 = cx[CX_W] as *i64
504 let vwo: i64 = cx[CX_VWO]
505 let two: i64 = cx[CX_TWO]
506 let xwo: i64 = cx[CX_XWO]
507 let nv: i64 = cx[CX_NV]
508 let ntr: i64 = cx[CX_NTR]
509 let nj: i64 = cx[CX_NJ]
510 let g: i64 = cx[CX_G]
511 let res: i64 = cx[CX_RES]
512 let unum: i64 = cx[CX_UNUM]
513 let uden: i64 = cx[CX_UDEN]
514 let regs: *i64 = cx[CX_REGS] as *i64
515 let jrank: *i64 = cx[CX_JRANK] as *i64
516 var j: i64 = 0
517 while j < nj { fsum[j] = 0; asum[j] = 0; fmax[j] = 0; j = j + 1 }
518 var r: i64 = 0
519 while r < NTR_REGIONS { rarea[r] = 0; straddle[r] = 0; var bnd: i64 = 0; while bnd < 4 { carea[r*4+bnd] = 0; bnd = bnd + 1 } r = r + 1 }
520 var t: i64 = 0
521 while t < ntr {
522 let i0: i64 = w[two + 1 + t*3]
523 let i1: i64 = w[two + 1 + t*3 + 1]
524 let i2: i64 = w[two + 1 + t*3 + 2]
525 if ntb_idx3(i0, i1, i2, nv) == 1 {
526 let j0: i64 = w[xwo + 4 + i0*3 + 2]
527 let j1: i64 = w[xwo + 4 + i1*3 + 2]
528 let j2: i64 = w[xwo + 4 + i2*3 + 2]
529 var same: i64 = 0
530 if j0 == j1 { if j1 == j2 { if j0 >= 0 { if j0 < nj { same = 1 } } } }
531 var reg: i64 = NTR_MISS
532 if j0 >= 0 { if j0 < nj { reg = regs[j0] } }
533 if same == 1 { if jrank[j0] >= 0 { if reg >= 0 { if reg < NTR_REGIONS {
534 let rk: i64 = jrank[j0]
535 let x0: i64 = ntr_px_sub(w[xwo + 4 + i0*3], j0, 0, mode, g, gRt[reg], tpRt[reg], rk, res)
536 let y0: i64 = ntr_px_sub(w[xwo + 4 + i0*3 + 1], j0, 1, mode, g, gRt[reg], tpRt[reg], rk, res)
537 let x1: i64 = ntr_px_sub(w[xwo + 4 + i1*3], j0, 0, mode, g, gRt[reg], tpRt[reg], rk, res)
538 let y1: i64 = ntr_px_sub(w[xwo + 4 + i1*3 + 1], j0, 1, mode, g, gRt[reg], tpRt[reg], rk, res)
539 let x2: i64 = ntr_px_sub(w[xwo + 4 + i2*3], j0, 0, mode, g, gRt[reg], tpRt[reg], rk, res)
540 let y2: i64 = ntr_px_sub(w[xwo + 4 + i2*3 + 1], j0, 1, mode, g, gRt[reg], tpRt[reg], rk, res)
541 let du1: i64 = x1 - x0
542 let dv1: i64 = y1 - y0
543 let du2: i64 = x2 - x0
544 let dv2: i64 = y2 - y0
545 let det: i64 = du1*dv2 - du2*dv1
546 if det != 0 {
547 var c: i64 = 0
548 var su: i64 = 0
549 var sv: i64 = 0
550 while c < 3 {
551 let p0: i64 = w[vwo + 1 + i0*3 + c] * unum / uden
552 let p1: i64 = w[vwo + 1 + i1*3 + c] * unum / uden - p0
553 let p2: i64 = w[vwo + 1 + i2*3 + c] * unum / uden - p0
554 // dP/du = (P1*dv2 - P2*dv1)/det ; dP/dv = (P2*du1 - P1*du2)/det (um per sub-px)
555 let gu: i64 = (p1*dv2 - p2*dv1) * NTR_SUB / det
556 let gv: i64 = (p2*du1 - p1*du2) * NTR_SUB / det
557 su = su + gu*gu
558 sv = sv + gv*gv
559 c = c + 1
560 }
561 let f: i64 = ntr_max(tm_isqrt(su), tm_isqrt(sv)) // um per px, worse axis
562 var area: i64 = det
563 if area < 0 { area = 0 - area }
564 fsum[j0] = fsum[j0] + f*area
565 asum[j0] = asum[j0] + area
566 if f > fmax[j0] { fmax[j0] = f }
567 rarea[reg] = rarea[reg] + area
568 let m: i64 = ntr_bands_at_footprint(f)
569 if (m & RLF_BAND_PRIMARY) == RLF_BAND_PRIMARY { carea[reg*4] = carea[reg*4] + area }
570 if (m & RLF_BAND_SECONDARY) == RLF_BAND_SECONDARY { carea[reg*4+1] = carea[reg*4+1] + area }
571 if (m & RLF_BAND_POREGRID) == RLF_BAND_POREGRID { carea[reg*4+2] = carea[reg*4+2] + area }
572 if (m & RLF_BAND_ORIFICE) == RLF_BAND_ORIFICE { carea[reg*4+3] = carea[reg*4+3] + area }
573 }
574 } } } }
575 if same == 0 { if reg >= 0 { if reg < NTR_REGIONS { straddle[reg] = straddle[reg] + 1 } } }
576 }
577 t = t + 1
578 }
579 return 0
580}
581// area-weighted mean footprint over a region's joints, and its worst tile mean; out[0]=mean out[1]=worst
582func ntr_region_footprint(r: i64, nj: i64, regs: *i64, fsum: *i64, asum: *i64, out: *i64) -> i64 {
583 var fs: i64 = 0
584 var as2: i64 = 0
585 var worst: i64 = 0
586 var j: i64 = 0
587 while j < nj {
588 if regs[j] == r { if asum[j] > 0 {
589 fs = fs + fsum[j]
590 as2 = as2 + asum[j]
591 let fm: i64 = fsum[j] / asum[j]
592 if fm > worst { worst = fm }
593 } }
594 j = j + 1
595 }
596 out[0] = 0
597 if as2 > 0 { out[0] = fs / as2 }
598 out[1] = worst
599 return 0
600}
601
602// ------------------------------------------------------------------------------------------------
603// THE BUST APEX from the asset's own DYNA anchors. The first side=-1 row and the first side=+1 row are
604// the lateral bust pair (nx_nxa_dyna emits them first; the R7 region rows follow). From each anchor, the
605// apex is the MOST ANTERIOR vertex within the anchor's own influence radius laterally and in stature --
606// the DYNA-derived half gap, never a fixed band. out: [0..2]=L apex units, [3..5]=R apex, [6]=found
607// count, [7]=front sign (+1 if the anchors lie above the anterior midplane).
608func ntr_apex(cx: *i64, dwo: i64, out: *i64) -> i64 {
609 let w: *i64 = cx[CX_W] as *i64
610 let vwo: i64 = cx[CX_VWO]
611 let nv: i64 = cx[CX_NV]
612 let ax: *i64 = cx[CX_AX] as *i64
613 var k: i64 = 0
614 while k < 8 { out[k] = 0; k = k + 1 }
615 if dwo < 0 { return 0 }
616 let ndb: i64 = w[dwo]
617 let dst: i64 = w[dwo + 1]
618 if ndb < 2 { return 0 }
619 if dst < 8 { return 0 }
620 let sa: i64 = ax[0]
621 let la: i64 = ax[1]
622 let aa: i64 = ax[2]
623 let mida: i64 = ax[5]
624 let anc: *i64 = sys_mmap(3*NTR_WORD) as *i64
625 var side: i64 = 0
626 var found: i64 = 0
627 while side < 2 {
628 var want: i64 = 0 - 1
629 if side == 1 { want = 1 }
630 var row: i64 = NTR_MISS
631 var d: i64 = 0
632 while d < ndb {
633 if row < 0 { if w[dwo + 2 + d*dst] == want { row = d } }
634 d = d + 1
635 }
636 if row >= 0 {
637 let base: i64 = dwo + 2 + row*dst
638 anc[0] = w[base + 1]
639 anc[1] = w[base + 2]
640 anc[2] = w[base + 3]
641 let infl: i64 = w[base + 7]
642 var fsign: i64 = 1
643 if anc[aa] < mida { fsign = 0 - 1 }
644 out[7] = fsign
645 var best: i64 = NTR_MISS
646 var bestv: i64 = 0
647 var i: i64 = 0
648 while i < nv {
649 let pl: i64 = w[vwo + 1 + i*3 + la]
650 let ps: i64 = w[vwo + 1 + i*3 + sa]
651 if ntr_abs(pl - anc[la]) <= infl { if ntr_abs(ps - anc[sa]) <= infl {
652 let pa: i64 = w[vwo + 1 + i*3 + aa] * fsign
653 if best < 0 { best = i; bestv = pa }
654 if pa > bestv { best = i; bestv = pa }
655 } }
656 i = i + 1
657 }
658 if best >= 0 {
659 out[side*3] = w[vwo + 1 + best*3]
660 out[side*3 + 1] = w[vwo + 1 + best*3 + 1]
661 out[side*3 + 2] = w[vwo + 1 + best*3 + 2]
662 found = found + 1
663 }
664 }
665 side = side + 1
666 }
667 out[6] = found
668 sys_munmap(anc as *u8, 3*NTR_WORD)
669 return found
670}
671
672// ------------------------------------------------------------------------------------------------
673// RASTERIZE the region's chart-interior triangles into two per-texel fields: the LATERAL and STATURE
674// offsets (um) of the surface point from the NEARER bust apex (a tangent-plane frame at the chest --
675// the apex projects anteriorly, so lateral/stature span the areola disc), plus a coverage byte. Texels
676// no chart-interior triangle touches stay uncovered: they still receive skin and relief, never anatomy.
677// Integer edge functions in SUB-pixel units; either winding accepted.
678func ntr_raster(cx: *i64, reg: i64, gRr: i64, tpRr: i64, fa: *i64, fb: *i64, cov: *u8) -> i64 {
679 let w: *i64 = cx[CX_W] as *i64
680 let vwo: i64 = cx[CX_VWO]
681 let two: i64 = cx[CX_TWO]
682 let xwo: i64 = cx[CX_XWO]
683 let nv: i64 = cx[CX_NV]
684 let ntr: i64 = cx[CX_NTR]
685 let nj: i64 = cx[CX_NJ]
686 let g: i64 = cx[CX_G]
687 let res: i64 = cx[CX_RES]
688 let unum: i64 = cx[CX_UNUM]
689 let uden: i64 = cx[CX_UDEN]
690 let regs: *i64 = cx[CX_REGS] as *i64
691 let jrank: *i64 = cx[CX_JRANK] as *i64
692 let ax: *i64 = cx[CX_AX] as *i64
693 let apex: *i64 = cx[CX_APEX] as *i64
694 let la: i64 = ax[1]
695 let sa: i64 = ax[0]
696 // per-vertex offsets to the nearer apex, um
697 let va: *i64 = sys_mmap(nv*NTR_WORD + 64) as *i64
698 let vb: *i64 = sys_mmap(nv*NTR_WORD + 64) as *i64
699 var i: i64 = 0
700 while i < nv {
701 var bestd: i64 = NTR_MISS
702 var s: i64 = 0
703 while s < 2 {
704 let dl: i64 = (w[vwo + 1 + i*3 + la] - apex[s*3 + la]) * unum / uden
705 let ds: i64 = (w[vwo + 1 + i*3 + sa] - apex[s*3 + sa]) * unum / uden
706 let dx: i64 = (w[vwo + 1 + i*3] - apex[s*3]) * unum / uden
707 let dy: i64 = (w[vwo + 1 + i*3 + 1] - apex[s*3 + 1]) * unum / uden
708 let dz: i64 = (w[vwo + 1 + i*3 + 2] - apex[s*3 + 2]) * unum / uden
709 let d2: i64 = dx*dx + dy*dy + dz*dz
710 var take: i64 = 0
711 if bestd < 0 { take = 1 }
712 if bestd >= 0 { if d2 < bestd { take = 1 } }
713 if take == 1 { bestd = d2; va[i] = dl; vb[i] = ds }
714 s = s + 1
715 }
716 i = i + 1
717 }
718 var t: i64 = 0
719 while t < ntr {
720 let i0: i64 = w[two + 1 + t*3]
721 var i1: i64 = w[two + 1 + t*3 + 1]
722 var i2: i64 = w[two + 1 + t*3 + 2]
723 if ntb_idx3(i0, i1, i2, nv) == 1 {
724 let j0: i64 = w[xwo + 4 + i0*3 + 2]
725 var ok: i64 = 0
726 if j0 >= 0 { if j0 < nj { if regs[j0] == reg { if jrank[j0] >= 0 {
727 if w[xwo + 4 + i1*3 + 2] == j0 { if w[xwo + 4 + i2*3 + 2] == j0 { ok = 1 } }
728 } } } }
729 if ok == 1 {
730 let rk: i64 = jrank[j0]
731 let x0: i64 = ntr_px_sub(w[xwo + 4 + i0*3], j0, 0, 1, g, gRr, tpRr, rk, res)
732 let y0: i64 = ntr_px_sub(w[xwo + 4 + i0*3 + 1], j0, 1, 1, g, gRr, tpRr, rk, res)
733 var x1: i64 = ntr_px_sub(w[xwo + 4 + i1*3], j0, 0, 1, g, gRr, tpRr, rk, res)
734 var y1: i64 = ntr_px_sub(w[xwo + 4 + i1*3 + 1], j0, 1, 1, g, gRr, tpRr, rk, res)
735 var x2: i64 = ntr_px_sub(w[xwo + 4 + i2*3], j0, 0, 1, g, gRr, tpRr, rk, res)
736 var y2: i64 = ntr_px_sub(w[xwo + 4 + i2*3 + 1], j0, 1, 1, g, gRr, tpRr, rk, res)
737 var area2: i64 = (x1 - x0)*(y2 - y0) - (x2 - x0)*(y1 - y0)
738 if area2 < 0 {
739 let tx: i64 = x1
740 x1 = x2
741 x2 = tx
742 let ty: i64 = y1
743 y1 = y2
744 y2 = ty
745 let ti: i64 = i1
746 i1 = i2
747 i2 = ti
748 area2 = 0 - area2
749 }
750 if area2 > 0 {
751 let bx0: i64 = ntr_clamp(ntr_min(ntr_min(x0, x1), x2) / NTR_SUB, 0, res - 1)
752 let bx1: i64 = ntr_clamp(ntr_max(ntr_max(x0, x1), x2) / NTR_SUB, 0, res - 1)
753 let by0: i64 = ntr_clamp(ntr_min(ntr_min(y0, y1), y2) / NTR_SUB, 0, res - 1)
754 let by1: i64 = ntr_clamp(ntr_max(ntr_max(y0, y1), y2) / NTR_SUB, 0, res - 1)
755 var py: i64 = by0
756 while py <= by1 {
757 let cy: i64 = py*NTR_SUB + NTR_SUB/2
758 var px: i64 = bx0
759 while px <= bx1 {
760 let cxp: i64 = px*NTR_SUB + NTR_SUB/2
761 let w0: i64 = (x1 - cxp)*(y2 - cy) - (x2 - cxp)*(y1 - cy)
762 let w1: i64 = (x2 - cxp)*(y0 - cy) - (x0 - cxp)*(y2 - cy)
763 let w2: i64 = (x0 - cxp)*(y1 - cy) - (x1 - cxp)*(y0 - cy)
764 if w0 >= 0 { if w1 >= 0 { if w2 >= 0 {
765 let o: i64 = py*res + px
766 fa[o] = (w0*va[i0] + w1*va[i1] + w2*va[i2]) / area2
767 fb[o] = (w0*vb[i0] + w1*vb[i1] + w2*vb[i2]) / area2
768 cov[o] = NTR_BYTE_MAX as u8
769 } } }
770 px = px + 1
771 }
772 py = py + 1
773 }
774 }
775 }
776 }
777 t = t + 1
778 }
779 sys_munmap(va as *u8, nv*NTR_WORD + 64)
780 sys_munmap(vb as *u8, nv*NTR_WORD + 64)
781 return 0
782}
783
784// ------------------------------------------------------------------------------------------------
785// ANATOMY HEIGHT at a covered texel, microns above the skin: nipple dome + Montgomery gland domes.
786// a,b = lateral/stature offsets from the apex (um). an = ledger slots (radii already halved from the
787// diameters): RA areola radius, RN nipple radius, HN nipple projection, NMONT, RG gland radius, HG gland
788// height, SEED. Domes are paraboloid caps -- a declared model shape; the SIZES are the cited ones.
789// Gland centres sit on the areola annulus at hashed angle+radius from the registry seed, so two bakes
790// agree and two genomes differ.
791func ntr_anat_h(a: i64, b: i64, an: *i64) -> i64 {
792 let ra: i64 = an[NTR_AN_RA]
793 let rn: i64 = an[NTR_AN_RN]
794 let hn: i64 = an[NTR_AN_HN]
795 let nmont: i64 = an[NTR_AN_NMONT]
796 let rg: i64 = an[NTR_AN_RG]
797 let hg: i64 = an[NTR_AN_HG]
798 let gseed: i64 = an[NTR_AN_SEED]
799 let d2: i64 = a*a + b*b
800 var h: i64 = 0
801 if rn > 0 { if d2 < rn*rn { h = hn * (rn*rn - d2) / (rn*rn) } }
802 if d2 < ra*ra { if d2 >= rn*rn { if nmont > 0 { if rg > 0 {
803 let ring: i64 = ra - rn - 2*rg
804 var k: i64 = 0
805 while k < nmont {
806 let hj: i64 = rlf_hash3(gseed, k, 1)
807 let hr: i64 = rlf_hash3(gseed, k, 2)
808 // angle in RLF_UNIT turns: k/N of a turn plus up to a third of a slot of jitter
809 let angq: i64 = (k * RLF_UNIT / nmont + (hj % (RLF_UNIT / (nmont*3) + 1))) % RLF_UNIT
810 var rad: i64 = rn + rg
811 if ring > 0 { rad = rad + hr % (ring + 1) }
812 let sector: i64 = angq * NTR_NGON / RLF_UNIT
813 let gx: i64 = rad * ntr_cos16(sector) / NTR_E3
814 let gy: i64 = rad * ntr_sin16(sector) / NTR_E3
815 let ex: i64 = a - gx
816 let ey: i64 = b - gy
817 let e2: i64 = ex*ex + ey*ey
818 if e2 < rg*rg {
819 let hk: i64 = hg * (rg*rg - e2) / (rg*rg)
820 if hk > h { h = hk }
821 }
822 k = k + 1
823 }
824 } } } }
825 return h
826}
827
828// ------------------------------------------------------------------------------------------------
829// THE MICRO TILE: height (um, quantised through the 16-bit channel so a reader decodes exactly what the
830// normal was derived from) and the normal derived from it with periodic neighbours. mask = bands.
831func ntr_bake_tile(seed: i64, mask: i64, hq: *i64, nrgb: *u8, drgb: *u8) -> i64 {
832 let px: i64 = rlf_tile_px()
833 var y: i64 = 0
834 while y < px {
835 var x: i64 = 0
836 while x < px {
837 let h: i64 = rlf_relief_um2_tile_bands(x, y, seed, mask)
838 let v: i64 = ntr_h_enc(h)
839 hq[y*px + x] = ntr_h_dec(v)
840 let o: i64 = (y*px + x)*NTR_RGB
841 drgb[o] = (v / NTR_BYTE) as u8
842 drgb[o+1] = (v % NTR_BYTE) as u8
843 drgb[o+2] = NTR_BYTE_MAX as u8
844 x = x + 1
845 }
846 y = y + 1
847 }
848 let n3: *i64 = sys_mmap(3*NTR_WORD) as *i64
849 let run: i64 = 2 * rlf_tile_texel_um()
850 y = 0
851 while y < px {
852 var x: i64 = 0
853 while x < px {
854 let xp: i64 = rlf_wrap(x + 1, px)
855 let xm: i64 = rlf_wrap(x - 1, px)
856 let yp: i64 = rlf_wrap(y + 1, px)
857 let ym: i64 = rlf_wrap(y - 1, px)
858 ntb_encode_normal_um(hq[y*px + xp] - hq[y*px + xm], hq[yp*px + x] - hq[ym*px + x], run, n3)
859 let o: i64 = (y*px + x)*NTR_RGB
860 nrgb[o] = n3[0] as u8
861 nrgb[o+1] = n3[1] as u8
862 nrgb[o+2] = n3[2] as u8
863 x = x + 1
864 }
865 y = y + 1
866 }
867 sys_munmap(n3 as *u8, 3*NTR_WORD)
868 return 0
869}
870// mean gradient magnitude of a height field, in permil of slope (um per um x 1000): (|dx|+|dy|)/(2 run)
871// averaged over the field. Periodic neighbours when per=1. THE GEOMETRY-NOT-PAINT RULER.
872func ntr_mean_slope_e3(hq: *i64, px: i64, run_um: i64, per: i64) -> i64 {
873 var acc: i64 = 0
874 var y: i64 = 0
875 while y < px {
876 var x: i64 = 0
877 while x < px {
878 var xp: i64 = x + 1
879 var xm: i64 = x - 1
880 var yp: i64 = y + 1
881 var ym: i64 = y - 1
882 if per == 1 { xp = rlf_wrap(xp, px); xm = rlf_wrap(xm, px); yp = rlf_wrap(yp, px); ym = rlf_wrap(ym, px) }
883 if per == 0 { xp = ntr_clamp(xp, 0, px-1); xm = ntr_clamp(xm, 0, px-1); yp = ntr_clamp(yp, 0, px-1); ym = ntr_clamp(ym, 0, px-1) }
884 acc = acc + ntr_abs(hq[y*px + xp] - hq[y*px + xm]) + ntr_abs(hq[yp*px + x] - hq[ym*px + x])
885 x = x + 1
886 }
887 y = y + 1
888 }
889 if run_um <= 0 { return 0 }
890 return acc * NTR_E3 / (2 * run_um) / (px*px)
891}
892// mean slope over a MASKED window of a res x res height field (non-periodic): texels where sel[o] != 0.
893// Returns permil slope; -1 when the selection is empty (an empty set is not evidence).
894func ntr_mean_slope_sel_e3(hq: *i64, res: i64, run_um: i64, sel: *u8) -> i64 {
895 var acc: i64 = 0
896 var n: i64 = 0
897 var y: i64 = 0
898 while y < res {
899 var x: i64 = 0
900 while x < res {
901 if sel[y*res + x] != (0 as u8) {
902 let xp: i64 = ntr_clamp(x + 1, 0, res-1)
903 let xm: i64 = ntr_clamp(x - 1, 0, res-1)
904 let yp: i64 = ntr_clamp(y + 1, 0, res-1)
905 let ym: i64 = ntr_clamp(y - 1, 0, res-1)
906 acc = acc + ntr_abs(hq[y*res + xp] - hq[y*res + xm]) + ntr_abs(hq[yp*res + x] - hq[ym*res + x])
907 n = n + 1
908 }
909 x = x + 1
910 }
911 y = y + 1
912 }
913 if n == 0 { return NTR_MISS }
914 if run_um <= 0 { return NTR_MISS }
915 return acc * NTR_E3 / (2 * run_um) / n
916}
917// THE PORE PERIOD, measured on the tile's height field: high-pass by a box mean over a window derived
918// from the pit radius, count texels that are strict local minima below half the pit depth (each plateau
919// once, ties broken by scan order), then period = repeat / sqrt(count). out[0]=count out[1]=period_um.
920func ntr_pore_period(hq: *i64, px: i64, repeat_um: i64, out: *i64) -> i64 {
921 let cell: i64 = rlf_tile_lat(RLF_PORE_PITCH_UM)
922 var rpx: i64 = rlf_pore_radius() * cell / RLF_UNIT
923 if rpx < 1 { rpx = 1 }
924 let win: i64 = 2*rpx + NTR_HP_PAD
925 let hp: *i64 = sys_mmap(px*px*NTR_WORD + 64) as *i64
926 var y: i64 = 0
927 while y < px {
928 var x: i64 = 0
929 while x < px {
930 var s: i64 = 0
931 var n: i64 = 0
932 var dy: i64 = 0 - win
933 while dy <= win {
934 var dx: i64 = 0 - win
935 while dx <= win {
936 s = s + hq[rlf_wrap(y+dy, px)*px + rlf_wrap(x+dx, px)]
937 n = n + 1
938 dx = dx + 1
939 }
940 dy = dy + 1
941 }
942 hp[y*px + x] = hq[y*px + x] - s/n
943 x = x + 1
944 }
945 y = y + 1
946 }
947 let thr: i64 = 0 - RLF_PORE_UM / 2
948 var cnt: i64 = 0
949 y = 0
950 while y < px {
951 var x: i64 = 0
952 while x < px {
953 let v: i64 = hp[y*px + x]
954 if v <= thr {
955 var ismin: i64 = 1
956 var dy: i64 = 0 - 1
957 while dy <= 1 {
958 var dx: i64 = 0 - 1
959 while dx <= 1 {
960 var self: i64 = 0
961 if dx == 0 { if dy == 0 { self = 1 } }
962 if self == 0 { if ismin == 1 {
963 let q: i64 = hp[rlf_wrap(y+dy, px)*px + rlf_wrap(x+dx, px)]
964 if q < v { ismin = 0 }
965 if q == v {
966 // a tie later in scan order owns the plateau; an earlier tie already counted it
967 if dy > 0 { ismin = 0 }
968 if dy == 0 { if dx > 0 { ismin = 0 } }
969 }
970 } }
971 dx = dx + 1
972 }
973 dy = dy + 1
974 }
975 if ismin == 1 { cnt = cnt + 1 }
976 }
977 x = x + 1
978 }
979 y = y + 1
980 }
981 sys_munmap(hp as *u8, px*px*NTR_WORD + 64)
982 out[0] = cnt
983 out[1] = 0
984 if cnt > 0 { out[1] = repeat_um * NTR_E3 / tm_isqrt(cnt * NTR_E6) }
985 return 0
986}
987
988// ------------------------------------------------------------------------------------------------
989// locate a section's TOC index without checksum verification (for a section this organ is about to
990// replace). nxa_find verifies; this is the cheap TOC scan for existence.
991func ntr_toc_index(b: *u8, ns: i64, tag: *u8) -> i64 {
992 var s: i64 = 0
993 while s < ns { if nt_tageq(b, NT_HDR + s*NT_TOCE, tag) == 1 { return s } s = s + 1 }
994 return NTR_MISS
995}
996// read the anatomy ledger into an[]: 1 = complete, 0 = a row is missing (the caller refuses)
997func ntr_read_ledger(an: *i64) -> i64 {
998 an[NTR_AN_RA] = ntr_conf("areola_dia_um" as *u8)
999 an[NTR_AN_RN] = ntr_conf("nipple_dia_um" as *u8)
1000 an[NTR_AN_HN] = ntr_conf("nipple_h_um" as *u8)
1001 an[NTR_AN_DL] = ntr_conf("areola_dl_e3" as *u8)
1002 an[NTR_AN_NMONT] = ntr_conf("mont_count" as *u8)
1003 an[NTR_AN_RG] = ntr_conf("mont_dia_um" as *u8)
1004 an[NTR_AN_HG] = ntr_conf("mont_h_um" as *u8)
1005 an[NTR_AN_SEED] = ntr_conf("mont_seed" as *u8)
1006 var k: i64 = 0
1007 while k < NTR_AN_N { if an[k] < 0 { return 0 } k = k + 1 }
1008 if an[NTR_AN_RA] == 0 { return 0 }
1009 if an[NTR_AN_RN] == 0 { return 0 }
1010 if an[NTR_AN_RG] == 0 { return 0 }
1011 // diameters -> radii
1012 an[NTR_AN_RA] = an[NTR_AN_RA] / 2
1013 an[NTR_AN_RN] = an[NTR_AN_RN] / 2
1014 an[NTR_AN_RG] = an[NTR_AN_RG] / 2
1015 return 1
1016}
1017
1018// ------------------------------------------------------------------------------------------------
1019// THE MEASURE + BAKE CORE. mode: 0 = measure only (prints the ruler, writes nothing), 1 = bake and write.
1020// res: unique-atlas px (0 = the resolution the asset's TEXM was baked at, read from its header word 1;
1021// refuses if there is no TEXM and no res). ctl: NTR_CTL_* bits. Returns NTR_OK or a named refusal.
1022func ntr_run(inp: *u8, outp: *u8, res_in: i64, seed: i64, hue: i64, ctl: i64, mode: i64) -> i64 {
1023 let lp: *i64 = sys_mmap(NTR_WORD*2) as *i64
1024 let b: *u8 = sys_read_file(inp, lp)
1025 if (b as i64) == 0 { nt_err("TEXR-REFUSE cannot read input NXA\n" as *u8); return NTR_E_IO }
1026 let flen: i64 = lp[0]
1027 if ntr_source_safe(b,flen)==0 {nt_err("TEXR-REFUSE source section extent or index invalid\n" as *u8);return NTR_E_BAD}
1028 if flen < NT_HDR { nt_err("TEXR-REFUSE file too short to be an NXA\n" as *u8); return NTR_E_BAD }
1029 if nt_rd64(b, 0) != nxa_magic() { nt_err("TEXR-REFUSE not an NXA (bad magic)\n" as *u8); return NTR_E_BAD }
1030 let ns: i64 = nt_rd64(b, 16)
1031 if ns < 1 { nt_err("TEXR-REFUSE section count invalid\n" as *u8); return NTR_E_BAD }
1032 if ns >= NT_MAXSEC { nt_err("TEXR-REFUSE section table full\n" as *u8); return NTR_E_BAD }
1033 let w: *i64 = b as *i64
1034 let vwo: i64 = nxa_find(b, flen, nxa_tag4("VERT" as *u8))
1035 let two: i64 = nxa_find(b, flen, nxa_tag4("TRIS" as *u8))
1036 let xwo: i64 = nxa_find(b, flen, nxa_tag4("TEXC" as *u8))
1037 let swo: i64 = nxa_find(b, flen, nxa_tag4("SKEL" as *u8))
1038 let dwo: i64 = nxa_find(b, flen, nxa_tag4("DYNA" as *u8))
1039 let mwo: i64 = nxa_find(b, flen, nxa_tag4("TEXM" as *u8))
1040 if vwo < 0 { nt_err("TEXR-REFUSE no valid VERT section\n" as *u8); return NTR_E_NOSEC }
1041 if two < 0 { nt_err("TEXR-REFUSE no valid TRIS section\n" as *u8); return NTR_E_NOSEC }
1042 if xwo < 0 { nt_err("TEXR-REFUSE no TEXC section -- run nx_nxa_texc first, there are no charts to lay out\n" as *u8); return NTR_E_NOSEC }
1043 if swo < 0 { nt_err("TEXR-REFUSE no valid SKEL section\n" as *u8); return NTR_E_NOSEC }
1044 let nv: i64 = w[vwo]
1045 let ntr: i64 = w[two]
1046 let nj: i64 = w[swo]
1047 let g: i64 = w[xwo + 2]
1048 if w[xwo] != nv { nt_err("TEXR-REFUSE TEXC count does not match VERT\n" as *u8); return NTR_E_MISMATCH }
1049 if g < 1 { nt_err("TEXR-REFUSE TEXC grid invalid\n" as *u8); return NTR_E_BAD }
1050 var res: i64 = res_in
1051 if res <= 0 { if mwo >= 0 { res = w[mwo + 1] } }
1052 if res <= 0 { nt_err("TEXR-REFUSE no resolution given and no TEXM to read one from -- refusing to invent one\n" as *u8); return NTR_E_BAD }
1053 if png_buf_cap(res,res)<0 || res*res>(9223372036854775807-64)/NTR_WORD {nt_err("TEXR-REFUSE resolution byte extent overflow\n" as *u8);return NTR_E_BAD}
1054 if res<g {nt_err("TEXR-REFUSE resolution cannot represent source chart grid\n" as *u8);return NTR_E_BAD}
1055 // axes + unit
1056 let ax: *i64 = sys_mmap(8*NTR_WORD) as *i64
1057 if ntr_axes(w, vwo, nv, ax) == 0 { nt_err("TEXR-REFUSE degenerate vertex span\n" as *u8); return NTR_E_BAD }
1058 let upax: i64 = ntb_up_axis(w, vwo, nv)
1059 // layout
1060 let regs: *i64 = sys_mmap(NTB_MAGIC_4096*NTR_WORD) as *i64
1061 let jrank: *i64 = sys_mmap(NTB_MAGIC_4096*NTR_WORD) as *i64
1062 let gR: *i64 = sys_mmap(NTR_REGIONS*NTR_WORD) as *i64
1063 let njr: *i64 = sys_mmap(NTR_REGIONS*NTR_WORD) as *i64
1064 let rj: *i64 = sys_mmap(NTR_REGIONS*NTB_MAGIC_4096*NTR_WORD) as *i64
1065 let nj2: i64 = ntr_layout(b, flen, regs, jrank, gR, njr, rj)
1066 if nj2 < 0 { return 0 - nj2 }
1067 let apex: *i64 = sys_mmap(8*NTR_WORD) as *i64
1068 // the context
1069 let cx: *i64 = sys_mmap(CX_N*NTR_WORD + 64) as *i64
1070 cx[CX_W] = w as i64
1071 cx[CX_VWO] = vwo
1072 cx[CX_TWO] = two
1073 cx[CX_XWO] = xwo
1074 cx[CX_SWO] = swo
1075 cx[CX_NV] = nv
1076 cx[CX_NTR] = ntr
1077 cx[CX_NJ] = nj
1078 cx[CX_G] = g
1079 cx[CX_RES] = res
1080 cx[CX_UNUM] = RLF_BODY_UM
1081 cx[CX_UDEN] = ax[4]
1082 cx[CX_REGS] = regs as i64
1083 cx[CX_JRANK] = jrank as i64
1084 cx[CX_AX] = ax as i64
1085 cx[CX_APEX] = apex as i64
1086 let tpR: *i64 = sys_mmap(NTR_REGIONS*NTR_WORD) as *i64
1087 let tpW: *i64 = sys_mmap(NTR_REGIONS*NTR_WORD) as *i64
1088 let gW: *i64 = sys_mmap(NTR_REGIONS*NTR_WORD) as *i64
1089 var r: i64 = 0
1090 while r < NTR_REGIONS { tpR[r] = 0; if gR[r] > 0 { tpR[r] = res / gR[r] } tpW[r] = res / g; gW[r] = g; r = r + 1 }
1091 // joint extents
1092 let circ: *i64 = sys_mmap(nj*NTR_WORD + 64) as *i64
1093 let blen: *i64 = sys_mmap(nj*NTR_WORD + 64) as *i64
1094 let nown: *i64 = sys_mmap(nj*NTR_WORD + 64) as *i64
1095 ntr_joint_extents(cx, upax, circ, blen, nown)
1096 // footprints, both layouts
1097 let fsum: *i64 = sys_mmap(nj*NTR_WORD + 64) as *i64
1098 let asum: *i64 = sys_mmap(nj*NTR_WORD + 64) as *i64
1099 let fmax: *i64 = sys_mmap(nj*NTR_WORD + 64) as *i64
1100 let carea: *i64 = sys_mmap(NTR_REGIONS*4*NTR_WORD) as *i64
1101 let rarea: *i64 = sys_mmap(NTR_REGIONS*NTR_WORD) as *i64
1102 let strad: *i64 = sys_mmap(NTR_REGIONS*NTR_WORD) as *i64
1103 let fsumW: *i64 = sys_mmap(nj*NTR_WORD + 64) as *i64
1104 let asumW: *i64 = sys_mmap(nj*NTR_WORD + 64) as *i64
1105 let fmaxW: *i64 = sys_mmap(nj*NTR_WORD + 64) as *i64
1106 let careaW: *i64 = sys_mmap(NTR_REGIONS*4*NTR_WORD) as *i64
1107 let rareaW: *i64 = sys_mmap(NTR_REGIONS*NTR_WORD) as *i64
1108 let stradW: *i64 = sys_mmap(NTR_REGIONS*NTR_WORD) as *i64
1109 ntr_footprint(cx, 0, gW, tpW, fsumW, asumW, fmaxW, careaW, rareaW, stradW)
1110 ntr_footprint(cx, 1, gR, tpR, fsum, asum, fmax, carea, rarea, strad)
1111 let fo: *i64 = sys_mmap(2*NTR_WORD) as *i64
1112 let foW: *i64 = sys_mmap(2*NTR_WORD) as *i64
1113 let mpx: i64 = rlf_tile_px()
1114 if png_buf_cap(mpx,mpx)<0 || mpx*mpx>(9223372036854775807-64)/NTR_WORD {nt_err("TEXR-REFUSE microtile extent overflow\n" as *u8);return NTR_E_BAD}
1115 let mrep: i64 = rlf_tile_repeat_um()
1116 var mmask: i64 = rlf_tile_bands_carried()
1117 if (ctl & NTR_CTL_MICRO) == 0 { mmask = RLF_BAND_PRIMARY }
1118 // ---- THE RULER, printed for every region in both layouts ----
1119 nt_outs("TEXR-RULER stature_units=" as *u8); nt_outn(ax[4])
1120 nt_outs(" declared_um=" as *u8); nt_outn(RLF_BODY_UM)
1121 nt_outs(" nj=" as *u8); nt_outn(nj)
1122 nt_outs(" texc_grid=" as *u8); nt_outn(g)
1123 nt_outs(" res=" as *u8); nt_outn(res)
1124 nt_outs(" span_ruler_texel_um=" as *u8); nt_outn(rlf_texel_um(res))
1125 nt_outs(" (the span ruler; the per-tile Jacobian below is what the chart actually samples)\n" as *u8)
1126 r = 0
1127 while r < NTR_REGIONS {
1128 ntr_region_footprint(r, nj, regs, fsumW, asumW, foW)
1129 ntr_region_footprint(r, nj, regs, fsum, asum, fo)
1130 nt_outs(" region=" as *u8); nt_outs(ntr_region_name(r))
1131 nt_outs(" tiles=" as *u8); nt_outn(njr[r])
1132 nt_outs(" whole-body tile_px=" as *u8); nt_outn(tpW[r])
1133 nt_outs(" footprint_mean_um=" as *u8); nt_outn(foW[0])
1134 nt_outs(" worst_tile_um=" as *u8); nt_outn(foW[1])
1135 nt_outs(" | per-region gR=" as *u8); nt_outn(gR[r])
1136 nt_outs(" tile_px=" as *u8); nt_outn(tpR[r])
1137 nt_outs(" footprint_mean_um=" as *u8); nt_outn(fo[0])
1138 nt_outs(" worst_tile_um=" as *u8); nt_outn(fo[1])
1139 nt_outs(" straddling_tris=" as *u8); nt_outn(strad[r])
1140 nt_outs("\n UNIQUE adequacy (area permil carrying each band at its own footprint): primary=" as *u8)
1141 if rarea[r] > 0 { nt_outn(carea[r*4] * NTR_E3 / rarea[r]) } else { nt_outn(0) }
1142 nt_outs(" secondary=" as *u8); if rarea[r] > 0 { nt_outn(carea[r*4+1] * NTR_E3 / rarea[r]) } else { nt_outn(0) }
1143 nt_outs(" pore-grid=" as *u8); if rarea[r] > 0 { nt_outn(carea[r*4+2] * NTR_E3 / rarea[r]) } else { nt_outn(0) }
1144 nt_outs(" pore-orif=" as *u8); if rarea[r] > 0 { nt_outn(carea[r*4+3] * NTR_E3 / rarea[r]) } else { nt_outn(0) }
1145 nt_outs(" (whole-body primary=" as *u8); if rareaW[r] > 0 { nt_outn(careaW[r*4] * NTR_E3 / rareaW[r]) } else { nt_outn(0) }
1146 nt_outs(" secondary=" as *u8); if rareaW[r] > 0 { nt_outn(careaW[r*4+1] * NTR_E3 / rareaW[r]) } else { nt_outn(0) }
1147 nt_outs(")\n MICRO adequacy: tile_px=" as *u8); nt_outn(mpx)
1148 nt_outs(" texel_um=" as *u8); nt_outn(rlf_tile_texel_um())
1149 nt_outs(" repeat_um=" as *u8); nt_outn(mrep)
1150 nt_outs(" primary carried=" as *u8); nt_outn((mmask & RLF_BAND_PRIMARY) / RLF_BAND_PRIMARY)
1151 nt_outs(" secondary carried=" as *u8); nt_outn((mmask & RLF_BAND_SECONDARY) / RLF_BAND_SECONDARY)
1152 nt_outs(" pore-grid carried=" as *u8); nt_outn((mmask & RLF_BAND_POREGRID) / RLF_BAND_POREGRID)
1153 nt_outs(" pore-orif carried=" as *u8); nt_outn((mmask & RLF_BAND_ORIFICE) / RLF_BAND_ORIFICE)
1154 nt_outs(" (declared recipe sampling; physical calibration and renderer integration unverified)\n" as *u8)
1155 r = r + 1
1156 }
1157 if mode == 0 { return NTR_OK }
1158
1159 // ---- ledger (anatomy numbers), refused rather than defaulted ----
1160 let an: *i64 = sys_mmap(NTR_AN_N*NTR_WORD + 64) as *i64
1161 var k0: i64 = 0
1162 while k0 < NTR_AN_N { an[k0] = 0; k0 = k0 + 1 }
1163 if (ctl & (NTR_CTL_ANATOMY + NTR_CTL_PIGMENT)) != 0 {
1164 if ntr_read_ledger(an) == 0 { nt_err("TEXR-REFUSE knowledge/areola_relief.conf is missing or a row is unsourced (areola_dia_um nipple_dia_um nipple_h_um areola_dl_e3 mont_count mont_dia_um mont_h_um mont_seed) -- REFUSING to bake anatomy from a number nobody cited\n" as *u8); return NTR_E_CONF }
1165 }
1166 // ---- apex ----
1167 let nap: i64 = ntr_apex(cx, dwo, apex)
1168 nt_outs("TEXR-APEX found=" as *u8); nt_outn(nap)
1169 nt_outs(" L=" as *u8); nt_outn(apex[0]); nt_outs("," as *u8); nt_outn(apex[1]); nt_outs("," as *u8); nt_outn(apex[2])
1170 nt_outs(" R=" as *u8); nt_outn(apex[3]); nt_outs("," as *u8); nt_outn(apex[4]); nt_outs("," as *u8); nt_outn(apex[5])
1171 nt_outs(" front_sign=" as *u8); nt_outn(apex[7])
1172 if nap < 2 { nt_outs(" (no DYNA bust pair: anatomy relief is SKIPPED, named, not faked)" as *u8) }
1173 nt_outs("\n" as *u8)
1174 // ---- allocations shared across regions ----
1175 let npx: i64 = res*res
1176 let rgb: *u8 = sys_mmap(npx*NTR_RGB + 64)
1177 let hq: *i64 = sys_mmap(npx*NTR_WORD + 64) as *i64
1178 let fa: *i64 = sys_mmap(npx*NTR_WORD + 64) as *i64
1179 let fb: *i64 = sys_mmap(npx*NTR_WORD + 64) as *i64
1180 let cov: *u8 = sys_mmap(npx + 64)
1181 let fb_pixels: i64 = ntr_max(npx,mpx*mpx)
1182 let fbp: *i64 = sys_mmap(fb_pixels*NTR_WORD + 64) as *i64
1183 let thq: *i64 = sys_mmap(mpx*mpx*NTR_WORD + 64) as *i64
1184 let tdr: *u8 = sys_mmap(mpx*mpx*NTR_RGB + 64)
1185 let tnr: *u8 = sys_mmap(mpx*mpx*NTR_RGB + 64)
1186 let n3: *i64 = sys_mmap(3*NTR_WORD) as *i64
1187 let pal: *i64 = sys_mmap(3*NTR_WORD) as *i64
1188 let lab: *i64 = sys_mmap(3*NTR_WORD) as *i64
1189 let only_torso: i64 = (ctl & NTR_CTL_ONLY_TORSO) / NTR_CTL_ONLY_TORSO
1190 var nsets: i64 = 0
1191 if (ctl & NTR_CTL_SETS) != 0 {
1192 r = 0
1193 while r < NTR_REGIONS {
1194 var take: i64 = 0
1195 if njr[r] > 0 { take = 1 }
1196 if only_torso == 1 { if r != NTB_TORSO { take = 0 } }
1197 nsets = nsets + take
1198 r = r + 1
1199 }
1200 }
1201 let setw: i64 = NTR_SET_META + NTR_NMAPS*NTR_MAPREC
1202 let meta: *i64 = sys_mmap(NTR_REGIONS*setw*NTR_WORD + 64) as *i64
1203 // Capacity derives from the shared encoder bound for every emitted map.
1204 let blobcap: i64 = ntr_blob_capacity(res,mpx,nsets)
1205 if blobcap<0 || blobcap>9223372036854775807-64 {nt_err("TEXR-REFUSE blob extent overflow\n" as *u8);return NTR_E_BAD}
1206 let blob: *u8 = sys_mmap(blobcap + 64)
1207 var bo: i64 = 0
1208 // the micro tile is region-independent: bake ONCE, store per set (the page may later want per-region
1209 // seeds; the section format already carries a record per set so that is a data change, not a format one)
1210 ntr_bake_tile(seed, mmask, thq, tnr, tdr)
1211 let pp: *i64 = sys_mmap(2*NTR_WORD) as *i64
1212 ntr_pore_period(thq, mpx, mrep, pp)
1213 let tslope: i64 = ntr_mean_slope_e3(thq, mpx, 2*rlf_tile_texel_um(), 1)
1214 nt_outs("TEXR-MICRO tile_px=" as *u8); nt_outn(mpx)
1215 nt_outs(" repeat_um=" as *u8); nt_outn(mrep)
1216 nt_outs(" bands_mask=" as *u8); nt_outn(mmask)
1217 nt_outs(" pits=" as *u8); nt_outn(pp[0])
1218 nt_outs(" pore_period_um=" as *u8); nt_outn(pp[1])
1219 nt_outs(" (law pitch " as *u8); nt_outn(RLF_PORE_PITCH_UM); nt_outs("um) mean_slope_permil=" as *u8); nt_outn(tslope)
1220 nt_outs("\n" as *u8)
1221 if NTR_PROBE_ON == 1 {
1222 NTR_PROBE_THQ = ntr_dup(thq as *u8, mpx*mpx*NTR_WORD)
1223 NTR_PROBE_TNRM = ntr_dup(tnr, mpx*mpx*NTR_RGB)
1224 NTR_PROBE_MPX = mpx
1225 NTR_PROBE_RES = res
1226 NTR_PROBE_SETW = setw
1227 }
1228 var setidx: i64 = 0
1229 r = 0
1230 while r < NTR_REGIONS {
1231 var doreg: i64 = 0
1232 if nsets > 0 { if njr[r] > 0 { doreg = 1 } }
1233 if only_torso == 1 { if r != NTB_TORSO { doreg = 0 } }
1234 if doreg == 1 {
1235 let tp: i64 = tpR[r]
1236 let gr: i64 = gR[r]
1237 let mb: i64 = setidx*setw
1238 ntr_region_footprint(r, nj, regs, fsum, asum, fo)
1239 meta[mb + NTR_S_REGION] = r
1240 meta[mb + NTR_S_GR] = gr
1241 meta[mb + NTR_S_NJR] = njr[r]
1242 meta[mb + NTR_S_TPX] = tp
1243 meta[mb + NTR_S_FMEAN] = fo[0]
1244 meta[mb + NTR_S_FWORST] = fo[1]
1245 meta[mb + NTR_S_BANDS_U] = ntr_bands_at_footprint(fo[0])
1246 meta[mb + NTR_S_BANDS_M] = mmask
1247 meta[mb + NTR_S_PERIOD] = pp[1]
1248 meta[mb + NTR_S_ALX] = NTR_MISS
1249 meta[mb + NTR_S_ALY] = NTR_MISS
1250 meta[mb + NTR_S_ARX] = NTR_MISS
1251 meta[mb + NTR_S_ARY] = NTR_MISS
1252 meta[mb + NTR_S_ARPX] = 0
1253 meta[mb + NTR_S_DL] = 0
1254 var capm: i64 = 0
1255 if rarea[r] > 0 { capm = carea[r*4] * NTR_E3 / rarea[r] }
1256 meta[mb + NTR_S_CARRIED] = capm
1257 // coverage + apex frame fields
1258 var q: i64 = 0
1259 while q < npx { cov[q] = 0 as u8; fa[q] = 0; fb[q] = 0; q = q + 1 }
1260 var anat_here: i64 = 0
1261 if r == NTB_TORSO { if nap >= 2 { anat_here = 1 } }
1262 if anat_here == 1 { ntr_raster(cx, r, gr, tp, fa, fb, cov) }
1263 // region base albedo ONCE (the locus does root-finding)
1264 ntb_albedo_genome(hue, r, pal)
1265 let lbase: i64 = si_lstar_of_gene(hue, si_dl_of_region(r))
1266 let ra: i64 = an[NTR_AN_RA]
1267 let dl: i64 = an[NTR_AN_DL]
1268 var apxl: i64 = NTR_MISS
1269 var apxbest: i64 = 0
1270 // ---- ALBEDO ----
1271 var y: i64 = 0
1272 while y < res {
1273 let ty: i64 = y / tp
1274 var x: i64 = 0
1275 while x < res {
1276 let tx: i64 = x / tp
1277 let rk: i64 = ty*gr + tx
1278 var cr: i64 = NTB_VOID_R
1279 var cg: i64 = NTB_VOID_G
1280 var cb: i64 = NTB_VOID_B
1281 if tx < gr { if rk < njr[r] {
1282 cr = pal[0]; cg = pal[1]; cb = pal[2]
1283 let o: i64 = y*res + x
1284 if (ctl & NTR_CTL_PIGMENT) != 0 { if cov[o] != (0 as u8) {
1285 let d2: i64 = fa[o]*fa[o] + fb[o]*fb[o]
1286 let edge: i64 = RLF_PORE_PITCH_UM
1287 let rout: i64 = ra + edge
1288 if d2 < rout*rout {
1289 let d: i64 = tm_isqrt(d2)
1290 var wgt: i64 = NTR_E3
1291 if d > ra { wgt = (rout - d) * NTR_E3 / edge }
1292 si_srgb_of_lab(lbase - dl*wgt/NTR_E3, SI_A_STAR, SI_B_STAR, lab)
1293 cr = lab[0]; cg = lab[1]; cb = lab[2]
1294 if apxl < 0 { apxl = o; apxbest = d2 }
1295 if d2 < apxbest { apxl = o; apxbest = d2 }
1296 }
1297 } }
1298 let hh: i64 = x*NTB_HASH_A + y*NTB_HASH_B + seed*NTB_HASH_C
1299 let dd: i64 = (hh >> 10) % 7 - 3
1300 cr = ntr_clamp(cr + dd, 0, NTR_BYTE_MAX)
1301 cg = ntr_clamp(cg + dd, 0, NTR_BYTE_MAX)
1302 cb = ntr_clamp(cb + dd, 0, NTR_BYTE_MAX)
1303 } }
1304 let oo: i64 = (y*res + x)*NTR_RGB
1305 rgb[oo] = cr as u8
1306 rgb[oo+1] = cg as u8
1307 rgb[oo+2] = cb as u8
1308 x = x + 1
1309 }
1310 y = y + 1
1311 }
1312 if apxl >= 0 { meta[mb + NTR_S_ALX] = apxl % res; meta[mb + NTR_S_ALY] = apxl / res; meta[mb + NTR_S_DL] = dl }
1313 // areola radius in px of THIS region's mean footprint (a receipt number, the gate re-derives)
1314 if fo[0] > 0 { meta[mb + NTR_S_ARPX] = ra / fo[0] }
1315 if NTR_PROBE_ON == 1 { if r == NTB_TORSO { NTR_PROBE_RGB = ntr_dup(rgb, npx*NTR_RGB) } }
1316 let alen: i64 = ntr_png_checked(rgb, res, res, fbp, fb_pixels, ((blob as i64) + bo) as *u8, blobcap-bo)
1317 if alen <= 0 { nt_err("TEXR-REFUSE albedo PNG encode produced nothing\n" as *u8); return NTR_E_BAD }
1318 if bo + alen > blobcap { nt_err("TEXR-REFUSE blob reserve exceeded -- refusing rather than truncating a map\n" as *u8); return NTR_E_BAD }
1319 meta[mb + NTR_SET_META + NTR_MAP_ALBEDO*NTR_MAPREC] = NTR_MAP_ALBEDO
1320 meta[mb + NTR_SET_META + NTR_MAP_ALBEDO*NTR_MAPREC + 1] = bo
1321 meta[mb + NTR_SET_META + NTR_MAP_ALBEDO*NTR_MAPREC + 2] = alen
1322 bo = bo + alen
1323 // ---- HEIGHT (um -> quantised) ----
1324 y = 0
1325 while y < res {
1326 let ty: i64 = y / tp
1327 var x: i64 = 0
1328 while x < res {
1329 let tx: i64 = x / tp
1330 let rk: i64 = ty*gr + tx
1331 var h: i64 = 0
1332 var covered: i64 = 0
1333 if tx < gr { if rk < njr[r] {
1334 let jj: i64 = rj[r*nj + rk]
1335 var ft: i64 = fo[0]
1336 if asum[jj] > 0 { ft = fsum[jj] / asum[jj] }
1337 if ft < 1 { ft = 1 }
1338 if (ctl & NTR_CTL_RELIEF) != 0 { h = rlf_relief_um2_span(x, y, ft*tp, tp, seed) }
1339 let o: i64 = y*res + x
1340 if cov[o] != (0 as u8) {
1341 covered = 1
1342 if (ctl & NTR_CTL_ANATOMY) != 0 { h = h + ntr_anat_h(fa[o], fb[o], an) }
1343 }
1344 } }
1345 let v: i64 = ntr_h_enc(h)
1346 hq[y*res + x] = ntr_h_dec(v)
1347 let oo: i64 = (y*res + x)*NTR_RGB
1348 rgb[oo] = (v / NTR_BYTE) as u8
1349 rgb[oo+1] = (v % NTR_BYTE) as u8
1350 rgb[oo+2] = 0 as u8
1351 if covered == 1 { rgb[oo+2] = NTR_BYTE_MAX as u8 }
1352 x = x + 1
1353 }
1354 y = y + 1
1355 }
1356 if NTR_PROBE_ON == 1 { if r == NTB_TORSO {
1357 NTR_PROBE_HQ = ntr_dup(hq as *u8, npx*NTR_WORD)
1358 NTR_PROBE_COV = ntr_dup(cov, npx)
1359 NTR_PROBE_FA = ntr_dup(fa as *u8, npx*NTR_WORD)
1360 NTR_PROBE_FB = ntr_dup(fb as *u8, npx*NTR_WORD)
1361 } }
1362 let dlen: i64 = ntr_png_checked(rgb, res, res, fbp, fb_pixels, ((blob as i64) + bo) as *u8, blobcap-bo)
1363 if dlen <= 0 { nt_err("TEXR-REFUSE height PNG encode produced nothing\n" as *u8); return NTR_E_BAD }
1364 if bo + dlen > blobcap { nt_err("TEXR-REFUSE blob reserve exceeded -- refusing rather than truncating a map\n" as *u8); return NTR_E_BAD }
1365 meta[mb + NTR_SET_META + NTR_MAP_DISP*NTR_MAPREC] = NTR_MAP_DISP
1366 meta[mb + NTR_SET_META + NTR_MAP_DISP*NTR_MAPREC + 1] = bo
1367 meta[mb + NTR_SET_META + NTR_MAP_DISP*NTR_MAPREC + 2] = dlen
1368 bo = bo + dlen
1369 // ---- NORMAL, derived from the quantised height with the tile's own texel run ----
1370 y = 0
1371 while y < res {
1372 let ty: i64 = y / tp
1373 var x: i64 = 0
1374 while x < res {
1375 let tx: i64 = x / tp
1376 let rk: i64 = ty*gr + tx
1377 var er: i64 = NTB_N_MID
1378 var eg: i64 = NTB_N_MID
1379 var eb: i64 = NTB_N_MID + NTB_N_SCALE
1380 if tx < gr { if rk < njr[r] {
1381 let jj: i64 = rj[r*nj + rk]
1382 var ft: i64 = fo[0]
1383 if asum[jj] > 0 { ft = fsum[jj] / asum[jj] }
1384 if ft < 1 { ft = 1 }
1385 ntr_normal_chart(hq,res,tp,x,y,ft,n3)
1386 er = n3[0]; eg = n3[1]; eb = n3[2]
1387 } }
1388 let oo: i64 = (y*res + x)*NTR_RGB
1389 rgb[oo] = er as u8
1390 rgb[oo+1] = eg as u8
1391 rgb[oo+2] = eb as u8
1392 x = x + 1
1393 }
1394 y = y + 1
1395 }
1396 if NTR_PROBE_ON == 1 { if r == NTB_TORSO { NTR_PROBE_NRM = ntr_dup(rgb, npx*NTR_RGB) } }
1397 let nlen: i64 = ntr_png_checked(rgb, res, res, fbp, fb_pixels, ((blob as i64) + bo) as *u8, blobcap-bo)
1398 if nlen <= 0 { nt_err("TEXR-REFUSE normal PNG encode produced nothing\n" as *u8); return NTR_E_BAD }
1399 if bo + nlen > blobcap { nt_err("TEXR-REFUSE blob reserve exceeded -- refusing rather than truncating a map\n" as *u8); return NTR_E_BAD }
1400 meta[mb + NTR_SET_META + NTR_MAP_NORMAL*NTR_MAPREC] = NTR_MAP_NORMAL
1401 meta[mb + NTR_SET_META + NTR_MAP_NORMAL*NTR_MAPREC + 1] = bo
1402 meta[mb + NTR_SET_META + NTR_MAP_NORMAL*NTR_MAPREC + 2] = nlen
1403 bo = bo + nlen
1404 // ---- MICRO TILE maps (encoded per set so each set is self-contained) ----
1405 let tdl: i64 = ntr_png_checked(tdr, mpx, mpx, fbp, fb_pixels, ((blob as i64) + bo) as *u8, blobcap-bo)
1406 if tdl <= 0 { nt_err("TEXR-REFUSE tile height PNG encode produced nothing\n" as *u8); return NTR_E_BAD }
1407 meta[mb + NTR_SET_META + NTR_MAP_TDISP*NTR_MAPREC] = NTR_MAP_TDISP
1408 meta[mb + NTR_SET_META + NTR_MAP_TDISP*NTR_MAPREC + 1] = bo
1409 meta[mb + NTR_SET_META + NTR_MAP_TDISP*NTR_MAPREC + 2] = tdl
1410 bo = bo + tdl
1411 let tnl: i64 = ntr_png_checked(tnr, mpx, mpx, fbp, fb_pixels, ((blob as i64) + bo) as *u8, blobcap-bo)
1412 if tnl <= 0 { nt_err("TEXR-REFUSE tile normal PNG encode produced nothing\n" as *u8); return NTR_E_BAD }
1413 if bo + tnl > blobcap { nt_err("TEXR-REFUSE blob reserve exceeded -- refusing rather than truncating a map\n" as *u8); return NTR_E_BAD }
1414 meta[mb + NTR_SET_META + NTR_MAP_TNORMAL*NTR_MAPREC] = NTR_MAP_TNORMAL
1415 meta[mb + NTR_SET_META + NTR_MAP_TNORMAL*NTR_MAPREC + 1] = bo
1416 meta[mb + NTR_SET_META + NTR_MAP_TNORMAL*NTR_MAPREC + 2] = tnl
1417 bo = bo + tnl
1418 if NTR_PROBE_ON == 1 { if r == NTB_TORSO { NTR_PROBE_META = ntr_dup(((meta as i64) + mb*NTR_WORD) as *u8, setw*NTR_WORD) } }
1419 nt_outs("TEXR-SET region=" as *u8); nt_outs(ntr_region_name(r))
1420 nt_outs(" gR=" as *u8); nt_outn(gr); nt_outs(" tile_px=" as *u8); nt_outn(tp)
1421 nt_outs(" footprint_mean_um=" as *u8); nt_outn(fo[0])
1422 nt_outs(" bands_unique=" as *u8); nt_outn(meta[mb + NTR_S_BANDS_U])
1423 nt_outs(" bands_micro=" as *u8); nt_outn(mmask)
1424 nt_outs(" albedo_bytes=" as *u8); nt_outn(alen)
1425 nt_outs(" height_bytes=" as *u8); nt_outn(dlen)
1426 nt_outs(" normal_bytes=" as *u8); nt_outn(nlen)
1427 nt_outs(" tile_height_bytes=" as *u8); nt_outn(tdl)
1428 nt_outs(" tile_normal_bytes=" as *u8); nt_outn(tnl)
1429 if anat_here == 1 { nt_outs(" areola_apex_px=" as *u8); nt_outn(meta[mb + NTR_S_ALX]); nt_outs("," as *u8); nt_outn(meta[mb + NTR_S_ALY]); nt_outs(" areola_r_px=" as *u8); nt_outn(meta[mb + NTR_S_ARPX]) }
1430 nt_outs("\n" as *u8)
1431 setidx = setidx + 1
1432 }
1433 r = r + 1
1434 }
1435 // ---- assemble TEXR words ----
1436 let blobbytes: i64 = bo
1437 var blobwords: i64 = blobbytes / NTR_WORD
1438 if blobwords*NTR_WORD < blobbytes { blobwords = blobwords + 1 }
1439 let jwords: i64 = nj*NTR_JOINT_WORDS
1440 let twords: i64 = NTR_HDR_WORDS + jwords + nsets*setw + blobwords
1441 // ---- rebuild the container: every section forward, TEXR replaced not duplicated ----
1442 let tri: i64 = ntr_toc_index(b, ns, "TEXR" as *u8)
1443 var nsec: i64 = ns
1444 if tri < 0 { nsec = ns + 1 }
1445 if nsec >= NT_MAXSEC { nt_err("TEXR-REFUSE section table full\n" as *u8); return NTR_E_BAD }
1446 let toclen: i64 = NT_HDR + nsec*NT_TOCE
1447 var total: i64 = toclen
1448 var s2: i64 = 0
1449 while s2 < ns {
1450 if s2 != tri {
1451 let carried_words:i64=nt_rd64(b,NT_HDR+s2*NT_TOCE+16)
1452 if carried_words>(9223372036854775807-total)/NTR_WORD {nt_err("TEXR-REFUSE carried extent overflow\n" as *u8);return NTR_E_BAD}
1453 total=total+carried_words*NTR_WORD
1454 }
1455 s2 = s2 + 1
1456 }
1457 if total>9223372036854775807-NT_PAD {nt_err("TEXR-REFUSE output padding overflow\n" as *u8);return NTR_E_BAD}
1458 if twords>(9223372036854775807-total-NT_PAD)/NTR_WORD {nt_err("TEXR-REFUSE output extent overflow\n" as *u8);return NTR_E_BAD}
1459 total = total + twords*NTR_WORD
1460 let nb: *u8 = sys_mmap_try(total + NT_PAD)
1461 if (nb as i64) == 0 { nt_err("TEXR-REFUSE cannot allocate output\n" as *u8); return NTR_E_IO }
1462 nt_wr64(nb, 0, nxa_magic())
1463 nt_wr64(nb, 8, NXA_VER)
1464 nt_wr64(nb, 16, nsec)
1465 var wo: i64 = toclen
1466 var ti: i64 = 0
1467 var s3: i64 = 0
1468 var carried: i64 = 0
1469 while s3 < ns {
1470 if s3 != tri {
1471 let e3: i64 = NT_HDR + s3*NT_TOCE
1472 let oldoff: i64 = nt_rd64(b, e3 + 8)
1473 let wl: i64 = nt_rd64(b, e3 + 16)
1474 let te: i64 = NT_HDR + ti*NT_TOCE
1475 nt_wr64(nb, te, nt_rd64(b, e3))
1476 nt_wr64(nb, te + 8, wo)
1477 nt_wr64(nb, te + 16, wl)
1478 var k2: i64 = 0
1479 while k2 < wl*NTR_WORD { nb[wo + k2] = b[oldoff + k2]; k2 = k2 + 1 }
1480 let pw2: *i64 = ((nb as i64) + wo) as *i64
1481 nt_wr64(nb, te + 24, nxa_check2(1, pw2, wl))
1482 wo = wo + wl*NTR_WORD
1483 ti = ti + 1
1484 carried = carried + 1
1485 }
1486 s3 = s3 + 1
1487 }
1488 let te4: i64 = NT_HDR + ti*NT_TOCE
1489 nt_wr64(nb, te4, nxa_tag4("TEXR" as *u8))
1490 nt_wr64(nb, te4 + 8, wo)
1491 nt_wr64(nb, te4 + 16, twords)
1492 var z: i64 = 0
1493 while z < twords*NTR_WORD { nb[wo + z] = 0 as u8; z = z + 1 }
1494 nt_wr64(nb, wo + NTR_W_NSETS*NTR_WORD, nsets)
1495 nt_wr64(nb, wo + NTR_W_RES*NTR_WORD, res)
1496 nt_wr64(nb, wo + NTR_W_MODEL*NTR_WORD, NTR_MODEL_SPECGLOSS)
1497 nt_wr64(nb, wo + NTR_W_SEED*NTR_WORD, seed)
1498 nt_wr64(nb, wo + NTR_W_HUE*NTR_WORD, hue)
1499 nt_wr64(nb, wo + NTR_W_BLOBW*NTR_WORD, blobwords)
1500 nt_wr64(nb, wo + NTR_W_NMAPS*NTR_WORD, NTR_NMAPS)
1501 nt_wr64(nb, wo + NTR_W_MPX*NTR_WORD, mpx)
1502 nt_wr64(nb, wo + NTR_W_MREP*NTR_WORD, mrep)
1503 nt_wr64(nb, wo + NTR_W_RANGE*NTR_WORD, NTR_DISP_RANGE_UM)
1504 nt_wr64(nb, wo + NTR_W_NJ*NTR_WORD, nj)
1505 nt_wr64(nb, wo + NTR_W_SETW*NTR_WORD, setw)
1506 nt_wr64(nb, wo + NTR_W_JW*NTR_WORD, NTR_JOINT_WORDS)
1507 nt_wr64(nb, wo + NTR_W_VER*NTR_WORD, NTR_VERSION)
1508 var j: i64 = 0
1509 while j < nj {
1510 let jo: i64 = wo + (NTR_HDR_WORDS + j*NTR_JOINT_WORDS)*NTR_WORD
1511 nt_wr64(nb, jo, regs[j])
1512 nt_wr64(nb, jo + NTR_WORD, jrank[j])
1513 nt_wr64(nb, jo + NTR_WORD*2, circ[j])
1514 nt_wr64(nb, jo + NTR_WORD*3, blen[j])
1515 j = j + 1
1516 }
1517 var m: i64 = 0
1518 while m < nsets*setw {
1519 nt_wr64(nb, wo + (NTR_HDR_WORDS + jwords + m)*NTR_WORD, meta[m])
1520 m = m + 1
1521 }
1522 let blobbase: i64 = wo + (NTR_HDR_WORDS + jwords + nsets*setw)*NTR_WORD
1523 var c3: i64 = 0
1524 while c3 < blobbytes { nb[blobbase + c3] = blob[c3]; c3 = c3 + 1 }
1525 let pw3: *i64 = ((nb as i64) + wo) as *i64
1526 nt_wr64(nb, te4 + 24, nxa_check2(1, pw3, twords))
1527 wo = wo + twords*NTR_WORD
1528 let tb2: *i64 = ((nb as i64) + NT_HDR) as *i64
1529 nt_wr64(nb, 24, nxa_check2(1, tb2, nsec*4))
1530 let wrote:i64=fsx_write(outp,nb,wo)
1531 if wrote!=wo {nt_err("TEXR-REFUSE atomic output write failed\n" as *u8);return NTR_E_IO}
1532 nt_outs("TEXR-OK sets=" as *u8); nt_outn(nsets)
1533 nt_outs(" res=" as *u8); nt_outn(res)
1534 nt_outs(" micro_px=" as *u8); nt_outn(mpx)
1535 nt_outs(" sections_carried=" as *u8); nt_outn(carried)
1536 nt_outs(" sections_out=" as *u8); nt_outn(nsec)
1537 nt_outs(" texr_bytes=" as *u8); nt_outn(twords*NTR_WORD)
1538 nt_outs(" file_bytes=" as *u8); nt_outn(wo)
1539 nt_outs(" (TEXM carried byte-for-byte; map set 0 unchanged)\n" as *u8)
1540 return NTR_OK
1541}
1542
1543// public verbs
1544func ntr_measure(inp: *u8, res: i64) -> i64 { return ntr_run(inp, inp, res, 0, 0, NTR_CTL_ALL, 0) }
1545func ntr_apply(inp: *u8, outp: *u8, res: i64, seed: i64, hue: i64) -> i64 { return ntr_run(inp, outp, res, seed, hue, NTR_CTL_ALL, 1) }
1546func ntr_apply_ctl(inp: *u8, outp: *u8, res: i64, seed: i64, hue: i64, ctl: i64) -> i64 { return ntr_run(inp, outp, res, seed, hue, ctl, 1) }
1547
1548// ------------------------------------------------------------------------------------------------
1549// READERS for gates and the page-half handoff: TEXR word offset (verified), and the ADEQUACY PREDICATE:
1550// 1 iff every region set is present (nsets == NTR_REGIONS) and every set's MICRO band mask carries the
1551// three coarser bands. Bound to the region count by construction -- zero sets cannot pass.
1552// TEXR structural boundary: reuse validated NXA section extent before reading records.
1553// Payload integrity is not a claim about decoded image quality or anatomical adequacy.
1554func ntr_texr_validate(b:*u8,flen:i64)->i64{
1555 let entry:i64=nxa_section_entry(b,flen,nxa_tag4("TEXR"))
1556 if entry<0{return entry}
1557 let w:*i64=b as *i64
1558 let words:i64=w[entry+2]
1559 if words<NTR_HDR_WORDS{return 0-3}
1560 let o:i64=w[entry+1]/NTR_WORD
1561 if w[o+NTR_W_VER]>NTR_VERSION{return 0-2}
1562 if w[o+NTR_W_VER]!=NTR_VERSION{return 0-3}
1563 if w[o+NTR_W_MODEL]!=NTR_MODEL_SPECGLOSS{return 0-3}
1564 if w[o+NTR_W_NMAPS]!=NTR_NMAPS{return 0-3}
1565 if w[o+NTR_W_JW]!=NTR_JOINT_WORDS{return 0-3}
1566 let stride:i64=NTR_SET_META+NTR_NMAPS*NTR_MAPREC
1567 if w[o+NTR_W_SETW]!=stride{return 0-3}
1568 if w[o+NTR_W_RES]<=0{return 0-3}
1569 if w[o+NTR_W_MPX]<=0{return 0-3}
1570 if w[o+NTR_W_MREP]<=0{return 0-3}
1571 if w[o+NTR_W_RANGE]<=0{return 0-3}
1572 let nj:i64=w[o+NTR_W_NJ]
1573 let ns:i64=w[o+NTR_W_NSETS]
1574 if nj<0{return 0-3}
1575 if ns<0{return 0-3};if ns>NTR_REGIONS{return 0-3}
1576 var remain:i64=words-NTR_HDR_WORDS
1577 if nj>remain/NTR_JOINT_WORDS{return 0-3}
1578 let jw:i64=nj*NTR_JOINT_WORDS
1579 remain=remain-jw
1580 if ns>remain/stride{return 0-3}
1581 remain=remain-ns*stride
1582 if w[o+NTR_W_BLOBW]!=remain{return 0-3}
1583 let blobBytes:i64=remain*NTR_WORD
1584 var seen:i64=0;var s:i64=0
1585 while s<ns{
1586 let at:i64=o+NTR_HDR_WORDS+jw+s*stride
1587 let region:i64=w[at+NTR_S_REGION]
1588 if region<0{return 0-3};if region>=NTR_REGIONS{return 0-3}
1589 let bit:i64=1<<region
1590 if (seen&bit)!=0{return 0-3};seen=seen|bit
1591 var m:i64=0
1592 while m<NTR_NMAPS{
1593 let rec:i64=at+NTR_SET_META+m*NTR_MAPREC
1594 if w[rec]!=m{return 0-3}
1595 let off:i64=w[rec+1];let bytes:i64=w[rec+2]
1596 if off<0{return 0-3};if off>blobBytes{return 0-3}
1597 if bytes<=0{return 0-3};if bytes>blobBytes-off{return 0-3}
1598 m=m+1
1599 }
1600 s=s+1
1601 }
1602 return o
1603}
1604
1605func ntr_texr_find(b: *u8, flen: i64) -> i64 { return ntr_texr_validate(b, flen) }
1606func ntr_texr_adequate(b: *u8, flen: i64) -> i64 {
1607 let o: i64 = ntr_texr_find(b, flen)
1608 if o < 0 { return 0 }
1609 let w: *i64 = b as *i64
1610 let nsets: i64 = w[o + NTR_W_NSETS]
1611 if nsets != NTR_REGIONS { return 0 }
1612 let nj: i64 = w[o + NTR_W_NJ]
1613 let setw: i64 = w[o + NTR_W_SETW]
1614 let want: i64 = RLF_BAND_PRIMARY + RLF_BAND_SECONDARY + RLF_BAND_POREGRID
1615 var s: i64 = 0
1616 while s < nsets {
1617 let mb: i64 = o + NTR_HDR_WORDS + nj*NTR_JOINT_WORDS + s*setw
1618 if (w[mb + NTR_S_BANDS_M] & want) != want { return 0 }
1619 s = s + 1
1620 }
1621 return 1
1622}
1623// the set record base (word offset) for a region, or -1
1624func ntr_texr_set(b: *u8, flen: i64, region: i64) -> i64 {
1625 let o: i64 = ntr_texr_find(b, flen)
1626 if o < 0 { return NTR_MISS }
1627 let w: *i64 = b as *i64
1628 let nsets: i64 = w[o + NTR_W_NSETS]
1629 let nj: i64 = w[o + NTR_W_NJ]
1630 let setw: i64 = w[o + NTR_W_SETW]
1631 var s: i64 = 0
1632 while s < nsets {
1633 let mb: i64 = o + NTR_HDR_WORDS + nj*NTR_JOINT_WORDS + s*setw
1634 if w[mb + NTR_S_REGION] == region { return mb }
1635 s = s + 1
1636 }
1637 return NTR_MISS
1638}