code wiki / (root) / nx_breast_drape_pose.nx

nx_breast_drape_pose.nx source

↩ module page · 132 lines · 6533 B

1// nx_breast_drape_pose.nx -- pose-aware breast gravity-drape validator. 2// 3// Existing nx_breast_physics measures generic gravity_drape (centroid-Y 4// vs top-Y of breast blob) but does NOT factor in the POSE. A 5// leaning-forward pose should show PENDANT breasts (long droop, centroid 6// far below top); a standing facing-camera pose shows TEARDROP (moderate 7// droop). Image #46 has a leaning-forward pose but the breasts read 8// "sitting on the chest" instead of hanging forward. 9// 10// Math: 11// aspect_q10 = breast_region_height * 1024 / breast_region_width 12// centroid_y_pct_q10 = (centroid_y - region_top) * 1024 / region_height 13// Expected ranges per pose-tag: 14// STANDING_FACING: aspect 1024..1331, centroid 614..768 15// LEANING_FORWARD: aspect 1331..1843, centroid 768..870 (pendant) 16// LAYING_BACK: aspect 716..921, centroid 410..614 (flat-spread) 17// ARCHED_PRESENTING: aspect 1024..1331, centroid 410..614 (lifted) 18// 19// Output flat-array: 20// result[0] = aspect_q10 21// result[1] = centroid_y_pct_q10 22// result[2] = aspect_score_q10 23// result[3] = centroid_score_q10 24// result[4] = composite_q10 25// result[5] = verdict (0=GRAVITY_VIOLATION, 1=SLIGHT_MISMATCH, 2=NATURAL) 26 27// nx_safety_envelope: 28// intended_use: AUTO_APPLIED -- primitive-specific tuning queued 29// sil_target: SIL1 30// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail] 31// verdict: NOT_YET_EVALUATED 32 33import "nx_syscalls.nx" 34import "nx_phi.nx" 35const NX_MAGIC_1024: i64 = 1024 36const NX_MAGIC_1331: i64 = 1331 37const NX_MAGIC_1843: i64 = 1843 38const NX_MAGIC_1434: i64 = 1434 39 40const NX_DRAPE_POSE_UNKNOWN: i64 = 0 41const NX_DRAPE_POSE_STANDING_FACING: i64 = 1 42const NX_DRAPE_POSE_LEANING_FORWARD: i64 = 2 43const NX_DRAPE_POSE_LAYING_BACK: i64 = 3 44const NX_DRAPE_POSE_ARCHED_PRESENTING: i64 = 4 45const NX_DRAPE_POSE_RECLINING_TWISTED: i64 = 5 // hybrid — torso twisted back, chest forward 46const NX_DRAPE_POSE_SIDE_PROFILE: i64 = 6 // pure side view, single breast in silhouette 47 48const NX_DRAPE_VERDICT_GRAVITY_VIOLATION: i64 = 0 49const NX_DRAPE_VERDICT_SLIGHT_MISMATCH: i64 = 1 50const NX_DRAPE_VERDICT_NATURAL: i64 = 2 51 52const NX_DRAPE_RES_ASPECT_Q10: i64 = 0 53const NX_DRAPE_RES_CENTROID_Q10: i64 = 1 54const NX_DRAPE_RES_ASPECT_SCORE: i64 = 2 55const NX_DRAPE_RES_CENTROID_SCORE: i64 = 3 56const NX_DRAPE_RES_COMPOSITE: i64 = 4 57const NX_DRAPE_RES_VERDICT: i64 = 5 58const NX_DRAPE_RES_FIELDS: i64 = 6 59 60// Look up expected aspect range per pose: returns (min,max) via out_min/out_max. 61func nx_drape_aspect_range(pose: i64, out_min: *i64, out_max: *i64) -> i64 { 62 if pose == NX_DRAPE_POSE_STANDING_FACING { out_min[0] = NX_MAGIC_1024; out_max[0] = NX_MAGIC_1331; return 0 } 63 if pose == NX_DRAPE_POSE_LEANING_FORWARD { out_min[0] = NX_MAGIC_1331; out_max[0] = NX_MAGIC_1843; return 0 } 64 if pose == NX_DRAPE_POSE_LAYING_BACK { out_min[0] = 716; out_max[0] = 921; return 0 } 65 if pose == NX_DRAPE_POSE_ARCHED_PRESENTING { out_min[0] = NX_MAGIC_1024; out_max[0] = NX_MAGIC_1331; return 0 } 66 if pose == NX_DRAPE_POSE_RECLINING_TWISTED { out_min[0] = 921; out_max[0] = NX_MAGIC_1434; return 0 } 67 if pose == NX_DRAPE_POSE_SIDE_PROFILE { out_min[0] = NX_MAGIC_1024; out_max[0] = NX_MAGIC_1434; return 0 } 68 out_min[0] = 716; out_max[0] = NX_MAGIC_1843 69 return 0 70} 71 72func nx_drape_centroid_range(pose: i64, out_min: *i64, out_max: *i64) -> i64 { 73 if pose == NX_DRAPE_POSE_STANDING_FACING { out_min[0] = 614; out_max[0] = 768; return 0 } 74 if pose == NX_DRAPE_POSE_LEANING_FORWARD { out_min[0] = 768; out_max[0] = 870; return 0 } 75 if pose == NX_DRAPE_POSE_LAYING_BACK { out_min[0] = 410; out_max[0] = 614; return 0 } 76 if pose == NX_DRAPE_POSE_ARCHED_PRESENTING { out_min[0] = 410; out_max[0] = 614; return 0 } 77 if pose == NX_DRAPE_POSE_RECLINING_TWISTED { out_min[0] = 512; out_max[0] = 717; return 0 } 78 if pose == NX_DRAPE_POSE_SIDE_PROFILE { out_min[0] = 614; out_max[0] = 819; return 0 } 79 out_min[0] = 410; out_max[0] = 870 80 return 0 81} 82 83// Score a measured value against [vmin, vmax]: 1024 inside, decays linearly outside. 84func nx_drape_in_range_score(measured: i64, vmin: i64, vmax: i64, 85 spread_outside: i64) -> i64 { 86 if measured >= vmin { if measured <= vmax { return NX_MAGIC_1024 } } 87 var dist: i64 = 0 88 if measured < vmin { dist = vmin - measured } 89 if measured > vmax { dist = measured - vmax } 90 if dist >= spread_outside { return 0 } 91 return NX_MAGIC_1024 - ((dist * NX_MAGIC_1024) / spread_outside) 92} 93 94// Score breast drape for given pose. 95// breast_height_px: bounding box height of one breast blob 96// breast_width_px: bounding box width 97// centroid_offset_from_top_px: centroid_y - top_y 98// pose: NX_DRAPE_POSE_* 99// result: pre-allocated NX_DRAPE_RES_FIELDS i64 array 100func nx_breast_drape_score(breast_height_px: i64, breast_width_px: i64, 101 centroid_offset_from_top_px: i64, 102 pose: i64, result: *i64) -> i64 { 103 var i: i64 = 0 104 while i < NX_DRAPE_RES_FIELDS { result[i] = 0; i = i + 1 } 105 if breast_width_px <= 0 { return 1 } 106 if breast_height_px <= 0 { return 2 } 107 108 let aspect_q10: i64 = (breast_height_px * NX_MAGIC_1024) / breast_width_px 109 let centroid_q10: i64 = (centroid_offset_from_top_px * NX_MAGIC_1024) / breast_height_px 110 result[NX_DRAPE_RES_ASPECT_Q10] = aspect_q10 111 result[NX_DRAPE_RES_CENTROID_Q10] = centroid_q10 112 113 let amin: *i64 = (sys_mmap(8 + 8)) as *i64 114 let amax: *i64 = (sys_mmap(8 + 8)) as *i64 115 nx_drape_aspect_range(pose, amin, amax) 116 let cmin: *i64 = (sys_mmap(8 + 8)) as *i64 117 let cmax: *i64 = (sys_mmap(8 + 8)) as *i64 118 nx_drape_centroid_range(pose, cmin, cmax) 119 120 let aspect_score: i64 = nx_drape_in_range_score(aspect_q10, amin[0], amax[0], 410) 121 let centroid_score: i64 = nx_drape_in_range_score(centroid_q10, cmin[0], cmax[0], 256) 122 result[NX_DRAPE_RES_ASPECT_SCORE] = aspect_score 123 result[NX_DRAPE_RES_CENTROID_SCORE] = centroid_score 124 let composite: i64 = (aspect_score + centroid_score) / 2 125 result[NX_DRAPE_RES_COMPOSITE] = composite 126 127 var verdict: i64 = NX_DRAPE_VERDICT_GRAVITY_VIOLATION 128 if composite >= 768 { verdict = NX_DRAPE_VERDICT_NATURAL } 129 else { if composite >= 410 { verdict = NX_DRAPE_VERDICT_SLIGHT_MISMATCH } } 130 result[NX_DRAPE_RES_VERDICT] = verdict 131 return 0 132}