code wiki / (root) / nx_response_signal.nx

nx_response_signal.nx source

↩ module page · 170 lines · 6247 B

1// nx_response_signal.nx -- arousal response signals for eroticism ladder. 2// 3// The eroticism rank 10 (ACTIVE_PENETRATION_WITH_RESPONSE) requires 4// the substrate to detect VISIBLE arousal response signals beyond just 5// pose. Three independent axes: 6// 7// parted_lips — dark region between upper/lower lip in mid-face 8// flushed_skin — R-elevation on cheek region vs torso baseline 9// skin_sheen — moisture/sweat sheen above baseline specular 10// 11// Combined: a count 0..3 of which signals are present. Feeds the 12// response_count input of nx_eroticism_ladder. 13// 14// Output flat-array: 15// result[0] = parted_lips_present (0/1) 16// result[1] = flushed_skin_present (0/1) 17// result[2] = skin_sheen_present (0/1) 18// result[3] = signal_count (sum) 19// result[4] = parted_dark_pixels (raw) 20// result[5] = cheek_R_minus_torso_R (delta, signed) 21// result[6] = sheen_bright_pixel_pct (x100) 22 23// nx_safety_envelope: 24// intended_use: AUTO_APPLIED -- primitive-specific tuning queued 25// sil_target: SIL1 26// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail] 27// verdict: NOT_YET_EVALUATED 28 29import "nx_syscalls.nx" 30import "nx_image.nx" 31 32const NX_RESP_RES_PARTED: i64 = 0 33const NX_RESP_RES_FLUSHED: i64 = 1 34const NX_RESP_RES_SHEEN: i64 = 2 35const NX_RESP_RES_COUNT: i64 = 3 36const NX_RESP_RES_PARTED_DARK: i64 = 4 37const NX_RESP_RES_CHEEK_DELTA: i64 = 5 38const NX_RESP_RES_SHEEN_PCT: i64 = 6 39const NX_RESP_RES_FIELDS: i64 = 7 40 41// Thresholds. 42const NX_RESP_PARTED_DARK_MIN: i64 = 12 // need ≥12 dark pixels in mouth band 43const NX_RESP_FLUSHED_R_DELTA_MIN: i64 = 15 // cheek R must be ≥ 15 above torso R baseline 44const NX_RESP_SHEEN_PCT_MIN: i64 = 8 // ≥ 8% bright (Y > 220) in skin 45 46// Detect a dark region (mouth opening) in the middle horizontal band of 47// the face bbox. Returns the count of pixels with luminance < 60. 48func nx_resp_count_dark_in_mouth_band(rgb: *Image, 49 face_min_x: i64, face_min_y: i64, 50 face_max_x: i64, face_max_y: i64) -> i64 { 51 let face_h: i64 = face_max_y - face_min_y + 1 52 // Mouth band: ~70% to ~85% down the face. 53 let y0: i64 = face_min_y + (face_h * 70) / 100 54 let y1: i64 = face_min_y + (face_h * 85) / 100 55 var count: i64 = 0 56 var y: i64 = y0 57 while y < y1 { 58 if y >= 0 { if y < rgb.height { 59 var x: i64 = face_min_x 60 while x <= face_max_x { 61 if x >= 0 { if x < rgb.width { 62 let r: i64 = nx_image_get(rgb, x, y, 0) 63 let g: i64 = nx_image_get(rgb, x, y, 1) 64 let bch: i64 = nx_image_get(rgb, x, y, 2) 65 let yv: i64 = (77 * r + 150 * g + 29 * bch) / 256 66 if yv < 60 { count = count + 1 } 67 } } 68 x = x + 1 69 } 70 } } 71 y = y + 1 72 } 73 return count 74} 75 76// Mean R channel in a rectangular region. 77func nx_resp_mean_r(rgb: *Image, x0: i64, y0: i64, x1: i64, y1: i64) -> i64 { 78 var sum: i64 = 0 79 var n: i64 = 0 80 var y: i64 = y0 81 while y < y1 { 82 if y >= 0 { if y < rgb.height { 83 var x: i64 = x0 84 while x < x1 { 85 if x >= 0 { if x < rgb.width { 86 sum = sum + nx_image_get(rgb, x, y, 0) 87 n = n + 1 88 } } 89 x = x + 1 90 } 91 } } 92 y = y + 1 93 } 94 if n == 0 { return 0 } 95 return sum / n 96} 97 98// Percent of skin-mask pixels with luminance > 220 (sheen). 99func nx_resp_sheen_pct(rgb: *Image, mask: *Image) -> i64 { 100 let w: i64 = rgb.width 101 let h: i64 = rgb.height 102 var bright: i64 = 0 103 var total: i64 = 0 104 var y: i64 = 0 105 while y < h { 106 var x: i64 = 0 107 while x < w { 108 if nx_image_get(mask, x, y, 0) > 0 { 109 total = total + 1 110 let r: i64 = nx_image_get(rgb, x, y, 0) 111 let g: i64 = nx_image_get(rgb, x, y, 1) 112 let bch: i64 = nx_image_get(rgb, x, y, 2) 113 let yv: i64 = (77 * r + 150 * g + 29 * bch) / 256 114 if yv > 220 { bright = bright + 1 } 115 } 116 x = x + 1 117 } 118 y = y + 1 119 } 120 if total == 0 { return 0 } 121 return (bright * 100) / total 122} 123 124// Compute response signals. 125// bbox_pack layout (i64 array, 12 entries): 126// [0..3] face x0, y0, x1, y1 127// [4..7] cheek x0, y0, x1, y1 128// [8..11] torso x0, y0, x1, y1 129// Args reduced to 4 to avoid nxc2 codegen bug with high-arity functions. 130func nx_response_signals(rgb: *Image, skin_mask: *Image, 131 bbox_pack: *i64, result: *i64) -> i64 { 132 var i: i64 = 0 133 while i < NX_RESP_RES_FIELDS { result[i] = 0; i = i + 1 } 134 135 let face_x0: i64 = bbox_pack[0] 136 let face_y0: i64 = bbox_pack[1] 137 let face_x1: i64 = bbox_pack[2] 138 let face_y1: i64 = bbox_pack[3] 139 let cheek_x0: i64 = bbox_pack[4] 140 let cheek_y0: i64 = bbox_pack[5] 141 let cheek_x1: i64 = bbox_pack[6] 142 let cheek_y1: i64 = bbox_pack[7] 143 let torso_x0: i64 = bbox_pack[8] 144 let torso_y0: i64 = bbox_pack[9] 145 let torso_x1: i64 = bbox_pack[10] 146 let torso_y1: i64 = bbox_pack[11] 147 148 let dark: i64 = nx_resp_count_dark_in_mouth_band(rgb, face_x0, face_y0, face_x1, face_y1) 149 result[NX_RESP_RES_PARTED_DARK] = dark 150 var parted: i64 = 0 151 if dark >= NX_RESP_PARTED_DARK_MIN { parted = 1 } 152 result[NX_RESP_RES_PARTED] = parted 153 154 let cheek_r: i64 = nx_resp_mean_r(rgb, cheek_x0, cheek_y0, cheek_x1, cheek_y1) 155 let torso_r: i64 = nx_resp_mean_r(rgb, torso_x0, torso_y0, torso_x1, torso_y1) 156 let delta: i64 = cheek_r - torso_r 157 result[NX_RESP_RES_CHEEK_DELTA] = delta 158 var flushed: i64 = 0 159 if delta >= NX_RESP_FLUSHED_R_DELTA_MIN { flushed = 1 } 160 result[NX_RESP_RES_FLUSHED] = flushed 161 162 let pct: i64 = nx_resp_sheen_pct(rgb, skin_mask) 163 result[NX_RESP_RES_SHEEN_PCT] = pct 164 var sheen: i64 = 0 165 if pct >= NX_RESP_SHEEN_PCT_MIN { sheen = 1 } 166 result[NX_RESP_RES_SHEEN] = sheen 167 168 result[NX_RESP_RES_COUNT] = parted + flushed + sheen 169 return 0 170}