code wiki / _hdl_build / _ttt_test_trace.nx

_ttt_test_trace.nx source

↩ module page · 183 lines · 8383 B

1// nx_tictactoe_test.nx -- C0 substrate smoke. 2// 3// 12 deterministic assertions covering: 4// 1. new game has X to move + outcome ongoing 5// 2. 9 legal moves on a fresh board 6// 3. apply X to cell 4 -> board[4]=X, turn=O, 8 legal moves left 7// 4. illegal-move (occupied cell) is no-op 8// 5. illegal-move (out-of-range) is no-op 9// 6. horizontal-row win is detected (top row XXX) 10// 7. diagonal win is detected (0-4-8 XXX) 11// 8. draw is detected after 9 non-winning moves 12// 9. state-hash is deterministic for same state 13// 10. perfect-vs-perfect always ends in draw (game-theory invariant) 14// 11. perfect-vs-random never loses (perfect's score never negative) 15// 12. evaluate updates only the OUTCOME and WINNER cells (does not mutate board) 16// 17// Each failed assertion exits with a distinct non-zero code so the smoke 18// harness identifies which check fired. 19// 20// Composes [[feedback-bug-class-prevention-additive-not-restrictive]] + 21// ยง22 bug-class table entry "move-applies-to-wrong-cell" (the tic-tac-toe 22// equivalent of the W/S inversion class). 23 24import "nx_syscalls.nx" 25import "nx_tier.nx" 26import "nx_tictactoe.nx" 27import "nx_prng.nx" 28 29func tt_puts(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 } 30func tt_putn(v: i64) -> i64 { let bb: *u8 = sys_mmap(28); var m: i64=v; if m<0 {m=0-m;sys_write(1,"-" as *u8,1)}; let t: *u8 = sys_mmap(28); var k: i64=0; if m==0 {t[0]=48;k=1}; while m>0 {t[k]=48+(m%10); m=m/10; k=k+1}; var i: i64=0; while i<k {bb[i]=t[k-1-i]; i=i+1}; sys_write(1, bb, k); return 0 } 31 32func main() -> nx_int { 33 // ===== Assertion 1: new game state ================================== 34 let s1: *i64 = nx_ttt_new(NX_TTT_X) 35 if nx_ttt_turn(s1) != NX_TTT_X { return __syscall(93, 1, 0, 0, 0, 0, 0) } 36 if nx_ttt_outcome(s1) != NX_TTT_ONGOING { return __syscall(93, 1, 0, 0, 0, 0, 0) } 37 if nx_ttt_winner(s1) != 0 { return __syscall(93, 1, 0, 0, 0, 0, 0) } 38 39 // ===== Assertion 2: 9 legal moves on fresh board ==================== 40 if nx_ttt_legal_count(s1) != 9 { return __syscall(93, 2, 0, 0, 0, 0, 0) } 41 42 // ===== Assertion 3: apply X to cell 4 =============================== 43 if nx_ttt_try_apply(s1, 4) != 1 { return __syscall(93, 3, 0, 0, 0, 0, 0) } 44 if nx_ttt_cell(s1, 4) != NX_TTT_X { return __syscall(93, 3, 0, 0, 0, 0, 0) } 45 if nx_ttt_turn(s1) != NX_TTT_O { return __syscall(93, 3, 0, 0, 0, 0, 0) } 46 if nx_ttt_legal_count(s1) != 8 { return __syscall(93, 3, 0, 0, 0, 0, 0) } 47 48 // ===== Assertion 4: illegal move (occupied cell) is no-op =========== 49 let hash_before: i64 = nx_ttt_state_hash(s1) 50 if nx_ttt_try_apply(s1, 4) != 0 { return __syscall(93, 4, 0, 0, 0, 0, 0) } 51 if nx_ttt_state_hash(s1) != hash_before { return __syscall(93, 4, 0, 0, 0, 0, 0) } 52 53 // ===== Assertion 5: illegal move (out-of-range) is no-op ============ 54 if nx_ttt_try_apply(s1, -1) != 0 { return __syscall(93, 5, 0, 0, 0, 0, 0) } 55 if nx_ttt_try_apply(s1, 9) != 0 { return __syscall(93, 5, 0, 0, 0, 0, 0) } 56 if nx_ttt_state_hash(s1) != hash_before { return __syscall(93, 5, 0, 0, 0, 0, 0) } 57 58 // ===== Assertion 6: horizontal-row win detected ===================== 59 // Reset and play X: 0,1,2 / O: 3,4 (X wins top row) 60 let s2: *i64 = nx_ttt_new(NX_TTT_X) 61 nx_ttt_try_apply(s2, 0) // X@0 62 nx_ttt_try_apply(s2, 3) // O@3 63 nx_ttt_try_apply(s2, 1) // X@1 64 nx_ttt_try_apply(s2, 4) // O@4 65 nx_ttt_try_apply(s2, 2) // X@2 wins 66 if nx_ttt_outcome(s2) != NX_TTT_WIN { return __syscall(93, 6, 0, 0, 0, 0, 0) } 67 if nx_ttt_winner(s2) != NX_TTT_X { return __syscall(93, 6, 0, 0, 0, 0, 0) } 68 69 // ===== Assertion 7: diagonal win detected =========================== 70 let s3: *i64 = nx_ttt_new(NX_TTT_X) 71 nx_ttt_try_apply(s3, 0) // X@0 72 nx_ttt_try_apply(s3, 1) // O@1 73 nx_ttt_try_apply(s3, 4) // X@4 74 nx_ttt_try_apply(s3, 2) // O@2 75 nx_ttt_try_apply(s3, 8) // X@8 wins diagonal 76 if nx_ttt_outcome(s3) != NX_TTT_WIN { return __syscall(93, 7, 0, 0, 0, 0, 0) } 77 if nx_ttt_winner(s3) != NX_TTT_X { return __syscall(93, 7, 0, 0, 0, 0, 0) } 78 79 // ===== Assertion 8: draw detected =================================== 80 // X O X 81 // X O O 82 // O X X (moves: 0 1 2 4 3 6 7 8 5) 83 let s4: *i64 = nx_ttt_new(NX_TTT_X) 84 nx_ttt_try_apply(s4, 0) 85 nx_ttt_try_apply(s4, 1) 86 nx_ttt_try_apply(s4, 2) 87 nx_ttt_try_apply(s4, 4) 88 nx_ttt_try_apply(s4, 3) 89 nx_ttt_try_apply(s4, 6) 90 nx_ttt_try_apply(s4, 7) 91 nx_ttt_try_apply(s4, 8) 92 nx_ttt_try_apply(s4, 5) 93 tt_puts("A8: outcome=" as *u8); tt_putn(nx_ttt_outcome(s4)) 94 tt_puts(" DRAW-const=" as *u8); tt_putn(NX_TTT_DRAW) 95 tt_puts(" cells=" as *u8) 96 var dgi: i64 = 0 97 while dgi < 9 { tt_putn(s4[dgi]); tt_puts("." as *u8); dgi = dgi + 1 } 98 tt_puts("\n" as *u8) 99 if nx_ttt_outcome(s4) != NX_TTT_DRAW { tt_puts("A8-BRANCH-TAKEN calling exit(8)\n" as *u8); return __syscall(93, 8, 0, 0, 0, 0, 0) } 100 tt_puts("A8-BRANCH-NOT-TAKEN\n" as *u8) 101 102 // ===== Assertion 9: state-hash is deterministic ===================== 103 let s5: *i64 = nx_ttt_new(NX_TTT_X) 104 nx_ttt_try_apply(s5, 4) 105 nx_ttt_try_apply(s5, 0) 106 nx_ttt_try_apply(s5, 8) 107 let h_a: i64 = nx_ttt_state_hash(s5) 108 let h_b: i64 = nx_ttt_state_hash(s5) 109 if h_a != h_b { return __syscall(93, 9, 0, 0, 0, 0, 0) } 110 // Different state -> different hash (overwhelmingly likely) 111 nx_ttt_try_apply(s5, 1) 112 let h_c: i64 = nx_ttt_state_hash(s5) 113 if h_a == h_c { return __syscall(93, 9, 0, 0, 0, 0, 0) } 114 115 // ===== Assertion 10: perfect-vs-perfect always draws ================ 116 // Two minimax agents -- game theory: optimal play -> draw. 117 let s6: *i64 = nx_ttt_new(NX_TTT_X) 118 var moves_played: nx_int = 0 119 while nx_ttt_outcome(s6) == NX_TTT_ONGOING { 120 let mv: nx_int = nx_ttt_pick_perfect(s6, nx_ttt_turn(s6)) 121 if mv < 0 { return __syscall(93, 10, 0, 0, 0, 0, 0) } 122 nx_ttt_try_apply(s6, mv) 123 moves_played = moves_played + 1 124 if moves_played > 9 { return __syscall(93, 10, 0, 0, 0, 0, 0) } 125 } 126 if nx_ttt_outcome(s6) != NX_TTT_DRAW { return __syscall(93, 10, 0, 0, 0, 0, 0) } 127 128 // ===== Assertion 11: perfect-vs-random -- perfect never loses ======= 129 // 5 trials. Perfect (X) vs random (O). X must win or draw. 130 var prng_state: i64 = 0 131 let prng_ptr: *i64 = (&prng_state) as *i64 132 nx_prng_init(prng_ptr, 42 as i64) 133 var trial: nx_int = 0 134 while trial < 5 { 135 let s7: *i64 = nx_ttt_new(NX_TTT_X) 136 while nx_ttt_outcome(s7) == NX_TTT_ONGOING { 137 var mv: nx_int = -1 138 if nx_ttt_turn(s7) == NX_TTT_X { 139 mv = nx_ttt_pick_perfect(s7, NX_TTT_X) 140 } else { 141 mv = nx_ttt_pick_easy(s7, prng_ptr) 142 } 143 if mv < 0 { return __syscall(93, 11, 0, 0, 0, 0, 0) } 144 nx_ttt_try_apply(s7, mv) 145 } 146 if nx_ttt_outcome(s7) == NX_TTT_WIN { 147 if nx_ttt_winner(s7) != NX_TTT_X { return __syscall(93, 11, 0, 0, 0, 0, 0) } 148 } 149 // DRAW is acceptable; LOSS (X is winner -> not X) is the failure. 150 trial = trial + 1 151 } 152 153 // ===== Assertion 12: evaluate does not mutate board cells =========== 154 let s8: *i64 = nx_ttt_new(NX_TTT_X) 155 nx_ttt_try_apply(s8, 0) 156 nx_ttt_try_apply(s8, 4) 157 nx_ttt_try_apply(s8, 1) 158 // Snapshot board cells 159 var snapshot: nx_int = 0 160 snapshot = snapshot * 3 + s8[0] 161 snapshot = snapshot * 3 + s8[1] 162 snapshot = snapshot * 3 + s8[2] 163 snapshot = snapshot * 3 + s8[3] 164 snapshot = snapshot * 3 + s8[4] 165 snapshot = snapshot * 3 + s8[5] 166 snapshot = snapshot * 3 + s8[6] 167 snapshot = snapshot * 3 + s8[7] 168 snapshot = snapshot * 3 + s8[8] 169 nx_ttt_evaluate(s8) 170 var after: nx_int = 0 171 after = after * 3 + s8[0] 172 after = after * 3 + s8[1] 173 after = after * 3 + s8[2] 174 after = after * 3 + s8[3] 175 after = after * 3 + s8[4] 176 after = after * 3 + s8[5] 177 after = after * 3 + s8[6] 178 after = after * 3 + s8[7] 179 after = after * 3 + s8[8] 180 if after != snapshot { return __syscall(93, 12, 0, 0, 0, 0, 0) } 181 182 return 0 183}