code wiki / (root) / nx_race_visualizer.nx

nx_race_visualizer.nx source

↩ module page · 315 lines · 10789 B

1// nx_race_visualizer.nx -- ASCII visualizer for .race_telemetry.tsv. 2// 3// User directive 2026-05-16: "i want visualization with a manual and 4// scheduled and automatic capbility flag". 5// 6// Reads a TSV buffer (.race_telemetry.tsv contents) + a length, then 7// emits a horizontal bar chart of race-counts per timing-mode + a 8// verdict-tally summary. All output is ASCII; no terminal escapes. 9// 10// TSV schema (timing_mode column 9 ADDED 2026-05-16 four-pillar slice): 11// col 0 = timestamp (ISO 8601) 12// col 1 = event_type (lap | grader | bench | ...) 13// col 2 = bench_name 14// col 3 = lap_n (i64) 15// col 4 = duration_ms (i64) 16// col 5 = verdict (WIN | TIE | LOSE | RECORD_ONLY | SKIP | INFO) 17// col 6 = thermal_c 18// col 7 = notes 19// col 8 = timing_mode (MANUAL | EVENT | SCHEDULED | CONTINUOUS | 20// IDLE | THRESHOLD | PROBABILISTIC | QUORUM) 21// -- absent in old rows; default MANUAL 22// 23// Per cardinal user-owns-every-bit: caller supplies the TSV bytes; 24// substrate doesn't read the filesystem on its own. 25// 26// nx_capability_claims: 27// needs: [tsv_bytes, sys_write] 28// provides: [race_visualization] 29// safety: [no_unchecked_deref, no_floating_point, no_syscall_other_than_write, 30// bit_equal_reproducible, kind_isolated] 31// license: ORIGINAL 32// kind: racing_crew_specialist 33 34// nx_safety_envelope: 35// intended_use: AUTO_APPLIED -- primitive-specific tuning queued 36// sil_target: SIL1 37// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail] 38// verdict: NOT_YET_EVALUATED 39 40import "nx_race_timing.nx" 41import "nx_syscalls_x86_64.nx" 42 43const NXV_FD_STDOUT: i64 = 1 44 45// ---- Tiny ASCII output helpers (no nx_strfmt dep) ----------------- 46 47func nxv_strlen(s: *u8) -> i64 { 48 var n: i64 = 0 49 while s[n] != 0 { n = n + 1 } 50 return n 51} 52 53func nxv_emit(s: *u8) -> i64 { 54 let n: i64 = nxv_strlen(s) 55 sys_write(NXV_FD_STDOUT, s, n) 56 return n 57} 58 59func nxv_emit_n(s: *u8, n: i64) -> i64 { 60 sys_write(NXV_FD_STDOUT, s, n) 61 return n 62} 63 64// Emit an i64 in decimal (handles negatives). Uses sys_mmap(64) for 65// scratch -- matches the nx_strfmt pattern. Caller's syscall layer 66// must provide sys_mmap (we import nx_syscalls_x86_64.nx above). 67func nxv_emit_i64(v: i64) -> i64 { 68 if v == 0 { 69 sys_write(NXV_FD_STDOUT, "0" as *u8, 1) 70 return 1 71 } 72 let scratch: *u8 = sys_mmap(64) 73 var n: i64 = v 74 var sign: i64 = 0 75 if n < 0 { sign = 1; n = 0 - n } 76 var k: i64 = 0 77 while n > 0 { 78 scratch[k] = (0x30 + (n - (n / 10) * 10)) as u8 79 n = n / 10 80 k = k + 1 81 } 82 let out: *u8 = sys_mmap(64) 83 var oi: i64 = 0 84 if sign == 1 { out[oi] = 0x2d as u8; oi = oi + 1 } 85 var ri: i64 = k - 1 86 while ri >= 0 { 87 out[oi] = scratch[ri] 88 oi = oi + 1 89 ri = ri - 1 90 } 91 sys_write(NXV_FD_STDOUT, out, oi) 92 return oi 93} 94 95// Right-pad name to width with spaces. 96func nxv_emit_padded(name: *u8, name_len: i64, width: i64) -> i64 { 97 sys_write(NXV_FD_STDOUT, name, name_len) 98 var pad: i64 = width - name_len 99 while pad > 0 { 100 sys_write(NXV_FD_STDOUT, " " as *u8, 1) 101 pad = pad - 1 102 } 103 return width 104} 105 106// Emit `n` copies of a single character. 107func nxv_emit_repeat(ch: *u8, n: i64) -> i64 { 108 var i: i64 = 0 109 while i < n { 110 sys_write(NXV_FD_STDOUT, ch, 1) 111 i = i + 1 112 } 113 return n 114} 115 116// ---- TSV parsing (minimal: row-iterate, col-iterate) -------------- 117 118// Return the offset of the next byte after a newline at or after off, 119// or len if no newline found. 120func nxv_eol(buf: *u8, len: i64, off: i64) -> i64 { 121 var i: i64 = off 122 while i < len { 123 if buf[i] == 0x0a { return i + 1 } 124 i = i + 1 125 } 126 return len 127} 128 129// Return the offset of the next tab at or after off (within line), 130// or end_of_line if no tab found. 131func nxv_tab(buf: *u8, end: i64, off: i64) -> i64 { 132 var i: i64 = off 133 while i < end { 134 if buf[i] == 0x09 { return i } 135 if buf[i] == 0x0a { return i } 136 i = i + 1 137 } 138 return end 139} 140 141// Skip leading tabs to reach the K-th column on a line. Returns 142// (start, end) via out params. K is 0-indexed. 143func nxv_col(buf: *u8, line_end: i64, line_start: i64, k: i64, 144 out_start: *i64, out_end: *i64) -> i64 { 145 var col: i64 = 0 146 var s: i64 = line_start 147 while col < k { 148 let t: i64 = nxv_tab(buf, line_end, s) 149 if t >= line_end { 150 *out_start = line_end 151 *out_end = line_end 152 return -1 153 } 154 s = t + 1 155 col = col + 1 156 } 157 let e: i64 = nxv_tab(buf, line_end, s) 158 *out_start = s 159 *out_end = e 160 return 0 161} 162 163// ---- Visualizer entry point --------------------------------------- 164 165// Reads the TSV buffer + emits visualization to stdout. Returns 166// the number of data rows processed (header + comment lines skipped). 167func nx_race_visualize(tsv: *u8, len: i64) -> i64 { 168 // Per-timing-mode counters (sealed-enum size N). 169 let counts: *i64 = sys_mmap(64) as *i64 170 var i: i64 = 0 171 while i < 8 { counts[i] = 0; i = i + 1 } 172 173 // Verdict counters: WIN / TIE / LOSE / RECORD_ONLY / OTHER. 174 var n_win: i64 = 0 175 var n_tie: i64 = 0 176 var n_lose: i64 = 0 177 var n_record: i64 = 0 178 var n_other: i64 = 0 179 var total: i64 = 0 180 181 // Latency tracking: sum + count (mean), max, min. 182 var dur_sum: i64 = 0 183 var dur_max: i64 = 0 184 var dur_min: i64 = 0x7fffffffffffffff 185 var dur_n: i64 = 0 186 187 // Iterate rows. 188 var line_start: i64 = 0 189 while line_start < len { 190 let line_end: i64 = nxv_eol(tsv, len, line_start) 191 // Skip blank + comment lines. 192 if line_end - line_start > 0 { 193 if tsv[line_start] != 0x23 { // not '#' 194 // Column 8 = timing_mode (default MANUAL if absent). 195 var tm_start: i64 = 0 196 var tm_end: i64 = 0 197 let tm_rc: i64 = nxv_col(tsv, line_end - 1, line_start, 8, 198 &tm_start, &tm_end) 199 var mode: i64 = NX_RACE_MANUAL 200 if tm_rc == 0 { 201 let mlen: i64 = tm_end - tm_start 202 let parsed: i64 = nx_race_timing_parse( 203 (tsv as i64 + tm_start) as *u8, mlen) 204 if parsed < NX_RACE_TIMING_N { mode = parsed } 205 } 206 counts[mode] = counts[mode] + 1 207 208 // Column 5 = verdict. 209 var v_start: i64 = 0 210 var v_end: i64 = 0 211 let v_rc: i64 = nxv_col(tsv, line_end - 1, line_start, 5, 212 &v_start, &v_end) 213 if v_rc == 0 { 214 let vlen: i64 = v_end - v_start 215 let vptr: *u8 = (tsv as i64 + v_start) as *u8 216 if vlen == 3 { 217 if vptr[0] == 0x57 { n_win = n_win + 1 } // 'W' -> WIN 218 if vptr[0] == 0x54 { n_tie = n_tie + 1 } // 'T' -> TIE 219 } 220 if vlen == 4 { 221 if vptr[0] == 0x4c { n_lose = n_lose + 1 } // 'L' -> LOSE 222 } 223 if vlen == 11 { 224 if vptr[0] == 0x52 { n_record = n_record + 1 } // 'R' -> RECORD_ONLY 225 } 226 if vlen != 3 && vlen != 4 && vlen != 11 { 227 n_other = n_other + 1 228 } 229 } 230 231 // Column 4 = duration_ms (parse as i64). 232 var d_start: i64 = 0 233 var d_end: i64 = 0 234 let d_rc: i64 = nxv_col(tsv, line_end - 1, line_start, 4, 235 &d_start, &d_end) 236 if d_rc == 0 { 237 var k: i64 = d_start 238 var dv: i64 = 0 239 var any: i64 = 0 240 while k < d_end { 241 let c: i64 = tsv[k] as i64 242 if c >= 0x30 && c <= 0x39 { 243 dv = dv * 10 + (c - 0x30) 244 any = 1 245 } 246 k = k + 1 247 } 248 if any == 1 { 249 dur_sum = dur_sum + dv 250 dur_n = dur_n + 1 251 if dv > dur_max { dur_max = dv } 252 if dv < dur_min { dur_min = dv } 253 } 254 } 255 256 total = total + 1 257 } 258 } 259 line_start = line_end 260 } 261 262 // ---- Emit header ---- 263 nxv_emit("=== Nishi Racing Telemetry Visualizer ===\n" as *u8) 264 nxv_emit("Total rows: " as *u8); nxv_emit_i64(total); nxv_emit("\n" as *u8) 265 266 // ---- Verdict tally ---- 267 nxv_emit("Verdict tally:\n" as *u8) 268 nxv_emit(" WIN " as *u8); nxv_emit_i64(n_win); nxv_emit("\n" as *u8) 269 nxv_emit(" TIE " as *u8); nxv_emit_i64(n_tie); nxv_emit("\n" as *u8) 270 nxv_emit(" LOSE " as *u8); nxv_emit_i64(n_lose); nxv_emit("\n" as *u8) 271 nxv_emit(" RECORD_ONLY " as *u8); nxv_emit_i64(n_record); nxv_emit("\n" as *u8) 272 nxv_emit(" OTHER " as *u8); nxv_emit_i64(n_other); nxv_emit("\n" as *u8) 273 274 // ---- Latency summary ---- 275 nxv_emit("Latency (duration_ms):\n" as *u8) 276 if dur_n > 0 { 277 nxv_emit(" N " as *u8); nxv_emit_i64(dur_n); nxv_emit("\n" as *u8) 278 nxv_emit(" min " as *u8); nxv_emit_i64(dur_min); nxv_emit("\n" as *u8) 279 nxv_emit(" max " as *u8); nxv_emit_i64(dur_max); nxv_emit("\n" as *u8) 280 nxv_emit(" mean " as *u8); nxv_emit_i64(dur_sum / dur_n); nxv_emit("\n" as *u8) 281 } 282 283 // ---- Timing-mode horizontal bar chart ---- 284 nxv_emit("\nTiming-mode race counts:\n" as *u8) 285 var max_count: i64 = 0 286 var mi: i64 = 0 287 while mi < 8 { 288 if counts[mi] > max_count { max_count = counts[mi] } 289 mi = mi + 1 290 } 291 let BAR_WIDTH: i64 = 40 292 var m: i64 = 0 293 while m < 8 { 294 nxv_emit(" " as *u8) 295 nxv_emit_padded(nx_race_timing_name(m), nx_race_timing_name_len(m), 14) 296 nxv_emit(" |" as *u8) 297 var bar_len: i64 = 0 298 if max_count > 0 { 299 bar_len = (counts[m] * BAR_WIDTH) / max_count 300 } 301 nxv_emit_repeat("#" as *u8, bar_len) 302 nxv_emit_repeat(" " as *u8, BAR_WIDTH - bar_len) 303 nxv_emit("| " as *u8) 304 nxv_emit_i64(counts[m]) 305 // Mark unused modes for PREVENTION pillar visibility. 306 if counts[m] == 0 { 307 nxv_emit(" [UNUSED]" as *u8) 308 } 309 nxv_emit("\n" as *u8) 310 m = m + 1 311 } 312 313 nxv_emit("\n" as *u8) 314 return total 315}