code wiki / (root) / nx_bspfacts_lib.nx

nx_bspfacts_lib.nx source

↩ module page · 188 lines · 8745 B

1// nx_bspfacts_lib.nx -- BODYSLIDE PRESET FACT EXTRACTION CORE (the Bethesda body-preset dialect; 2// /compare/koikatsu card-class lane, admitted 2026-08-30). A BodySlide preset is an XML document: 3// root <SliderPresets>, one or more <Preset name= set=> elements each holding <Group name=/> rows 4// and <SetSlider name= size="big|small" value=/> rows. 5// SCHEMA GROUND TRUTH is the mirrored reference parser (BodySlide GPL-3.0, 6// src/components/SliderPresets.cpp: FloatAttribute("value")/100 on load, value=(int)(v*100) on 7// save) -- read as an ORACLE, no line of it copied; the licence governs the TOOL, each preset 8// file is its own author's work and stays an unpublished local oracle (k-wall as the KK lane). 9// Values are PERMIL of the slider's percent scale: value="57" -> 570; "57.5" -> 575; "-25" -> -250. 10// Extra decimal digits beyond the first are TRUNCATED (declared imprecision: the reference saver 11// emits integers, so anything past tenths is hand-edited noise). Out-of-range values are REPORTED, 12// never clamped -- numbers, not verdicts. 13// Byte-scan primitives are COMPOSED from the sibling dialect lib (kk_find/kk_count/kk_b), never 14// re-typed -- a bug fixed by extraction stays fixed; a 4th hand copy is the duplicate-ruler defect. 15// Slots: [0] is_preset [1] preset_count [2] setslider_count [3] group_count [4] big_count 16// [5] small_count [6] first_name_off [7] first_name_len [8] first_set_off [9] first_set_len 17// license_tier: ORIGINAL No hw writes (Rule 26). 18import "nx_syscalls.nx" 19import "nx_kkfacts_lib.nx" 20 21const BSP_N_SLOTS: i64 = 10 22const BSP_HEAD_SCAN: i64 = 512 // the root element must appear in the first 512 bytes (xml decl + BOM headroom) 23const BSP_PERMIL_PER_PCT: i64 = 10 // 1 percent = 10 permil 24const BSP_QUOTE: i64 = 34 // '"' -- constructed, the lexer never sees a quote inside a needle 25const BSP_CH_BIG: i64 = 98 // 'b' -- first byte of size="big" 26const BSP_CH_SMALL: i64 = 115 // 's' -- first byte of size="small" 27const BSP_CH_MINUS: i64 = 45 28const BSP_CH_DOT: i64 = 46 29const BSP_CH_0: i64 = 48 30const BSP_CH_9: i64 = 57 31const BSP_VAL_BAD: i64 = 0 - 1000000 // impossible slider value (-100000 percent); decode stops on it 32 33// find attribute <key> inside the tag window [at,end); returns the VALUE offset (first byte after 34// the opening quote) and writes its length to lenout[0]; -1 = absent, unquoted, or unterminated. 35func bsp_attr(buf: *u8, at: i64, end: i64, key: *u8, lenout: *i64) -> i64 { 36 lenout[0] = 0 - 1 37 let kpos: i64 = kk_find(buf, at, end, key) 38 if kpos < 0 { return 0 - 1 } 39 var m: i64 = 0 40 while key[m] != (0 as u8) { m = m + 1 } 41 var voff: i64 = kpos + m 42 if voff >= end { return 0 - 1 } 43 if kk_b(buf, voff) != BSP_QUOTE { return 0 - 1 } 44 voff = voff + 1 45 var q: i64 = voff 46 var closeq: i64 = 0 - 1 47 while q < end { if kk_b(buf, q) == BSP_QUOTE { closeq = q; q = end } else { q = q + 1 } } 48 if closeq < 0 { return 0 - 1 } 49 lenout[0] = closeq - voff 50 return voff 51} 52 53// count SetSlider size classes without a quote-bearing needle: find each size= and classify by the 54// first value byte ('b' or 's'). 55func bsp_count_size(buf: *u8, start: i64, end: i64, ch: i64) -> i64 { 56 var c: i64 = 0 57 var i: i64 = start 58 var go: i64 = 1 59 while go == 1 { 60 let s: i64 = kk_find(buf, i, end, "size=" as *u8) 61 if s < 0 { go = 0 } else { 62 if s + 6 < end { if kk_b(buf, s + 5) == BSP_QUOTE { if kk_b(buf, s + 6) == ch { c = c + 1 } } } 63 i = s + 5 64 } 65 } 66 return c 67} 68 69// parse a value attribute span to PERMIL. Accepts [-]digits[.digits]; first decimal digit kept, 70// the rest truncated (declared above). Anything else is BSP_VAL_BAD -- fail closed, never guess. 71func bsp_val_permil(buf: *u8, off: i64, len: i64) -> i64 { 72 if len <= 0 { return BSP_VAL_BAD } 73 var i: i64 = 0 74 var neg: i64 = 0 75 var whole: i64 = 0 76 var tenth: i64 = 0 77 var tset: i64 = 0 78 var dot: i64 = 0 79 var digits: i64 = 0 80 while i < len { 81 let c: i64 = kk_b(buf, off + i) 82 if c == BSP_CH_MINUS { 83 if i != 0 { return BSP_VAL_BAD } 84 neg = 1 85 } else { if c == BSP_CH_DOT { 86 if dot == 1 { return BSP_VAL_BAD } 87 dot = 1 88 } else { if c < BSP_CH_0 { return BSP_VAL_BAD } else { if c > BSP_CH_9 { return BSP_VAL_BAD } else { 89 digits = digits + 1 90 if dot == 0 { whole = whole * BSP_PERMIL_PER_PCT + ((c - BSP_CH_0) * BSP_PERMIL_PER_PCT) / BSP_PERMIL_PER_PCT } 91 if dot == 1 { if tset == 0 { tenth = c - BSP_CH_0; tset = 1 } } 92 } } } } 93 i = i + 1 94 } 95 if digits == 0 { return BSP_VAL_BAD } 96 var v: i64 = whole * BSP_PERMIL_PER_PCT + tenth 97 if neg == 1 { return 0 - v } 98 return v 99} 100 101// probe core: buf holds the first n bytes; file_size = true size (truncation honesty, as the KK 102// lane). facts[] slots documented in the header. Census only over a COMPLETE document -- a 103// truncated count is an undercount and abstains at -1. 104func bsp_probe(buf: *u8, n: i64, file_size: i64, facts: *i64) -> i64 { 105 var i: i64 = 0 106 while i < BSP_N_SLOTS { facts[i] = 0 - 1; i = i + 1 } 107 facts[0] = 0 108 if n < 32 { return 0 } 109 var head_end: i64 = BSP_HEAD_SCAN 110 if head_end > n { head_end = n } 111 let root: i64 = kk_find(buf, 0, head_end, "<SliderPresets" as *u8) 112 if root < 0 { return 0 } 113 let p0: i64 = kk_find(buf, root, n, "<Preset " as *u8) 114 if p0 < 0 { return 0 } 115 facts[0] = 1 116 if file_size <= n { 117 facts[1] = kk_count(buf, root, n, "<Preset " as *u8) 118 facts[2] = kk_count(buf, root, n, "<SetSlider " as *u8) 119 facts[3] = kk_count(buf, root, n, "<Group " as *u8) 120 facts[4] = bsp_count_size(buf, root, n, BSP_CH_BIG) 121 facts[5] = bsp_count_size(buf, root, n, BSP_CH_SMALL) 122 } 123 let tend: i64 = kk_find(buf, p0, n, ">" as *u8) 124 if tend > 0 { 125 let tmp: *i64 = sys_mmap(16) as *i64 126 let noff: i64 = bsp_attr(buf, p0, tend, "name=" as *u8, tmp) 127 if noff >= 0 { facts[6] = noff; facts[7] = tmp[0] } 128 let soff: i64 = bsp_attr(buf, p0, tend, "set=" as *u8, tmp) 129 if soff >= 0 { facts[8] = soff; facts[9] = tmp[0] } 130 } 131 return 0 132} 133 134// FIRST-preset slider decode, honest-partial (the kk_decode_sliders contract shape). sizesel: 135// 0 = big, 1 = small. Emits per-slider name spans (offsets into buf) + permil values. meta[0] = 136// declared rows of that size inside the first Preset window, or -1 when no preset exists. A 137// malformed row STOPS the parse so parsed < declared is visible, never papered over. 138func bsp_decode(buf: *u8, n: i64, sizesel: i64, name_offs: *i64, name_lens: *i64, vals: *i64, outcap: i64, meta: *i64) -> i64 { 139 meta[0] = 0 - 1 140 var head_end: i64 = BSP_HEAD_SCAN 141 if head_end > n { head_end = n } 142 let root: i64 = kk_find(buf, 0, head_end, "<SliderPresets" as *u8) 143 if root < 0 { return 0 } 144 let p0: i64 = kk_find(buf, root, n, "<Preset " as *u8) 145 if p0 < 0 { return 0 } 146 var wend: i64 = kk_find(buf, p0 + 1, n, "</Preset>" as *u8) 147 if wend < 0 { wend = n } 148 var want: i64 = BSP_CH_BIG 149 if sizesel == 1 { want = BSP_CH_SMALL } 150 meta[0] = 0 151 var parsed: i64 = 0 152 var cur: i64 = p0 153 var go: i64 = 1 154 let tmp: *i64 = sys_mmap(16) as *i64 155 while go == 1 { 156 let s: i64 = kk_find(buf, cur, wend, "<SetSlider " as *u8) 157 if s < 0 { go = 0 } else { 158 let tend: i64 = kk_find(buf, s, wend, ">" as *u8) 159 if tend < 0 { go = 0 } else { 160 let szoff: i64 = bsp_attr(buf, s, tend, "size=" as *u8, tmp) 161 var mine: i64 = 0 162 if szoff >= 0 { if tmp[0] >= 1 { if kk_b(buf, szoff) == want { mine = 1 } } } 163 if mine == 1 { 164 meta[0] = meta[0] + 1 165 let noff: i64 = bsp_attr(buf, s, tend, "name=" as *u8, tmp) 166 let nlen: i64 = tmp[0] 167 let voff: i64 = bsp_attr(buf, s, tend, "value=" as *u8, tmp) 168 let vlen: i64 = tmp[0] 169 var ok: i64 = 1 170 if noff < 0 { ok = 0 } 171 if voff < 0 { ok = 0 } 172 var pv: i64 = BSP_VAL_BAD 173 if ok == 1 { pv = bsp_val_permil(buf, voff, vlen) } 174 if pv == BSP_VAL_BAD { ok = 0 } 175 if ok == 0 { return parsed } 176 if parsed < outcap { 177 name_offs[parsed] = noff 178 name_lens[parsed] = nlen 179 vals[parsed] = pv 180 } 181 parsed = parsed + 1 182 } 183 cur = tend + 1 184 } 185 } 186 } 187 return parsed 188}