nx_emit.nx source
↩ module page · 138 lines · 5408 B
1// nx_emit.nx -- WRITING arc, rung W-ING-4: turn extracted scene DATA into the two
2// strings the downstream consumers actually want:
3// em_image_prompt -> a bare comma-tag prompt for IMAGE-GEN ("mira, garden, dusk")
4// em_companion_ctx -> a labeled context line for the COMPANION LLM
5// ("present: mira | desc: garden, dusk | dialogue: 1")
6//
7// Both are deterministic assemblies over (a) the roster names present in the scene
8// and (b) the descriptor-lexicon terms present in the scene -- reusing the
9// WHOLE-WORD counter from nx_ingest (DRY). CONTENT-AGNOSTIC: the operator's lane
10// supplies the real roster + lexicon (incl. outfit/MPL + register terms); the
11// engine emits the prompt/context identically for any register. It never invents
12// a tag -- only what is present in the text appears (proven by the empty-scene
13// neg-control in the gate).
14//
15// Pure integer, NO syscalls. Caller owns dst (>= a few hundred bytes is ample).
16//
17// license_tier: ORIGINAL
18// module: nishi-core.write.emit
19// depends: nishi-core.write.ingest
20// capability: WRITE_PROMPT_EMIT
21import "nx_ingest.nx"
22import "nx_ingest_scene.nx" // ig_* scene/entity extractor
23
24// append null-terminated src to dst at dlen; return new length (dst kept null-term)
25func em_append(dst: *u8, dlen: i64, src: *u8) -> i64 {
26 var d: i64 = dlen
27 var s: i64 = 0
28 while src[s] != (0 as u8) { dst[d] = src[s]; d = d + 1; s = s + 1 }
29 dst[d] = 0 as u8
30 return d
31}
32
33// append the null-terminated term at terms[off..]; return new length
34func em_append_at(dst: *u8, dlen: i64, terms: *u8, off: i64) -> i64 {
35 var d: i64 = dlen
36 var s: i64 = off
37 while terms[s] != (0 as u8) { dst[d] = terms[s]; d = d + 1; s = s + 1 }
38 dst[d] = 0 as u8
39 return d
40}
41
42// append a non-negative integer in decimal; return new length
43func em_append_num(dst: *u8, dlen: i64, v: i64) -> i64 {
44 var d: i64 = dlen
45 if v == 0 { dst[d] = 48 as u8; d = d + 1; dst[d] = 0 as u8; return d }
46 var m: i64 = v
47 if m < 0 { dst[d] = 45 as u8; d = d + 1; m = 0 - m }
48 var tmp: i64 = m
49 var ndig: i64 = 0
50 while tmp > 0 { ndig = ndig + 1; tmp = tmp / 10 }
51 let p: i64 = d + ndig
52 dst[p] = 0 as u8
53 var q: i64 = p
54 while m > 0 { q = q - 1; dst[q] = (48 + (m % 10)) as u8; m = m / 10 }
55 return p
56}
57
58// append each lexicon term that is present (whole-word) in t[a..b), comma-joined.
59// lead=1 prefixes ", " before the first emitted item (to chain onto prior content).
60func em_append_present(dst: *u8, dlen: i64, t: *u8, a: i64, b: i64, terms: *u8, nterms: i64, lead: i64) -> i64 {
61 var d: i64 = dlen
62 var first: i64 = 1 - lead
63 var k: i64 = 0
64 while k < nterms {
65 let off: i64 = ig_name_off(terms, k)
66 if ig_count_name_in(t, a, b, terms, off) > 0 {
67 if first == 0 { d = em_append(dst, d, ", \x00" as *u8) }
68 d = em_append_at(dst, d, terms, off)
69 first = 0
70 }
71 k = k + 1
72 }
73 return d
74}
75
76// IMAGE-GEN prompt: present roster names, then present descriptor terms, comma-joined
77func em_image_prompt(t: *u8, a: i64, b: i64, roster: *u8, nnames: i64, dterms: *u8, ndterms: i64, dst: *u8) -> i64 {
78 dst[0] = 0 as u8
79 let dn: i64 = em_append_present(dst, 0, t, a, b, roster, nnames, 0)
80 var lead2: i64 = 0
81 if dn > 0 { lead2 = 1 }
82 let d: i64 = em_append_present(dst, dn, t, a, b, dterms, ndterms, lead2)
83 return d
84}
85
86// IMAGE-GEN prompt, FOCUS-FIRST: the scene's subject character (focus, by mention
87// count -- from ig_extract) leads the prompt because image models weight early
88// tokens most; then the other present characters, then the descriptor tags.
89func em_image_prompt_focus(t: *u8, a: i64, b: i64, roster: *u8, nnames: i64, focus: i64, dterms: *u8, ndterms: i64, dst: *u8) -> i64 {
90 dst[0] = 0 as u8
91 var d: i64 = 0
92 if focus >= 0 {
93 if focus < nnames {
94 d = em_append_at(dst, d, roster, ig_name_off(roster, focus))
95 }
96 }
97 var k: i64 = 0
98 while k < nnames {
99 if k != focus {
100 let off: i64 = ig_name_off(roster, k)
101 if ig_count_name_in(t, a, b, roster, off) > 0 {
102 if d > 0 { d = em_append(dst, d, ", \x00" as *u8) }
103 d = em_append_at(dst, d, roster, off)
104 }
105 }
106 k = k + 1
107 }
108 var lead: i64 = 0
109 if d > 0 { lead = 1 }
110 d = em_append_present(dst, d, t, a, b, dterms, ndterms, lead)
111 return d
112}
113
114// COMPANION context: "present: <names> | desc: <terms> | dialogue: <n>"
115func em_companion_ctx(t: *u8, a: i64, b: i64, roster: *u8, nnames: i64, dterms: *u8, ndterms: i64, dst: *u8) -> i64 {
116 dst[0] = 0 as u8
117 var d: i64 = em_append(dst, 0, "present: \x00" as *u8)
118 let after_names: i64 = d
119 d = em_append_present(dst, d, t, a, b, roster, nnames, 0)
120 if d == after_names { d = em_append(dst, d, "(none)\x00" as *u8) }
121 d = em_append(dst, d, " | desc: \x00" as *u8)
122 let after_desc: i64 = d
123 d = em_append_present(dst, d, t, a, b, dterms, ndterms, 0)
124 if d == after_desc { d = em_append(dst, d, "(none)\x00" as *u8) }
125 d = em_append(dst, d, " | dialogue: \x00" as *u8)
126 var dq: i64 = 0
127 var ins: i64 = 0
128 var p: i64 = a
129 while p < b {
130 if t[p] == (34 as u8) {
131 if ins == 0 { dq = dq + 1 }
132 ins = 1 - ins
133 }
134 p = p + 1
135 }
136 d = em_append_num(dst, d, dq)
137 return d
138}