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