nx_mannequin_index.nx source
↩ module page · 144 lines · 5309 B
1// nx_mannequin_index.nx -- composite "is this a living person or a mannequin" verdict.
2//
3// Aggregates the per-axis detector scores into a single Q10 composite +
4// sealed verdict. Designed to grow as new mannequin-failure detectors
5// land (skin micro-texture, pressure deformation, joint articulation,
6// lash transition); today scores subsurface scatter + specular falloff
7// + the existing nx_skin_texture PLASTIC_SHEEN signal.
8//
9// Composite formula (each axis 0..1024, weighted average):
10// composite = (sss_score * w_sss + spec_score * w_spec + texture_score * w_tex) / sum_w
11// where higher composite = more LIVING_PERSON, lower = more MANNEQUIN.
12//
13// Sealed verdict:
14// OBVIOUS_PLASTIC composite < 256
15// MANNEQUIN_FEEL 256..512
16// SLIGHTLY_OFF 512..768
17// LIVING_PERSON >= 768
18//
19// Output flat-array:
20// result[0] = composite_q10
21// result[1] = verdict
22// result[2] = sss_score_q10
23// result[3] = spec_score_q10
24// result[4] = texture_score_q10
25// result[5] = n_axes_contributing
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_subsurface_warmth.nx"
35import "nx_specular_falloff.nx"
36
37const NX_MANN_RES_COMPOSITE: i64 = 0
38const NX_MANN_RES_VERDICT: i64 = 1
39const NX_MANN_RES_SSS: i64 = 2
40const NX_MANN_RES_SPEC: i64 = 3
41const NX_MANN_RES_TEXTURE: i64 = 4
42const NX_MANN_RES_AXES: i64 = 5
43const NX_MANN_RES_FIELDS: i64 = 6
44
45const NX_MANN_OBVIOUS_PLASTIC: i64 = 0
46const NX_MANN_MANNEQUIN_FEEL: i64 = 1
47const NX_MANN_SLIGHTLY_OFF: i64 = 2
48const NX_MANN_LIVING_PERSON: i64 = 3
49
50// Axis weights (relative, will be normalized).
51const NX_MANN_W_SSS: i64 = 3 // subsurface scatter is high signal
52const NX_MANN_W_SPEC: i64 = 2 // specular falloff
53const NX_MANN_W_TEXTURE: i64 = 2 // skin micro-texture (from nx_skin_texture)
54
55// Aggregate per-axis scores into mannequin verdict.
56// Caller passes per-axis Q10 scores (use -1 to mark "not measured").
57func nx_mannequin_score(sss_score_q10: i64,
58 spec_score_q10: i64,
59 texture_score_q10: i64,
60 result: *i64) -> i64 {
61 var i: i64 = 0
62 while i < NX_MANN_RES_FIELDS { result[i] = 0; i = i + 1 }
63 result[NX_MANN_RES_SSS] = sss_score_q10
64 result[NX_MANN_RES_SPEC] = spec_score_q10
65 result[NX_MANN_RES_TEXTURE] = texture_score_q10
66
67 var sum_score: i64 = 0
68 var sum_w: i64 = 0
69 var axes: i64 = 0
70 if sss_score_q10 >= 0 {
71 sum_score = sum_score + sss_score_q10 * NX_MANN_W_SSS
72 sum_w = sum_w + NX_MANN_W_SSS
73 axes = axes + 1
74 }
75 if spec_score_q10 >= 0 {
76 sum_score = sum_score + spec_score_q10 * NX_MANN_W_SPEC
77 sum_w = sum_w + NX_MANN_W_SPEC
78 axes = axes + 1
79 }
80 if texture_score_q10 >= 0 {
81 sum_score = sum_score + texture_score_q10 * NX_MANN_W_TEXTURE
82 sum_w = sum_w + NX_MANN_W_TEXTURE
83 axes = axes + 1
84 }
85 result[NX_MANN_RES_AXES] = axes
86
87 if sum_w == 0 {
88 result[NX_MANN_RES_COMPOSITE] = 0
89 result[NX_MANN_RES_VERDICT] = NX_MANN_OBVIOUS_PLASTIC
90 return 0
91 }
92 let composite: i64 = sum_score / sum_w
93 result[NX_MANN_RES_COMPOSITE] = composite
94
95 var verdict: i64 = NX_MANN_OBVIOUS_PLASTIC
96 if composite >= 768 { verdict = NX_MANN_LIVING_PERSON }
97 else {
98 if composite >= 512 { verdict = NX_MANN_SLIGHTLY_OFF }
99 else {
100 if composite >= 256 { verdict = NX_MANN_MANNEQUIN_FEEL }
101 }
102 }
103 result[NX_MANN_RES_VERDICT] = verdict
104 return 0
105}
106
107// Corrective phrases that push the generator toward a living-person render.
108// Returns a count; phrases written into the out-array as ints (caller looks
109// them up by id since we don't have first-class string arrays).
110const NX_MANN_PHRASE_NATURAL_SKIN_TEXTURE: i64 = 1
111const NX_MANN_PHRASE_VISIBLE_PORES: i64 = 2
112const NX_MANN_PHRASE_SSS_WARMTH: i64 = 3
113const NX_MANN_PHRASE_SOFT_SPECULAR: i64 = 4
114const NX_MANN_PHRASE_PRESSURE_DEFORMATION: i64 = 5
115const NX_MANN_PHRASE_TENDON_SHADOWS: i64 = 6
116
117func nx_mannequin_corrective_phrases(verdict: i64, sss_score: i64,
118 spec_score: i64, texture_score: i64,
119 phrase_ids: *i64) -> i64 {
120 var n: i64 = 0
121 if verdict == NX_MANN_LIVING_PERSON { return 0 }
122
123 if texture_score < 512 {
124 phrase_ids[n] = NX_MANN_PHRASE_NATURAL_SKIN_TEXTURE
125 n = n + 1
126 phrase_ids[n] = NX_MANN_PHRASE_VISIBLE_PORES
127 n = n + 1
128 }
129 if sss_score < 512 {
130 phrase_ids[n] = NX_MANN_PHRASE_SSS_WARMTH
131 n = n + 1
132 }
133 if spec_score < 512 {
134 phrase_ids[n] = NX_MANN_PHRASE_SOFT_SPECULAR
135 n = n + 1
136 }
137 // Pressure + tendon detectors not yet shipped — emit phrases anyway as
138 // preventive hints (they don't conflict with anything in the registry).
139 phrase_ids[n] = NX_MANN_PHRASE_PRESSURE_DEFORMATION
140 n = n + 1
141 phrase_ids[n] = NX_MANN_PHRASE_TENDON_SHADOWS
142 n = n + 1
143 return n
144}