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}