nx_emotion_authenticity.nx source
↩ module page · 144 lines · 5585 B
1// nx_emotion_authenticity.nx -- Best Acting axis: micro-expression authenticity.
2//
3// Beyond nx_emotion_congruence (declared emotion vs measured emotion),
4// this checks whether the emotion reads AUTHENTIC vs performed.
5// Real emotions involve coordinated multiple muscle groups:
6// - Duchenne smile: zygomaticus + orbicularis oculi (eye-corner crinkle)
7// - Authentic surprise: brow + jaw + eye widening together
8// - Sad: brow inner-corner up, eyelids droop, lip corners down
9// AI-rendered emotions often only animate ONE muscle group (mouth-only
10// smile, eyes flat) producing dead-face / uncanny effect.
11//
12// 5-axis check:
13// 1. eye_crinkle_q10 Duchenne marker for smile
14// 2. brow_movement_q10 brow follows the emotion
15// 3. lip_curve_q10 mouth shape matches emotion
16// 4. jaw_state_q10 jaw open/closed appropriate
17// 5. gaze_engagement_q10 eyes engaged vs glazed
18//
19// Composite scored against declared emotion → authenticity verdict.
20
21// nx_safety_envelope:
22// intended_use: AUTO_APPLIED -- primitive-specific tuning queued
23// sil_target: SIL1
24// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail]
25// verdict: NOT_YET_EVALUATED
26
27import "nx_syscalls.nx"
28const NX_MAGIC_1024: i64 = 1024
29
30const NX_EAU_RES_EYE_CRINKLE: i64 = 0
31const NX_EAU_RES_BROW: i64 = 1
32const NX_EAU_RES_LIP: i64 = 2
33const NX_EAU_RES_JAW: i64 = 3
34const NX_EAU_RES_GAZE: i64 = 4
35const NX_EAU_RES_COMPOSITE: i64 = 5
36const NX_EAU_RES_VERDICT: i64 = 6
37const NX_EAU_RES_FAIL_AXIS: i64 = 7
38const NX_EAU_RES_FIELDS: i64 = 8
39
40const NX_EAU_V_DEAD_FACE: i64 = 0
41const NX_EAU_V_PERFORMED: i64 = 1
42const NX_EAU_V_BELIEVABLE: i64 = 2
43const NX_EAU_V_AUTHENTIC: i64 = 3
44
45const NX_EAU_AXIS_EYE_CRINKLE: i64 = 0
46const NX_EAU_AXIS_BROW: i64 = 1
47const NX_EAU_AXIS_LIP: i64 = 2
48const NX_EAU_AXIS_JAW: i64 = 3
49const NX_EAU_AXIS_GAZE: i64 = 4
50
51// Emotion ids (subset matching emotion_congruence taxonomy).
52const NX_EAU_EMO_NEUTRAL: i64 = 0
53const NX_EAU_EMO_HAPPY: i64 = 1
54const NX_EAU_EMO_SAD: i64 = 2
55const NX_EAU_EMO_SURPRISED: i64 = 3
56const NX_EAU_EMO_AROUSED: i64 = 4
57const NX_EAU_EMO_PENSIVE: i64 = 5
58
59// For each declared emotion, return target Q10 values for each axis.
60// out[0]=eye, out[1]=brow, out[2]=lip, out[3]=jaw, out[4]=gaze.
61func nx_eau_targets(emotion: i64, out: *i64) -> i64 {
62 if emotion == NX_EAU_EMO_HAPPY {
63 out[0] = 819; out[1] = 614; out[2] = 870; out[3] = 614; out[4] = 870
64 return 0
65 }
66 if emotion == NX_EAU_EMO_SAD {
67 out[0] = 256; out[1] = 768; out[2] = 870; out[3] = 410; out[4] = 256
68 return 0
69 }
70 if emotion == NX_EAU_EMO_SURPRISED {
71 out[0] = 819; out[1] = 920; out[2] = 614; out[3] = 870; out[4] = 920
72 return 0
73 }
74 if emotion == NX_EAU_EMO_AROUSED {
75 out[0] = 614; out[1] = 410; out[2] = 819; out[3] = 614; out[4] = 870
76 return 0
77 }
78 if emotion == NX_EAU_EMO_PENSIVE {
79 out[0] = 410; out[1] = 410; out[2] = 410; out[3] = 256; out[4] = 410
80 return 0
81 }
82 // Neutral default
83 out[0] = 410; out[1] = 410; out[2] = 410; out[3] = 410; out[4] = 614
84 return 0
85}
86
87// Score authenticity: how close measured axis values are to target for declared emotion.
88func nx_emotion_authenticity(declared_emotion: i64,
89 eye_crinkle_q10: i64, brow_q10: i64,
90 lip_q10: i64, jaw_q10: i64, gaze_q10: i64,
91 result: *i64) -> i64 {
92 var i: i64 = 0
93 while i < NX_EAU_RES_FIELDS { result[i] = 0; i = i + 1 }
94
95 let targets: *i64 = (sys_mmap(5 * 8 + 16)) as *i64
96 nx_eau_targets(declared_emotion, targets)
97
98 // Per-axis score = 1024 - |measured - target| / 1.
99 var d0: i64 = eye_crinkle_q10 - targets[0]
100 var d1: i64 = brow_q10 - targets[1]
101 var d2: i64 = lip_q10 - targets[2]
102 var d3: i64 = jaw_q10 - targets[3]
103 var d4: i64 = gaze_q10 - targets[4]
104 if d0 < 0 { d0 = -d0 }
105 if d1 < 0 { d1 = -d1 }
106 if d2 < 0 { d2 = -d2 }
107 if d3 < 0 { d3 = -d3 }
108 if d4 < 0 { d4 = -d4 }
109 var s0: i64 = NX_MAGIC_1024 - d0
110 var s1: i64 = NX_MAGIC_1024 - d1
111 var s2: i64 = NX_MAGIC_1024 - d2
112 var s3: i64 = NX_MAGIC_1024 - d3
113 var s4: i64 = NX_MAGIC_1024 - d4
114 if s0 < 0 { s0 = 0 }
115 if s1 < 0 { s1 = 0 }
116 if s2 < 0 { s2 = 0 }
117 if s3 < 0 { s3 = 0 }
118 if s4 < 0 { s4 = 0 }
119
120 result[NX_EAU_RES_EYE_CRINKLE] = s0
121 result[NX_EAU_RES_BROW] = s1
122 result[NX_EAU_RES_LIP] = s2
123 result[NX_EAU_RES_JAW] = s3
124 result[NX_EAU_RES_GAZE] = s4
125
126 // Composite: weighted average. Eye crinkle + gaze are heaviest (Duchenne markers).
127 let composite: i64 = (s0 * 25 + s1 * 15 + s2 * 20 + s3 * 15 + s4 * 25) / 100
128 result[NX_EAU_RES_COMPOSITE] = composite
129
130 var verdict: i64 = NX_EAU_V_DEAD_FACE
131 if composite >= 410 { verdict = NX_EAU_V_PERFORMED }
132 if composite >= 614 { verdict = NX_EAU_V_BELIEVABLE }
133 if composite >= 819 { verdict = NX_EAU_V_AUTHENTIC }
134 result[NX_EAU_RES_VERDICT] = verdict
135
136 var min_s: i64 = s0
137 var min_a: i64 = NX_EAU_AXIS_EYE_CRINKLE
138 if s1 < min_s { min_s = s1; min_a = NX_EAU_AXIS_BROW }
139 if s2 < min_s { min_s = s2; min_a = NX_EAU_AXIS_LIP }
140 if s3 < min_s { min_s = s3; min_a = NX_EAU_AXIS_JAW }
141 if s4 < min_s { min_s = s4; min_a = NX_EAU_AXIS_GAZE }
142 result[NX_EAU_RES_FAIL_AXIS] = min_a
143 return 0
144}