nx_relief_lib.nx source
↩ module page · 526 lines · 29739 B
1// nx_relief_lib.nx -- ★THE ONE OWNER OF THE SKIN MICRO-RELIEF LAW, and of the height field the baked
2// normal map is made of. Extracted 2026-08-25 so the BAKER and the SDF RENDERER cannot disagree about
3// the relief they are respectively writing and reading. [@penner2011] /compare/graphics row
4// "RENDER: pre-integrated SSS bound to baked normals".
5//
6// WHY A LEAF LIB AND NOT A MIRROR. Until today nx_sdfrender carried HAND-COPIED constants
7// (SSS_RELIEF_UM / SSS_RELIEF_LAMBDA_UM) with a comment naming the baker as their source, and a gate
8// tooth that compared the copies. That is the duplicate-ruler defect wearing a test: the copy can only
9// be caught AFTER it drifts, and only if somebody keeps running the comparison. The reason given for
10// the copy was real -- importing nx_nxa_texbake_lib drags nx_nxa + nx_trimesh + nx_png + nx_skin_ita
11// into every consumer of the renderer -- so the fix is not "import the baker", it is to give the law
12// its own LEAF home that both sides import. This file imports nothing but nx_syscalls.
13//
14// THE SEAM, stated so the next reader does not move the wrong half:
15// * THIS FILE owns the PHYSICS -- microns of relief, the wavelength it varies over, the slope that
16// follows from the two, and the value-noise field itself.
17// * nx_nxa_texbake_lib keeps the TEXEL ENCODING -- the byte midpoint/scale that turn a slope into an
18// RGB8 tangent-space texel. That is an atlas format decision, not a property of skin.
19// * nx_sdfrender reads the physics and never sees a texel: a ray-marched implicit surface has no UV,
20// so it samples the SAME height field volumetrically at the lattice its own sampler can resolve.
21//
22// license_tier: ORIGINAL No hw writes (Rule 26).
23import "nx_syscalls.nx"
24
25// ---------------------------------------------------------------------------------------------
26// THE FIELD. Moved VERBATIM out of nx_trimesh (tm_hash3 / tm_noise3), which now delegates here, so
27// every existing caller of tm_noise3 -- the baker, nx_worldpipe, the engine gates -- is behaviourally
28// identical BY CONSTRUCTION rather than by a second implementation that happens to agree today.
29// The two shift amounts are the incumbent's xxHash-class avalanche constants; they are NAMED here
30// rather than retyped as bare literals so the move is auditable, and they are not tunable.
31const RLF_HASH_A: i64 = 374761393
32const RLF_HASH_B: i64 = 668265263
33const RLF_HASH_C: i64 = 1610612741
34const RLF_HASH_MIX: i64 = 1274126177
35const RLF_AVAL_1: i64 = 13
36const RLF_AVAL_2: i64 = 16
37const RLF_UNIT: i64 = 1024 // interpolation unit AND the hash codomain modulus
38const RLF_OFF: i64 = 1048576 // coordinate bias so floor-division stays positive for negative coords
39// the field's codomain is 0..RLF_H_RANGE inclusive, i.e. RLF_UNIT-1. The compiler takes literal const
40// initialisers only, so the relationship is PINNED BY A GATE TOOTH instead of by arithmetic here.
41const RLF_H_RANGE: i64 = 1023
42
43func rlf_hash3(a: i64, b: i64, c: i64) -> i64 {
44 var h: i64 = a*RLF_HASH_A + b*RLF_HASH_B + c*RLF_HASH_C
45 h = h ^ (h >> RLF_AVAL_1); h = h * RLF_HASH_MIX; h = h ^ (h >> RLF_AVAL_2)
46 if h < 0 { h = 0 - h }
47 return h % RLF_UNIT
48}
49// trilinear value noise at 3D position, lattice scale S. Returns 0..RLF_H_RANGE (smooth).
50func rlf_noise3(x: i64, y: i64, z: i64, S: i64) -> i64 {
51 let ox: i64 = x + RLF_OFF; let oy: i64 = y + RLF_OFF; let oz: i64 = z + RLF_OFF
52 let gx: i64 = ox/S; let gy: i64 = oy/S; let gz: i64 = oz/S
53 let fx: i64 = (ox - gx*S)*RLF_UNIT/S; let fy: i64 = (oy - gy*S)*RLF_UNIT/S; let fz: i64 = (oz - gz*S)*RLF_UNIT/S
54 let c000: i64 = rlf_hash3(gx,gy,gz); let c100: i64 = rlf_hash3(gx+1,gy,gz)
55 let c010: i64 = rlf_hash3(gx,gy+1,gz); let c110: i64 = rlf_hash3(gx+1,gy+1,gz)
56 let c001: i64 = rlf_hash3(gx,gy,gz+1); let c101: i64 = rlf_hash3(gx+1,gy,gz+1)
57 let c011: i64 = rlf_hash3(gx,gy+1,gz+1); let c111: i64 = rlf_hash3(gx+1,gy+1,gz+1)
58 let x00: i64 = c000 + (c100-c000)*fx/RLF_UNIT
59 let x10: i64 = c010 + (c110-c010)*fx/RLF_UNIT
60 let x01: i64 = c001 + (c101-c001)*fx/RLF_UNIT
61 let x11: i64 = c011 + (c111-c011)*fx/RLF_UNIT
62 let y0: i64 = x00 + (x10-x00)*fy/RLF_UNIT
63 let y1: i64 = x01 + (x11-x01)*fy/RLF_UNIT
64 return y0 + (y1-y0)*fz/RLF_UNIT
65}
66
67// ---------------------------------------------------------------------------------------------
68// THE LAW. Micro-relief in MICRONS with the wavelength it varies over. Every strength downstream is
69// DERIVED from these two against a run, never dialled -- which is the parameter this whole class of
70// pipeline usually hand-waves. They also tell the truth about resolution: at 4096 over a whole body a
71// texel is ~415um, so fine wrinkles (~1.2mm) resolve and PORES (~100um) DO NOT. Pores need a
72// per-region atlas, not a bigger whole-body one; that is a rung, not a knob.
73const RLF_RELIEF_UM: i64 = 60
74const RLF_RELIEF_LAMBDA_UM: i64 = 1200
75const RLF_BODY_UM: i64 = 1700000 // a 1.7 m adult; the atlas covers the whole body
76// peak tilt of a relief of full swing A over wavelength L is about 4A/L -- rise 2A over run L/2, plain
77// geometry, stated as a model and not measured.
78const RLF_TILT_K: i64 = 4
79
80func rlf_texel_um(res: i64) -> i64 {
81 var t: i64 = RLF_BODY_UM / res
82 if t < 1 { t = 1 }
83 return t
84}
85// the relief lattice in TEXELS at an atlas resolution. Clamped at 2 because a lattice under two texels
86// is not band-limited by the grid it is sampled on -- and the clamp is exactly why an atlas can encode
87// LESS relief than the law states (see rlf_atlas_tilt_full).
88func rlf_relief_lat(res: i64) -> i64 {
89 var lat: i64 = RLF_RELIEF_LAMBDA_UM / rlf_texel_um(res)
90 if lat < 2 { lat = 2 }
91 return lat
92}
93// relief height at an ATLAS texel, 0..RLF_H_RANGE, band-limited to the stated wavelength.
94func rlf_relief_h(x: i64, y: i64, res: i64, seed: i64) -> i64 {
95 return rlf_noise3(x, y, seed, rlf_relief_lat(res))
96}
97// the SAME height field sampled VOLUMETRICALLY at a caller-declared lattice, for a consumer that has
98// no UV to index an atlas with. Same hash, same interpolation, same codomain -- it is the baked map's
99// field, read where a ray-marcher can reach it.
100func rlf_relief_h3(x: i64, y: i64, z: i64, cell: i64) -> i64 {
101 var c: i64 = cell
102 if c < 2 { c = 2 }
103 return rlf_noise3(x, y, z, c)
104}
105// a raw height DELTA (codomain units) -> the physical rise it represents, in microns.
106func rlf_slope_um(dh: i64) -> i64 { return dh * RLF_RELIEF_UM / RLF_H_RANGE }
107// the run a two-sided texel gradient spans, in microns.
108func rlf_run_um(res: i64) -> i64 { return 2 * rlf_texel_um(res) }
109// THE RELIEF'S OWN ANGULAR WIDTH on a caller's unit-normal scale (n_full = the value of a full normal
110// component). This is the quantity a diffusion length competes against.
111func rlf_tilt_full(n_full: i64) -> i64 { return n_full * RLF_TILT_K * RLF_RELIEF_UM / RLF_RELIEF_LAMBDA_UM }
112// THE PEAK TILT AN ATLAS AT res CAN ACTUALLY ENCODE -- full swing over the two-texel run. Below
113// rlf_tilt_full whenever the texel footprint has clamped the lattice, which on a whole-body atlas it
114// always has. Published rather than assumed, so nobody reads a re-sampled atlas as the law itself.
115func rlf_atlas_tilt_full(res: i64, n_full: i64) -> i64 { return n_full * RLF_RELIEF_UM / rlf_run_um(res) }
116
117// ---------------------------------------------------------------------------------------------
118// MULTI-BAND MICRO-RELIEF AND THE DISCRETE PORE LAYER (2026-08-28). /compare/graphics C5, symbol
119// rlf_pore_mask. STRICTLY ADDITIVE: nothing above this line changed, so nx_trimesh,
120// nx_worldpipe_core, nx_nxa_texbake_lib and nx_sdfrender keep byte-identical behaviour and
121// nx_sdfrender_sss_gate's T1 (flat skin bit-exact) and T4 (shader field IS baker field) still hold.
122//
123// WHY MORE BANDS, IN ARITHMETIC RATHER THAN TASTE. rlf_tilt_full says tilt = RLF_TILT_K * A / L,
124// so what a shading model can see of a band is its ASPECT RATIO A/L, never its depth. One band at
125// 1200um is therefore a ceiling no amplitude can lift: nx_sdfrender_sss_gate measures the shipped
126// sampler reaching 19 of the law's own 51 on the N.L axis. A SHALLOWER, FINER band outranks a
127// deeper coarse one -- band 1 below is a third the depth of band 0 and carries 60% more tilt.
128// That is the measured shape of "the bodies are low definition": right shape, one frequency.
129//
130// A PORE IS NOT A SPATIAL FREQUENCY, AND THAT DISTINCTION IS THE RUNG. A follicular orifice is a
131// sparse discrete pit carrying TWO scales -- a coarse PITCH between orifices and a fine DIAMETER --
132// and only the pitch has to be carried by a lattice. So the pore layer is a jittered-grid cellular
133// field (one pit per cell, centre displaced by a bounded hash offset, blue-noise spaced rather than
134// gridded) with the pit profile evaluated ANALYTICALLY from the distance to that centre. A noise
135// octave cannot represent it at any resolution, which is why a third octave would not have done and
136// a mask was needed.
137//
138// THE NUMBERS ARE DATA. Every band has a row in knowledge/relief_bands.conf carrying its source and
139// nx_relief_bands_gate REFUSES if a const here and its row disagree. Two of three sources read
140// UNPINNED: the estate's sovereign index returned no dermatology literature on 2026-08-28 and
141// nothing was mirrored, so an honest absence is recorded rather than a borrowed citation.
142//
143// DECLARED IMPRECISION, and it is the whole finding: a band only exists once the sampler's lattice
144// can carry it. rlf_band_res_needed publishes, per band, the atlas resolution at which a band stops
145// being clamped away -- and on a WHOLE-BODY atlas neither new band survives (415um texels at 4096
146// against a 250um band), while both resolve comfortably on a per-REGION atlas. This file therefore
147// ships the capability AND the statement of where it is real, so nobody reads a pore claim as
148// delivered on the whole-body path. That was predicted in this file's own header on 2026-08-25
149// (pores need a per-region atlas, not a bigger whole-body one); it is now arithmetic a gate can
150// check instead of a sentence.
151
152// ---- band 1: SECONDARY LINES ----
153const RLF_SEC_UM: i64 = 20 // swing, microns
154const RLF_SEC_LAMBDA_UM: i64 = 250 // wavelength, microns
155// ---- band 2: PORES (discrete) ----
156const RLF_PORE_UM: i64 = 30 // pit depth, microns
157const RLF_PORE_PITCH_UM: i64 = 800 // mean spacing between orifices, microns
158const RLF_PORE_DIA_UM: i64 = 100 // orifice diameter, microns
159const RLF_NBAND: i64 = 3
160// jitter swing of a pit centre, in RLF_UNIT-of-a-cell. Bounded to HALF a cell ON PURPOSE: that
161// bound is exactly what makes the 3x3 neighbourhood search below EXACT rather than approximate,
162// because no point's nearest centre can then lie outside its own or an adjacent cell.
163const RLF_PORE_JIT: i64 = 512
164// larger than any squared distance the 3x3 search can produce, so it is a safe starting minimum.
165const RLF_PORE_D2_INF: i64 = 1000000000
166// pi x 1000, for the pore coverage fraction. The only transcendental in this file.
167const RLF_PI_E3: i64 = 3142
168
169// per-band angular width on a caller's unit-normal scale. rlf_tilt_full IS this at band 0 and the
170// gate pins that identity -- ONE ruler, so a band can never be measured by a second convention.
171func rlf_band_tilt_full(n_full: i64, a_um: i64, lambda_um: i64) -> i64 {
172 if lambda_um <= 0 { return 0 }
173 return n_full * RLF_TILT_K * a_um / lambda_um
174}
175// the lattice, in texels, a band of this wavelength wants at an atlas resolution -- the same clamp
176// rlf_relief_lat applies, because the reason for the clamp is the sampling grid, not the band.
177func rlf_band_lat(res: i64, lambda_um: i64) -> i64 {
178 var lat: i64 = lambda_um / rlf_texel_um(res)
179 if lat < 2 { lat = 2 }
180 return lat
181}
182// 1 = this resolution carries the band. 0 = the texel footprint clamped it away, and what gets
183// baked is a DIFFERENT, coarser band wearing this one's name.
184func rlf_band_resolvable(res: i64, lambda_um: i64) -> i64 {
185 if lambda_um / rlf_texel_um(res) < 2 { return 0 }
186 return 1
187}
188// THE ACTIONABLE NUMBER: the atlas resolution at which a band of this wavelength stops being
189// clamped, over a surface spanning span_um. Two texels per wavelength is the same bar
190// rlf_relief_lat enforces, so this INVERTS that function rather than inventing a second rule.
191func rlf_band_res_needed(span_um: i64, lambda_um: i64) -> i64 {
192 if lambda_um <= 0 { return 0 }
193 return 2 * span_um / lambda_um
194}
195
196// texel footprint over an ARBITRARY span. rlf_texel_um is exactly this at the whole-body span, and
197// the gate pins that identity -- the whole-body case is a special case here, not a second rule.
198func rlf_texel_um_span(span_um: i64, res: i64) -> i64 {
199 var t: i64 = span_um / res
200 if t < 1 { t = 1 }
201 return t
202}
203// 1 = an atlas of this resolution over a region of this span carries the band. rlf_band_resolvable
204// is exactly this at the whole-body span, which is why a per-REGION atlas can carry what a
205// whole-body one clamps away -- the same band, a smaller footprint.
206func rlf_band_resolvable_span(span_um: i64, res: i64, lambda_um: i64) -> i64 {
207 if lambda_um / rlf_texel_um_span(span_um, res) < 2 { return 0 }
208 return 1
209}
210
211// jittered pit centre for lattice cell (cx,cy), one axis, in RLF_UNIT-of-a-cell.
212func rlf_pore_centre(cx: i64, cy: i64, axis: i64) -> i64 {
213 let h: i64 = rlf_hash3(cx, cy, axis + 1)
214 return (RLF_UNIT / 2) + (h % (RLF_PORE_JIT + 1)) - (RLF_PORE_JIT / 2)
215}
216// squared distance from (x,y) to the nearest pit centre, in (RLF_UNIT-of-a-cell) squared.
217func rlf_pore_d2(x: i64, y: i64, cell: i64) -> i64 {
218 var c: i64 = cell
219 if c < 2 { c = 2 }
220 let ox: i64 = x + RLF_OFF
221 let oy: i64 = y + RLF_OFF
222 let gx: i64 = ox / c
223 let gy: i64 = oy / c
224 let px: i64 = (ox - gx * c) * RLF_UNIT / c
225 let py: i64 = (oy - gy * c) * RLF_UNIT / c
226 var best: i64 = RLF_PORE_D2_INF
227 var dy: i64 = 0 - 1
228 while dy <= 1 {
229 var dx: i64 = 0 - 1
230 while dx <= 1 {
231 let qx: i64 = dx * RLF_UNIT + rlf_pore_centre(gx + dx, gy + dy, 0) - px
232 let qy: i64 = dy * RLF_UNIT + rlf_pore_centre(gx + dx, gy + dy, 1) - py
233 let d: i64 = qx * qx + qy * qy
234 if d < best { best = d }
235 dx = dx + 1
236 }
237 dy = dy + 1
238 }
239 return best
240}
241// the pit radius in RLF_UNIT-of-a-cell: the orifice diameter as a fraction of the pitch. DERIVED
242// from the two conf numbers, so changing either moves the pit and nothing has to be re-tuned.
243func rlf_pore_radius() -> i64 { return RLF_UNIT * RLF_PORE_DIA_UM / (2 * RLF_PORE_PITCH_UM) }
244// the fraction of skin a pit covers, in permil. Sparse BY CONSTRUCTION -- this is what makes the
245// layer a pore field and not a fourth noise octave, and it is published so the claim is checkable.
246func rlf_pore_area_permil() -> i64 {
247 let r: i64 = rlf_pore_radius()
248 return RLF_PI_E3 * r * r / (RLF_UNIT * RLF_UNIT)
249}
250// THE PORE MASK. 0 on flat skin, rising to RLF_H_RANGE at a pit centre, quadratic in between so
251// the pit floor is smooth and the rim carries no cusp.
252func rlf_pore_mask(x: i64, y: i64, cell: i64) -> i64 {
253 let r: i64 = rlf_pore_radius()
254 if r <= 0 { return 0 }
255 let r2: i64 = r * r
256 let d2: i64 = rlf_pore_d2(x, y, cell)
257 if d2 >= r2 { return 0 }
258 return (r2 - d2) * RLF_H_RANGE / r2
259}
260// A PIT IS NOT A WAVE, so its tilt is NOT rlf_tilt_full's 4A/L. The steepest point of a quadratic
261// pit of depth D and radius R is the rim, where the wall rises D over the run R: slope = D/R.
262// Stated separately rather than forced through the wave formula, because reusing a derivation that
263// does not apply is how a plausible number stops being a derived one.
264func rlf_pore_tilt_full(n_full: i64) -> i64 {
265 let r_um: i64 = RLF_PORE_DIA_UM / 2
266 if r_um <= 0 { return 0 }
267 return n_full * RLF_PORE_UM / r_um
268}
269// the total swing the three bands can produce, in microns. A function and not a const because the
270// compiler takes literal const initialisers only; the gate pins the relationship.
271func rlf_total_um() -> i64 { return RLF_RELIEF_UM + RLF_SEC_UM + RLF_PORE_UM }
272// the tilt the CONTINUOUS bands (0 and 1) together present to a shading model. They compose in
273// SLOPE, which is why this is a sum. A band the atlas cannot carry contributes NOTHING -- the
274// honest number rather than the nominal one. The pore term is deliberately NOT summed in here: it
275// covers about 1% of the surface, so adding a sparse peak to a continuous one would report an
276// envelope as an average. Ask rlf_pore_tilt_full and rlf_pore_area_permil for that band.
277func rlf_multi_tilt_full(res: i64, n_full: i64) -> i64 {
278 var t: i64 = rlf_band_tilt_full(n_full, RLF_RELIEF_UM, RLF_RELIEF_LAMBDA_UM)
279 if rlf_band_resolvable(res, RLF_SEC_LAMBDA_UM) == 1 {
280 t = t + rlf_band_tilt_full(n_full, RLF_SEC_UM, RLF_SEC_LAMBDA_UM)
281 }
282 return t
283}
284// THE COMPOSED ATLAS HEIGHT, IN MICRONS. Bands 0 and 1 are additive noise weighted by their own
285// swings; the pore layer SUBTRACTS, because an orifice is a pit and not a bump. Signed on purpose:
286// a pit below the mean surface is the physical truth, and clamping it here would hide it.
287func rlf_relief_um2(x: i64, y: i64, res: i64, seed: i64) -> i64 {
288 let h0: i64 = rlf_relief_h(x, y, res, seed)
289 let h1: i64 = rlf_noise3(x, y, seed, rlf_band_lat(res, RLF_SEC_LAMBDA_UM))
290 let pm: i64 = rlf_pore_mask(x, y, rlf_band_lat(res, RLF_PORE_PITCH_UM))
291 let up: i64 = (h0 * RLF_RELIEF_UM + h1 * RLF_SEC_UM) / RLF_H_RANGE
292 return up - pm * RLF_PORE_UM / RLF_H_RANGE
293}
294
295// ---------------------------------------------------------------------------------------------
296// PER-REGION COMPOSITION OVER AN ARBITRARY SPAN (2026-08-30, /compare/graphics GR29
297// gpe_skin_microsurface, the ASSET half). STRICTLY ADDITIVE: nothing above this line changed.
298// rlf_band_resolvable_span already said WHERE a band is real; these are the field and the run at
299// that span, so a per-REGION atlas (nx_nxa_texbake_region_lib) bakes the same law the whole-body
300// baker bakes, on the footprint it actually has. ONE declared difference from rlf_relief_um2: a
301// band the span cannot carry contributes NOTHING here, where the whole-body composition clamps its
302// lattice to two texels and bakes a coarser alias wearing the band's name. That alias is exactly
303// what the GR29 row measured as "painted", so the region path refuses to produce it.
304// the lattice in texels a band wants over span_um at res -- the same 2-texel clamp, same reason
305func rlf_band_lat_span(span_um: i64, res: i64, lambda_um: i64) -> i64 {
306 var lat: i64 = lambda_um / rlf_texel_um_span(span_um, res)
307 if lat < 2 { lat = 2 }
308 return lat
309}
310// the two-sided gradient run over span_um at res, in microns (rlf_run_um is this at the body span)
311func rlf_run_um_span(span_um: i64, res: i64) -> i64 { return 2 * rlf_texel_um_span(span_um, res) }
312// the bands (span,res) carries, as a mask: 1 primary, 2 secondary, 4 pore-grid (pitch), 8 pore-orifice
313// (diameter). The orifice is a STRICTER bar than its lattice and is a separate bit for that reason.
314func rlf_bands_carried_span(span_um: i64, res: i64) -> i64 {
315 var m: i64 = 0
316 if rlf_band_resolvable_span(span_um, res, RLF_RELIEF_LAMBDA_UM) == 1 { m = m + 1 }
317 if rlf_band_resolvable_span(span_um, res, RLF_SEC_LAMBDA_UM) == 1 { m = m + 2 }
318 if rlf_band_resolvable_span(span_um, res, RLF_PORE_PITCH_UM) == 1 { m = m + 4 }
319 if rlf_band_resolvable_span(span_um, res, RLF_PORE_DIA_UM) == 1 { m = m + 8 }
320 return m
321}
322// how many of the four bands a mask carries
323func rlf_bands_count(mask: i64) -> i64 {
324 var n: i64 = 0
325 if (mask & 1) == 1 { n = n + 1 }
326 if (mask & 2) == 2 { n = n + 1 }
327 if (mask & 4) == 4 { n = n + 1 }
328 if (mask & 8) == 8 { n = n + 1 }
329 return n
330}
331// the pore mask with an EXPLICIT pit radius (RLF_UNIT-of-a-cell). rlf_pore_mask is this at the
332// derived radius and now delegates -- one pit profile. The explicit radius exists for the case the
333// span carries the pore LATTICE but not the ORIFICE: the region baker then widens the pit to one
334// texel and scales its depth by the area ratio (a shallower dimple, announced as DIMPLE), instead of
335// baking a sub-texel pit that lands on a texel centre by luck and reads as sparkle.
336func rlf_pore_mask_r(x: i64, y: i64, cell: i64, r: i64) -> i64 {
337 if r <= 0 { return 0 }
338 let r2: i64 = r * r
339 let d2: i64 = rlf_pore_d2(x, y, cell)
340 if d2 >= r2 { return 0 }
341 return (r2 - d2) * RLF_H_RANGE / r2
342}
343// THE COMPOSED HEIGHT OVER span_um AT res, IN MICRONS. Bands enter only where carried (see the
344// header above). Pore pits enter when the LATTICE carries; the pit radius is the derived one when
345// the orifice carries and one texel otherwise, depth scaled by (r_true/r_eff)^2 so the pit keeps
346// its volume. Signed: a pit is below the mean surface.
347func rlf_relief_um2_span(x: i64, y: i64, span_um: i64, res: i64, seed: i64) -> i64 {
348 let m: i64 = rlf_bands_carried_span(span_um, res)
349 var up: i64 = 0
350 if (m & 1) == 1 { up = up + rlf_noise3(x, y, seed, rlf_band_lat_span(span_um, res, RLF_RELIEF_LAMBDA_UM)) * RLF_RELIEF_UM }
351 if (m & 2) == 2 { up = up + rlf_noise3(x, y, seed, rlf_band_lat_span(span_um, res, RLF_SEC_LAMBDA_UM)) * RLF_SEC_UM }
352 up = up / RLF_H_RANGE
353 if (m & 4) == 4 {
354 let cell: i64 = rlf_band_lat_span(span_um, res, RLF_PORE_PITCH_UM)
355 let rt: i64 = rlf_pore_radius()
356 var re: i64 = rt
357 var depth: i64 = RLF_PORE_UM
358 if (m & 8) != 8 {
359 // one texel, in RLF_UNIT-of-a-cell
360 let one: i64 = RLF_UNIT / cell
361 if one > rt { re = one; depth = RLF_PORE_UM * rt * rt / (re * re) }
362 }
363 up = up - rlf_pore_mask_r(x, y, cell, re) * depth / RLF_H_RANGE
364 }
365 return up
366}
367
368// ---------------------------------------------------------------------------------------------
369// THE PERIODIC MICRO TILE (2026-08-30, GR29 gpe_skin_microsurface asset half; operator directive
370// "for per region we need texture ... make sure we aren't just doing more smooth surface painting").
371// STRICTLY ADDITIVE: nothing above this line changed.
372//
373// WHY A TILE AT ALL, IN ARITHMETIC. A UNIQUE atlas samples the body at (chart extent / tile px), and
374// on the incumbent per-joint cylindrical charts that extent is a bone's CIRCUMFERENCE -- measured by
375// nx_nxa_texbake_region_lib per tile, not assumed from stature. No single-atlas resolution this
376// estate ships can put two texels inside a 250um secondary line around a 900mm chest. The field's
377// practice for exactly this case is a TILING micro-relief whose texel pitch is physical by
378// construction and which the shader repeats per chart at (chart extent / tile repeat): MetaHuman
379// keeps wrinkles in the unique normal and puts PORES in a tiling texture [@mh-materials], and the
380// scan vendors ship the micro band as its own channel beside the unique displacement [@txyz-vface].
381// The unique per-region atlas therefore carries what its footprint can (measured), and the three
382// finer bands ride THIS tile, from the SAME law and the SAME constants.
383//
384// PERIODICITY IS EXACT, NOT FADED. The tile's texel is two per pore ORIFICE (the same 2-texel bar
385// every band is judged by, applied to the finest feature), so every band's lattice is an integer
386// count of texels; the tile edge is the LCM of those lattices, so every band closes on itself; and
387// the lattice hash wraps its cell index modulo the tile's cell count, so the texel at x and the
388// texel at x + tile are the SAME bytes. A gate tooth pins that identity rather than a blend seam.
389// RLF_TILE_LCM_MULT buys a longer physical repeat than the bare LCM (240 texels = 12mm) so the
390// repeat is not visible as a grid at arm's length; 2 gives 24mm, one gland field wide.
391const RLF_TILE_LCM_MULT: i64 = 2
392// band mask bits shared with rlf_bands_carried_span: 1 primary, 2 secondary, 4 pore-grid, 8 orifice
393const RLF_BAND_PRIMARY: i64 = 1
394const RLF_BAND_SECONDARY: i64 = 2
395const RLF_BAND_POREGRID: i64 = 4
396const RLF_BAND_ORIFICE: i64 = 8
397const RLF_BAND_ALL: i64 = 15
398
399func rlf_gcd(a: i64, b: i64) -> i64 {
400 var x: i64 = a
401 var y: i64 = b
402 if x < 0 { x = 0 - x }
403 if y < 0 { y = 0 - y }
404 while y != 0 { let t: i64 = x % y; x = y; y = t }
405 return x
406}
407func rlf_lcm(a: i64, b: i64) -> i64 {
408 let g: i64 = rlf_gcd(a, b)
409 if g == 0 { return 0 }
410 return a / g * b
411}
412// the tile's texel pitch in microns: two texels per pore orifice, DERIVED from the orifice diameter
413func rlf_tile_texel_um() -> i64 { return RLF_PORE_DIA_UM / 2 }
414// a band's lattice on the tile, in texels (24 / 5 / 16 at the shipped constants)
415func rlf_tile_lat(lambda_um: i64) -> i64 {
416 var l: i64 = lambda_um / rlf_tile_texel_um()
417 if l < 2 { l = 2 }
418 return l
419}
420// the tile edge in texels: LCM of the three lattices, times the repeat multiplier (480 at shipped constants)
421func rlf_tile_px() -> i64 {
422 let l0: i64 = rlf_tile_lat(RLF_RELIEF_LAMBDA_UM)
423 let l1: i64 = rlf_tile_lat(RLF_SEC_LAMBDA_UM)
424 let lp: i64 = rlf_tile_lat(RLF_PORE_PITCH_UM)
425 return rlf_lcm(rlf_lcm(l0, l1), lp) * RLF_TILE_LCM_MULT
426}
427// the physical repeat of one tile, in microns (24000 at shipped constants)
428func rlf_tile_repeat_um() -> i64 { return rlf_tile_px() * rlf_tile_texel_um() }
429// the bands the tile carries, by the same 2-texel bar rlf_bands_carried_span applies -- all four
430// at the shipped constants, and a gate pins that rather than this comment
431func rlf_tile_bands_carried() -> i64 { return rlf_bands_carried_span(rlf_tile_repeat_um(), rlf_tile_px()) }
432// wrap a texel coordinate onto [0, px)
433func rlf_wrap(x: i64, px: i64) -> i64 {
434 var r: i64 = x % px
435 if r < 0 { r = r + px }
436 return r
437}
438// the lattice hash with the cell index wrapped modulo per: cell per-1 and cell 0 are neighbours
439func rlf_hash3_per(gx: i64, gy: i64, z: i64, per: i64) -> i64 {
440 return rlf_hash3(rlf_wrap(gx, per), rlf_wrap(gy, per), z)
441}
442// PERIODIC value noise: rlf_noise3's interpolation on a lattice of S texels whose cell count per
443// edge is per, so the field repeats every S*per texels. No RLF_OFF bias -- the coordinate is wrapped
444// onto the tile first, which is what keeps the seam ON the tile edge instead of at an arbitrary
445// multiple of S.
446func rlf_noise3_per(x: i64, y: i64, z: i64, S: i64, per: i64) -> i64 {
447 let px: i64 = S * per
448 let ox: i64 = rlf_wrap(x, px)
449 let oy: i64 = rlf_wrap(y, px)
450 let gx: i64 = ox / S
451 let gy: i64 = oy / S
452 let fx: i64 = (ox - gx*S)*RLF_UNIT/S
453 let fy: i64 = (oy - gy*S)*RLF_UNIT/S
454 let c00: i64 = rlf_hash3_per(gx, gy, z, per)
455 let c10: i64 = rlf_hash3_per(gx+1, gy, z, per)
456 let c01: i64 = rlf_hash3_per(gx, gy+1, z, per)
457 let c11: i64 = rlf_hash3_per(gx+1, gy+1, z, per)
458 let x0: i64 = c00 + (c10-c00)*fx/RLF_UNIT
459 let x1: i64 = c01 + (c11-c01)*fx/RLF_UNIT
460 return x0 + (x1-x0)*fy/RLF_UNIT
461}
462// periodic pit centre: the same jitter, the cell index wrapped
463func rlf_pore_centre_per(cx: i64, cy: i64, axis: i64, per: i64) -> i64 {
464 let h: i64 = rlf_hash3_per(cx, cy, axis + 1, per)
465 return (RLF_UNIT / 2) + (h % (RLF_PORE_JIT + 1)) - (RLF_PORE_JIT / 2)
466}
467// periodic squared distance to the nearest pit centre; the 3x3 search is exact for the same reason
468// as rlf_pore_d2 (jitter bounded to half a cell), and the wrap makes the tile edge a real neighbour
469func rlf_pore_d2_per(x: i64, y: i64, cell: i64, per: i64) -> i64 {
470 var c: i64 = cell
471 if c < 2 { c = 2 }
472 let px: i64 = c * per
473 let ox: i64 = rlf_wrap(x, px)
474 let oy: i64 = rlf_wrap(y, px)
475 let gx: i64 = ox / c
476 let gy: i64 = oy / c
477 let fx: i64 = (ox - gx * c) * RLF_UNIT / c
478 let fy: i64 = (oy - gy * c) * RLF_UNIT / c
479 var best: i64 = RLF_PORE_D2_INF
480 var dy: i64 = 0 - 1
481 while dy <= 1 {
482 var dx: i64 = 0 - 1
483 while dx <= 1 {
484 let qx: i64 = dx * RLF_UNIT + rlf_pore_centre_per(gx + dx, gy + dy, 0, per) - fx
485 let qy: i64 = dy * RLF_UNIT + rlf_pore_centre_per(gx + dx, gy + dy, 1, per) - fy
486 let d: i64 = qx * qx + qy * qy
487 if d < best { best = d }
488 dx = dx + 1
489 }
490 dy = dy + 1
491 }
492 return best
493}
494// the periodic pit profile, same quadratic as rlf_pore_mask_r
495func rlf_pore_mask_per(x: i64, y: i64, cell: i64, r: i64, per: i64) -> i64 {
496 if r <= 0 { return 0 }
497 let r2: i64 = r * r
498 let d2: i64 = rlf_pore_d2_per(x, y, cell, per)
499 if d2 >= r2 { return 0 }
500 return (r2 - d2) * RLF_H_RANGE / r2
501}
502// THE COMPOSED TILE HEIGHT IN MICRONS with a band mask (the gate's smooth control is this with a
503// narrower mask -- same code path, so the control differs from the subject ONLY in the bands).
504// Periodic in x and y with period rlf_tile_px(). Signed: pits go below the mean surface.
505func rlf_relief_um2_tile_bands(x: i64, y: i64, seed: i64, mask: i64) -> i64 {
506 let px: i64 = rlf_tile_px()
507 var up: i64 = 0
508 if (mask & RLF_BAND_PRIMARY) == RLF_BAND_PRIMARY {
509 let l0: i64 = rlf_tile_lat(RLF_RELIEF_LAMBDA_UM)
510 up = up + rlf_noise3_per(x, y, seed, l0, px / l0) * RLF_RELIEF_UM
511 }
512 if (mask & RLF_BAND_SECONDARY) == RLF_BAND_SECONDARY {
513 let l1: i64 = rlf_tile_lat(RLF_SEC_LAMBDA_UM)
514 up = up + rlf_noise3_per(x, y, seed, l1, px / l1) * RLF_SEC_UM
515 }
516 up = up / RLF_H_RANGE
517 if (mask & RLF_BAND_POREGRID) == RLF_BAND_POREGRID {
518 let lp: i64 = rlf_tile_lat(RLF_PORE_PITCH_UM)
519 up = up - rlf_pore_mask_per(x, y, lp, rlf_pore_radius(), px / lp) * RLF_PORE_UM / RLF_H_RANGE
520 }
521 return up
522}
523// the shipped tile: every band
524func rlf_relief_um2_tile(x: i64, y: i64, seed: i64) -> i64 {
525 return rlf_relief_um2_tile_bands(x, y, seed, RLF_BAND_ALL)
526}