code wiki / (root) / nx_tone.nx

nx_tone.nx source

↩ module page · 155 lines · 6294 B

1// nx_tone.nx -- WRITING arc, rung W-TONE-1: the VOICE / TONE FINGERPRINT. 2// Operator intent: "help me write to people in my tone." This organ turns a 3// reference text (the operator's own writing) into a deterministic, length- 4// invariant STYLE VECTOR, and scores how close any candidate draft sits to it -- 5// so a generated draft can be checked/steered toward "my voice". 6// 7// Pure integer, NO floats, NO syscalls, NO own buffers (the nx_note shape, so any 8// lane -- tutor, local-LLM, daemon -- can call it). Reuses nx_writecraft 9// (counts) + nx_writestyle (prose signals) -- DRY, never reimplemented. 10// 11// A FINGERPRINT is TN_DIMS dims, each normalized to PER-1000 so a 40-word note 12// and a 40k-word corpus compare directly (the key to honest matching): 13// tf[0] avg sentence length (words *1000 / sentences) -- cadence 14// tf[1] avg word length letters (letters *1000 / words) -- diction weight 15// tf[2] syllables per word (syll *1000 / words) -- lexical density 16// tf[3] complex-word ratio (complex *1000 / words) -- formality 17// tf[4] adverb ratio (adverbs *1000 / words) 18// tf[5] passive ratio (passive *1000 / words) 19// tf[6] weak/filler ratio (weak *1000 / words) 20// tf[7] dialogue fraction (chars-in-quotes *1000 / chars) -- voice 21// tf[8] hard-sentence ratio (hard *1000 / sentences) -- rhythm 22// tf[9] comma density (commas *1000 / words) -- subordination 23// tf[10] exclamation density (bangs *1000 / sentences) -- emotional pitch 24// tf[11] question density (qmarks *1000 / sentences) -- rhetorical style 25// 26// tn_dist = L1 (sum of abs diffs). tn_closer is a THRESHOLD-FREE comparator 27// (which of two voices a candidate matches) so there is NO magic-number cutoff in 28// the organ; a match THRESHOLD, when one is needed, arrives as operator DATA 29// (rule 11), exactly like the register classifier's threshold. 30// 31// HONEST LIMIT (v1): L1 over raw per-1000 dims is dominated by the high-dynamic- 32// range dims (cadence tf[0] can reach ~30000 while ratio dims sit ~0..400). The 33// gate proves correct voice-matching on contrasting samples; PER-DIMENSION 34// WEIGHTING (operator DATA weights, like register weights) is the next rung. 35// 36// license_tier: ORIGINAL 37// module: nishi-core.write.tone 38// depends: nishi-core.write.craft, nishi-core.write.style 39// capability: WRITE_TONE_FINGERPRINT 40import "nx_writecraft.nx" 41import "nx_writestyle.nx" 42 43const TN_DIMS: i64 = 12 44 45// count commas / exclamations / questions into out3[0..2] 46func tn_punct(t: *u8, n: i64, out3: *i64) -> i64 { 47 var commas: i64 = 0 48 var bangs: i64 = 0 49 var quest: i64 = 0 50 var i: i64 = 0 51 while i < n { 52 let c: i64 = t[i] as i64 53 if c == 44 { commas = commas + 1 } // , 54 if c == 33 { bangs = bangs + 1 } // ! 55 if c == 63 { quest = quest + 1 } // ? 56 i = i + 1 57 } 58 out3[0] = commas 59 out3[1] = bangs 60 out3[2] = quest 61 return 0 62} 63 64// fill tf[0..TN_DIMS-1] (caller allocs tf >= 12 i64); sc = caller scratch >= 8 i64 65// (reused for wc_counts, then ws_signals, then tn_punct). returns the word count. 66func tn_fingerprint(t: *u8, n: i64, tf: *i64, sc: *i64) -> i64 { 67 wc_counts(t, n, sc) 68 var W: i64 = sc[1] 69 var S: i64 = sc[2] 70 let Y: i64 = sc[3] 71 let C: i64 = sc[4] 72 let L: i64 = sc[0] 73 if W < 1 { W = 1 } 74 if S < 1 { S = 1 } 75 tf[0] = (W * 1000) / S 76 tf[1] = (L * 1000) / W 77 tf[2] = (Y * 1000) / W 78 tf[3] = (C * 1000) / W 79 ws_signals(t, n, sc) 80 tf[4] = (sc[0] * 1000) / W // adverbs 81 tf[5] = (sc[1] * 1000) / W // passive 82 tf[6] = (sc[2] * 1000) / W // weak 83 tf[7] = sc[3] // dialogue_milli (already per-1000) 84 tf[8] = (sc[5] * 1000) / S // hard sentences 85 tn_punct(t, n, sc) 86 tf[9] = (sc[0] * 1000) / W // commas 87 tf[10] = (sc[1] * 1000) / S // exclamations 88 tf[11] = (sc[2] * 1000) / S // questions 89 return W 90} 91 92// L1 distance between two fingerprints over k dims (lower = more alike) 93func tn_dist(a: *i64, b: *i64, k: i64) -> i64 { 94 var sum: i64 = 0 95 var i: i64 = 0 96 while i < k { 97 var d: i64 = a[i] - b[i] 98 if d < 0 { d = 0 - d } 99 sum = sum + d 100 i = i + 1 101 } 102 return sum 103} 104 105// 1 if cand is closer to voice a than to voice b (threshold-free), else 0 106func tn_closer(cand: *i64, a: *i64, b: *i64, k: i64) -> i64 { 107 let da: i64 = tn_dist(cand, a, k) 108 let db: i64 = tn_dist(cand, b, k) 109 if da < db { return 1 } 110 return 0 111} 112 113// ---- W-TONE-2: PER-DIMENSION WEIGHTING + DRIFT (closes the v1 L1-dominance limit) 114// Weights w[] are PER-1000 (w[i]=1000 => unweighted; w[i]=0 => ignore that dim), 115// so an operator DATA file can damp the high-range cadence dim and let diction / 116// signal dims carry the match -- no magic numbers baked into the organ (rule 11). 117 118// weighted L1: sum w[i]*|a-b| / 1000. w[i]=1000 reproduces tn_dist exactly. 119func tn_dist_w(a: *i64, b: *i64, w: *i64, k: i64) -> i64 { 120 var sum: i64 = 0 121 var i: i64 = 0 122 while i < k { 123 var d: i64 = a[i] - b[i] 124 if d < 0 { d = 0 - d } 125 sum = sum + (w[i] * d) / 1000 126 i = i + 1 127 } 128 return sum 129} 130 131// 1 if cand is weighted-closer to voice a than to voice b, else 0 132func tn_closer_w(cand: *i64, a: *i64, b: *i64, w: *i64, k: i64) -> i64 { 133 let da: i64 = tn_dist_w(cand, a, w, k) 134 let db: i64 = tn_dist_w(cand, b, w, k) 135 if da < db { return 1 } 136 return 0 137} 138 139// per-dim SIGNED weighted deviation of cand from a voice ref into out[0..k-1]: 140// out[i] > 0 => cand is HIGHER than the ref on dim i (steer it down) 141// out[i] < 0 => cand is LOWER than the ref on dim i (steer it up) 142// returns the total weighted L1 distance (== tn_dist_w for non-negative weights). 143func tn_drift(cand: *i64, ref: *i64, w: *i64, k: i64, out: *i64) -> i64 { 144 var total: i64 = 0 145 var i: i64 = 0 146 while i < k { 147 let raw: i64 = cand[i] - ref[i] 148 out[i] = (w[i] * raw) / 1000 149 var d: i64 = out[i] 150 if d < 0 { d = 0 - d } 151 total = total + d 152 i = i + 1 153 } 154 return total 155}