nx_crawl_sufficiency.nx source
↩ module page · 391 lines · 19681 B
1// nx_crawl_sufficiency.nx -- ADAPTIVE STOP: "do we know enough to stop fetching?" -- the /compare/webscraping
2// R5 contract (symbol cs_confidence). Concept source: crawl4ai's AdaptiveCrawler (docs/core/adaptive-crawling.md,
3// StatisticalStrategy in adaptive_crawler.py): confidence = coverage + consistency + saturation, stop at a bar.
4// Sovereign form, integer permil, no floats, no LLM, no embeddings:
5// COVERAGE for each query term t: doc_cov = df_t * 1000 / docs; freq = ilog2(1+tf_t) / ilog2(1+max_tf);
6// term = doc_cov * (1000 + freq_boost * freq / 1000) / 1000, capped at 1000; coverage = mean over terms.
7// CONSISTENCY permil of counted docs that are ON TOPIC: hold at least relevant_permil of the query terms (min 1).
8// (The source uses mean pairwise Jaccard of term sets, which REWARDS redundancy -- five copies of one
9// page score as perfectly consistent. Ours cannot be inflated by copies: see the dedup below.)
10// SATURATION 1000 - new_terms(last window) * 1000 / new_terms(first window): diminishing discovery of NEW
11// vocabulary. UNOBSERVABLE (not 0, not 1000) until two windows exist or when the term set is full.
12// confidence = (w_cov * coverage + w_con * consistency + w_sat * saturation) / (w_cov + w_con + w_sat)
13// EXCEED over the source: a doc whose simhash lands within simhash_ham of an already-counted doc is a NEAR-DUPLICATE
14// and is not counted anywhere (not df, not tf, not the new-term history) -- it is tallied as a dup so the partition
15// docs_added == counted + dups always sums. A crawl that keeps fetching copies therefore cannot talk itself into
16// confidence, which is exactly the failure the source's Jaccard consistency has.
17// THIRD STATE: cs_should_stop returns CONTINUE / CONFIDENT / SATURATED, and saturation carries its own
18// sat_observed flag: when the term set fills, saturation is UNOBSERVABLE and counts as 0 in confidence (it can
19// only make the verdict more conservative, never acquit) and the report names it.
20// EVERY NUMBER IS A CONF ROW: knowledge/crawl_sufficiency.conf (nx_lane_conf semantics), bootstrap defaults
21// below cite their source. Consumers: nx_web_crawl_step (query-scoped crawl), the research fetch, the CLI.
22// license_tier: ORIGINAL module: nishi-core.search.sufficiency No hw writes (Rule 26).
23import "nx_syscalls.nx"
24import "nx_lane_conf.nx"
25import "nx_search_inverted.nx"
26import "nx_intlog.nx"
27import "nx_simhash.nx"
28
29const CS_CONF: *u8 = "knowledge/crawl_sufficiency.conf"
30// bootstrap defaults (rule 17: argv > conf > these). Source per row:
31const CS_DEF_W_COVERAGE: i64 = 400 // crawl4ai StatisticalStrategy.calculate_confidence: 0.4 * coverage
32const CS_DEF_W_CONSISTENCY: i64 = 300 // 0.3 * consistency
33const CS_DEF_W_SATURATION: i64 = 300 // 0.3 * saturation
34const CS_DEF_CONFIDENCE_STOP: i64 = 700 // AdaptiveConfig.confidence_threshold = 0.7
35const CS_DEF_SATURATION_STOP: i64 = 800 // AdaptiveConfig.saturation_threshold = 0.8
36const CS_DEF_FREQ_BOOST: i64 = 500 // term_score = doc_coverage * (1 + 0.5 * freq_signal)
37const CS_DEF_RELEVANT_PERMIL: i64 = 500 // ours: on topic = holds at least half the query terms (min 1); the source has no per-doc topic test
38const CS_DEF_WINDOW: i64 = 1 // the source compares the FIRST and the LATEST new-term count; a wider window smooths
39const CS_DEF_SIMHASH_HAM: i64 = 4 // WC_SIMHASH_HAM: the crawler's own near-duplicate bar (Manku 2007 uses 3), one estate value
40const CS_DEF_TERMSET_CAP: i64 = 65536 // distinct term hashes tracked for saturation; full -> saturation UNOBSERVABLE, announced
41const CS_DEF_HIST_CAP: i64 = 4096 // counted docs tracked (fingerprints + new-term history); full -> refuses further docs, announced
42const CS_DEF_REQUIRE_SAT_OBSERVED: i64 = 1 // OURS: CONFIDENT needs an OBSERVED saturation. MEASURED 2026-08-24: one Wikipedia page holding
43 // every query term scored exactly 700 = the bar with saturation unobserved (400+300+0), so the
44 // source's arithmetic stops on a single page. An unobserved axis may not acquit (abstain law).
45const CS_QMAX: i64 = 64 // query terms held; excess is REFUSED and counted in dropped_terms, never silently ignored
46const CS_PERMIL: i64 = 1000
47const CS_HDR: i64 = 64 // i64 slots in the state header
48
49// header slots
50const CS_S_QN: i64 = 0
51const CS_S_DOCS: i64 = 1
52const CS_S_DUPS: i64 = 2
53const CS_S_RELEVANT: i64 = 3
54const CS_S_TERMSET_CAP: i64 = 4
55const CS_S_TERMSET_USED: i64 = 5
56const CS_S_TERMSET_FULL: i64 = 6
57const CS_S_HIST_CAP: i64 = 7
58const CS_S_HIST_N: i64 = 8
59const CS_S_HIST_FULL: i64 = 9
60const CS_S_CONFIDENCE: i64 = 10
61const CS_S_COVERAGE: i64 = 11
62const CS_S_CONSISTENCY: i64 = 12
63const CS_S_SATURATION: i64 = 13
64const CS_S_SAT_OBSERVED: i64 = 14
65const CS_S_STOP: i64 = 15
66const CS_S_MAXTF: i64 = 16
67const CS_S_DROPPED_TERMS: i64 = 17
68const CS_S_CONF_SRC: i64 = 18
69const CS_S_FIRST_NEW: i64 = 19
70const CS_S_LAST_NEW: i64 = 20
71const CS_S_W_COV: i64 = 21
72const CS_S_W_CON: i64 = 22
73const CS_S_W_SAT: i64 = 23
74const CS_S_CONF_STOP: i64 = 24
75const CS_S_SAT_STOP: i64 = 25
76const CS_S_FREQ_BOOST: i64 = 26
77const CS_S_RELEVANT_PERMIL: i64 = 27
78const CS_S_WINDOW: i64 = 28
79const CS_S_HAM: i64 = 29
80const CS_S_P_QHASH: i64 = 30
81const CS_S_P_QTF: i64 = 31
82const CS_S_P_QDF: i64 = 32
83const CS_S_P_TERMSET: i64 = 33
84const CS_S_P_HIST: i64 = 34
85const CS_S_P_FPS: i64 = 35
86const CS_S_P_SEEN: i64 = 36
87const CS_S_REQUIRE_SAT: i64 = 37
88// stop reasons
89const CS_CONTINUE: i64 = 0
90const CS_CONFIDENT: i64 = 1
91const CS_SATURATED: i64 = 2
92
93static cs_conf_src_g: i64
94func cs_conf_one(key: *u8, dflt: i64) -> i64 {
95 let v: i64 = lc_geti(CS_CONF, "" as *u8, key, 0 - 1)
96 if v < 0 { return dflt }
97 cs_conf_src_g = 1
98 return v
99}
100
101// ---- tokenizer: the inverted index's own token class and hash, so a query term and a doc term agree ----
102// Calls fn(hash) for each token by writing hashes into out (cap slots); returns the token count (uncapped).
103func cs_tokens(text: *u8, n: i64, out: *i64, cap: i64) -> i64 {
104 var i: i64 = 0
105 var count: i64 = 0
106 while i < n {
107 if nx_inv_is_token_char(text[i] as i64) == 0 { i = i + 1 } else {
108 let s: i64 = i
109 var go: i64 = 1
110 while go == 1 { if i >= n { go = 0 } else { if nx_inv_is_token_char(text[i] as i64) == 1 { i = i + 1 } else { go = 0 } } }
111 let len: i64 = i - s
112 if len >= NX_INV_MIN_TOKEN_LEN { if len <= NX_INV_MAX_TOKEN_LEN {
113 if count < cap { out[count] = nx_inv_hash_bytes_lower((text as i64 + s) as *u8, len) }
114 count = count + 1
115 } }
116 }
117 }
118 return count
119}
120
121// ---- state ----------------------------------------------------------------------------------------
122func cs_new_t(query: *u8, qlen: i64, termset_cap: i64, hist_cap: i64) -> *i64 {
123 let st: *i64 = sys_mmap(CS_HDR * 8) as *i64
124 cs_conf_src_g = 0
125 st[CS_S_W_COV] = cs_conf_one("w_coverage" as *u8, CS_DEF_W_COVERAGE)
126 st[CS_S_W_CON] = cs_conf_one("w_consistency" as *u8, CS_DEF_W_CONSISTENCY)
127 st[CS_S_W_SAT] = cs_conf_one("w_saturation" as *u8, CS_DEF_W_SATURATION)
128 st[CS_S_CONF_STOP] = cs_conf_one("confidence_stop" as *u8, CS_DEF_CONFIDENCE_STOP)
129 st[CS_S_SAT_STOP] = cs_conf_one("saturation_stop" as *u8, CS_DEF_SATURATION_STOP)
130 st[CS_S_FREQ_BOOST] = cs_conf_one("freq_boost" as *u8, CS_DEF_FREQ_BOOST)
131 st[CS_S_RELEVANT_PERMIL] = cs_conf_one("relevant_permil" as *u8, CS_DEF_RELEVANT_PERMIL)
132 st[CS_S_WINDOW] = cs_conf_one("window" as *u8, CS_DEF_WINDOW)
133 st[CS_S_HAM] = cs_conf_one("simhash_ham" as *u8, CS_DEF_SIMHASH_HAM)
134 st[CS_S_REQUIRE_SAT] = cs_conf_one("require_sat_observed" as *u8, CS_DEF_REQUIRE_SAT_OBSERVED)
135 if st[CS_S_W_COV] + st[CS_S_W_CON] + st[CS_S_W_SAT] <= 0 {
136 st[CS_S_W_COV] = CS_DEF_W_COVERAGE; st[CS_S_W_CON] = CS_DEF_W_CONSISTENCY; st[CS_S_W_SAT] = CS_DEF_W_SATURATION
137 }
138 if st[CS_S_WINDOW] < 1 { st[CS_S_WINDOW] = 1 }
139 st[CS_S_CONF_SRC] = cs_conf_src_g
140 var tcap: i64 = termset_cap
141 if tcap < 16 { tcap = 16 }
142 var hcap: i64 = hist_cap
143 if hcap < 2 { hcap = 2 }
144 st[CS_S_TERMSET_CAP] = tcap
145 st[CS_S_HIST_CAP] = hcap
146 let qh: *i64 = sys_mmap(CS_QMAX * 8) as *i64
147 let qtf: *i64 = sys_mmap(CS_QMAX * 8) as *i64
148 let qdf: *i64 = sys_mmap(CS_QMAX * 8) as *i64
149 let seen: *i64 = sys_mmap(CS_QMAX * 8) as *i64
150 st[CS_S_P_QHASH] = qh as i64
151 st[CS_S_P_QTF] = qtf as i64
152 st[CS_S_P_QDF] = qdf as i64
153 st[CS_S_P_SEEN] = seen as i64
154 st[CS_S_P_TERMSET] = sys_mmap(tcap * 8) as i64
155 st[CS_S_P_HIST] = sys_mmap(hcap * 8) as i64
156 st[CS_S_P_FPS] = sys_mmap(hcap * 8) as i64
157 // query terms, deduplicated; the cap refuses loudly
158 let raw: *i64 = sys_mmap((CS_QMAX * 4) * 8) as *i64
159 let nraw: i64 = cs_tokens(query, qlen, raw, CS_QMAX * 4)
160 var lim: i64 = nraw
161 if lim > CS_QMAX * 4 { lim = CS_QMAX * 4 }
162 var i: i64 = 0
163 var qn: i64 = 0
164 var dropped: i64 = 0
165 while i < lim {
166 var dup: i64 = 0
167 var j: i64 = 0
168 while j < qn { if qh[j] == raw[i] { dup = 1; j = qn } else { j = j + 1 } }
169 if dup == 0 {
170 if qn < CS_QMAX { qh[qn] = raw[i]; qtf[qn] = 0; qdf[qn] = 0; qn = qn + 1 } else { dropped = dropped + 1 }
171 }
172 i = i + 1
173 }
174 if nraw > lim { dropped = dropped + (nraw - lim) }
175 st[CS_S_QN] = qn
176 st[CS_S_DROPPED_TERMS] = dropped
177 sys_munmap(raw as *u8, (CS_QMAX * 4) * 8)
178 return st
179}
180func cs_new(query: *u8, qlen: i64) -> *i64 {
181 cs_conf_src_g = 0
182 let tcap: i64 = cs_conf_one("termset_cap" as *u8, CS_DEF_TERMSET_CAP)
183 let hcap: i64 = cs_conf_one("hist_cap" as *u8, CS_DEF_HIST_CAP)
184 return cs_new_t(query, qlen, tcap, hcap)
185}
186func cs_free(st: *i64) -> i64 {
187 sys_munmap(st[CS_S_P_QHASH] as *u8, CS_QMAX * 8)
188 sys_munmap(st[CS_S_P_QTF] as *u8, CS_QMAX * 8)
189 sys_munmap(st[CS_S_P_QDF] as *u8, CS_QMAX * 8)
190 sys_munmap(st[CS_S_P_SEEN] as *u8, CS_QMAX * 8)
191 sys_munmap(st[CS_S_P_TERMSET] as *u8, st[CS_S_TERMSET_CAP] * 8)
192 sys_munmap(st[CS_S_P_HIST] as *u8, st[CS_S_HIST_CAP] * 8)
193 sys_munmap(st[CS_S_P_FPS] as *u8, st[CS_S_HIST_CAP] * 8)
194 sys_munmap(st as *u8, CS_HDR * 8)
195 return 0
196}
197
198// open-addressing insert of a term hash; returns 1 new, 0 present, -1 full (a hash of 0 is remapped to 1)
199func cs_termset_add(st: *i64, h0: i64) -> i64 {
200 let tbl: *i64 = st[CS_S_P_TERMSET] as *i64
201 let cap: i64 = st[CS_S_TERMSET_CAP]
202 var h: i64 = h0
203 if h == 0 { h = 1 }
204 if st[CS_S_TERMSET_USED] * 4 >= cap * 3 { st[CS_S_TERMSET_FULL] = 1; return 0 - 1 } // 3/4 load: past it, probing degrades
205 var slot: i64 = (h & 0x7fffffffffffffff) % cap
206 var tries: i64 = 0
207 while tries < cap {
208 if tbl[slot] == 0 { tbl[slot] = h; st[CS_S_TERMSET_USED] = st[CS_S_TERMSET_USED] + 1; return 1 }
209 if tbl[slot] == h { return 0 }
210 slot = slot + 1
211 if slot >= cap { slot = 0 }
212 tries = tries + 1
213 }
214 st[CS_S_TERMSET_FULL] = 1
215 return 0 - 1
216}
217
218// ---- add one document's plain text. Returns 1 counted, 0 near-duplicate (tallied), -1 history full ----
219func cs_add_doc(st: *i64, text: *u8, n: i64) -> i64 {
220 if st[CS_S_HIST_N] >= st[CS_S_HIST_CAP] { st[CS_S_HIST_FULL] = 1; return 0 - 1 }
221 let fp: i64 = nx_simhash_fingerprint(text, n)
222 let fps: *i64 = st[CS_S_P_FPS] as *i64
223 var d: i64 = 0
224 while d < st[CS_S_HIST_N] {
225 if nx_simhash_hamming(fp, fps[d]) <= st[CS_S_HAM] { st[CS_S_DUPS] = st[CS_S_DUPS] + 1; return 0 }
226 d = d + 1
227 }
228 let qn: i64 = st[CS_S_QN]
229 let qh: *i64 = st[CS_S_P_QHASH] as *i64
230 let qtf: *i64 = st[CS_S_P_QTF] as *i64
231 let qdf: *i64 = st[CS_S_P_QDF] as *i64
232 let seen: *i64 = st[CS_S_P_SEEN] as *i64
233 var q: i64 = 0
234 while q < qn { seen[q] = 0; q = q + 1 }
235 var newterms: i64 = 0
236 var i: i64 = 0
237 while i < n {
238 if nx_inv_is_token_char(text[i] as i64) == 0 { i = i + 1 } else {
239 let s: i64 = i
240 var go: i64 = 1
241 while go == 1 { if i >= n { go = 0 } else { if nx_inv_is_token_char(text[i] as i64) == 1 { i = i + 1 } else { go = 0 } } }
242 let len: i64 = i - s
243 if len >= NX_INV_MIN_TOKEN_LEN { if len <= NX_INV_MAX_TOKEN_LEN {
244 let h: i64 = nx_inv_hash_bytes_lower((text as i64 + s) as *u8, len)
245 var k: i64 = 0
246 while k < qn { if qh[k] == h { qtf[k] = qtf[k] + 1; seen[k] = 1 } k = k + 1 }
247 if st[CS_S_TERMSET_FULL] == 0 { if cs_termset_add(st, h) == 1 { newterms = newterms + 1 } }
248 } }
249 }
250 }
251 var hits: i64 = 0
252 q = 0
253 while q < qn { if seen[q] == 1 { qdf[q] = qdf[q] + 1; hits = hits + 1 } if qtf[q] > st[CS_S_MAXTF] { st[CS_S_MAXTF] = qtf[q] } q = q + 1 }
254 var need: i64 = (qn * st[CS_S_RELEVANT_PERMIL] + CS_PERMIL - 1) / CS_PERMIL
255 if need < 1 { need = 1 }
256 if hits >= need { st[CS_S_RELEVANT] = st[CS_S_RELEVANT] + 1 }
257 let hist: *i64 = st[CS_S_P_HIST] as *i64
258 hist[st[CS_S_HIST_N]] = newterms
259 fps[st[CS_S_HIST_N]] = fp
260 st[CS_S_HIST_N] = st[CS_S_HIST_N] + 1
261 st[CS_S_DOCS] = st[CS_S_DOCS] + 1
262 return 1
263}
264
265// ---- the three metrics, permil ----------------------------------------------------------------------
266func cs_coverage(st: *i64) -> i64 {
267 let qn: i64 = st[CS_S_QN]
268 let docs: i64 = st[CS_S_DOCS]
269 if qn <= 0 { return 0 }
270 if docs <= 0 { return 0 }
271 let qtf: *i64 = st[CS_S_P_QTF] as *i64
272 let qdf: *i64 = st[CS_S_P_QDF] as *i64
273 let lmax: i64 = ilog2_1024(1 + st[CS_S_MAXTF])
274 var sum: i64 = 0
275 var q: i64 = 0
276 while q < qn {
277 var term: i64 = 0
278 if qdf[q] > 0 {
279 let doc_cov: i64 = qdf[q] * CS_PERMIL / docs
280 var freq: i64 = 0
281 if lmax > 0 { freq = ilog2_1024(1 + qtf[q]) * CS_PERMIL / lmax }
282 term = doc_cov * (CS_PERMIL + st[CS_S_FREQ_BOOST] * freq / CS_PERMIL) / CS_PERMIL
283 if term > CS_PERMIL { term = CS_PERMIL }
284 }
285 sum = sum + term
286 q = q + 1
287 }
288 return sum / qn
289}
290func cs_consistency(st: *i64) -> i64 {
291 if st[CS_S_DOCS] <= 0 { return 0 }
292 return st[CS_S_RELEVANT] * CS_PERMIL / st[CS_S_DOCS]
293}
294// writes CS_S_SAT_OBSERVED, CS_S_FIRST_NEW, CS_S_LAST_NEW; returns permil (0 when unobserved)
295func cs_saturation(st: *i64) -> i64 {
296 st[CS_S_SAT_OBSERVED] = 0
297 st[CS_S_FIRST_NEW] = 0
298 st[CS_S_LAST_NEW] = 0
299 if st[CS_S_TERMSET_FULL] == 1 { return 0 }
300 let w: i64 = st[CS_S_WINDOW]
301 let n: i64 = st[CS_S_HIST_N]
302 if n < 2 * w { return 0 }
303 let hist: *i64 = st[CS_S_P_HIST] as *i64
304 var first: i64 = 0
305 var last: i64 = 0
306 var i: i64 = 0
307 while i < w { first = first + hist[i]; last = last + hist[n - 1 - i]; i = i + 1 }
308 st[CS_S_SAT_OBSERVED] = 1
309 st[CS_S_FIRST_NEW] = first
310 st[CS_S_LAST_NEW] = last
311 if first <= 0 { return CS_PERMIL }
312 var sat: i64 = CS_PERMIL - last * CS_PERMIL / first
313 if sat < 0 { sat = 0 }
314 if sat > CS_PERMIL { sat = CS_PERMIL }
315 return sat
316}
317// THE CONTRACT: recompute all three, store them, return confidence permil
318func cs_confidence(st: *i64) -> i64 {
319 let cov: i64 = cs_coverage(st)
320 let con: i64 = cs_consistency(st)
321 let sat: i64 = cs_saturation(st)
322 st[CS_S_COVERAGE] = cov
323 st[CS_S_CONSISTENCY] = con
324 st[CS_S_SATURATION] = sat
325 let wsum: i64 = st[CS_S_W_COV] + st[CS_S_W_CON] + st[CS_S_W_SAT]
326 var conf: i64 = 0
327 if wsum > 0 { conf = (st[CS_S_W_COV] * cov + st[CS_S_W_CON] * con + st[CS_S_W_SAT] * sat) / wsum }
328 st[CS_S_CONFIDENCE] = conf
329 var stop: i64 = CS_CONTINUE
330 if conf >= st[CS_S_CONF_STOP] { stop = CS_CONFIDENT }
331 // an UNOBSERVED saturation axis cannot acquit: without two windows the crawl has not shown it stopped learning
332 if stop == CS_CONFIDENT { if st[CS_S_REQUIRE_SAT] == 1 { if st[CS_S_SAT_OBSERVED] == 0 { stop = CS_CONTINUE } } }
333 if stop == CS_CONTINUE { if st[CS_S_SAT_OBSERVED] == 1 { if sat >= st[CS_S_SAT_STOP] { stop = CS_SATURATED } } }
334 st[CS_S_STOP] = stop
335 return conf
336}
337func cs_should_stop(st: *i64) -> i64 {
338 cs_confidence(st)
339 return st[CS_S_STOP]
340}
341
342// ---- report: one line, every number named -------------------------------------------------------------
343func cs_r_num(out: *u8, cap: i64, o: i64, v: i64) -> i64 {
344 var oo: i64 = o
345 if v == 0 { if oo < cap { out[oo] = 48 as u8; oo = oo + 1 } return oo }
346 var m: i64 = v
347 if m < 0 { if oo < cap { out[oo] = 45 as u8; oo = oo + 1 } m = 0 - m }
348 let t: *u8 = sys_mmap(32)
349 var k: i64 = 0
350 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 }
351 while k > 0 { k = k - 1; if oo < cap { out[oo] = t[k]; oo = oo + 1 } }
352 sys_munmap(t, 32)
353 return oo
354}
355func cs_r_str(out: *u8, cap: i64, o: i64, s: *u8) -> i64 {
356 var oo: i64 = o
357 var i: i64 = 0
358 while s[i] != (0 as u8) { if oo < cap { out[oo] = s[i]; oo = oo + 1 } i = i + 1 }
359 return oo
360}
361func cs_report(st: *i64, out: *u8, cap: i64) -> i64 {
362 cs_confidence(st)
363 var o: i64 = 0
364 o = cs_r_str(out, cap, o, "SUFFICIENCY docs=" as *u8); o = cs_r_num(out, cap, o, st[CS_S_DOCS])
365 o = cs_r_str(out, cap, o, " dups=" as *u8); o = cs_r_num(out, cap, o, st[CS_S_DUPS])
366 o = cs_r_str(out, cap, o, " relevant=" as *u8); o = cs_r_num(out, cap, o, st[CS_S_RELEVANT])
367 o = cs_r_str(out, cap, o, " qterms=" as *u8); o = cs_r_num(out, cap, o, st[CS_S_QN])
368 o = cs_r_str(out, cap, o, " dropped_terms=" as *u8); o = cs_r_num(out, cap, o, st[CS_S_DROPPED_TERMS])
369 o = cs_r_str(out, cap, o, " coverage=" as *u8); o = cs_r_num(out, cap, o, st[CS_S_COVERAGE])
370 o = cs_r_str(out, cap, o, " consistency=" as *u8); o = cs_r_num(out, cap, o, st[CS_S_CONSISTENCY])
371 o = cs_r_str(out, cap, o, " saturation=" as *u8); o = cs_r_num(out, cap, o, st[CS_S_SATURATION])
372 o = cs_r_str(out, cap, o, " sat_observed=" as *u8); o = cs_r_num(out, cap, o, st[CS_S_SAT_OBSERVED])
373 o = cs_r_str(out, cap, o, " new_first=" as *u8); o = cs_r_num(out, cap, o, st[CS_S_FIRST_NEW])
374 o = cs_r_str(out, cap, o, " new_last=" as *u8); o = cs_r_num(out, cap, o, st[CS_S_LAST_NEW])
375 o = cs_r_str(out, cap, o, " termset=" as *u8); o = cs_r_num(out, cap, o, st[CS_S_TERMSET_USED])
376 o = cs_r_str(out, cap, o, "/" as *u8); o = cs_r_num(out, cap, o, st[CS_S_TERMSET_CAP])
377 o = cs_r_str(out, cap, o, " termset_full=" as *u8); o = cs_r_num(out, cap, o, st[CS_S_TERMSET_FULL])
378 o = cs_r_str(out, cap, o, " confidence=" as *u8); o = cs_r_num(out, cap, o, st[CS_S_CONFIDENCE])
379 o = cs_r_str(out, cap, o, " bar=" as *u8); o = cs_r_num(out, cap, o, st[CS_S_CONF_STOP])
380 o = cs_r_str(out, cap, o, " sat_bar=" as *u8); o = cs_r_num(out, cap, o, st[CS_S_SAT_STOP])
381 o = cs_r_str(out, cap, o, " conf_src=" as *u8)
382 if st[CS_S_CONF_SRC] == 1 { o = cs_r_str(out, cap, o, "file" as *u8) } else { o = cs_r_str(out, cap, o, "defaults" as *u8) }
383 o = cs_r_str(out, cap, o, " verdict=" as *u8)
384 if st[CS_S_STOP] == CS_CONFIDENT { o = cs_r_str(out, cap, o, "CONFIDENT-STOP" as *u8) }
385 if st[CS_S_STOP] == CS_SATURATED { o = cs_r_str(out, cap, o, "SATURATED-STOP" as *u8) }
386 if st[CS_S_STOP] == CS_CONTINUE { o = cs_r_str(out, cap, o, "CONTINUE" as *u8) }
387 if st[CS_S_TERMSET_FULL] == 1 { o = cs_r_str(out, cap, o, " (saturation UNOBSERVABLE: term set full)" as *u8) }
388 if st[CS_S_STOP] == CS_CONTINUE { if st[CS_S_CONFIDENCE] >= st[CS_S_CONF_STOP] { o = cs_r_str(out, cap, o, " (at the bar, but saturation UNOBSERVED: one window is not evidence of learning nothing new)" as *u8) } }
389 o = cs_r_str(out, cap, o, "\n" as *u8)
390 return o
391}