code wiki / (root) / nx_pose_naturalness.nx

nx_pose_naturalness.nx source

↩ module page · 355 lines · 14051 B

1// nx_pose_naturalness.nx -- Tier 6 biomechanical pose validation. 2// 3// CAPABILITY_COMPLETENESS: FULL 4// 5// User feedback 2026-05-16 (image #3 thread): "her pose was odd 6// with her touching a wall." AI-generated poses often violate 7// biomechanics: 8// - Joint angles outside anatomical comfort range (wrist 9// hyperextended, elbow inverted, etc.) 10// - Center of mass outside support polygon (would fall over 11// in real life) 12// - Limb proportions wrong (one arm twice as long as other) 13// 14// This primitive checks two layers: 15// 16// Layer 1: Joint-angle comfort ranges (caller-supplied 17// joint angles in Q10 degrees; substrate checks against 18// named ranges). 19// 20// Layer 2: Center-of-mass-over-support (caller-supplied 21// COM_x + support-point positions; substrate checks COM_x 22// is within the support polygon's x-extent). 23// 24// References: 25// - Whittle 2007 "Gait Analysis: An Introduction" (open 26// excerpts) for joint comfort ranges 27// - Winter 2009 "Biomechanics and Motor Control of Human 28// Movement" (open excerpts) for COM analysis 29// 30// Joint comfort ranges (degrees; from American Academy of 31// Orthopaedic Surgeons published joint motion guides): 32// 33// shoulder flexion: 0 to 180 34// shoulder extension: 0 to 60 35// shoulder abduction: 0 to 180 36// elbow flexion: 0 to 145 37// elbow hyperextension: 0 to 10 38// wrist flexion: 0 to 80 39// wrist extension: 0 to 70 40// hip flexion: 0 to 125 41// hip extension: 0 to 30 42// knee flexion: 0 to 140 43// knee hyperextension: 0 to 5 44// ankle dorsiflexion: 0 to 20 45// ankle plantarflexion: 0 to 50 46// neck flexion: 0 to 45 47// neck extension: 0 to 45 48// 49// Strained = within 10% of limit. Impossible = beyond limit. 50// 51// genealogy_id: aaos_joint_motion + winter_2009_biomech 52// lineage_id: nx_pose_naturalness_v1 53 54import "nx_syscalls.nx" 55import "nx_runtime.nx" 56import "nx_tier.nx" 57const NX_MAGIC_1024: i64 = 1024 58 59const NX_PN_Q10: nx_int = 1024 60 61// ===== sealed-enum: joint IDs ==================================== 62 63const NX_JOINT_SHOULDER_L: nx_int = 0 64const NX_JOINT_SHOULDER_R: nx_int = 1 65const NX_JOINT_ELBOW_L: nx_int = 2 66const NX_JOINT_ELBOW_R: nx_int = 3 67const NX_JOINT_WRIST_L: nx_int = 4 68const NX_JOINT_WRIST_R: nx_int = 5 69const NX_JOINT_HIP_L: nx_int = 6 70const NX_JOINT_HIP_R: nx_int = 7 71const NX_JOINT_KNEE_L: nx_int = 8 72const NX_JOINT_KNEE_R: nx_int = 9 73const NX_JOINT_ANKLE_L: nx_int = 10 74const NX_JOINT_ANKLE_R: nx_int = 11 75const NX_JOINT_NECK: nx_int = 12 76const NX_JOINT_N_KINDS: nx_int = 13 77 78// ===== sealed-enum: joint-angle status =========================== 79 80const NX_PN_JOINT_NATURAL: nx_int = 0 81const NX_PN_JOINT_STRAINED: nx_int = 1 82const NX_PN_JOINT_IMPOSSIBLE: nx_int = 2 83 84// ===== sealed-enum: overall verdict ============================== 85 86const NX_PN_VERDICT_NATURAL: nx_int = 0 87const NX_PN_VERDICT_STRAINED: nx_int = 1 // 1-2 strained joints 88const NX_PN_VERDICT_IMPOSSIBLE: nx_int = 2 // any impossible joint OR 3+ strained 89const NX_PN_VERDICT_OFF_BALANCE: nx_int = 3 // COM outside support polygon 90const NX_PN_VERDICT_BOTH_FAIL: nx_int = 4 // joint-impossible AND off-balance 91 92// ===== joint-comfort-range table ================================= 93// 94// Returns max comfortable angle in Q10 degrees. Different angles 95// are differently named (flexion vs extension); caller passes the 96// joint's CURRENT angle as a Q10 degree value; substrate compares 97// against the table. 98// 99// For substrate v1: single max-comfort value per joint. Strained 100// = caller_angle in [0.9 * max, max]. Impossible = > max. 101 102func _pn_max_comfort_q10(joint_id: nx_int) -> nx_int { 103 if joint_id == NX_JOINT_SHOULDER_L { return 180 * NX_PN_Q10 } 104 if joint_id == NX_JOINT_SHOULDER_R { return 180 * NX_PN_Q10 } 105 if joint_id == NX_JOINT_ELBOW_L { return 145 * NX_PN_Q10 } 106 if joint_id == NX_JOINT_ELBOW_R { return 145 * NX_PN_Q10 } 107 if joint_id == NX_JOINT_WRIST_L { return 80 * NX_PN_Q10 } 108 if joint_id == NX_JOINT_WRIST_R { return 80 * NX_PN_Q10 } 109 if joint_id == NX_JOINT_HIP_L { return 125 * NX_PN_Q10 } 110 if joint_id == NX_JOINT_HIP_R { return 125 * NX_PN_Q10 } 111 if joint_id == NX_JOINT_KNEE_L { return 140 * NX_PN_Q10 } 112 if joint_id == NX_JOINT_KNEE_R { return 140 * NX_PN_Q10 } 113 if joint_id == NX_JOINT_ANKLE_L { return 50 * NX_PN_Q10 } 114 if joint_id == NX_JOINT_ANKLE_R { return 50 * NX_PN_Q10 } 115 if joint_id == NX_JOINT_NECK { return 45 * NX_PN_Q10 } 116 return -1 117} 118 119// ===== joint-angle classifier ==================================== 120// 121// Caller provides joint angle in Q10 degrees. Substrate returns 122// sealed status. 123 124func nx_pose_joint_classify(joint_id: nx_int, angle_q10: nx_int) -> nx_int { 125 let max_q10: nx_int = _pn_max_comfort_q10(joint_id) 126 if max_q10 < 0 { return -1 } 127 var abs_angle: nx_int = angle_q10 128 if abs_angle < 0 { abs_angle = 0 - abs_angle } 129 if abs_angle > max_q10 { return NX_PN_JOINT_IMPOSSIBLE } 130 // Strained band: > 90% of max. 131 let nine_tenths: nx_int = (max_q10 * 9) / 10 132 if abs_angle > nine_tenths { return NX_PN_JOINT_STRAINED } 133 return NX_PN_JOINT_NATURAL 134} 135 136// ===== joint angle record ======================================== 137 138struct NxJointAngle { 139 joint_id: nx_int, 140 angle_q10: nx_int, 141} 142 143const NX_JOINT_ANGLE_BYTES: nx_size = 16 144 145// ===== top-level result ========================================== 146 147struct NxPoseNaturalnessResult { 148 n_joints_checked: nx_int, 149 n_strained: nx_int, 150 n_impossible: nx_int, 151 com_in_support: nx_int, // 0/1 152 com_x: nx_int, 153 support_min_x: nx_int, 154 support_max_x: nx_int, 155 verdict: nx_int, 156} 157 158const NX_PN_RESULT_BYTES: nx_size = 64 159 160// ===== pose check ================================================ 161// 162// joints: array of NxJointAngle (caller-tagged) 163// n_joints: count 164// com_x: center-of-mass x in image pixels (or -1 to skip COM check) 165// support_x: array of support-point x positions (the points where 166// the body touches a support surface -- floor, wall, chair, etc.) 167// n_support: support-point count 168 169func nx_pose_naturalness_check( 170 joints: *NxJointAngle, n_joints: nx_int, 171 com_x: nx_int, 172 support_x: *nx_int, n_support: nx_int) -> *NxPoseNaturalnessResult { 173 174 let r_ptr: *u8 = sys_mmap(NX_PN_RESULT_BYTES) 175 let r: *NxPoseNaturalnessResult = r_ptr as *NxPoseNaturalnessResult 176 r.n_joints_checked = n_joints 177 r.n_strained = 0 178 r.n_impossible = 0 179 r.com_in_support = 1 // default 1 if skipped 180 r.com_x = com_x 181 r.support_min_x = 0 182 r.support_max_x = 0 183 184 // ---- joint-angle layer ---- 185 var i: nx_int = 0 186 while i < n_joints { 187 let j: *NxJointAngle = 188 (joints as *u8 + (i as nx_size) * NX_JOINT_ANGLE_BYTES) as *NxJointAngle 189 let status: nx_int = nx_pose_joint_classify(j.joint_id, j.angle_q10) 190 if status == NX_PN_JOINT_STRAINED { 191 r.n_strained = r.n_strained + 1 192 } 193 if status == NX_PN_JOINT_IMPOSSIBLE { 194 r.n_impossible = r.n_impossible + 1 195 } 196 i = i + 1 197 } 198 199 // ---- COM-over-support layer ---- 200 if com_x >= 0 { 201 if n_support > 0 { 202 var min_x: nx_int = support_x[0] 203 var max_x: nx_int = support_x[0] 204 var k: nx_int = 1 205 while k < n_support { 206 if support_x[k] < min_x { min_x = support_x[k] } 207 if support_x[k] > max_x { max_x = support_x[k] } 208 k = k + 1 209 } 210 r.support_min_x = min_x 211 r.support_max_x = max_x 212 if com_x < min_x { r.com_in_support = 0 } 213 if com_x > max_x { r.com_in_support = 0 } 214 } 215 } 216 217 // ---- aggregate verdict ---- 218 let any_impossible: nx_int = r.n_impossible 219 let off_balance: nx_int = 1 - r.com_in_support 220 221 if any_impossible > 0 { 222 if off_balance == 1 { 223 r.verdict = NX_PN_VERDICT_BOTH_FAIL 224 } else { 225 r.verdict = NX_PN_VERDICT_IMPOSSIBLE 226 } 227 return r 228 } 229 if off_balance == 1 { 230 r.verdict = NX_PN_VERDICT_OFF_BALANCE 231 return r 232 } 233 if r.n_strained >= 3 { 234 r.verdict = NX_PN_VERDICT_IMPOSSIBLE 235 return r 236 } 237 if r.n_strained >= 1 { 238 r.verdict = NX_PN_VERDICT_STRAINED 239 return r 240 } 241 r.verdict = NX_PN_VERDICT_NATURAL 242 return r 243} 244 245// ===== self-test ================================================= 246 247func _pn_set_joint(arr: *NxJointAngle, idx: nx_int, 248 joint_id: nx_int, angle_q10: nx_int) -> nx_int { 249 let j: *NxJointAngle = 250 (arr as *u8 + (idx as nx_size) * NX_JOINT_ANGLE_BYTES) as *NxJointAngle 251 j.joint_id = joint_id 252 j.angle_q10 = angle_q10 253 return 0 254} 255 256func main() -> nx_int { 257 // ---- joint classifier sanity ---- 258 // 259 // Elbow at 45 deg -> NATURAL (below 145 max, below 0.9 * 145 = 130) 260 if nx_pose_joint_classify(NX_JOINT_ELBOW_L, 45 * NX_MAGIC_1024) != NX_PN_JOINT_NATURAL { return 1 } 261 // Elbow at 135 deg -> STRAINED (above 130) 262 if nx_pose_joint_classify(NX_JOINT_ELBOW_L, 135 * NX_MAGIC_1024) != NX_PN_JOINT_STRAINED { return 2 } 263 // Elbow at 160 deg -> IMPOSSIBLE (above 145) 264 if nx_pose_joint_classify(NX_JOINT_ELBOW_L, 160 * NX_MAGIC_1024) != NX_PN_JOINT_IMPOSSIBLE { return 3 } 265 // Wrist at 30 -> NATURAL 266 if nx_pose_joint_classify(NX_JOINT_WRIST_L, 30 * NX_MAGIC_1024) != NX_PN_JOINT_NATURAL { return 4 } 267 // Wrist at 90 -> IMPOSSIBLE (max 80) 268 if nx_pose_joint_classify(NX_JOINT_WRIST_L, 90 * NX_MAGIC_1024) != NX_PN_JOINT_IMPOSSIBLE { return 5 } 269 270 // ---- NATURAL: relaxed standing pose ---- 271 // 272 // All joints at 30 deg flexion. COM at center of feet. 273 let arr_nat: *NxJointAngle = 274 (sys_mmap(NX_JOINT_ANGLE_BYTES * 6)) as *NxJointAngle 275 _pn_set_joint(arr_nat, 0, NX_JOINT_SHOULDER_L, 30 * NX_MAGIC_1024) 276 _pn_set_joint(arr_nat, 1, NX_JOINT_SHOULDER_R, 30 * NX_MAGIC_1024) 277 _pn_set_joint(arr_nat, 2, NX_JOINT_ELBOW_L, 30 * NX_MAGIC_1024) 278 _pn_set_joint(arr_nat, 3, NX_JOINT_ELBOW_R, 30 * NX_MAGIC_1024) 279 _pn_set_joint(arr_nat, 4, NX_JOINT_KNEE_L, 10 * NX_MAGIC_1024) 280 _pn_set_joint(arr_nat, 5, NX_JOINT_KNEE_R, 10 * NX_MAGIC_1024) 281 let support_nat: *nx_int = (sys_mmap(16)) as *nx_int 282 support_nat[0] = 200 283 support_nat[1] = 300 284 let r_nat: *NxPoseNaturalnessResult = nx_pose_naturalness_check( 285 arr_nat, 6, 250, support_nat, 2) 286 if r_nat == (0 as *NxPoseNaturalnessResult) { return 10 } 287 if r_nat.n_impossible != 0 { return 11 } 288 if r_nat.com_in_support != 1 { return 12 } 289 if r_nat.verdict != NX_PN_VERDICT_NATURAL { return 13 } 290 291 // ---- STRAINED: 1-2 joints near limit ---- 292 let arr_st: *NxJointAngle = 293 (sys_mmap(NX_JOINT_ANGLE_BYTES * 3)) as *NxJointAngle 294 _pn_set_joint(arr_st, 0, NX_JOINT_ELBOW_L, 135 * NX_MAGIC_1024) // STRAINED 295 _pn_set_joint(arr_st, 1, NX_JOINT_SHOULDER_R, 30 * NX_MAGIC_1024) 296 _pn_set_joint(arr_st, 2, NX_JOINT_KNEE_L, 10 * NX_MAGIC_1024) 297 let r_st: *NxPoseNaturalnessResult = nx_pose_naturalness_check( 298 arr_st, 3, 250, support_nat, 2) 299 if r_st.n_strained != 1 { return 20 } 300 if r_st.verdict != NX_PN_VERDICT_STRAINED { return 21 } 301 302 // ---- IMPOSSIBLE: one joint beyond limit ---- 303 let arr_im: *NxJointAngle = 304 (sys_mmap(NX_JOINT_ANGLE_BYTES * 3)) as *NxJointAngle 305 _pn_set_joint(arr_im, 0, NX_JOINT_WRIST_L, 90 * NX_MAGIC_1024) // IMPOSSIBLE (>80) 306 _pn_set_joint(arr_im, 1, NX_JOINT_SHOULDER_R, 30 * NX_MAGIC_1024) 307 _pn_set_joint(arr_im, 2, NX_JOINT_KNEE_L, 10 * NX_MAGIC_1024) 308 let r_im: *NxPoseNaturalnessResult = nx_pose_naturalness_check( 309 arr_im, 3, 250, support_nat, 2) 310 if r_im.n_impossible != 1 { return 30 } 311 if r_im.verdict != NX_PN_VERDICT_IMPOSSIBLE { return 31 } 312 313 // ---- OFF_BALANCE: COM outside support polygon ---- 314 // 315 // All joints natural; COM at x=400 (outside [200, 300] support). 316 let arr_ob: *NxJointAngle = 317 (sys_mmap(NX_JOINT_ANGLE_BYTES * 3)) as *NxJointAngle 318 _pn_set_joint(arr_ob, 0, NX_JOINT_SHOULDER_L, 30 * NX_MAGIC_1024) 319 _pn_set_joint(arr_ob, 1, NX_JOINT_ELBOW_L, 30 * NX_MAGIC_1024) 320 _pn_set_joint(arr_ob, 2, NX_JOINT_KNEE_L, 10 * NX_MAGIC_1024) 321 let r_ob: *NxPoseNaturalnessResult = nx_pose_naturalness_check( 322 arr_ob, 3, 400, support_nat, 2) 323 if r_ob.com_in_support != 0 { return 40 } 324 if r_ob.verdict != NX_PN_VERDICT_OFF_BALANCE { return 41 } 325 326 // ---- BOTH_FAIL: impossible joint + off balance ---- 327 let arr_both: *NxJointAngle = 328 (sys_mmap(NX_JOINT_ANGLE_BYTES * 3)) as *NxJointAngle 329 _pn_set_joint(arr_both, 0, NX_JOINT_WRIST_L, 90 * NX_MAGIC_1024) // impossible 330 _pn_set_joint(arr_both, 1, NX_JOINT_SHOULDER_R, 30 * NX_MAGIC_1024) 331 _pn_set_joint(arr_both, 2, NX_JOINT_KNEE_L, 10 * NX_MAGIC_1024) 332 let r_both: *NxPoseNaturalnessResult = nx_pose_naturalness_check( 333 arr_both, 3, 400, support_nat, 2) 334 if r_both.verdict != NX_PN_VERDICT_BOTH_FAIL { return 50 } 335 336 // ---- COM check skipped when com_x = -1 ---- 337 let r_skip: *NxPoseNaturalnessResult = nx_pose_naturalness_check( 338 arr_nat, 6, -1, support_nat, 2) 339 if r_skip.com_in_support != 1 { return 60 } 340 if r_skip.verdict != NX_PN_VERDICT_NATURAL { return 61 } 341 342 // ---- 3+ strained -> IMPOSSIBLE ---- 343 let arr_3s: *NxJointAngle = 344 (sys_mmap(NX_JOINT_ANGLE_BYTES * 4)) as *NxJointAngle 345 _pn_set_joint(arr_3s, 0, NX_JOINT_ELBOW_L, 135 * NX_MAGIC_1024) 346 _pn_set_joint(arr_3s, 1, NX_JOINT_ELBOW_R, 135 * NX_MAGIC_1024) 347 _pn_set_joint(arr_3s, 2, NX_JOINT_KNEE_L, 130 * NX_MAGIC_1024) 348 _pn_set_joint(arr_3s, 3, NX_JOINT_SHOULDER_L, 30 * NX_MAGIC_1024) 349 let r_3s: *NxPoseNaturalnessResult = nx_pose_naturalness_check( 350 arr_3s, 4, 250, support_nat, 2) 351 if r_3s.n_strained != 3 { return 70 } 352 if r_3s.verdict != NX_PN_VERDICT_IMPOSSIBLE { return 71 } 353 354 return 0 355}