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}