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}