code wiki / (root) / nx_pov_shift.nx

nx_pov_shift.nx source

↩ module page · 473 lines · 17645 B

1// nx_pov_shift.nx -- 3rd-person -> 1st-person POV transform. 2// 3// Takes source text (post-HTML-strip, post-text-ingest) and emits 4// a variant where the chosen protagonist's pronouns shift to 5// first person. The OTHER characters' pronouns stay 3rd person. 6// 7// Use case (from user): 8// "the story needs to happen like a literotica story from my pov 9// which i think may be both but the output has to go into a joy 10// caption and future nishi captioning machine for some systems 11// like z image, for audio like erotica that we can train to be 12// like an erotic novel via comping to s class" 13// 14// So we emit TWO variants per source paragraph: 15// 1. USER_AS_I -- protagonist pronouns become "I/me/my". 16// Reads as literotica narrator-as-reader. 17// 2. COMPANION_AS_HER -- companion stays "she/her" so image-prompt 18// downstreams (z-image / JoyCaption / future 19// Nishi captioner) get a clean scene 20// description with the visible character 21// described in third person. 22// 23// Both variants are emitted; the caller picks per downstream 24// (audio narration uses USER_AS_I, image prompt uses 25// COMPANION_AS_HER, video sub-system picks based on its prompt 26// schema). 27// 28// Per cardinal feedback-build-intelligence-never-strip-features: 29// every replacement preserves SOURCE BYTE LENGTH AND ORDERING 30// outside of the substitution. No paragraph re-flow, no 31// punctuation normalization, no quotation-mark "smartening". 32// 33// nx_safety_envelope: 34// intended_use: "Transform 3rd-person source text to 1st- 35// person POV for the chosen protagonist 36// gender, leaving other characters in 3rd 37// person. Emits user-as-I and companion- 38// as-her variants per source span." 39// sil_target: SIL2 40// asil_target: QM 41// dal_target: DAL C 42// iec_62304_class: NONE 43// evidence: [no_floating_point, 44// bounded_pronoun_table_static, 45// word_boundary_replacement_no_substring_match, 46// source_preserved_outside_replacements, 47// capitalization_preserved] 48// hazard_register: [bug-tape-pronoun-in-name-falsely-matched, 49// bug-tape-possessive-his-her-confusion, 50// bug-tape-contraction-shes-hes-missed] 51// residual_risk: "ASCII pronoun coverage only; non-English 52// pronouns pass through unchanged. Proper 53// nouns matching pronoun spelling (e.g. 54// someone literally named 'He') would mis- 55// substitute -- caller pins protagonist name 56// in V2 to suppress." 57// verdict: NOT_YET_EVALUATED 58 59import "nx_syscalls.nx" 60 61// ===== sealed protagonist gender ================================== 62 63const NX_POV_GENDER_UNKNOWN: i64 = 0 64const NX_POV_GENDER_HE: i64 = 1 // male protagonist -> "I" 65const NX_POV_GENDER_SHE: i64 = 2 // female protagonist -> "I" 66const NX_POV_GENDER_THEY: i64 = 3 // they/them protagonist -> "I" 67 68// ===== sealed POV variant codes =================================== 69 70const NX_POV_VARIANT_SOURCE: i64 = 0 // verbatim source 71const NX_POV_VARIANT_USER_AS_I: i64 = 1 // protagonist -> I 72const NX_POV_VARIANT_COMPANION_3RD: i64 = 2 // protagonist stays 3rd 73 74// ===== byte helpers =============================================== 75 76func nx_pov_load_u8(p: *u8, i: i64) -> i64 { 77 let q: *u8 = ((p as i64) + i) as *u8 78 return *q 79} 80 81func nx_pov_store_u8(p: *u8, i: i64, v: i64) { 82 let q: *u8 = ((p as i64) + i) as *u8 83 *q = v as u8 84} 85 86func nx_pov_to_lower(c: i64) -> i64 { 87 if c >= 0x41 { 88 if c <= 0x5A { return c | 0x20 } 89 } 90 return c 91} 92 93func nx_pov_is_upper(c: i64) -> i64 { 94 if c >= 0x41 { 95 if c <= 0x5A { return 1 } 96 } 97 return 0 98} 99 100func nx_pov_is_alnum(c: i64) -> i64 { 101 if c >= 0x30 { 102 if c <= 0x39 { return 1 } 103 } 104 if c >= 0x41 { 105 if c <= 0x5A { return 1 } 106 } 107 if c >= 0x61 { 108 if c <= 0x7A { return 1 } 109 } 110 return 0 111} 112 113// ===== pronoun substitution table ================================= 114// 115// Given the protagonist gender and a lowercased source pronoun, 116// returns the replacement string (or 0-pointer if no substitution). 117// 118// Coverage: 119// he/she/they -> i him/her/them -> me his/her/their -> my 120// himself/herself/themself -> myself 121// he's/she's/they're -> I'm he'd/she'd/they'd -> I'd 122// he'll/she'll/they'll -> I'll 123 124func nx_pov_replacement(gender: i64, src_word: *u8, src_len: i64, 125 out_len: *i64) -> *u8 { 126 if gender == NX_POV_GENDER_UNKNOWN { *out_len = 0; return 0 as *u8 } 127 128 // Single-pronoun mapping driven by gender. 129 if gender == NX_POV_GENDER_HE { 130 if src_len == 2 { 131 // "he" 132 if nx_pov_load_u8(src_word, 0) == 0x68 { 133 if nx_pov_load_u8(src_word, 1) == 0x65 { 134 *out_len = 1 135 return "I" as *u8 136 } 137 } 138 } 139 if src_len == 3 { 140 // "him" / "his" 141 let a: i64 = nx_pov_load_u8(src_word, 0) 142 let b: i64 = nx_pov_load_u8(src_word, 1) 143 let c: i64 = nx_pov_load_u8(src_word, 2) 144 if a == 0x68 { 145 if b == 0x69 { 146 if c == 0x6D { *out_len = 2; return "me" as *u8 } // him 147 if c == 0x73 { *out_len = 2; return "my" as *u8 } // his 148 } 149 } 150 } 151 if src_len == 4 { 152 // "he's" or "he'd" 153 let a: i64 = nx_pov_load_u8(src_word, 0) 154 let b: i64 = nx_pov_load_u8(src_word, 1) 155 let c: i64 = nx_pov_load_u8(src_word, 2) 156 let d: i64 = nx_pov_load_u8(src_word, 3) 157 if a == 0x68 { 158 if b == 0x65 { 159 if c == 0x27 { 160 if d == 0x73 { *out_len = 3; return "I'm" as *u8 } 161 if d == 0x64 { *out_len = 3; return "I'd" as *u8 } 162 } 163 } 164 } 165 } 166 if src_len == 5 { 167 // "he'll" 168 let a: i64 = nx_pov_load_u8(src_word, 0) 169 let b: i64 = nx_pov_load_u8(src_word, 1) 170 let c: i64 = nx_pov_load_u8(src_word, 2) 171 let d: i64 = nx_pov_load_u8(src_word, 3) 172 let e: i64 = nx_pov_load_u8(src_word, 4) 173 if a == 0x68 { 174 if b == 0x65 { 175 if c == 0x27 { 176 if d == 0x6C { 177 if e == 0x6C { 178 *out_len = 4; return "I'll" as *u8 179 } 180 } 181 } 182 } 183 } 184 } 185 if src_len == 7 { 186 // "himself" 187 let l_him: *u8 = "himself" as *u8 188 var ok: i64 = 1 189 var i: i64 = 0 190 while i < 7 { 191 if nx_pov_load_u8(src_word, i) != nx_pov_load_u8(l_him, i) { 192 ok = 0; i = 7 193 } 194 i = i + 1 195 } 196 if ok == 1 { *out_len = 6; return "myself" as *u8 } 197 } 198 } 199 200 if gender == NX_POV_GENDER_SHE { 201 if src_len == 3 { 202 // "she" / "her" 203 let a: i64 = nx_pov_load_u8(src_word, 0) 204 let b: i64 = nx_pov_load_u8(src_word, 1) 205 let c: i64 = nx_pov_load_u8(src_word, 2) 206 if a == 0x73 { 207 if b == 0x68 { 208 if c == 0x65 { *out_len = 1; return "I" as *u8 } // she 209 } 210 } 211 if a == 0x68 { 212 if b == 0x65 { 213 if c == 0x72 { 214 // "her" is ambiguous: object or possessive. 215 // Most safe default: possessive ("her dress" -> "my dress"). 216 // Caller can refine post-shift. 217 *out_len = 2; return "my" as *u8 218 } 219 } 220 } 221 } 222 if src_len == 4 { 223 // "hers" / "she's" 224 let a: i64 = nx_pov_load_u8(src_word, 0) 225 let b: i64 = nx_pov_load_u8(src_word, 1) 226 let c: i64 = nx_pov_load_u8(src_word, 2) 227 let d: i64 = nx_pov_load_u8(src_word, 3) 228 if a == 0x73 { 229 if b == 0x68 { 230 if c == 0x65 { 231 if d == 0x27 { 232 // would need 5th char to be 's'/'d'/'l'; 233 // 4 chars exactly = "she'" without close -- 234 // skip. 235 *out_len = 0; return 0 as *u8 236 } 237 } 238 } 239 } 240 if a == 0x68 { 241 if b == 0x65 { 242 if c == 0x72 { 243 if d == 0x73 { *out_len = 4; return "mine" as *u8 } 244 } 245 } 246 } 247 } 248 if src_len == 5 { 249 // "she's" / "she'd" 250 let a: i64 = nx_pov_load_u8(src_word, 0) 251 let b: i64 = nx_pov_load_u8(src_word, 1) 252 let c: i64 = nx_pov_load_u8(src_word, 2) 253 let d: i64 = nx_pov_load_u8(src_word, 3) 254 let e: i64 = nx_pov_load_u8(src_word, 4) 255 if a == 0x73 { 256 if b == 0x68 { 257 if c == 0x65 { 258 if d == 0x27 { 259 if e == 0x73 { *out_len = 3; return "I'm" as *u8 } 260 if e == 0x64 { *out_len = 3; return "I'd" as *u8 } 261 } 262 } 263 } 264 } 265 } 266 if src_len == 6 { 267 // "she'll" 268 let l_shll: *u8 = "she'll" as *u8 269 var ok2: i64 = 1 270 var j: i64 = 0 271 while j < 6 { 272 if nx_pov_load_u8(src_word, j) != nx_pov_load_u8(l_shll, j) { 273 ok2 = 0; j = 6 274 } 275 j = j + 1 276 } 277 if ok2 == 1 { *out_len = 4; return "I'll" as *u8 } 278 } 279 if src_len == 7 { 280 // "herself" 281 let l_her: *u8 = "herself" as *u8 282 var okh: i64 = 1 283 var jh: i64 = 0 284 while jh < 7 { 285 if nx_pov_load_u8(src_word, jh) != nx_pov_load_u8(l_her, jh) { 286 okh = 0; jh = 7 287 } 288 jh = jh + 1 289 } 290 if okh == 1 { *out_len = 6; return "myself" as *u8 } 291 } 292 } 293 294 if gender == NX_POV_GENDER_THEY { 295 if src_len == 4 { 296 // "they" 297 let l_they: *u8 = "they" as *u8 298 var oth: i64 = 1 299 var ti: i64 = 0 300 while ti < 4 { 301 if nx_pov_load_u8(src_word, ti) != nx_pov_load_u8(l_they, ti) { 302 oth = 0; ti = 4 303 } 304 ti = ti + 1 305 } 306 if oth == 1 { *out_len = 1; return "I" as *u8 } 307 } 308 if src_len == 4 { 309 // "them" 310 let l_them: *u8 = "them" as *u8 311 var otm: i64 = 1 312 var tj: i64 = 0 313 while tj < 4 { 314 if nx_pov_load_u8(src_word, tj) != nx_pov_load_u8(l_them, tj) { 315 otm = 0; tj = 4 316 } 317 tj = tj + 1 318 } 319 if otm == 1 { *out_len = 2; return "me" as *u8 } 320 } 321 if src_len == 5 { 322 // "their" 323 let l_their: *u8 = "their" as *u8 324 var oth2: i64 = 1 325 var tk: i64 = 0 326 while tk < 5 { 327 if nx_pov_load_u8(src_word, tk) != nx_pov_load_u8(l_their, tk) { 328 oth2 = 0; tk = 5 329 } 330 tk = tk + 1 331 } 332 if oth2 == 1 { *out_len = 2; return "my" as *u8 } 333 } 334 } 335 336 *out_len = 0 337 return 0 as *u8 338} 339 340// ===== capitalize-helper ========================================== 341// 342// If src_was_capitalized, return the first byte of out in uppercase; 343// otherwise lowercase. Returns byte to write. 344 345func nx_pov_apply_case(out_byte: i64, was_upper: i64) -> i64 { 346 if was_upper == 1 { 347 if out_byte >= 0x61 { 348 if out_byte <= 0x7A { return out_byte - 0x20 } 349 } 350 } 351 return out_byte 352} 353 354// ===== top-level POV-shift transform ============================== 355// 356// Walk source bytes, identify candidate word spans, attempt 357// pronoun replacement. Write to dst buffer. Returns dst byte 358// count. 359 360func nx_pov_shift(gender: i64, src: *u8, n: i64, 361 dst: *u8, dst_cap: i64) -> i64 { 362 if dst_cap <= 0 { return 0 } 363 if n <= 0 { return 0 } 364 365 let scratch_raw: *u8 = sys_mmap(16) 366 let scratch: *i64 = scratch_raw as *i64 367 368 var dpos: i64 = 0 369 var p: i64 = 0 370 let BUDGET: i64 = n + 2 371 var iter: i64 = 0 372 while p < n { 373 if iter >= BUDGET { p = n } 374 if p < n { 375 let c: i64 = nx_pov_load_u8(src, p) 376 // Word boundary: previous byte is non-alphanumeric (or BOF). 377 var at_boundary: i64 = 1 378 if p > 0 { 379 let prev: i64 = nx_pov_load_u8(src, p - 1) 380 if nx_pov_is_alnum(prev) == 1 { at_boundary = 0 } 381 if prev == 0x27 { at_boundary = 0 } // apostrophe-inside 382 } 383 // Word start: alpha char. 384 if at_boundary == 1 { 385 if nx_pov_is_alnum(c) == 1 { 386 // Find word end (alnum + apostrophe allowed inside). 387 var q: i64 = p 388 let WORD_BUDGET: i64 = 32 389 var w_iter: i64 = 0 390 var scanning: i64 = 1 391 while scanning == 1 { 392 if w_iter >= WORD_BUDGET { scanning = 0 } 393 if scanning == 1 { 394 if q >= n { scanning = 0 } 395 if scanning == 1 { 396 let qc: i64 = nx_pov_load_u8(src, q) 397 if nx_pov_is_alnum(qc) == 1 { q = q + 1 } 398 if nx_pov_is_alnum(qc) == 0 { 399 if qc == 0x27 { 400 // include apostrophe only if followed by alnum 401 if q + 1 < n { 402 let nc: i64 = nx_pov_load_u8(src, q + 1) 403 if nx_pov_is_alnum(nc) == 1 { q = q + 1 } 404 if nx_pov_is_alnum(nc) == 0 { scanning = 0 } 405 } 406 if q + 1 >= n { scanning = 0 } 407 } 408 if qc != 0x27 { scanning = 0 } 409 } 410 } 411 } 412 w_iter = w_iter + 1 413 } 414 let wlen: i64 = q - p 415 // Build lowercase copy in a stack buffer. 416 let lower_raw: *u8 = sys_mmap(64) 417 var li: i64 = 0 418 while li < wlen { 419 nx_pov_store_u8(lower_raw, li, 420 nx_pov_to_lower(nx_pov_load_u8(src, p + li))) 421 li = li + 1 422 } 423 let was_upper: i64 = nx_pov_is_upper(c) 424 425 var out_len: i64 = 0 426 let rep: *u8 = nx_pov_replacement(gender, lower_raw, wlen, 427 &out_len) 428 if rep != (0 as *u8) { 429 if out_len > 0 { 430 // Write replacement with caps preserved on first char. 431 var ri: i64 = 0 432 while ri < out_len { 433 if dpos < dst_cap { 434 let rb: i64 = nx_pov_load_u8(rep, ri) 435 var wb: i64 = rb 436 if ri == 0 { 437 wb = nx_pov_apply_case(rb, was_upper) 438 } 439 nx_pov_store_u8(dst, dpos, wb) 440 dpos = dpos + 1 441 } 442 ri = ri + 1 443 } 444 p = q 445 iter = iter + 1 446 continue 447 } 448 } 449 // No replacement: pass through verbatim. 450 var ki: i64 = 0 451 while ki < wlen { 452 if dpos < dst_cap { 453 nx_pov_store_u8(dst, dpos, nx_pov_load_u8(src, p + ki)) 454 dpos = dpos + 1 455 } 456 ki = ki + 1 457 } 458 p = q 459 iter = iter + 1 460 continue 461 } 462 } 463 // Non-word byte: pass through. 464 if dpos < dst_cap { 465 nx_pov_store_u8(dst, dpos, c) 466 dpos = dpos + 1 467 } 468 p = p + 1 469 } 470 iter = iter + 1 471 } 472 return dpos 473}