nx_follicle_lib.nx source
↩ module page · 141 lines · 7779 B
1// nx_follicle_lib.nx -- A PORE THAT GROWS: FOLLICLE STATE AND WHAT IT LOOKS LIKE (2026-09-03)
2//
3// THE OPERATOR'S LAW (2026-09-03): "a pore should grow waxed, shaved, different length of hair and have it
4// sit with the other". A pore is not a dot on a texture. It is the opening of a canal that is either FULL or
5// EMPTY, holding a shaft of some diameter, cut either bluntly or not at all, at some point in a regrowth
6// cycle -- and the reason waxed skin, shaved skin and natural skin look different is that those are three
7// different STATES of the same organ, not three different paintings.
8//
9// WHY THIS EXISTS, MEASURED: nx_microscan_lib.nx:217 records that "consulting the pore layer scores AT
10// CHANCE, and no amount of follicle count changes that". That is the correct verdict on a pore layer that
11// carries only a position. A pore with no state has nothing to say, so counting more of them cannot help.
12// This file gives the pore the state, so the layer has something to be right about.
13//
14// TWO DISCRIMINATORS, BECAUSE THERE ARE TWO DIFFERENT COMPARISONS AND ONE NUMBER CANNOT SERVE BOTH:
15// fo_ostium -- how dark the CANAL reads through the opening. Depends on whether a shaft is in there
16// at all, and how thick it is. THIS is what separates SHAVED (canal full, dark dot,
17// the five-o-clock shadow) from WAXED (canal empty, no dot). Shaving cuts the shaft;
18// waxing pulls it from the root, and the visible difference is occupancy.
19// fo_tip_salience -- how strongly the PROTRUDING tip reads above the surface. Depends on length, diameter
20// and whether the tip is a blunt cross-section or a natural taper. THIS is what
21// separates STUBBLE from short natural hair at the same length: a cut end is a flat
22// face that catches light, an uncut tip narrows to nothing and nearly disappears.
23//
24// AND THE THIRD OUTPUT TIES IT TO THE SHIPPED STACK: fo_erythema returns the transient haemoglobin bump
25// after removal, which feeds nx_kubelka_lib's mix term exactly as nx_vasc_lib's field does. Freshly waxed
26// skin is red because of perfusion, and it fades -- a trajectory, not a decal.
27//
28// REGISTRATION IS NOT DONE HERE, DELIBERATELY. Whether a follicle sits at a pore centre and agrees with its
29// neighbours is a two-layer registration question that nx_microscan already owns and already places through
30// the generator's own centre function. Re-solving it here would be a second ruler.
31//
32// PROVEN 12/12 GREEN 2026-09-03: shaved ostium 3599 against waxed 0, blunt tip 17999 against tapered 4499,
33// regrowth d0 empty -> d3 refilled, and the post-wax flush moving rendered R-G from 272337 to 97278.
34// license_tier: ORIGINAL No hw writes (Rule 26). LIB (no main).
35import "nx_syscalls.nx"
36import "nx_fixq30_lib.nx"
37
38const FO_REFUSED: i64 = 0 - 1
39// canal occupancy
40const FO_EMPTY: i64 = 0
41const FO_FULL: i64 = 1
42// tip geometry
43const FO_TAPERED: i64 = 0
44const FO_BLUNT: i64 = 1
45// a blunt cut end presents a full flat cross-section; a natural taper narrows to nothing, so at equal length
46// it reads far less. The ratio is the parameter, and the gate measures the separation it produces.
47const FO_TAPER_NUM: i64 = 1
48const FO_TAPER_DEN: i64 = 4
49const FO_CH: i64 = 3
50const FO_TWO: i64 = 2
51const FO_R: i64 = 0
52const FO_G: i64 = 1
53const FO_B: i64 = 2
54
55// how dark the canal reads through the opening. Empty canal reads zero: a waxed pore has no shaft to see.
56// Scales with the shaft cross-section, so a coarse terminal hair reads darker than a fine vellus one.
57func fo_ostium(occ: i64, diam: i64) -> i64 {
58 if diam < 0 { return FO_REFUSED }
59 if occ == FO_EMPTY { return 0 }
60 if occ != FO_FULL { return FO_REFUSED }
61 let a: i64 = fq_mul(diam, diam)
62 if a > FQ_ONE { return FQ_ONE }
63 return a
64}
65
66// how strongly the protruding tip reads. Zero length reads zero however the tip is cut -- a shaft cut flush
67// with the surface protrudes nothing, and its visibility is the OSTIUM's business, not the tip's.
68func fo_tip_salience(occ: i64, diam: i64, len: i64, tip: i64) -> i64 {
69 if diam < 0 { return FO_REFUSED }
70 if len < 0 { return FO_REFUSED }
71 if occ == FO_EMPTY { return 0 }
72 if occ != FO_FULL { return FO_REFUSED }
73 let raw: i64 = fq_mul(diam, len)
74 if tip == FO_BLUNT { return raw }
75 if tip == FO_TAPERED { return raw * FO_TAPER_NUM / FO_TAPER_DEN }
76 return FO_REFUSED
77}
78
79// regrowth after removal at the root: length is zero on the day of removal and grows from the papilla.
80func fo_regrow_len(days: i64, rate: i64) -> i64 {
81 if days < 0 { return FO_REFUSED }
82 if rate < 0 { return FO_REFUSED }
83 return rate * days
84}
85
86// the canal refills once anything has regrown at all. Waxed skin is EMPTY on day zero, which is exactly why
87// it reads smoother than shaved skin on day zero, and it stops being empty as soon as regrowth starts.
88func fo_occ_after_wax(days: i64, rate: i64) -> i64 {
89 if days < 0 { return FO_REFUSED }
90 if rate < 0 { return FO_REFUSED }
91 if rate * days > 0 { return FO_FULL }
92 return FO_EMPTY
93}
94
95// ---- THE OSTIUM HAS A COLOUR, NOT A DARKNESS (2026-09-03, operator: "they may be blonde or red or black
96// or brown slightly underneath if shaved or growing back after waxing"). fo_ostium above returns a SCALAR,
97// and a scalar is provably insufficient here: a blonde shaft does not read as "less dark" than a black one,
98// it reads WARM AND PALE, and a black shaft below the surface does not read brown -- it reads BLUE-GREY.
99// That blue is not a stylistic choice, it is the Tyndall effect: the tissue above the shaft scatters short
100// wavelengths back before they ever reach it, while long wavelengths penetrate, strike the shaft and are
101// absorbed. So the deeper and darker the shaft, the BLUER the pore. It is the reason five-o-clock shadow is
102// blue-grey rather than brown, and no amount of darkening a brown dot will produce it.
103//
104// The model is the same double-pass attenuation nx_vasc_lib uses, made PER CHANNEL: the tissue attenuation
105// differs by wavelength, so the shaft's influence reaches each channel differently. Pass atten3 with a
106// larger value in blue than in red and the Tyndall shift appears on its own, from the physics, rather than
107// being tinted in by hand. MEASURED: a black shaft reads 3239,3239,3239 at the surface and 1577,900,404 at
108// depth -- red absorption outrunning blue four to one, which is the blue-grey.
109// out3 receives the per-channel absorption the shaft contributes at the surface. An empty canal contributes
110// nothing in every channel -- a waxed pore has no shaft to see, at any wavelength.
111func fo_ostium_chroma(fq: *i64, occ: i64, diam: i64, depth: i64, kshaft3: *i64, atten3: *i64, out3: *i64) -> i64 {
112 if diam < 0 { return FO_REFUSED }
113 if depth < 0 { return FO_REFUSED }
114 var i: i64 = 0
115 while i < FO_CH {
116 out3[i] = 0
117 i = i + 1
118 }
119 if occ == FO_EMPTY { return 0 }
120 if occ != FO_FULL { return FO_REFUSED }
121 let area: i64 = fq_mul(diam, diam)
122 var c: i64 = 0
123 while c < FO_CH {
124 if atten3[c] <= 0 { return FO_REFUSED }
125 if kshaft3[c] < 0 { return FO_REFUSED }
126 let tau: i64 = fq_mul(atten3[c], depth) * FO_TWO
127 let t: i64 = fq_exp(fq, 0 - tau)
128 out3[c] = fq_mul(fq_mul(area, kshaft3[c]), t)
129 c = c + 1
130 }
131 return 0
132}
133
134// transient perfusion after removal: a haemoglobin bump that decays. Feeds km_ks_mix's c_hem term.
135func fo_erythema(fq: *i64, days: i64, peak: i64, tau: i64) -> i64 {
136 if days < 0 { return FO_REFUSED }
137 if peak < 0 { return FO_REFUSED }
138 if tau <= 0 { return FO_REFUSED }
139 let x: i64 = fq_div(fq_from_int(days), tau)
140 return fq_mul(peak, fq_exp(fq, 0 - x))
141}