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}