nx_areola_pigment.nx source
↩ module page · 163 lines · 6822 B
1// nx_areola_pigment.nx -- areola pigment vs reproductive-state validator.
2//
3// Image #46: areolas read brown/dark for a young non-pregnant figure.
4// Real-world ranges:
5// NULLIPAROUS_YOUNG (no births, under 25): light pink/tan
6// NULLIPAROUS_ADULT (no births, 25-40): pink/tan, slightly darker
7// POSTPARTUM (had children): meaningfully darker
8// PREGNANT (currently pregnant): darkest, melanin elevated
9// LACTATING (breastfeeding): dark to very dark
10//
11// Math:
12// pigment_intensity = 255 - max(R,G,B) (darkness, 0=white .. 255=black)
13// hue_warmth = R - G (positive = warm brown)
14// Expected ranges per state (pigment_intensity, hue_warmth):
15// NULLIPAROUS_YOUNG pigment 60..130 warmth 25..70
16// NULLIPAROUS_ADULT pigment 90..160 warmth 35..90
17// POSTPARTUM pigment 130..200 warmth 55..115
18// PREGNANT pigment 160..230 warmth 70..140
19// LACTATING pigment 140..220 warmth 60..125
20//
21// Output flat-array:
22// result[0] = mean_R
23// result[1] = mean_G
24// result[2] = mean_B
25// result[3] = pigment_intensity
26// result[4] = hue_warmth
27// result[5] = pigment_score_q10 (1024 = in declared-state range)
28// result[6] = warmth_score_q10
29// result[7] = composite_q10
30// result[8] = verdict (0=TOO_DARK_FOR_STATE, 1=AMBIGUOUS, 2=MATCH, 3=TOO_LIGHT_FOR_STATE)
31
32// nx_safety_envelope:
33// intended_use: AUTO_APPLIED -- primitive-specific tuning queued
34// sil_target: SIL1
35// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail]
36// verdict: NOT_YET_EVALUATED
37
38import "nx_syscalls.nx"
39import "nx_image.nx"
40const NX_MAGIC_1024: i64 = 1024
41
42const NX_REPRO_UNKNOWN: i64 = 0
43const NX_REPRO_NULLIPAROUS_YOUNG: i64 = 1
44const NX_REPRO_NULLIPAROUS_ADULT: i64 = 2
45const NX_REPRO_POSTPARTUM: i64 = 3
46const NX_REPRO_PREGNANT: i64 = 4
47const NX_REPRO_LACTATING: i64 = 5
48
49const NX_AREOLA_VERDICT_TOO_DARK: i64 = 0
50const NX_AREOLA_VERDICT_AMBIGUOUS: i64 = 1
51const NX_AREOLA_VERDICT_MATCH: i64 = 2
52const NX_AREOLA_VERDICT_TOO_LIGHT: i64 = 3
53
54const NX_AREOLA_RES_MEAN_R: i64 = 0
55const NX_AREOLA_RES_MEAN_G: i64 = 1
56const NX_AREOLA_RES_MEAN_B: i64 = 2
57const NX_AREOLA_RES_PIGMENT: i64 = 3
58const NX_AREOLA_RES_WARMTH: i64 = 4
59const NX_AREOLA_RES_PIG_SCORE: i64 = 5
60const NX_AREOLA_RES_WARM_SCORE: i64 = 6
61const NX_AREOLA_RES_COMPOSITE: i64 = 7
62const NX_AREOLA_RES_VERDICT: i64 = 8
63const NX_AREOLA_RES_FIELDS: i64 = 9
64
65// Per-state pigment range lookup. min/max written to out args.
66func nx_areola_pigment_range(state: i64, out_min: *i64, out_max: *i64) -> i64 {
67 if state == NX_REPRO_NULLIPAROUS_YOUNG { out_min[0] = 60; out_max[0] = 130; return 0 }
68 if state == NX_REPRO_NULLIPAROUS_ADULT { out_min[0] = 90; out_max[0] = 160; return 0 }
69 if state == NX_REPRO_POSTPARTUM { out_min[0] = 130; out_max[0] = 200; return 0 }
70 if state == NX_REPRO_PREGNANT { out_min[0] = 160; out_max[0] = 230; return 0 }
71 if state == NX_REPRO_LACTATING { out_min[0] = 140; out_max[0] = 220; return 0 }
72 out_min[0] = 60; out_max[0] = 230
73 return 0
74}
75
76func nx_areola_warmth_range(state: i64, out_min: *i64, out_max: *i64) -> i64 {
77 if state == NX_REPRO_NULLIPAROUS_YOUNG { out_min[0] = 25; out_max[0] = 70; return 0 }
78 if state == NX_REPRO_NULLIPAROUS_ADULT { out_min[0] = 35; out_max[0] = 90; return 0 }
79 if state == NX_REPRO_POSTPARTUM { out_min[0] = 55; out_max[0] = 115; return 0 }
80 if state == NX_REPRO_PREGNANT { out_min[0] = 70; out_max[0] = 140; return 0 }
81 if state == NX_REPRO_LACTATING { out_min[0] = 60; out_max[0] = 125; return 0 }
82 out_min[0] = 25; out_max[0] = 140
83 return 0
84}
85
86// Linear in-range score with linear falloff outside.
87func nx_areola_score_in_range(v: i64, vmin: i64, vmax: i64, spread: i64) -> i64 {
88 if v >= vmin { if v <= vmax { return NX_MAGIC_1024 } }
89 var dist: i64 = 0
90 if v < vmin { dist = vmin - v }
91 if v > vmax { dist = v - vmax }
92 if dist >= spread { return 0 }
93 return NX_MAGIC_1024 - ((dist * NX_MAGIC_1024) / spread)
94}
95
96// Measure areola pixels. Caller provides RGB image + areola binary mask
97// (255 = areola pixel) + reproductive state + result array.
98func nx_areola_pigment(rgb: *Image, areola_mask: *Image,
99 reproductive_state: i64, result: *i64) -> i64 {
100 var i: i64 = 0
101 while i < NX_AREOLA_RES_FIELDS { result[i] = 0; i = i + 1 }
102 let w: i64 = rgb.width
103 let h: i64 = rgb.height
104 var sum_r: i64 = 0
105 var sum_g: i64 = 0
106 var sum_b: i64 = 0
107 var count: i64 = 0
108 var y: i64 = 0
109 while y < h {
110 var x: i64 = 0
111 while x < w {
112 let m: i64 = nx_image_get(areola_mask, x, y, 0)
113 if m > 0 {
114 sum_r = sum_r + nx_image_get(rgb, x, y, 0)
115 sum_g = sum_g + nx_image_get(rgb, x, y, 1)
116 sum_b = sum_b + nx_image_get(rgb, x, y, 2)
117 count = count + 1
118 }
119 x = x + 1
120 }
121 y = y + 1
122 }
123 if count == 0 { return 1 }
124 let mean_r: i64 = sum_r / count
125 let mean_g: i64 = sum_g / count
126 let mean_b: i64 = sum_b / count
127 result[NX_AREOLA_RES_MEAN_R] = mean_r
128 result[NX_AREOLA_RES_MEAN_G] = mean_g
129 result[NX_AREOLA_RES_MEAN_B] = mean_b
130
131 var max_rgb: i64 = mean_r
132 if mean_g > max_rgb { max_rgb = mean_g }
133 if mean_b > max_rgb { max_rgb = mean_b }
134 let pigment: i64 = 255 - max_rgb
135 let warmth: i64 = mean_r - mean_g
136 result[NX_AREOLA_RES_PIGMENT] = pigment
137 result[NX_AREOLA_RES_WARMTH] = warmth
138
139 let pmin: *i64 = (sys_mmap(8 + 8)) as *i64
140 let pmax: *i64 = (sys_mmap(8 + 8)) as *i64
141 nx_areola_pigment_range(reproductive_state, pmin, pmax)
142 let wmin: *i64 = (sys_mmap(8 + 8)) as *i64
143 let wmax: *i64 = (sys_mmap(8 + 8)) as *i64
144 nx_areola_warmth_range(reproductive_state, wmin, wmax)
145
146 let pig_score: i64 = nx_areola_score_in_range(pigment, pmin[0], pmax[0], 60)
147 let warm_score: i64 = nx_areola_score_in_range(warmth, wmin[0], wmax[0], 30)
148 result[NX_AREOLA_RES_PIG_SCORE] = pig_score
149 result[NX_AREOLA_RES_WARM_SCORE] = warm_score
150 let composite: i64 = (pig_score + warm_score) / 2
151 result[NX_AREOLA_RES_COMPOSITE] = composite
152
153 var verdict: i64 = NX_AREOLA_VERDICT_AMBIGUOUS
154 if composite >= 768 {
155 verdict = NX_AREOLA_VERDICT_MATCH
156 } else {
157 // Out of range — direction tells us if too dark or too light.
158 if pigment > pmax[0] { verdict = NX_AREOLA_VERDICT_TOO_DARK }
159 else { if pigment < pmin[0] { verdict = NX_AREOLA_VERDICT_TOO_LIGHT } }
160 }
161 result[NX_AREOLA_RES_VERDICT] = verdict
162 return 0
163}