code wiki / (root) / nx_verdict_phrase.nx

nx_verdict_phrase.nx source

↩ module page · 480 lines · 20020 B

1// nx_verdict_phrase.nx -- human-legible phrasing for grader verdicts. 2// 3// CAPABILITY_COMPLETENESS: FULL (for shipped tier-axis verdicts; 4// new graders register their phrasing via nx_verdict_phrase_set) 5// 6// User direction 2026-05-16: 7// "also make sure the math is human legible like the skin is 8// too plastic or something even more explanatory or she is 50 9// percent asian in this pic etc" 10// 11// The substrate's tier-axis verdicts are sealed integers + Q10 12// scores -- great for the closed-loop trainer + ledger, useless 13// for a human reading the report. This primitive bridges the 14// machine-readable verdict world with the human-readable 15// explanation world. 16// 17// Two phrasing modes: 18// 19// MODE_TERSE -- one short clause, suitable for batch 20// dashboards: "skin too plastic" 21// "background fragmented" 22// "vein signal absent" 23// MODE_VERBOSE -- one full sentence with the score reading: 24// "Skin texture verdict PLASTIC_SHEEN at 25// energy 0.83 (expected 0.4-0.7 for 26// natural skin); over-smoothed render." 27// 28// Phrases are emitted as NUL-terminated UTF-8 byte sequences. 29// Caller passes a buffer of NX_VP_MAX_PHRASE_LEN bytes; primitive 30// writes the phrase and returns the byte count written. 31// 32// CONTINUOUS-CLASS PHRASING (e.g., "30% east-asian / 50% 33// european / 20% african" -- the user's "50 percent asian" 34// example): the phrasing is done via NxVerdictBlend struct 35// (sealed-enum class list + per-class Q10 weight) -> phrase 36// rendering with thresholds (drop classes below 5% Q10, sort 37// descending, format "X% kind / Y% kind / Z% kind"). 38// 39// genealogy_id: substrate_legibility_layer_2026_05_16 40// lineage_id: nx_verdict_phrase_v1 41 42// nx_safety_envelope: 43// intended_use: AUTO_APPLIED -- primitive-specific tuning queued 44// sil_target: SIL1 45// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail] 46// verdict: NOT_YET_EVALUATED 47 48import "nx_syscalls.nx" 49import "nx_runtime.nx" 50import "nx_tier.nx" 51 52const NX_VP_Q10: nx_int = 1024 53const NX_VP_MAX_PHRASE_LEN: nx_int = 512 54const NX_VP_BLEND_MAX_CLASSES: nx_int = 16 55const NX_VP_BLEND_MIN_PERCENT_Q10: nx_int = 51 // 5% Q10 56 57// ===== sealed-enum: phrasing mode ================================ 58 59const NX_VP_MODE_TERSE: nx_int = 0 60const NX_VP_MODE_VERBOSE: nx_int = 1 61 62// ===== axis-id constants (mirror nx_domain_profile NX_AXIS_*) ==== 63 64const NX_VP_AXIS_T1_SKIN_TONE_ITA: nx_int = 4 65const NX_VP_AXIS_T2_GLCM_TEXTURE: nx_int = 5 66const NX_VP_AXIS_T2_PORE_DETAIL: nx_int = 7 67const NX_VP_AXIS_T4_UNDERTONE: nx_int = 12 68const NX_VP_AXIS_T5_VEIN_SIGNAL: nx_int = 16 69const NX_VP_AXIS_T5_SUBSURFACE_SIGNAL: nx_int = 17 70const NX_VP_AXIS_T5B_MICRO_EXPRESSION: nx_int = 22 71const NX_VP_AXIS_T6_SHADOW_DIRECTION: nx_int = 23 72const NX_VP_AXIS_T6_FINGER_COUNT: nx_int = 24 73const NX_VP_AXIS_T6_LIMB_COUNT: nx_int = 25 74const NX_VP_AXIS_T6_POSE_NATURALNESS: nx_int = 26 75const NX_VP_AXIS_T6_SURFACE_PLANE: nx_int = 27 76const NX_VP_AXIS_T6_SILHOUETTE_INTEG: nx_int = 28 77 78// ===== blend struct ============================================== 79 80struct NxVerdictBlend { 81 n_classes: nx_int, 82 class_ids: *nx_int, // sealed enum per axis 83 class_names: **u8, // NUL-terminated UTF-8 names 84 weights_q10: *nx_int, // per-class weight (sum should be Q10) 85} 86 87const NX_VP_BLEND_BYTES: nx_size = 40 88 89// ===== string helpers ============================================ 90 91func _vp_strlen(s: *u8) -> nx_int { 92 var n: nx_int = 0 93 var safety: nx_int = 0 94 while safety < NX_VP_MAX_PHRASE_LEN { 95 if s[n] == (0 as u8) { return n } 96 n = n + 1 97 safety = safety + 1 98 } 99 return n 100} 101 102func _vp_strcpy(dst: *u8, src: *u8, dst_pos: nx_int) -> nx_int { 103 let len: nx_int = _vp_strlen(src) 104 var i: nx_int = 0 105 while i < len { 106 dst[dst_pos + i] = src[i] 107 i = i + 1 108 } 109 return dst_pos + len 110} 111 112// Format an integer as decimal ASCII into dst starting at pos. 113// Returns the new position. 114func _vp_format_int(dst: *u8, pos: nx_int, value: nx_int) -> nx_int { 115 if value == 0 { 116 dst[pos] = 48 as u8 // '0' 117 return pos + 1 118 } 119 var v: nx_int = value 120 var negative: nx_int = 0 121 if v < 0 { 122 negative = 1 123 v = 0 - v 124 } 125 // Collect digits in reverse. 126 let buf: *u8 = (sys_mmap(32)) as *u8 127 var n_digits: nx_int = 0 128 while v > 0 { 129 let d: nx_int = v % 10 130 let ch: nx_int = 48 + d // '0' + d 131 buf[n_digits] = ch as u8 132 v = v / 10 133 n_digits = n_digits + 1 134 } 135 var out_pos: nx_int = pos 136 if negative == 1 { 137 dst[out_pos] = 45 as u8 // '-' 138 out_pos = out_pos + 1 139 } 140 var k: nx_int = n_digits - 1 141 while k >= 0 { 142 dst[out_pos] = buf[k] 143 out_pos = out_pos + 1 144 k = k - 1 145 } 146 return out_pos 147} 148 149// Q10 -> percent (0..100) for human display. 150func _vp_q10_to_percent(q10: nx_int) -> nx_int { 151 let pct: nx_int = (q10 * 100) / NX_VP_Q10 152 return pct 153} 154 155// ===== phrase templates (per axis x verdict) ===================== 156// 157// For each shipped tier-axis we ship a sealed table of phrases 158// indexed by (verdict_value, mode). Caller adds new mappings 159// via nx_verdict_phrase_set (queued). 160// 161// Each helper returns a NUL-terminated UTF-8 byte string. 162 163func _vp_skin_tone_phrase(verdict: nx_int, mode: nx_int) -> *u8 { 164 if mode == NX_VP_MODE_TERSE { 165 if verdict == 0 { return "very light skin tone" as *u8 } 166 if verdict == 1 { return "light skin tone" as *u8 } 167 if verdict == 2 { return "intermediate skin tone" as *u8 } 168 if verdict == 3 { return "tan skin tone" as *u8 } 169 if verdict == 4 { return "brown skin tone" as *u8 } 170 if verdict == 5 { return "dark skin tone" as *u8 } 171 return "unknown skin tone" as *u8 172 } 173 if verdict == 0 { return "Very light skin tone (Fitzpatrick I; ITA > 55 deg)" as *u8 } 174 if verdict == 1 { return "Light skin tone (Fitzpatrick II; ITA 41-55 deg)" as *u8 } 175 if verdict == 2 { return "Intermediate skin tone (Fitzpatrick III; ITA 28-41 deg)" as *u8 } 176 if verdict == 3 { return "Tan skin tone (Fitzpatrick IV; ITA 10-28 deg)" as *u8 } 177 if verdict == 4 { return "Brown skin tone (Fitzpatrick V; ITA -30 to 10 deg)" as *u8 } 178 if verdict == 5 { return "Dark skin tone (Fitzpatrick VI; ITA < -30 deg)" as *u8 } 179 return "Unknown skin tone classification" as *u8 180} 181 182func _vp_undertone_phrase(verdict: nx_int, mode: nx_int) -> *u8 { 183 if mode == NX_VP_MODE_TERSE { 184 if verdict == 1 { return "cool undertone" as *u8 } 185 if verdict == 2 { return "neutral undertone" as *u8 } 186 if verdict == 3 { return "warm undertone" as *u8 } 187 return "undertone not detected" as *u8 188 } 189 if verdict == 1 { return "Cool undertone (pink/red-leaning chroma; CIELab h_ab < 45 deg)" as *u8 } 190 if verdict == 2 { return "Neutral undertone (balanced chroma; CIELab h_ab 45-55 deg)" as *u8 } 191 if verdict == 3 { return "Warm undertone (golden/yellow-leaning chroma; CIELab h_ab > 55 deg)" as *u8 } 192 return "Undertone not detected -- pixels not in typical skin chroma band" as *u8 193} 194 195func _vp_skin_texture_phrase(verdict: nx_int, mode: nx_int) -> *u8 { 196 if mode == NX_VP_MODE_TERSE { 197 if verdict == 1 { return "natural skin texture" as *u8 } 198 if verdict == 2 { return "skin too plastic" as *u8 } 199 if verdict == 3 { return "skin too noisy" as *u8 } 200 return "skin texture unknown" as *u8 201 } 202 if verdict == 1 { return "Natural skin texture (GLCM energy 0.4-0.7; visible micro-variation)" as *u8 } 203 if verdict == 2 { return "Plastic-sheen skin (GLCM energy > 0.9; over-smoothed render typical of SDXL / Midjourney)" as *u8 } 204 if verdict == 3 { return "Over-noisy skin (GLCM contrast > 0.5; texture corrupted or grain too coarse)" as *u8 } 205 return "Skin texture not classified" as *u8 206} 207 208func _vp_vein_signal_phrase(verdict: nx_int, mode: nx_int) -> *u8 { 209 if mode == NX_VP_MODE_TERSE { 210 if verdict == 0 { return "no visible veins (plastic render tell)" as *u8 } 211 if verdict == 1 { return "faint vein signal" as *u8 } 212 if verdict == 2 { return "natural vein visibility" as *u8 } 213 if verdict == 3 { return "veins overpronounced" as *u8 } 214 return "vein signal unknown" as *u8 215 } 216 if verdict == 0 { return "Vein-signal absent: thin-skin regions show no blue subsurface shift -- typical of AI-smoothed renders" as *u8 } 217 if verdict == 1 { return "Weak vein signal: borderline blue-shift in thin-skin regions" as *u8 } 218 if verdict == 2 { return "Normal vein visibility: subsurface signal present in wrist / inside-elbow / temple regions" as *u8 } 219 if verdict == 3 { return "Over-pronounced veins: signal density > 25%; medical-photo or surgical look" as *u8 } 220 return "Vein signal not classified" as *u8 221} 222 223func _vp_micro_expression_phrase(verdict: nx_int, mode: nx_int) -> *u8 { 224 if mode == NX_VP_MODE_TERSE { 225 if verdict == 0 { return "dead face (no aliveness cues)" as *u8 } 226 if verdict == 1 { return "flat face (few aliveness cues)" as *u8 } 227 if verdict == 2 { return "weak aliveness signal" as *u8 } 228 if verdict == 3 { return "alive expression" as *u8 } 229 return "expression liveliness unknown" as *u8 230 } 231 if verdict == 0 { return "Face shows 0-1 of 6 expected aliveness micro-cues (bilateral asymmetry / eyelid shadow / sclera vessels / pupil asymmetry / mouth tension / nostril asymmetry) -- canonical AI 'dead face' tell" as *u8 } 232 if verdict == 1 { return "Face shows 2-3 of 6 aliveness cues -- flat presentation; missing key tells" as *u8 } 233 if verdict == 2 { return "Face shows 4 of 6 aliveness cues -- weak but present" as *u8 } 234 if verdict == 3 { return "Face shows 5-6 of 6 aliveness cues -- natural expression" as *u8 } 235 return "Expression liveliness not classified" as *u8 236} 237 238func _vp_finger_count_phrase(verdict: nx_int, mode: nx_int) -> *u8 { 239 if mode == NX_VP_MODE_TERSE { 240 if verdict == 0 { return "5 fingers detected" as *u8 } 241 if verdict == 1 { return "extra finger detected" as *u8 } 242 if verdict == 2 { return "missing finger" as *u8 } 243 if verdict == 3 { return "fused fingers" as *u8 } 244 if verdict == 4 { return "finger detection noisy" as *u8 } 245 return "finger count unknown" as *u8 246 } 247 if verdict == 0 { return "5 finger-runs detected on scan line -- anatomically correct" as *u8 } 248 if verdict == 1 { return "6+ finger-runs detected -- the canonical generative-AI extra-finger failure" as *u8 } 249 if verdict == 2 { return "Only 4 finger-runs detected -- missing finger or hand partially occluded" as *u8 } 250 if verdict == 3 { return "3 or fewer finger-runs -- fingers fused into paddle shapes" as *u8 } 251 if verdict == 4 { return "Scan line shows > 16 tiny runs -- segmentation noisy, cannot count reliably" as *u8 } 252 return "Finger count not classified" as *u8 253} 254 255func _vp_aperture_phrase(verdict: nx_int, mode: nx_int) -> *u8 { 256 if mode == NX_VP_MODE_TERSE { 257 if verdict == 0 { return "coherent background" as *u8 } 258 if verdict == 1 { return "depth-of-field wrong (background too sharp)" as *u8 } 259 if verdict == 2 { return "no atmospheric perspective" as *u8 } 260 if verdict == 3 { return "fragmented background" as *u8 } 261 if verdict == 4 { return "background is a mess (3+ checks failed)" as *u8 } 262 return "background not classified" as *u8 263 } 264 if verdict == 0 { return "Background through aperture passes all four physics checks (depth-of-field, atmospheric color shift, edge density, edge uniformity)" as *u8 } 265 if verdict == 1 { return "Depth-of-field failure: background as sharp as foreground -- AI did not apply lens blur" as *u8 } 266 if verdict == 2 { return "Atmospheric-perspective failure: no cool/blue shift in distant content -- AI flattened depth cues" as *u8 } 267 if verdict == 3 { return "Fragmented background: edge density or uniformity outside real-scene distribution" as *u8 } 268 if verdict == 4 { return "Background implausible: 3+ physics checks failed -- AI-generated random fragments instead of coherent scene" as *u8 } 269 return "Background not classified" as *u8 270} 271 272// ===== top-level phrase fetch ==================================== 273// 274// Returns the static phrase string for (axis_id, verdict_value, mode). 275// Returns "unsupported axis" if no template registered. 276 277func nx_verdict_phrase_for(axis_id: nx_int, verdict: nx_int, mode: nx_int) -> *u8 { 278 if axis_id == NX_VP_AXIS_T1_SKIN_TONE_ITA { 279 return _vp_skin_tone_phrase(verdict, mode) 280 } 281 if axis_id == NX_VP_AXIS_T4_UNDERTONE { 282 return _vp_undertone_phrase(verdict, mode) 283 } 284 if axis_id == NX_VP_AXIS_T2_GLCM_TEXTURE { 285 return _vp_skin_texture_phrase(verdict, mode) 286 } 287 if axis_id == NX_VP_AXIS_T5_VEIN_SIGNAL { 288 return _vp_vein_signal_phrase(verdict, mode) 289 } 290 if axis_id == NX_VP_AXIS_T5B_MICRO_EXPRESSION { 291 return _vp_micro_expression_phrase(verdict, mode) 292 } 293 if axis_id == NX_VP_AXIS_T6_FINGER_COUNT { 294 return _vp_finger_count_phrase(verdict, mode) 295 } 296 if axis_id == NX_VP_AXIS_T6_SURFACE_PLANE { 297 return _vp_aperture_phrase(verdict, mode) 298 } 299 return "axis phrasing not registered" as *u8 300} 301 302// ===== blend-class phrasing (continuous-class, e.g. "50% asian") == 303// 304// Renders a comma-separated descending list of "PERCENT% 305// class_name" entries, dropping classes whose weight is below 306// NX_VP_BLEND_MIN_PERCENT_Q10 (default 5%). Caller provides the 307// class names; substrate sorts by descending weight + formats. 308 309func nx_verdict_blend_render( 310 blend: *NxVerdictBlend, out_buf: *u8) -> nx_int { 311 312 if blend.n_classes <= 0 { 313 out_buf[0] = 0 as u8 314 return 0 315 } 316 if blend.n_classes > NX_VP_BLEND_MAX_CLASSES { 317 out_buf[0] = 0 as u8 318 return 0 319 } 320 321 // Sort indices by descending weight using selection sort. 322 let n: nx_int = blend.n_classes 323 let order: *nx_int = (sys_mmap((n as nx_size) * 8)) as *nx_int 324 var i: nx_int = 0 325 while i < n { 326 order[i] = i 327 i = i + 1 328 } 329 var p: nx_int = 0 330 while p < n - 1 { 331 var max_idx: nx_int = p 332 var q: nx_int = p + 1 333 while q < n { 334 let a: nx_int = blend.weights_q10[order[max_idx]] 335 let b: nx_int = blend.weights_q10[order[q]] 336 if b > a { max_idx = q } 337 q = q + 1 338 } 339 if max_idx != p { 340 let tmp: nx_int = order[p] 341 order[p] = order[max_idx] 342 order[max_idx] = tmp 343 } 344 p = p + 1 345 } 346 347 var out_pos: nx_int = 0 348 var emitted: nx_int = 0 349 var k: nx_int = 0 350 while k < n { 351 let idx: nx_int = order[k] 352 let w: nx_int = blend.weights_q10[idx] 353 if w >= NX_VP_BLEND_MIN_PERCENT_Q10 { 354 if emitted > 0 { 355 out_buf[out_pos] = 32 as u8 // ' ' 356 out_buf[out_pos + 1] = 47 as u8 // '/' 357 out_buf[out_pos + 2] = 32 as u8 // ' ' 358 out_pos = out_pos + 3 359 } 360 let pct: nx_int = _vp_q10_to_percent(w) 361 out_pos = _vp_format_int(out_buf, out_pos, pct) 362 out_buf[out_pos] = 37 as u8 // '%' 363 out_buf[out_pos + 1] = 32 as u8 // ' ' 364 out_pos = out_pos + 2 365 let name: *u8 = blend.class_names[idx] 366 out_pos = _vp_strcpy(out_buf, name, out_pos) 367 emitted = emitted + 1 368 } 369 k = k + 1 370 } 371 out_buf[out_pos] = 0 as u8 372 return out_pos 373} 374 375// ===== self-test ================================================= 376 377func main() -> nx_int { 378 // ---- skin-tone terse + verbose ---- 379 let p_st_terse: *u8 = nx_verdict_phrase_for( 380 NX_VP_AXIS_T1_SKIN_TONE_ITA, 1, NX_VP_MODE_TERSE) 381 let p_st_verbose: *u8 = nx_verdict_phrase_for( 382 NX_VP_AXIS_T1_SKIN_TONE_ITA, 1, NX_VP_MODE_VERBOSE) 383 if _vp_strlen(p_st_terse) != _vp_strlen("light skin tone" as *u8) { return 1 } 384 if _vp_strlen(p_st_verbose) < 30 { return 2 } 385 386 // ---- skin texture: "plastic" verdict yields the user's 387 // example phrase "skin too plastic" ---- 388 let p_plastic: *u8 = nx_verdict_phrase_for( 389 NX_VP_AXIS_T2_GLCM_TEXTURE, 2, NX_VP_MODE_TERSE) 390 let expected_plastic: *u8 = "skin too plastic" as *u8 391 if _vp_strlen(p_plastic) != _vp_strlen(expected_plastic) { return 10 } 392 var k: nx_int = 0 393 while k < _vp_strlen(expected_plastic) { 394 if p_plastic[k] != expected_plastic[k] { return 11 } 395 k = k + 1 396 } 397 398 // ---- micro-expression: "dead face" verdict ---- 399 let p_dead: *u8 = nx_verdict_phrase_for( 400 NX_VP_AXIS_T5B_MICRO_EXPRESSION, 0, NX_VP_MODE_TERSE) 401 if _vp_strlen(p_dead) < 10 { return 20 } 402 403 // ---- finger count: "extra finger" ---- 404 let p_extra: *u8 = nx_verdict_phrase_for( 405 NX_VP_AXIS_T6_FINGER_COUNT, 1, NX_VP_MODE_TERSE) 406 if _vp_strlen(p_extra) < 5 { return 30 } 407 408 // ---- unregistered axis fallback ---- 409 let p_unk: *u8 = nx_verdict_phrase_for(999, 0, NX_VP_MODE_TERSE) 410 if _vp_strlen(p_unk) < 10 { return 40 } 411 412 // ---- blend renderer ("50% east asian / 30% european / 20% latino") 413 // 414 // Q10 weights: east-asian 512, european 307, latino 205 415 let names: **u8 = (sys_mmap(24)) as **u8 416 names[0] = "east asian" as *u8 417 names[1] = "european" as *u8 418 names[2] = "latino" as *u8 419 let weights: *nx_int = (sys_mmap(24)) as *nx_int 420 weights[0] = 512 421 weights[1] = 307 422 weights[2] = 205 423 let class_ids: *nx_int = (sys_mmap(24)) as *nx_int 424 class_ids[0] = 0 425 class_ids[1] = 1 426 class_ids[2] = 2 427 428 let blend_ptr: *u8 = sys_mmap(NX_VP_BLEND_BYTES) 429 let blend: *NxVerdictBlend = blend_ptr as *NxVerdictBlend 430 blend.n_classes = 3 431 blend.class_ids = class_ids 432 blend.class_names = names 433 blend.weights_q10 = weights 434 435 let out_buf: *u8 = (sys_mmap(NX_VP_MAX_PHRASE_LEN as nx_size)) as *u8 436 let len: nx_int = nx_verdict_blend_render(blend, out_buf) 437 if len < 20 { return 50 } 438 // Should start with "50%" since the largest weight (512 Q10 = 50%) 439 // gets emitted first. 440 if out_buf[0] != (53 as u8) { return 51 } // '5' 441 if out_buf[1] != (48 as u8) { return 52 } // '0' 442 if out_buf[2] != (37 as u8) { return 53 } // '%' 443 if out_buf[3] != (32 as u8) { return 54 } // ' ' 444 if out_buf[4] != (101 as u8) { return 55 } // 'e' (east asian) 445 446 // ---- blend with class below 5% threshold should be dropped ---- 447 let w_low: *nx_int = (sys_mmap(24)) as *nx_int 448 w_low[0] = 512 // 50% 449 w_low[1] = 470 // 46% 450 w_low[2] = 42 // 4% -- below threshold; should be dropped 451 let class_ids2: *nx_int = (sys_mmap(24)) as *nx_int 452 class_ids2[0] = 0 453 class_ids2[1] = 1 454 class_ids2[2] = 2 455 let blend2_ptr: *u8 = sys_mmap(NX_VP_BLEND_BYTES) 456 let blend2: *NxVerdictBlend = blend2_ptr as *NxVerdictBlend 457 blend2.n_classes = 3 458 blend2.class_ids = class_ids2 459 blend2.class_names = names 460 blend2.weights_q10 = w_low 461 let out_buf2: *u8 = (sys_mmap(NX_VP_MAX_PHRASE_LEN as nx_size)) as *u8 462 let len2: nx_int = nx_verdict_blend_render(blend2, out_buf2) 463 // Search for "/" -- exactly one separator (two classes emitted). 464 var slash_count: nx_int = 0 465 var s: nx_int = 0 466 while s < len2 { 467 if out_buf2[s] == (47 as u8) { slash_count = slash_count + 1 } 468 s = s + 1 469 } 470 if slash_count != 1 { return 60 } 471 472 // ---- format_int sanity ---- 473 let small_buf: *u8 = (sys_mmap(16)) as *u8 474 let pos: nx_int = _vp_format_int(small_buf, 0, 42) 475 if pos != 2 { return 70 } 476 if small_buf[0] != (52 as u8) { return 71 } // '4' 477 if small_buf[1] != (50 as u8) { return 72 } // '2' 478 479 return 0 480}