code wiki / _hdl_build / nx_user_sim_games.nx

nx_user_sim_games.nx source

↩ module page · 142 lines · 6438 B

1// nx_user_sim_games.nx -- CHAOS-USER simulator for the C-stage games 2// (operator: "playtesting isnt just api but actually mimics real users"). 3// Drives nx_tictactoe and nx_checkers through the EXACT functions the 4// browser click-handlers call (nx_ttt_try_apply / nx_ttt_pick / 5// nx_chk_apply_*) -- the WASM export surface IS the UI's entry surface -- 6// but in REAL-USER patterns, not happy-path API order: 7// * misclicks: occupied cells, out-of-range indices, double-clicks 8// * impatient clicking after the game has ended 9// * full seeded games to termination against the AI 10// Asserts the invariants a real player experiences: illegal input is a 11// visible no-op (state hash unchanged), games terminate, the perfect AI 12// never loses, a finished board ignores clicks. 13// 14// This is the P0 guided-bot seed from the honesty roadmap. Honest scope: 15// covers the export surface; true DOM-event/pixel mimicry needs the SS22.5 16// sovereign browser-test-runner (PM plan). Catches the W/S-inversion CLASS 17// at the state layer: user input -> wrong state transition. 18// 19// Exit: 0 all green (prints USER-SIM ALL-PASS); 1-6 = failing invariant. 20// license_tier: ORIGINAL 21import "nx_syscalls.nx" 22import "nx_tier.nx" 23import "nx_tictactoe.nx" 24import "nx_checkers.nx" 25import "nx_prng.nx" 26const K_MAGIC_20260610: i64 = 20260610 27 28func us_puts(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 } 29 30func main() -> nx_int { 31 var prng_state: i64 = 0 32 let rng: *i64 = (&prng_state) as *i64 33 nx_prng_init(rng, K_MAGIC_20260610) 34 35 // ===== tic-tac-toe: 20 chaos games, user(X, random clicker) vs perfect AI(O) 36 var g: i64 = 0 37 while g < 20 { 38 let s: *i64 = nx_ttt_new(NX_TTT_X) 39 var applied: i64 = 0 40 var spins: i64 = 0 41 while nx_ttt_outcome(s) == NX_TTT_ONGOING { 42 spins = spins + 1 43 if spins > 500 { return 2 } // game must terminate 44 if nx_ttt_turn(s) == NX_TTT_X { 45 // chaos click: -2..10 (misclicks included) 46 let cell: i64 = nx_prng_range(rng, 13) - 2 47 let h0: i64 = nx_ttt_state_hash(s) 48 let r: i64 = nx_ttt_try_apply(s, cell) 49 if r == 0 { 50 if nx_ttt_state_hash(s) != h0 { return 1 } // rejected click mutated state 51 } 52 if r == 1 { applied = applied + 1 } 53 // impatient double-click on the same cell 54 if r == 1 { 55 let h1: i64 = nx_ttt_state_hash(s) 56 if nx_ttt_try_apply(s, cell) != 0 { return 1 } 57 if nx_ttt_state_hash(s) != h1 { return 1 } 58 } 59 } else { 60 let mv: nx_int = nx_ttt_pick_perfect(s, NX_TTT_O) 61 if mv < 0 { return 2 } 62 nx_ttt_try_apply(s, mv) 63 applied = applied + 1 64 } 65 } 66 if applied > 9 { return 2 } 67 // perfect AI must never LOSE to a random clicker 68 if nx_ttt_outcome(s) == NX_TTT_WIN { 69 if nx_ttt_winner(s) == NX_TTT_X { return 3 } 70 } 71 // finished board ignores further clicks 72 let hend: i64 = nx_ttt_state_hash(s) 73 var k: i64 = 0 74 while k < 5 { 75 let c2: i64 = nx_prng_range(rng, 9) 76 if nx_ttt_try_apply(s, c2) != 0 { return 4 } 77 k = k + 1 78 } 79 if nx_ttt_state_hash(s) != hend { return 4 } 80 g = g + 1 81 } 82 83 // ===== checkers: 10 seeded games, BOTH sides random users picking from 84 // what the UI would highlight (legal jumps first per mandatory-capture, 85 // else legal moves), plus random illegal pokes every turn. 86 let mvbuf: *i64 = sys_mmap(8 * 4 * 64) as *i64 87 var cg: i64 = 0 88 while cg < 10 { 89 let cs: *i64 = nx_chk_new() 90 var plies: i64 = 0 91 while plies < 200 { 92 if nx_chk_outcome(cs) != NX_CHK_ONGOING { plies = 200 } 93 if plies < 200 { 94 let side: i64 = nx_chk_turn(cs) 95 // random illegal poke: apply a random simple move; if rejected, 96 // board must be unchanged (compare piece count + turn) 97 let n_before: i64 = nx_chk_count_side(cs, side) 98 let t_before: i64 = nx_chk_turn(cs) 99 let fr: i64 = nx_prng_range(rng, 8) 100 let fc: i64 = nx_prng_range(rng, 8) 101 let pr: i64 = nx_prng_range(rng, 8) 102 let pc: i64 = nx_prng_range(rng, 8) 103 let pk: i64 = nx_chk_apply_simple_move(cs, fr, fc, pr, pc) 104 if pk == 0 { 105 if nx_chk_count_side(cs, side) != n_before { return 5 } 106 } 107 if pk == 1 { plies = plies + 1 } 108 if pk == 0 { 109 if nx_chk_turn(cs) != t_before { return 5 } 110 // legitimate user move: jumps first (mandatory), else simple 111 var nmv: i64 = nx_chk_legal_jumps(cs, side, mvbuf) 112 var is_jump: i64 = 1 113 if nmv == 0 { 114 nmv = nx_chk_legal_moves(cs, side, mvbuf) 115 is_jump = 0 116 } 117 if nmv == 0 { plies = 200 } // no moves: outcome decided 118 if nmv > 0 { 119 let pick: i64 = nx_prng_range(rng, nmv) 120 let q: i64 = pick * 4 121 var ar: i64 = 0 122 if is_jump == 1 { ar = nx_chk_apply_jump(cs, mvbuf[q], mvbuf[q+1], mvbuf[q+2], mvbuf[q+3]) } 123 if is_jump == 0 { ar = nx_chk_apply_simple_move(cs, mvbuf[q], mvbuf[q+1], mvbuf[q+2], mvbuf[q+3]) } 124 if ar != 1 { return 5 } // UI-highlighted move must apply 125 plies = plies + 1 126 } 127 } 128 } 129 } 130 // post-game (or cap): a finished game must reject any further move 131 if nx_chk_outcome(cs) != NX_CHK_ONGOING { 132 let nb: i64 = nx_chk_count_side(cs, NX_CHK_RED) + nx_chk_count_side(cs, NX_CHK_BLACK) 133 nx_chk_apply_simple_move(cs, 5, 0, 4, 1) 134 let na: i64 = nx_chk_count_side(cs, NX_CHK_RED) + nx_chk_count_side(cs, NX_CHK_BLACK) 135 if na != nb { return 6 } 136 } 137 cg = cg + 1 138 } 139 140 us_puts("USER-SIM ALL-PASS\n" as *u8) 141 return 0 142}