nx_tictactoe_html_contract_test.nx source
↩ module page · 159 lines · 7240 B
1// nx_tictactoe_html_contract_test.nx -- bits-up HTML-contract checker.
2//
3// Reads stdin (piped index.html), asserts the UI contract:
4// - exactly 9 cell elements are present (marker: `data-cell-idx="N"`)
5// - the 9 instances appear in ORDER N=0,1,2,3,4,5,6,7,8
6//
7// This catches the click-handler-maps-to-wrong-cell bug class (the
8// tic-tac-toe equivalent of the W/S inversion). Composes ยง22.1
9// bug-class prevention without needing a third-party browser driver.
10//
11// Per [[feedback-no-third-party-in-build-path-source-of-truth-is-bits-up]]:
12// pure NishiLang. No grep, no awk, no Playwright, no Selenium. Just bytes.
13//
14// Bench harness pipes the HTML via stdin so this test is portable across
15// machines without hardcoded paths:
16// cat dist/tictactoe/index.html | qemu-riscv64-static <binary>
17//
18// genealogy_id: nx_tictactoe_html_contract_v1_2026_05_19
19// lineage_id: bits_up_html_attribute_contract_checker
20
21// nx_safety_envelope:
22// intended_use: c0_ui_contract_smoke
23// sil_target: SIL1
24// evidence: [stdin_buffer_scan + ordered_digit_check]
25// verdict: NOT_YET_EVALUATED
26
27import "nx_syscalls.nx"
28import "nx_tier.nx"
29
30const NX_HTML_BUF_BYTES: i64 = 65536 // 64 KB enough for tic-tac-toe page
31
32// Look for ASCII bytes: d a t a - c e l l - i d x = "
33// Then capture one digit '0'..'8', then assert '"' closes.
34// Returns the count of matches found AND fills idx_out[] with the digits in order.
35func nx_scan_cell_idx(buf: *u8, n: i64, idx_out: *u8) -> i64 {
36 let needle_len: i64 = 16 // strlen("data-cell-idx=\"")
37 // We compare byte-by-byte rather than use a real string-search.
38 // Linear scan; for a ~5 KB tic-tac-toe page this is microseconds.
39 var found: i64 = 0
40 var i: i64 = 0
41 while i + needle_len < n {
42 // Check the needle bytes at position i.
43 let m0: i64 = buf[i + 0] as i64
44 if m0 == 100 { // 'd'
45 let m1: i64 = buf[i + 1] as i64
46 if m1 == 97 { // 'a'
47 let m2: i64 = buf[i + 2] as i64
48 if m2 == 116 { // 't'
49 let m3: i64 = buf[i + 3] as i64
50 if m3 == 97 { // 'a'
51 let m4: i64 = buf[i + 4] as i64
52 if m4 == 45 { // '-'
53 let m5: i64 = buf[i + 5] as i64
54 if m5 == 99 { // 'c'
55 let m6: i64 = buf[i + 6] as i64
56 if m6 == 101 { // 'e'
57 let m7: i64 = buf[i + 7] as i64
58 if m7 == 108 { // 'l'
59 let m8: i64 = buf[i + 8] as i64
60 if m8 == 108 { // 'l'
61 let m9: i64 = buf[i + 9] as i64
62 if m9 == 45 { // '-'
63 let m10: i64 = buf[i + 10] as i64
64 if m10 == 105 { // 'i'
65 let m11: i64 = buf[i + 11] as i64
66 if m11 == 100 { // 'd'
67 let m12: i64 = buf[i + 12] as i64
68 if m12 == 120 { // 'x'
69 let m13: i64 = buf[i + 13] as i64
70 if m13 == 61 { // '='
71 let m14: i64 = buf[i + 14] as i64
72 if m14 == 34 { // '"'
73 let digit: i64 = buf[i + 15] as i64
74 // ASCII '0' = 48, '8' = 56
75 if digit >= 48 {
76 if digit <= 56 {
77 let close: i64 = buf[i + 16] as i64
78 if close == 34 { // '"'
79 idx_out[found] = (digit - 48) as u8
80 found = found + 1
81 i = i + 17
82 // Continue without falling through
83 }
84 }
85 }
86 }
87 }
88 }
89 }
90 }
91 }
92 }
93 }
94 }
95 }
96 }
97 }
98 }
99 }
100 }
101 i = i + 1
102 }
103 return found
104}
105
106func main() -> i64 {
107 // Allocate buffer + index-storage on the substrate heap.
108 let buf: *u8 = sys_mmap(NX_HTML_BUF_BYTES)
109 let idx_out: *u8 = sys_mmap(64)
110
111 // Read all of stdin (fd 0) into buf.
112 var total: i64 = 0
113 var keep_reading: i64 = 1
114 while keep_reading == 1 {
115 let space: i64 = NX_HTML_BUF_BYTES - total
116 if space <= 0 {
117 keep_reading = 0
118 }
119 if keep_reading == 1 {
120 let n: i64 = sys_read(0, (buf as i64 + total) as *u8, space)
121 if n <= 0 {
122 keep_reading = 0
123 }
124 if n > 0 {
125 total = total + n
126 }
127 }
128 }
129
130 if total <= 0 {
131 // Stdin was empty -- harness misconfigured.
132 return __syscall(SYS_EXIT, 100, 0, 0, 0, 0, 0)
133 }
134
135 // Scan for the contract markers.
136 let found: i64 = nx_scan_cell_idx(buf, total, idx_out)
137
138 // Assertion 1: exactly 9 matches.
139 if found != 9 {
140 // Distinct exit code so harness can diagnose.
141 return __syscall(SYS_EXIT, 1, 0, 0, 0, 0, 0)
142 }
143
144 // Assertion 2: the 9 indices appear in order 0,1,2,3,4,5,6,7,8.
145 var k: i64 = 0
146 while k < 9 {
147 let want: i64 = k
148 let got: i64 = idx_out[k] as i64
149 if got != want {
150 // Encode both the position (k) and the bad value (got) in exit.
151 // Exit codes 10..18 = position 0..8 was wrong.
152 return __syscall(SYS_EXIT, 10 + k, 0, 0, 0, 0, 0)
153 }
154 k = k + 1
155 }
156
157 // All assertions passed.
158 return 0
159}