code wiki / (root) / nx_crawl_sufficiency.nx

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}