code wiki / (root) / nx_arc.nx

nx_arc.nx source

↩ module page · 324 lines · 12812 B

1// nx_arc.nx -- canonical arc + arc-stage substrate. 2// 3// Arcs are the "events" of the day-as-a-movie metaphor (per cardinal 4// feedback-arc-and-moment-are-king-cinematographer-pattern). Each arc 5// is a sequence of stages; each stage has pools of allowed locations, 6// outfits, poses, expressions, and camera angles from which moments 7// are drawn. 8// 9// nx_moment_resolve.nx (next brick) consumes Arc + ArcStage and emits 10// a fully-populated *DirectorsNote (from nx_directors_note.nx). 11// 12// === Flat-array layout pattern (per nx_canon_proportions) ============ 13// 14// To keep struct shapes fixed-size and avoid variable-length field 15// pointers (which trip the substrate's <=8-arg codegen lesson), this 16// primitive uses TWO flat i64 buffers attached to Arc: 17// 18// arc.stages : flat array, NX_ARC_STAGE_FIELDS i64 per stage 19// arc.pools : flat array of pool ID values; stages reference 20// their pools via (offset, len) indices into this buffer 21// 22// This is the same shape nx_canon_proportions uses for its 30-entry 23// canon table (5 i64 per entry, accessed via nx_canon_get). 24// 25// === Composition ===================================================== 26// 27// nx_directors_note.nx -- ArcSource sealed-enum is re-exported here 28// so the same value can flow into a 29// DirectorsNote.source_tag 30// nx_tier.nx -- nx_int alias 31// nx_string_ops.nx -- (when caller wants to attach string names) 32// 33// genealogy_id: propp_morphology + campbell_monomyth + lindenmayer_systems + 34// syd_field_three_act_structure 35// lineage_id: nx_arc_v1 36 37// nx_safety_envelope: 38// intended_use: AUTO_APPLIED -- primitive-specific tuning queued 39// sil_target: SIL1 40// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail] 41// verdict: NOT_YET_EVALUATED 42 43import "nx_syscalls.nx" 44import "nx_tier.nx" 45 46// ===== Sealed-enum: ArcSource ===================================== 47// 48// How the arc was created. Mirrors the Python ArcSource semantics 49// but typed as i64 sealed constants per the NishiLang convention. 50 51const NX_AS_AUTHORED: nx_int = 0 // hand-curated arc in DB 52const NX_AS_DYNAMIC: nx_int = 1 // LLM-generated from chat 53const NX_AS_GENERATED: nx_int = 2 // story-engine pre-generated 54const NX_AS_COMPANION: nx_int = 3 // companion authored her own arc 55const NX_AS_N_SOURCES: nx_int = 4 56 57func nx_as_source_is_valid(s: nx_int) -> nx_int { 58 if s < 0 { return 0 } 59 if s >= NX_AS_N_SOURCES { return 0 } 60 return 1 61} 62 63// ===== Sealed-enum: Awareness ===================================== 64// 65// How aware the character is of being observed. Drives the 66// downstream Perspective + framing decisions. 67 68const NX_AW_UNAWARE: nx_int = 0 // doesn't know she's seen 69const NX_AW_PEEKING: nx_int = 1 // hint of awareness 70const NX_AW_AWARE: nx_int = 2 // knows but acting natural 71const NX_AW_EYE_CONTACT: nx_int = 3 // looking at camera/viewer 72const NX_AW_DIRECT_ENGAGEMENT: nx_int = 4 // actively addressing 73const NX_AW_N_LEVELS: nx_int = 5 74 75func nx_aw_level_is_valid(a: nx_int) -> nx_int { 76 if a < 0 { return 0 } 77 if a >= NX_AW_N_LEVELS { return 0 } 78 return 1 79} 80 81// ===== Sealed-enum: EmotionalTone ================================= 82// 83// Stage-level emotional tone. Per cardinal feedback-graduated- 84// intervention: this is a TYPED axis, not a free-text string. 85// Composes with nx_directors_note.AffectAxis (per-moment) — the 86// stage-tone bounds the per-moment-affect. 87 88const NX_ET_NEUTRAL: nx_int = 0 89const NX_ET_TENDER: nx_int = 1 90const NX_ET_PLAYFUL: nx_int = 2 91const NX_ET_INTENSE: nx_int = 3 92const NX_ET_LONGING: nx_int = 4 93const NX_ET_TRIUMPHANT: nx_int = 5 94const NX_ET_REVERENT: nx_int = 6 95const NX_ET_DOMESTIC: nx_int = 7 // mundane chores, cooking, etc. 96const NX_ET_ANTICIPATORY: nx_int = 8 97const NX_ET_AFTERGLOW: nx_int = 9 98const NX_ET_N_TONES: nx_int = 10 99 100func nx_et_tone_is_valid(t: nx_int) -> nx_int { 101 if t < 0 { return 0 } 102 if t >= NX_ET_N_TONES { return 0 } 103 return 1 104} 105 106// ===== Stage field layout (flat array offsets) ===================== 107// 108// Each ArcStage occupies NX_ARC_STAGE_FIELDS i64 slots in the arc's 109// flat stages buffer. Field offsets: 110 111const NX_AST_F_STAGE_NUMBER: nx_int = 0 112const NX_AST_F_AWARENESS: nx_int = 1 113const NX_AST_F_EMOTIONAL_TONE: nx_int = 2 114const NX_AST_F_MIN_AROUSAL_Q10: nx_int = 3 115const NX_AST_F_MAX_AROUSAL_Q10: nx_int = 4 116const NX_AST_F_IMAGES_BEFORE_ADVANCE: nx_int = 5 117const NX_AST_F_LOCATION_POOL_OFF: nx_int = 6 // offset into arc.pools 118const NX_AST_F_LOCATION_POOL_LEN: nx_int = 7 119const NX_AST_F_OUTFIT_POOL_OFF: nx_int = 8 120const NX_AST_F_OUTFIT_POOL_LEN: nx_int = 9 121const NX_AST_F_POSE_POOL_OFF: nx_int = 10 122const NX_AST_F_POSE_POOL_LEN: nx_int = 11 123const NX_AST_F_EXPRESSION_POOL_OFF: nx_int = 12 124const NX_AST_F_EXPRESSION_POOL_LEN: nx_int = 13 125const NX_AST_F_CAMERA_POOL_OFF: nx_int = 14 126const NX_AST_F_CAMERA_POOL_LEN: nx_int = 15 127const NX_ARC_STAGE_FIELDS: nx_int = 16 128 129// ===== Arc struct ================================================= 130// 131// Top-level arc. Owns the stages buffer + pools buffer. Compact; 132// references everything by pointer + count. 133 134struct Arc { 135 arc_id: nx_int, 136 n_stages: nx_int, 137 source: nx_int, // NX_AS_AUTHORED / DYNAMIC / ... 138 default_domain: nx_int, // NX_CD_* default content domain 139 n_pool_ids: nx_int, // total pool-ID count in pools[] 140 stages: *i64, // n_stages * NX_ARC_STAGE_FIELDS 141 pools: *i64 // n_pool_ids 142} 143 144const NX_ARC_BYTES: nx_int = 56 // 7 fields * 8 145 146// ===== Builder ==================================================== 147// 148// Allocate an Arc with empty stages + pools buffers sized for the 149// caller's n_stages and n_pool_ids. Caller fills via _set helpers. 150 151func nx_arc_alloc(arc_id: nx_int, n_stages: nx_int, 152 source: nx_int, default_domain: nx_int, 153 n_pool_ids: nx_int) -> *Arc { 154 let arc: *Arc = (sys_mmap(NX_ARC_BYTES)) as *Arc 155 arc.arc_id = arc_id 156 arc.n_stages = n_stages 157 arc.source = source 158 arc.default_domain = default_domain 159 arc.n_pool_ids = n_pool_ids 160 // Stages: n_stages * 16 fields * 8 bytes/i64 161 let stages_bytes: nx_int = n_stages * NX_ARC_STAGE_FIELDS * NX_SIZEOF_NX_INT 162 arc.stages = (sys_mmap(stages_bytes)) as *i64 163 // Pools: n_pool_ids * 8 bytes/i64 164 let pools_bytes: nx_int = n_pool_ids * NX_SIZEOF_NX_INT 165 if n_pool_ids > 0 { 166 arc.pools = (sys_mmap(pools_bytes)) as *i64 167 } else { 168 // Allocate a one-slot buffer so caller can safely query; 169 // empty pool indicated by len=0 on every stage anyway. 170 arc.pools = (sys_mmap(NX_SIZEOF_NX_INT)) as *i64 171 } 172 // Init stages to zero (NishiLang's mmap returns zero pages, but 173 // explicit zeroing makes the contract clear). 174 var i: nx_int = 0 175 while i < n_stages * NX_ARC_STAGE_FIELDS { 176 arc.stages[i] = 0 177 i = i + 1 178 } 179 var j: nx_int = 0 180 while j < n_pool_ids { 181 arc.pools[j] = 0 182 j = j + 1 183 } 184 return arc 185} 186 187// ===== Stage field accessors ======================================= 188// 189// Read/write one field of a stage by stage index + field offset. 190// Caller must respect bounds; out-of-range returns 0 / no-op. 191 192func nx_arc_stage_get(arc: *Arc, stage_idx: nx_int, field: nx_int) -> nx_int { 193 if stage_idx < 0 { return 0 } 194 if stage_idx >= arc.n_stages { return 0 } 195 if field < 0 { return 0 } 196 if field >= NX_ARC_STAGE_FIELDS { return 0 } 197 return arc.stages[stage_idx * NX_ARC_STAGE_FIELDS + field] 198} 199 200func nx_arc_stage_set(arc: *Arc, stage_idx: nx_int, field: nx_int, value: nx_int) -> nx_int { 201 if stage_idx < 0 { return 0 - 1 } 202 if stage_idx >= arc.n_stages { return 0 - 1 } 203 if field < 0 { return 0 - 1 } 204 if field >= NX_ARC_STAGE_FIELDS { return 0 - 1 } 205 arc.stages[stage_idx * NX_ARC_STAGE_FIELDS + field] = value 206 return 0 207} 208 209// ===== Pool accessors ============================================== 210// 211// Read/write a pool ID by offset into the shared pools buffer. 212// The (offset, len) pair stored in a stage's location/outfit/pose/... 213// fields identifies which slice of pools[] belongs to that stage. 214 215func nx_arc_pool_get(arc: *Arc, idx: nx_int) -> nx_int { 216 if idx < 0 { return 0 } 217 if idx >= arc.n_pool_ids { return 0 } 218 return arc.pools[idx] 219} 220 221func nx_arc_pool_set(arc: *Arc, idx: nx_int, value: nx_int) -> nx_int { 222 if idx < 0 { return 0 - 1 } 223 if idx >= arc.n_pool_ids { return 0 - 1 } 224 arc.pools[idx] = value 225 return 0 226} 227 228// ===== Convenience: scalar stage setters (named for readability) === 229 230func nx_arc_stage_set_basic(arc: *Arc, stage_idx: nx_int, 231 stage_number: nx_int, awareness: nx_int, 232 emotional_tone: nx_int, 233 min_arousal_q10: nx_int, max_arousal_q10: nx_int, 234 images_before_advance: nx_int) -> nx_int { 235 nx_arc_stage_set(arc, stage_idx, NX_AST_F_STAGE_NUMBER, stage_number) 236 nx_arc_stage_set(arc, stage_idx, NX_AST_F_AWARENESS, awareness) 237 nx_arc_stage_set(arc, stage_idx, NX_AST_F_EMOTIONAL_TONE, emotional_tone) 238 nx_arc_stage_set(arc, stage_idx, NX_AST_F_MIN_AROUSAL_Q10, min_arousal_q10) 239 nx_arc_stage_set(arc, stage_idx, NX_AST_F_MAX_AROUSAL_Q10, max_arousal_q10) 240 nx_arc_stage_set(arc, stage_idx, NX_AST_F_IMAGES_BEFORE_ADVANCE, images_before_advance) 241 return 0 242} 243 244// Wire a pool's (offset, len) onto a stage. pool_kind selects which 245// of the 5 pool-pair fields to write: 0=location, 1=outfit, 2=pose, 246// 3=expression, 4=camera. Returns -1 on invalid kind. 247 248const NX_ARC_POOL_LOCATION: nx_int = 0 249const NX_ARC_POOL_OUTFIT: nx_int = 1 250const NX_ARC_POOL_POSE: nx_int = 2 251const NX_ARC_POOL_EXPRESSION: nx_int = 3 252const NX_ARC_POOL_CAMERA: nx_int = 4 253const NX_ARC_POOL_N_KINDS: nx_int = 5 254 255func nx_arc_pool_kind_is_valid(k: nx_int) -> nx_int { 256 if k < 0 { return 0 } 257 if k >= NX_ARC_POOL_N_KINDS { return 0 } 258 return 1 259} 260 261func nx_arc_stage_set_pool(arc: *Arc, stage_idx: nx_int, 262 pool_kind: nx_int, 263 pool_offset: nx_int, pool_len: nx_int) -> nx_int { 264 if nx_arc_pool_kind_is_valid(pool_kind) == 0 { return 0 - 1 } 265 var off_field: nx_int = 0 266 var len_field: nx_int = 0 267 if pool_kind == NX_ARC_POOL_LOCATION { 268 off_field = NX_AST_F_LOCATION_POOL_OFF 269 len_field = NX_AST_F_LOCATION_POOL_LEN 270 } 271 if pool_kind == NX_ARC_POOL_OUTFIT { 272 off_field = NX_AST_F_OUTFIT_POOL_OFF 273 len_field = NX_AST_F_OUTFIT_POOL_LEN 274 } 275 if pool_kind == NX_ARC_POOL_POSE { 276 off_field = NX_AST_F_POSE_POOL_OFF 277 len_field = NX_AST_F_POSE_POOL_LEN 278 } 279 if pool_kind == NX_ARC_POOL_EXPRESSION { 280 off_field = NX_AST_F_EXPRESSION_POOL_OFF 281 len_field = NX_AST_F_EXPRESSION_POOL_LEN 282 } 283 if pool_kind == NX_ARC_POOL_CAMERA { 284 off_field = NX_AST_F_CAMERA_POOL_OFF 285 len_field = NX_AST_F_CAMERA_POOL_LEN 286 } 287 nx_arc_stage_set(arc, stage_idx, off_field, pool_offset) 288 nx_arc_stage_set(arc, stage_idx, len_field, pool_len) 289 return 0 290} 291 292// Read the (offset, len) pair for a given stage + pool kind. Sentinel 293// writeback: pool_off and pool_len are returned via caller-supplied 294// *i64 buffers so the function stays <= 8 args. 295 296func nx_arc_stage_get_pool(arc: *Arc, stage_idx: nx_int, pool_kind: nx_int, 297 out_off: *i64, out_len: *i64) -> nx_int { 298 if nx_arc_pool_kind_is_valid(pool_kind) == 0 { return 0 - 1 } 299 var off_field: nx_int = 0 300 var len_field: nx_int = 0 301 if pool_kind == NX_ARC_POOL_LOCATION { 302 off_field = NX_AST_F_LOCATION_POOL_OFF 303 len_field = NX_AST_F_LOCATION_POOL_LEN 304 } 305 if pool_kind == NX_ARC_POOL_OUTFIT { 306 off_field = NX_AST_F_OUTFIT_POOL_OFF 307 len_field = NX_AST_F_OUTFIT_POOL_LEN 308 } 309 if pool_kind == NX_ARC_POOL_POSE { 310 off_field = NX_AST_F_POSE_POOL_OFF 311 len_field = NX_AST_F_POSE_POOL_LEN 312 } 313 if pool_kind == NX_ARC_POOL_EXPRESSION { 314 off_field = NX_AST_F_EXPRESSION_POOL_OFF 315 len_field = NX_AST_F_EXPRESSION_POOL_LEN 316 } 317 if pool_kind == NX_ARC_POOL_CAMERA { 318 off_field = NX_AST_F_CAMERA_POOL_OFF 319 len_field = NX_AST_F_CAMERA_POOL_LEN 320 } 321 out_off[0] = nx_arc_stage_get(arc, stage_idx, off_field) 322 out_len[0] = nx_arc_stage_get(arc, stage_idx, len_field) 323 return 0 324}