code wiki / (root) / nx_greeting.nx

nx_greeting.nx source

↩ module page · 138 lines · 6472 B

1// nx_greeting.nx -- WRITING arc, rung W-GR-1: THE THREE-BEAT GREETING. 2// 3// Closes gap-queue row #4 of nx_writebench (pop 342/378). The ruler (arXiv:2601.14324) finds the 4// greeting is THE LOAD-BEARING ARTIFACT of a character bot: it is the only text every user reads, 5// it sets the register, and it is what makes an opening feel like a scene instead of a bio. Its 6// structure is three ordered beats: 7// 1. SELF-INTRODUCTION -- who is speaking 8// 2. SCENARIO / INCITING INCIDENT -- the situation that has already started 9// 3. DIRECT ADDRESS -- the turn to the reader that hands them the move 10// 11// ★WHY ORDER IS PART OF THE MEASUREMENT AND NOT A STYLE OPINION. The same three beats in a 12// different order produce a different artifact: address-first is a chatbot prompt, scenario-first 13// with no introduction is a narrator, and introduction-only is a character sheet. The ruler's 14// finding is about a SEQUENCE, so a detector that only counted presence would call all four of 15// those "complete" and measure nothing. Presence and order are reported separately, and the 16// headline requires both. 17// 18// ★CRAFT, NOT COMPLIANCE. This organ scores STRUCTURE. It has no view on register or content: an 19// SFW greeting and an explicit one are graded by the same three beats, because the thing being 20// measured is whether the opening works, not what it is about. Register lives in nx_register and 21// the legal line lives upstream in rg_decision -- see [[feedback-hosting-legal-posture]]. 22// 23// Lexicons stay pure operator DATA (the packed NUL-terminated terms + parallel weights contract 24// nx_register/nx_dialogue/nx_realperson already use), so a lane can tune or translate the markers 25// without touching this logic, and this organ authors no content. 26// 27// Pure integer, NO syscalls, caller owns every buffer. 28// license_tier: ORIGINAL 29// module: nishi-core.write.greeting 30// capability: WRITE_GREETING_STRUCTURE 31 32const GR_BEAT_INTRO: i64 = 0 33const GR_BEAT_SCENARIO: i64 = 1 34const GR_BEAT_ADDRESS: i64 = 2 35const GR_NOT_FOUND: i64 = 0 - 1 36 37// verdicts 38const GR_FLAT: i64 = 0 // fewer than two beats: a bio or a line of dialogue, not a greeting 39const GR_PARTIAL: i64 = 1 // two beats, or three out of canonical order 40const GR_COMPLETE: i64 = 2 // all three, in order 41 42func gr_lower(c: i64) -> i64 { if c >= 65 { if c <= 90 { return c + 32 } } return c } 43func gr_len(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } 44func gr_is_wordch(c: i64) -> i64 { 45 if c >= 97 { if c <= 122 { return 1 } } 46 if c >= 65 { if c <= 90 { return 1 } } 47 if c >= 48 { if c <= 57 { return 1 } } 48 if c == 39 { return 1 } // apostrophe: "I'm" is one token 49 return 0 50} 51 52// FIRST position of a whole-word, case-insensitive match, or GR_NOT_FOUND. 53// ★POSITION, NOT JUST PRESENCE -- the order check is the whole point, so the primitive has to 54// return WHERE, and it must not lose that index while searching (the banked parser law). 55func gr_find_word(buf: *u8, a: i64, b: i64, term: *u8) -> i64 { 56 let m: i64 = gr_len(term) 57 if m == 0 { return GR_NOT_FOUND } 58 if b - a < m { return GR_NOT_FOUND } 59 var i: i64 = a 60 while i + m <= b { 61 var j: i64 = 0 62 var same: i64 = 1 63 while j < m { 64 if gr_lower(buf[i + j] as i64) != gr_lower(term[j] as i64) { same = 0; j = m } else { j = j + 1 } 65 } 66 if same == 1 { 67 var lok: i64 = 1 68 var rok: i64 = 1 69 if i > a { if gr_is_wordch(buf[i - 1] as i64) == 1 { lok = 0 } } 70 if i + m < b { if gr_is_wordch(buf[i + m] as i64) == 1 { rok = 0 } } 71 if lok == 1 { if rok == 1 { return i } } 72 } 73 i = i + 1 74 } 75 return GR_NOT_FOUND 76} 77 78// earliest position at which ANY packed term appears, or GR_NOT_FOUND. 79func gr_beat_pos(buf: *u8, a: i64, b: i64, terms: *u8, nterms: i64) -> i64 { 80 var best: i64 = GR_NOT_FOUND 81 var ti: i64 = 0 82 var off: i64 = 0 83 while ti < nterms { 84 let t: *u8 = ((terms as i64) + off) as *u8 85 let tl: i64 = gr_len(t) 86 if tl == 0 { ti = nterms } else { 87 let p: i64 = gr_find_word(buf, a, b, t) 88 if p != GR_NOT_FOUND { if best == GR_NOT_FOUND { best = p } else { if p < best { best = p } } } 89 off = off + tl + 1 90 ti = ti + 1 91 } 92 } 93 return best 94} 95 96// how many of the three beats are present at all 97func gr_beats_present(pi: i64, ps: i64, pa: i64) -> i64 { 98 var n: i64 = 0 99 if pi != GR_NOT_FOUND { n = n + 1 } 100 if ps != GR_NOT_FOUND { n = n + 1 } 101 if pa != GR_NOT_FOUND { n = n + 1 } 102 return n 103} 104 105// are the three first-occurrences in canonical order? 1 = yes. Only meaningful when all three exist. 106func gr_ordered(pi: i64, ps: i64, pa: i64) -> i64 { 107 if pi == GR_NOT_FOUND { return 0 } 108 if ps == GR_NOT_FOUND { return 0 } 109 if pa == GR_NOT_FOUND { return 0 } 110 if pi < ps { if ps < pa { return 1 } } 111 return 0 112} 113 114// THE VERDICT. Presence alone is never COMPLETE -- see the order note in the header. 115func gr_verdict(pi: i64, ps: i64, pa: i64) -> i64 { 116 let n: i64 = gr_beats_present(pi, ps, pa) 117 if n < 2 { return GR_FLAT } 118 if n == 3 { if gr_ordered(pi, ps, pa) == 1 { return GR_COMPLETE } } 119 return GR_PARTIAL 120} 121 122// permil score for a board: beats carry 2/3 of the weight, order the remaining 1/3, so a well-formed 123// but scrambled greeting still scores well above a one-beat stub without ever reaching COMPLETE. 124const GR_PERMIL: i64 = 1000 125// ⚠INTEGER TRUNCATION MADE A PERFECT GREETING SCORE 999. (3*1000*2)/9 = 666 and 1000/3 = 333, so the 126// best possible artifact came out one permil short of the scale it is measured on. That is a small 127// lie with a real cost: every board would print 999 for a flawless opening and someone would 128// eventually spend a cycle hunting the missing permil, or worse, "fix" the greeting. 129// ★A SCORING FUNCTION WHOSE MAXIMUM IS NOT ITS STATED MAXIMUM IS A DEFECT, NOT A ROUNDING DETAIL. 130// COMPLETE is defined as all three beats in order, so it returns the full scale exactly; the 131// partial ladder keeps the truncating form, where losing a permil changes nothing. 132func gr_score_permil(pi: i64, ps: i64, pa: i64) -> i64 { 133 if gr_ordered(pi, ps, pa) == 1 { return GR_PERMIL } 134 let n: i64 = gr_beats_present(pi, ps, pa) 135 var s: i64 = (n * GR_PERMIL * 2) / 9 // 0 / 222 / 444 / 666 136 if s > GR_PERMIL { s = GR_PERMIL } 137 return s 138}