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}