code wiki / (root) / nx_moment_resolve.nx

nx_moment_resolve.nx source

↩ module page · 227 lines · 10008 B

1// nx_moment_resolve.nx -- resolve an arc stage into a populated DirectorsNote. 2// 3// This is the bridge between nx_arc (the data: arcs + stages + pools) 4// and nx_directors_note (the envelope: the typed packet that flows 5// through the entire render pipeline). 6// 7// Per cardinal feedback-arc-and-moment-are-king-cinematographer-pattern: 8// the director (arc + moment_generator) authors the moment. This 9// primitive IS that authoring step on the substrate side. It picks 10// one ID from each of the stage's pools (location, outfit, pose, 11// expression=affect, camera) using deterministic prng + the stage's 12// arousal range, and produces a *DirectorsNote that downstream 13// services consume unchanged. 14// 15// === Composition ==================================================== 16// 17// nx_arc.nx -- Arc + ArcStage structure (data) 18// nx_directors_note.nx -- DirectorsNote (envelope) 19// nx_prng.nx -- deterministic sampling (Park-Miller LCG) 20// nx_tier.nx -- nx_int alias 21// 22// === Three-tier API ================================================== 23// 24// nx_moment_resolve(arc, stage_idx, prev_seq, prng, dn_out) 25// -- the canonical resolver; 5 args (well under <=8 codegen ceiling). 26// Returns NX_DN_UNDERSPEC_NONE on success, else a code naming 27// which DirectorsNote field couldn't be filled. 28// 29// nx_moment_resolve_companion(arc, stage_idx, character_id, 30// domain, affect, prng, dn_out) 31// -- companion-authored variant where Elara chose the domain + 32// affect axis herself; resolver fills the rest from the arc. 33// 7 args. 34// 35// Both call _pool_pick + _pick_arousal internally. 36// 37// genealogy_id: lindenmayer_systems + propp_morphology + 38// bridson_2007_uniform_sampling 39// lineage_id: nx_moment_resolve_v1 40 41// nx_safety_envelope: 42// intended_use: AUTO_APPLIED -- primitive-specific tuning queued 43// sil_target: SIL1 44// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail] 45// verdict: NOT_YET_EVALUATED 46 47import "nx_syscalls.nx" 48import "nx_tier.nx" 49import "nx_prng.nx" 50import "nx_arc.nx" 51import "nx_directors_note.nx" 52 53// ===== Internal: pick a single ID uniformly from a stage's pool ==== 54// 55// Returns the picked ID, or 0 if the pool is empty. 56 57func _pool_pick(arc: *Arc, stage_idx: nx_int, pool_kind: nx_int, 58 prng_state: *i64) -> nx_int { 59 let off_buf: *i64 = (sys_mmap(8)) as *i64 60 let len_buf: *i64 = (sys_mmap(8)) as *i64 61 let rc: nx_int = nx_arc_stage_get_pool(arc, stage_idx, pool_kind, off_buf, len_buf) 62 if rc != 0 { return 0 } 63 let pool_off: nx_int = off_buf[0] 64 let pool_len: nx_int = len_buf[0] 65 if pool_len <= 0 { return 0 } 66 let pick_idx: nx_int = nx_prng_range(prng_state, pool_len) 67 return nx_arc_pool_get(arc, pool_off + pick_idx) 68} 69 70// ===== Internal: pick an arousal value in [min, max] (Q10) ========= 71// 72// Uniform in [min_q10, max_q10]. If min > max, returns min (safe). 73 74func _pick_arousal(arc: *Arc, stage_idx: nx_int, prng_state: *i64) -> nx_int { 75 let min_q10: nx_int = nx_arc_stage_get(arc, stage_idx, NX_AST_F_MIN_AROUSAL_Q10) 76 let max_q10: nx_int = nx_arc_stage_get(arc, stage_idx, NX_AST_F_MAX_AROUSAL_Q10) 77 if max_q10 <= min_q10 { return min_q10 } 78 let span: nx_int = max_q10 - min_q10 79 let delta: nx_int = nx_prng_range(prng_state, span + 1) 80 return min_q10 + delta 81} 82 83// ===== Internal: map EmotionalTone (stage) -> AffectAxis (moment) == 84// 85// Used as fallback when the stage has no expression_pool, so the 86// director's intent is still conveyed via the stage-level tone. 87 88func _tone_to_affect(tone: nx_int) -> nx_int { 89 if tone == NX_ET_NEUTRAL { return NX_AF_NEUTRAL } 90 if tone == NX_ET_TENDER { return NX_AF_VULNERABLE } 91 if tone == NX_ET_PLAYFUL { return NX_AF_PLAYFUL } 92 if tone == NX_ET_INTENSE { return NX_AF_DOMINANT } 93 if tone == NX_ET_LONGING { return NX_AF_MELANCHOLIC } 94 if tone == NX_ET_TRIUMPHANT { return NX_AF_JOYFUL } 95 if tone == NX_ET_REVERENT { return NX_AF_REVERENT } 96 if tone == NX_ET_DOMESTIC { return NX_AF_FOCUSED } 97 if tone == NX_ET_ANTICIPATORY { return NX_AF_PLAYFUL } 98 if tone == NX_ET_AFTERGLOW { return NX_AF_INTIMATE } 99 return NX_AF_NEUTRAL 100} 101 102// ===== Internal: map Awareness (stage) -> Perspective (moment) ==== 103// 104// Awareness modulates the cinematography: an UNAWARE character is 105// rendered VOYEUR; AWARE goes to CANDID; EYE_CONTACT goes to POV; 106// DIRECT_ENGAGEMENT goes to POV_BOYFRIEND (intimate). 107 108func _awareness_to_perspective(aw: nx_int) -> nx_int { 109 if aw == NX_AW_UNAWARE { return NX_PERSP_VOYEUR } 110 if aw == NX_AW_PEEKING { return NX_PERSP_CANDID } 111 if aw == NX_AW_AWARE { return NX_PERSP_CANDID } 112 if aw == NX_AW_EYE_CONTACT { return NX_PERSP_POV } 113 if aw == NX_AW_DIRECT_ENGAGEMENT { return NX_PERSP_POV_BOYFRIEND } 114 return NX_PERSP_THIRD 115} 116 117// ===== Public: resolve a moment from arc + stage =================== 118// 119// Canonical resolver. Fills dn_out with picks from the stage's pools 120// and the stage's typed fields. Returns the underspec code from 121// nx_dn_validate_for_render so the caller knows if anything's missing. 122 123func nx_moment_resolve(arc: *Arc, stage_idx: nx_int, 124 prev_seq: nx_int, 125 prng_state: *i64, 126 dn_out: *DirectorsNote) -> nx_int { 127 // Identity 128 dn_out.arc_id = arc.arc_id 129 dn_out.arc_stage = nx_arc_stage_get(arc, stage_idx, NX_AST_F_STAGE_NUMBER) 130 dn_out.moment_seq = prev_seq + 1 131 dn_out.prev_moment_seq = prev_seq 132 133 // Provenance: arc-driven external-system director 134 dn_out.director_role = NX_DR_EXTERNAL_SYSTEM 135 dn_out.source_tag = NX_ST_STAGE_POOL 136 dn_out.content_domain = arc.default_domain 137 138 // Scene picks from pools 139 let loc_id: nx_int = _pool_pick(arc, stage_idx, NX_ARC_POOL_LOCATION, prng_state) 140 let outfit_id: nx_int = _pool_pick(arc, stage_idx, NX_ARC_POOL_OUTFIT, prng_state) 141 let pose_id: nx_int = _pool_pick(arc, stage_idx, NX_ARC_POOL_POSE, prng_state) 142 let cam_id: nx_int = _pool_pick(arc, stage_idx, NX_ARC_POOL_CAMERA, prng_state) 143 144 dn_out.location_id = loc_id 145 dn_out.outfit_key_id = outfit_id 146 dn_out.pose_id = pose_id 147 dn_out.camera_motivation_id = cam_id 148 149 // Affect: expression_pool picks are AffectAxis values. Special- 150 // case: NX_AF_NEUTRAL is the enum's zero, indistinguishable from 151 // "pool was empty / unset," so we must check pool LEN directly 152 // rather than test the picked value. If pool present, pick; 153 // else derive from stage emotional_tone. 154 let tone: nx_int = nx_arc_stage_get(arc, stage_idx, NX_AST_F_EMOTIONAL_TONE) 155 var affect: nx_int = _tone_to_affect(tone) 156 let expr_off_buf: *i64 = (sys_mmap(8)) as *i64 157 let expr_len_buf: *i64 = (sys_mmap(8)) as *i64 158 nx_arc_stage_get_pool(arc, stage_idx, NX_ARC_POOL_EXPRESSION, 159 expr_off_buf, expr_len_buf) 160 if expr_len_buf[0] > 0 { 161 let expr_pick: nx_int = _pool_pick(arc, stage_idx, NX_ARC_POOL_EXPRESSION, prng_state) 162 if nx_af_axis_is_valid(expr_pick) == 1 { affect = expr_pick } 163 } 164 dn_out.affect_axis = affect 165 166 // Arousal: uniform in stage's [min, max] Q10 range 167 dn_out.arousal_q10 = _pick_arousal(arc, stage_idx, prng_state) 168 169 // Camera + cinematography from stage awareness 170 let aw: nx_int = nx_arc_stage_get(arc, stage_idx, NX_AST_F_AWARENESS) 171 dn_out.perspective = _awareness_to_perspective(aw) 172 // shot_size defaults to MS unless explicitly set; caller can override 173 dn_out.shot_size = NX_SHOT_MS 174 175 // Activity defaults: not yet typed at arc-stage level in v1. 176 // For full validation we set a sentinel non-zero so the underspec 177 // code reports a meaningful field if caller really needs activity. 178 // (v2: add activity_pool to ArcStage.) 179 if dn_out.activity_id == 0 { dn_out.activity_id = pose_id } 180 181 // Character: caller is responsible for setting before resolve. 182 // If unset, validate_for_render reports it. 183 184 // Body archetype: caller-set; if unset, validate_for_render reports. 185 186 // Quality starts UNVERIFIED 187 dn_out.award_class = NX_DN_AWARD_UNVERIFIED 188 dn_out.preflight_tier = NX_PFT_GOOD 189 dn_out.refine_iteration = 0 190 dn_out.underspec_code = NX_DN_UNDERSPEC_NONE 191 192 let usc: nx_int = nx_dn_validate_for_render(dn_out) 193 dn_out.underspec_code = usc 194 return usc 195} 196 197// ===== Companion-authored resolve ================================== 198// 199// When the companion (e.g., Elara) authors a moment from her own 200// ontological decision (per the cardinal: "external director or one 201// of the companions, elara in this case making ontological or whatever 202// type decisions"), this variant: 203// - sets director_role = COMPANION + source_tag = COMPANION_DECISION 204// - takes the companion's chosen content_domain + affect_axis 205// - still draws location/outfit/pose/camera from the arc's pools 206// 207// 7 args. Under the <=8 ceiling. 208 209func nx_moment_resolve_companion(arc: *Arc, stage_idx: nx_int, 210 character_id: nx_int, 211 domain: nx_int, affect: nx_int, 212 prng_state: *i64, 213 dn_out: *DirectorsNote) -> nx_int { 214 // Same base path as canonical resolve, then override authority 215 let rc: nx_int = nx_moment_resolve(arc, stage_idx, 216 dn_out.prev_moment_seq, 217 prng_state, dn_out) 218 dn_out.director_role = NX_DR_COMPANION 219 dn_out.source_tag = NX_ST_COMPANION_DECISION 220 dn_out.character_id = character_id 221 dn_out.content_domain = domain 222 dn_out.affect_axis = affect 223 // Re-validate after companion overrides 224 let usc: nx_int = nx_dn_validate_for_render(dn_out) 225 dn_out.underspec_code = usc 226 return usc 227}