code wiki / (root) / nx_tictactoe_html_contract_test.nx

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}