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}