code wiki / (root) / nx_storydb.nx

nx_storydb.nx source

↩ module page · 252 lines · 8624 B

1// nx_storydb.nx -- top-level container for ingested narrative. 2// 3// A StoryDb holds everything produced by an ingestion session: 4// - beats arena (every StoryBeat across every medium) 5// - scenes arena (groups of consecutive related beats) 6// - chapters arena (groups of scenes that share arc context) 7// - media_pool (raw bytes of every ingested item) 8// - summary arena (string bytes for synopsis / extracted text) 9// 10// One StoryDb per ingestion session. Multiple sessions for the 11// same user merge into the persistent corpus through nx_corpus_ 12// (separate module, not built yet). 13// 14// All arenas are fixed-capacity append-only; downstream consumers 15// hold cheap i64 indices, never pointers. 16// 17// See: docs/NISHI_MEDIA_INGESTION_ROADMAP.md 18// 19// nx_safety_envelope: 20// intended_use: "Top-level container for one ingestion 21// session. Holds beats / scenes / chapters / 22// media-pool / summary-strings." 23// sil_target: SIL2 24// asil_target: QM 25// dal_target: DAL C 26// iec_62304_class: NONE 27// evidence: [no_floating_point, 28// fixed_capacity_append_only_arenas, 29// cheap_i64_indices_no_pointers_to_caller, 30// media_pool_reference_indirected] 31// hazard_register: [bug-tape-arena-overflow-silent, 32// bug-tape-scene-spans-disjoint-from-beats] 33// residual_risk: "Default caps may overflow on book-length 34// ingestions; caller can request larger 35// StoryDb via nx_storydb_alloc_sized." 36// verdict: NOT_YET_EVALUATED 37 38import "nx_syscalls.nx" 39import "nx_storybeat.nx" 40import "nx_media_pool.nx" 41const NX_MAGIC_1024: i64 = 1024 42 43// ===== StoryScene ================================================= 44// 45// Sequence of consecutive beats that share location / time / 46// dramatic unit. beat_first/beat_last are inclusive indices into 47// StoryDb.beats. 48 49struct StoryScene { 50 beat_first: i64, 51 beat_last: i64, 52 location_id: i64, 53 arc_stage: i64, // NX_ARCSTAGE_* 54 domain_code: i64, // dominant NX_DOMAIN_* 55 title_off: i64, // offset into summary arena 56 title_len: i64, 57 flags: i64, // reserved 58} 59 60const NX_STORYSCENE_BYTES: i64 = 64 61 62// ===== StoryChapter =============================================== 63// 64// Sequence of scenes that share narrative arc. Top-level structural 65// unit (a literotica "chapter", a video "act", an album "part"). 66 67struct StoryChapter { 68 scene_first: i64, 69 scene_last: i64, 70 arc_id: i64, // 0 if no fitted arc 71 title_off: i64, 72 title_len: i64, 73 flags: i64, 74} 75 76const NX_STORYCHAPTER_BYTES: i64 = 48 77 78// ===== StoryDb container ========================================== 79 80const NX_STORYDB_BEATS_CAP: i64 = 65536 // 8 MiB worth of beats 81const NX_STORYDB_SCENES_CAP: i64 = 4096 82const NX_STORYDB_CHAPTERS_CAP: i64 = 256 83const NX_STORYDB_SUMMARY_CAP: i64 = 1048576 // 1 MiB summary text 84 85struct StoryDb { 86 beats: *StoryBeat, 87 n_beats: i64, 88 beats_cap: i64, 89 90 scenes: *StoryScene, 91 n_scenes: i64, 92 scenes_cap: i64, 93 94 chapters: *StoryChapter, 95 n_chapters: i64, 96 chapters_cap: i64, 97 98 summary_buf: *u8, 99 summary_len: i64, 100 summary_cap: i64, 101 102 media_pool: *MediaPool, // shared; owned by StoryDb 103} 104 105const NX_STORYDB_BYTES: i64 = 120 106 107// ===== allocation ================================================= 108 109func nx_storydb_alloc_sized(beats_cap: i64, scenes_cap: i64, 110 chapters_cap: i64, summary_cap: i64, 111 media_cap: i64) -> *StoryDb { 112 var bc: i64 = beats_cap 113 var sc: i64 = scenes_cap 114 var cc: i64 = chapters_cap 115 var uc: i64 = summary_cap 116 var mc: i64 = media_cap 117 if bc <= 0 { bc = NX_STORYDB_BEATS_CAP } 118 if sc <= 0 { sc = NX_STORYDB_SCENES_CAP } 119 if cc <= 0 { cc = NX_STORYDB_CHAPTERS_CAP } 120 if uc <= 0 { uc = NX_STORYDB_SUMMARY_CAP } 121 if mc <= 0 { mc = NX_MAGIC_1024 } 122 123 let raw: *u8 = sys_mmap(NX_STORYDB_BYTES) 124 let db: *StoryDb = raw as *StoryDb 125 db.beats = (sys_mmap(bc * NX_STORYBEAT_BYTES)) as *StoryBeat 126 db.n_beats = 0 127 db.beats_cap = bc 128 db.scenes = (sys_mmap(sc * NX_STORYSCENE_BYTES)) as *StoryScene 129 db.n_scenes = 0 130 db.scenes_cap = sc 131 db.chapters = (sys_mmap(cc * NX_STORYCHAPTER_BYTES)) as *StoryChapter 132 db.n_chapters = 0 133 db.chapters_cap = cc 134 db.summary_buf = sys_mmap(uc) 135 db.summary_len = 0 136 db.summary_cap = uc 137 db.media_pool = nx_media_pool_alloc(mc) 138 return db 139} 140 141func nx_storydb_alloc() -> *StoryDb { 142 return nx_storydb_alloc_sized(0, 0, 0, 0, 0) 143} 144 145// ===== accessors ================================================== 146 147func nx_storydb_beat_at(db: *StoryDb, i: i64) -> *StoryBeat { 148 return (((db.beats as i64) + i * NX_STORYBEAT_BYTES) as *StoryBeat) 149} 150 151func nx_storydb_scene_at(db: *StoryDb, i: i64) -> *StoryScene { 152 return (((db.scenes as i64) + i * NX_STORYSCENE_BYTES) as *StoryScene) 153} 154 155func nx_storydb_chapter_at(db: *StoryDb, i: i64) -> *StoryChapter { 156 return (((db.chapters as i64) + i * NX_STORYCHAPTER_BYTES) as *StoryChapter) 157} 158 159// ===== append helpers ============================================= 160 161// Returns the new beat id (= index into beats), or -1 on overflow. 162func nx_storydb_push_beat(db: *StoryDb, beat_kind: i64, media_type: i64, 163 media_id: i64, span_start: i64, 164 span_end: i64) -> i64 { 165 if db.n_beats >= db.beats_cap { return -1 } 166 let id: i64 = db.n_beats 167 let b: *StoryBeat = nx_storydb_beat_at(db, id) 168 nx_beat_init(b, beat_kind, media_type, media_id, span_start, span_end) 169 db.n_beats = db.n_beats + 1 170 return id 171} 172 173// Open a new scene starting at beat_first. Returns scene_id or -1. 174// scene.beat_last is left set to beat_first until nx_storydb_close_scene. 175func nx_storydb_open_scene(db: *StoryDb, beat_first: i64, 176 location_id: i64, arc_stage: i64, 177 domain_code: i64) -> i64 { 178 if db.n_scenes >= db.scenes_cap { return -1 } 179 let id: i64 = db.n_scenes 180 let s: *StoryScene = nx_storydb_scene_at(db, id) 181 s.beat_first = beat_first 182 s.beat_last = beat_first 183 s.location_id = location_id 184 s.arc_stage = arc_stage 185 s.domain_code = domain_code 186 s.title_off = 0 187 s.title_len = 0 188 s.flags = 0 189 db.n_scenes = db.n_scenes + 1 190 return id 191} 192 193func nx_storydb_close_scene(db: *StoryDb, scene_id: i64, beat_last: i64) { 194 if scene_id < 0 { return } 195 if scene_id >= db.n_scenes { return } 196 let s: *StoryScene = nx_storydb_scene_at(db, scene_id) 197 s.beat_last = beat_last 198} 199 200func nx_storydb_open_chapter(db: *StoryDb, scene_first: i64, 201 arc_id: i64) -> i64 { 202 if db.n_chapters >= db.chapters_cap { return -1 } 203 let id: i64 = db.n_chapters 204 let c: *StoryChapter = nx_storydb_chapter_at(db, id) 205 c.scene_first = scene_first 206 c.scene_last = scene_first 207 c.arc_id = arc_id 208 c.title_off = 0 209 c.title_len = 0 210 c.flags = 0 211 db.n_chapters = db.n_chapters + 1 212 return id 213} 214 215func nx_storydb_close_chapter(db: *StoryDb, chapter_id: i64, 216 scene_last: i64) { 217 if chapter_id < 0 { return } 218 if chapter_id >= db.n_chapters { return } 219 let c: *StoryChapter = nx_storydb_chapter_at(db, chapter_id) 220 c.scene_last = scene_last 221} 222 223// ===== byte-level helpers (must precede their callers) ============ 224 225func nx_storydb_load_u8(p: *u8, i: i64) -> i64 { 226 let q: *u8 = ((p as i64) + i) as *u8 227 return *q 228} 229 230func nx_storydb_store_u8(p: *u8, i: i64, v: i64) { 231 let q: *u8 = ((p as i64) + i) as *u8 232 *q = v as u8 233} 234 235// ===== summary arena helpers ====================================== 236// 237// Append a UTF-8 string into the summary arena and return its 238// offset. Returns -1 on overflow. Caller stores (offset, length) 239// in the beat / scene / chapter title fields. 240 241func nx_storydb_intern_summary(db: *StoryDb, s: *u8, n: i64) -> i64 { 242 if db.summary_len + n > db.summary_cap { return -1 } 243 let off: i64 = db.summary_len 244 var i: i64 = 0 245 while i < n { 246 let src_byte: i64 = nx_storydb_load_u8(s, i) 247 nx_storydb_store_u8(db.summary_buf, off + i, src_byte) 248 i = i + 1 249 } 250 db.summary_len = db.summary_len + n 251 return off 252}