nx_moment_resolve_test.nx source
↩ module page · 229 lines · 9664 B
1// nx_moment_resolve_test.nx -- smoke for arc -> DirectorsNote bridge.
2
3import "nx_syscalls.nx"
4import "nx_tier.nx"
5import "nx_prng.nx"
6import "nx_arc.nx"
7import "nx_directors_note.nx"
8import "nx_moment_resolve.nx"
9
10func main() -> nx_int {
11 // Build a 1-stage arc with concrete pools so resolve has data
12 // to pick from. ContentDomain MUNDANE.
13 //
14 // Pools layout in arc.pools[]:
15 // [0..1] location IDs: 100, 101
16 // [2..3] outfit IDs: 200, 201
17 // [4..5] pose IDs: 300, 301
18 // [6] expression: NX_AF_FOCUSED (=8)
19 // [7] camera: 500
20 let arc: *Arc = nx_arc_alloc(1, 1, NX_AS_AUTHORED, NX_CD_MUNDANE, 8)
21 nx_arc_pool_set(arc, 0, 100)
22 nx_arc_pool_set(arc, 1, 101)
23 nx_arc_pool_set(arc, 2, 200)
24 nx_arc_pool_set(arc, 3, 201)
25 nx_arc_pool_set(arc, 4, 300)
26 nx_arc_pool_set(arc, 5, 301)
27 nx_arc_pool_set(arc, 6, NX_AF_FOCUSED)
28 nx_arc_pool_set(arc, 7, 500)
29
30 nx_arc_stage_set_basic(arc, 0,
31 1, NX_AW_UNAWARE, NX_ET_DOMESTIC,
32 300, 512, 5)
33 nx_arc_stage_set_pool(arc, 0, NX_ARC_POOL_LOCATION, 0, 2)
34 nx_arc_stage_set_pool(arc, 0, NX_ARC_POOL_OUTFIT, 2, 2)
35 nx_arc_stage_set_pool(arc, 0, NX_ARC_POOL_POSE, 4, 2)
36 nx_arc_stage_set_pool(arc, 0, NX_ARC_POOL_EXPRESSION, 6, 1)
37 nx_arc_stage_set_pool(arc, 0, NX_ARC_POOL_CAMERA, 7, 1)
38
39 let prng: *i64 = (sys_mmap(8)) as *i64
40 nx_prng_init(prng, 1234)
41
42 // === Test 1: canonical resolver fills DirectorsNote ============
43 let dn: *DirectorsNote = nx_dn_alloc()
44 // Caller pre-fills character + archetype (resolve doesn't invent them)
45 dn.character_id = 7
46 dn.body_archetype_id = 9
47 let usc: nx_int = nx_moment_resolve(arc, 0, 0 - 1, prng, dn)
48
49 // Arc identity propagated
50 if dn.arc_id != 1 { return 1 }
51 if dn.arc_stage != 1 { return 2 }
52 if dn.moment_seq != 0 { return 3 } // prev_seq = -1 -> seq = 0
53 if dn.prev_moment_seq != 0 - 1 { return 4 }
54
55 // Provenance: external-system arc-driven
56 if dn.director_role != NX_DR_EXTERNAL_SYSTEM { return 5 }
57 if dn.source_tag != NX_ST_STAGE_POOL { return 6 }
58 if dn.content_domain != NX_CD_MUNDANE { return 7 }
59
60 // Location picked from {100, 101}
61 if dn.location_id < 100 { return 10 }
62 if dn.location_id > 101 { return 11 }
63
64 // Outfit picked from {200, 201}
65 if dn.outfit_key_id < 200 { return 12 }
66 if dn.outfit_key_id > 201 { return 13 }
67
68 // Pose picked from {300, 301}
69 if dn.pose_id < 300 { return 14 }
70 if dn.pose_id > 301 { return 15 }
71
72 // Camera picked from {500}
73 if dn.camera_motivation_id != 500 { return 16 }
74
75 // Affect: expression pool had NX_AF_FOCUSED -> dn.affect_axis = FOCUSED
76 if dn.affect_axis != NX_AF_FOCUSED { return 17 }
77
78 // Arousal in [300, 512]
79 if dn.arousal_q10 < 300 { return 18 }
80 if dn.arousal_q10 > 512 { return 19 }
81
82 // Awareness UNAWARE -> Perspective VOYEUR
83 if dn.perspective != NX_PERSP_VOYEUR { return 20 }
84 if dn.shot_size != NX_SHOT_MS { return 21 }
85
86 // Quality starts UNVERIFIED + GOOD-tier-by-default
87 if dn.award_class != NX_DN_AWARD_UNVERIFIED { return 22 }
88 if dn.preflight_tier != NX_PFT_GOOD { return 23 }
89 if dn.refine_iteration != 0 { return 24 }
90
91 // Validation: character + body_archetype pre-set; all pools filled
92 // -> UNDERSPEC_NONE.
93 if usc != NX_DN_UNDERSPEC_NONE { return 25 }
94 if dn.underspec_code != NX_DN_UNDERSPEC_NONE { return 26 }
95
96 // === Test 2: determinism — same seed -> same picks =============
97 let prng2: *i64 = (sys_mmap(8)) as *i64
98 nx_prng_init(prng2, 1234)
99 let dn2: *DirectorsNote = nx_dn_alloc()
100 dn2.character_id = 7
101 dn2.body_archetype_id = 9
102 nx_moment_resolve(arc, 0, 0 - 1, prng2, dn2)
103 if dn2.location_id != dn.location_id { return 30 }
104 if dn2.outfit_key_id != dn.outfit_key_id { return 31 }
105 if dn2.pose_id != dn.pose_id { return 32 }
106 if dn2.arousal_q10 != dn.arousal_q10 { return 33 }
107
108 // === Test 3: different seed -> at least one different pick =====
109 let prng3: *i64 = (sys_mmap(8)) as *i64
110 nx_prng_init(prng3, 5678)
111 let dn3: *DirectorsNote = nx_dn_alloc()
112 dn3.character_id = 7
113 dn3.body_archetype_id = 9
114 nx_moment_resolve(arc, 0, 0 - 1, prng3, dn3)
115 // PRNG-driven picks should diverge — at least one of these differs.
116 var any_diff: nx_int = 0
117 if dn3.location_id != dn.location_id { any_diff = 1 }
118 if dn3.outfit_key_id != dn.outfit_key_id { any_diff = 1 }
119 if dn3.pose_id != dn.pose_id { any_diff = 1 }
120 if dn3.arousal_q10 != dn.arousal_q10 { any_diff = 1 }
121 if any_diff != 1 { return 40 }
122
123 // === Test 4: companion-authored variant ========================
124 //
125 // Elara picks EROTIC domain + PLAYFUL_BRATTY affect; resolver
126 // pulls the rest from the arc's pools.
127 let prng4: *i64 = (sys_mmap(8)) as *i64
128 nx_prng_init(prng4, 9999)
129 let dn4: *DirectorsNote = nx_dn_alloc()
130 dn4.character_id = 42
131 dn4.body_archetype_id = 9
132 let usc4: nx_int = nx_moment_resolve_companion(arc, 0, 42,
133 NX_CD_EROTIC,
134 NX_AF_PLAYFUL_BRATTY,
135 prng4, dn4)
136
137 if dn4.director_role != NX_DR_COMPANION { return 50 }
138 if dn4.source_tag != NX_ST_COMPANION_DECISION { return 51 }
139 if dn4.character_id != 42 { return 52 }
140 if dn4.content_domain != NX_CD_EROTIC { return 53 }
141 if dn4.affect_axis != NX_AF_PLAYFUL_BRATTY { return 54 }
142 // Pool picks still came from arc
143 if dn4.location_id < 100 { return 55 }
144 if dn4.location_id > 101 { return 56 }
145 if usc4 != NX_DN_UNDERSPEC_NONE { return 57 }
146
147 // === Test 5: arousal pinned when min == max ====================
148 //
149 // Build a degenerate stage with min == max; arousal should return
150 // exactly that value regardless of prng.
151 let arc2: *Arc = nx_arc_alloc(2, 1, NX_AS_AUTHORED, NX_CD_INTIMATE, 8)
152 nx_arc_pool_set(arc2, 0, 100)
153 nx_arc_pool_set(arc2, 1, 200)
154 nx_arc_pool_set(arc2, 2, 300)
155 nx_arc_pool_set(arc2, 3, NX_AF_INTIMATE)
156 nx_arc_pool_set(arc2, 4, 500)
157 nx_arc_stage_set_basic(arc2, 0,
158 1, NX_AW_AWARE, NX_ET_TENDER,
159 614, 614, 5)
160 nx_arc_stage_set_pool(arc2, 0, NX_ARC_POOL_LOCATION, 0, 1)
161 nx_arc_stage_set_pool(arc2, 0, NX_ARC_POOL_OUTFIT, 1, 1)
162 nx_arc_stage_set_pool(arc2, 0, NX_ARC_POOL_POSE, 2, 1)
163 nx_arc_stage_set_pool(arc2, 0, NX_ARC_POOL_EXPRESSION, 3, 1)
164 nx_arc_stage_set_pool(arc2, 0, NX_ARC_POOL_CAMERA, 4, 1)
165
166 let prng5: *i64 = (sys_mmap(8)) as *i64
167 nx_prng_init(prng5, 1)
168 let dn5: *DirectorsNote = nx_dn_alloc()
169 dn5.character_id = 1
170 dn5.body_archetype_id = 1
171 nx_moment_resolve(arc2, 0, 5, prng5, dn5)
172 if dn5.arousal_q10 != 614 { return 60 }
173 if dn5.moment_seq != 6 { return 61 }
174 if dn5.prev_moment_seq != 5 { return 62 }
175 // Awareness AWARE -> CANDID
176 if dn5.perspective != NX_PERSP_CANDID { return 63 }
177 // Tone TENDER (no expression pool -> falls through to tone) -- but
178 // here we DID set expression pool to NX_AF_INTIMATE, so affect
179 // becomes INTIMATE.
180 if dn5.affect_axis != NX_AF_INTIMATE { return 64 }
181
182 // === Test 6: tone fallback when expression pool absent =========
183 //
184 // Build a stage with NO expression pool; affect should derive
185 // from emotional_tone via _tone_to_affect. TONE=TRIUMPHANT
186 // -> AFFECT=JOYFUL.
187 let arc3: *Arc = nx_arc_alloc(3, 1, NX_AS_AUTHORED, NX_CD_NARRATIVE, 4)
188 nx_arc_pool_set(arc3, 0, 100)
189 nx_arc_pool_set(arc3, 1, 200)
190 nx_arc_pool_set(arc3, 2, 300)
191 nx_arc_pool_set(arc3, 3, 500)
192 nx_arc_stage_set_basic(arc3, 0,
193 1, NX_AW_DIRECT_ENGAGEMENT, NX_ET_TRIUMPHANT,
194 700, 900, 1)
195 nx_arc_stage_set_pool(arc3, 0, NX_ARC_POOL_LOCATION, 0, 1)
196 nx_arc_stage_set_pool(arc3, 0, NX_ARC_POOL_OUTFIT, 1, 1)
197 nx_arc_stage_set_pool(arc3, 0, NX_ARC_POOL_POSE, 2, 1)
198 nx_arc_stage_set_pool(arc3, 0, NX_ARC_POOL_CAMERA, 3, 1)
199 // expression pool intentionally NOT set (offset/len stay 0/0)
200
201 let prng6: *i64 = (sys_mmap(8)) as *i64
202 nx_prng_init(prng6, 7)
203 let dn6: *DirectorsNote = nx_dn_alloc()
204 dn6.character_id = 1
205 dn6.body_archetype_id = 1
206 nx_moment_resolve(arc3, 0, 0, prng6, dn6)
207
208 // Awareness DIRECT_ENGAGEMENT -> POV_BOYFRIEND
209 if dn6.perspective != NX_PERSP_POV_BOYFRIEND { return 70 }
210 // Tone TRIUMPHANT -> Affect JOYFUL (via _tone_to_affect)
211 if dn6.affect_axis != NX_AF_JOYFUL { return 71 }
212 if dn6.content_domain != NX_CD_NARRATIVE { return 72 }
213
214 // === Test 7: underspec when character + archetype missing =====
215 //
216 // Don't pre-fill character_id or body_archetype_id -- resolve
217 // should still run but the validator should flag missing required
218 // fields.
219 let prng7: *i64 = (sys_mmap(8)) as *i64
220 nx_prng_init(prng7, 11)
221 let dn7: *DirectorsNote = nx_dn_alloc()
222 let usc7: nx_int = nx_moment_resolve(arc, 0, 0, prng7, dn7)
223 // character_id and body_archetype_id are still 0
224 if usc7 == NX_DN_UNDERSPEC_NONE { return 80 } // must report something
225 // dn7.underspec_code must equal what nx_moment_resolve returned
226 if dn7.underspec_code != usc7 { return 81 }
227
228 return 0
229}