code wiki / _hdl_build / nx_sudoku_wasm_gate.nx

nx_sudoku_wasm_gate.nx source

↩ module page · 236 lines · 11305 B

1// nx_sudoku_wasm_gate.nx -- native proof of the sovereign sudoku browser game (base-relative, so the 2// SAME code this drives with base=an mmap'd buffer is the code that runs in the browser with base=0). 3// 4// INHERITS THE VERDICT BASE CLASS (gv_ctr/gv_head/gv_check/gv_verdict) rather than hand-rolling one. 5// The first version of this gate counted its own teeth and called sys_exit itself; /api/build REFUSED 6// it at the door (nx_gatedry: CUSTOM-VERDICT, L009/D001) and was right to. A hand-rolled counter can 7// print "passed 22/20", and a gate that returns a bare 0 after printing RED silently blesses every 8// failure it finds, because the control plane derives GREEN from the EXIT CODE. Inheriting makes 9// declared == executed by construction. 10// 11// WHAT THIS GATE EXISTS TO STOP. A game module can pass every logic test and still render nothing -- 12// absent drawing code has no failure mode, so a blank framebuffer looks exactly like a working one to 13// a logic-only suite. So there are teeth on the PIXELS as well as the rules, each with a control: 14// * render must produce many distinct colours (a blank or single-colour frame FAILS) 15// * the frame must DIFFER from a poisoned buffer (proves render WROTE, rather than the buffer 16// happening to already hold something) 17// * a digit must actually be drawn: placing a value must change the pixels inside THAT cell, and an 18// empty cell must measure exactly 0 ink, which is what stops a saturating measure passing 19// * and the anti-vacuity twin -- placing a value on a GIVEN must change NOTHING, pixels included 20// license_tier: ORIGINAL No hw writes (Rule 26). expect_exit: 0 21import "nx_gate_verdict.nx" 22import "nx_sudoku_wasm.nx" 23 24// distinct framebuffer colours, saturating at cap. A blank frame yields 1. 25func distinct_colors(base: i64, cap: i64) -> i64 { 26 let fb: *i64 = (base + O_FB) as *i64 27 let seen: *i64 = sys_mmap(cap*8) as *i64 28 var n: i64 = 0 29 var i: i64 = 0 30 let total: i64 = ww()*hh() 31 while i < total { 32 let c: i64 = fb[i] 33 var found: i64 = 0 34 var j: i64 = 0 35 while j<n { if seen[j]==c { found=1; j=n } else { j=j+1 } } 36 if found==0 { if n<cap { seen[n]=c; n=n+1 } } 37 i = i + 1 38 } 39 return n 40} 41 42// Count glyph pixels inside one cell -- the direct evidence a digit was drawn. 43// The background is sampled from the CELL'S OWN interior corner, not from the page margin. An earlier 44// version used fb[0] (page background) and therefore counted every interior pixel as ink: it returned 45// 576 = 24*24 for a full and an empty cell alike. A measure that saturates discriminates nothing, and 46// it made the neg-control tooth pass vacuously too. The corner at +4,+4 is provably outside the glyph 47// box (an 18px glyph in a 30px cell starts at +6) and inside the grid rules. 48func cell_ink(base: i64, idx: i64) -> i64 { 49 let fb: *i64 = (base + O_FB) as *i64 50 let r: i64 = idx/9 51 let c: i64 = idx-r*9 52 let x0: i64 = MARGIN + c*CELL + 4 53 let y0: i64 = MARGIN + r*CELL + 4 54 let bgc: i64 = fb[y0*ww() + x0] 55 var n: i64 = 0 56 var y: i64 = y0 57 while y < y0+CELL-8 { 58 var x: i64 = x0 59 while x < x0+CELL-8 { 60 if fb[y*ww()+x]!=bgc { n=n+1 } 61 x=x+1 62 } 63 y=y+1 64 } 65 return n 66} 67func first_empty(base: i64) -> i64 { 68 var i: i64 = 0 69 while i<81 { if given(base,i)==0 { return i } i=i+1 } 70 return 0-1 71} 72func first_given(base: i64) -> i64 { 73 var i: i64 = 0 74 while i<81 { if given(base,i)==1 { return i } i=i+1 } 75 return 0-1 76} 77 78func main() -> i64 { 79 gv_head("=== NX-SUDOKU-WASM GATE (sovereign browser sudoku; base-relative, no JS, no float) ===" as *u8) 80 let ctr: *i64 = gv_ctr() 81 let buf: *u8 = sys_mmap(O_END + 4096) 82 let base: i64 = buf as i64 83 84 // ---------- T1: a puzzle is generated, within the proven clue bounds ---------- 85 let c0: i64 = init_impl(base) 86 gv_puts(" clues=" as *u8); gv_num(c0); gv_puts(" (floor 17, must be < 81)\n" as *u8) 87 var ok: i64 = 0 88 if c0>=17 { if c0<81 { ok=1 } } 89 gv_check("T1 generated puzzle respects the 17-clue floor and is not a full grid" as *u8, ok, ctr) 90 91 // ---------- T2: the generated puzzle has EXACTLY ONE solution ---------- 92 // counted on a scratch copy through the SHARED core -- the same counter the bank tool uses. 93 sc_copy(base+O_CORE, SC_G, SC_WORK) 94 let nsol: i64 = sc_count(base+O_CORE, SC_WORK, 0, 0) 95 gv_puts(" solutions=" as *u8); gv_num(nsol); gv_puts("\n" as *u8) 96 gv_check("T2 puzzle is uniquely solvable (counted, not assumed)" as *u8, nsol==1, ctr) 97 98 // ---------- T3: every given agrees with the solution it was carved from ---------- 99 ok = 1 100 var i: i64 = 0 101 while i<81 { if given(base,i)==1 { if sc_get(base+O_CORE,SC_G,i)!=soln(base,i) { ok=0 } } i=i+1 } 102 gv_check("T3 every given clue matches the solution" as *u8, ok, ctr) 103 104 // ---------- T4: RENDER NON-VACUITY -- a blank or single-colour frame must fail ---------- 105 let nc: i64 = distinct_colors(base, 64) 106 gv_puts(" distinct_colors=" as *u8); gv_num(nc); gv_puts(" (a blank frame would be 1)\n" as *u8) 107 gv_check("T4 render produced a non-trivial frame (>=6 distinct colours)" as *u8, nc>=6, ctr) 108 109 // ---------- T5: render actually WRITES -- control against a poisoned buffer ---------- 110 let fb: *i64 = (base + O_FB) as *i64 111 let poison: i64 = 123456789 112 var p: i64 = 0 113 while p < ww()*hh() { fb[p]=poison; p=p+1 } 114 var poisoned: i64 = 0 115 if fb[0]==poison { poisoned=1 } 116 // assert the fixture REACHED the condition before asserting the outcome 117 gv_check("T5a fixture reached the poisoned state" as *u8, poisoned, ctr) 118 render_impl(base) 119 var left: i64 = 0 120 p = 0 121 while p < ww()*hh() { if fb[p]==poison { left=left+1 } p=p+1 } 122 gv_puts(" poison_pixels_remaining=" as *u8); gv_num(left); gv_puts("\n" as *u8) 123 gv_check("T5b render overwrote every pixel (it truly draws the whole frame)" as *u8, left==0, ctr) 124 125 // ---------- T6: placing a digit draws it -- pixels inside THAT cell must change ---------- 126 let e: i64 = first_empty(base) 127 // select FIRST and re-render, so the selection highlight is not what gets measured; the only 128 // difference between before and after is then the glyph itself. 129 st_put(base, S_SEL, e) 130 render_impl(base) 131 let before: i64 = cell_ink(base, e) 132 tick_impl(base, 5) 133 let after: i64 = cell_ink(base, e) 134 gv_puts(" cell_ink empty=" as *u8); gv_num(before) 135 gv_puts(" with_digit=" as *u8); gv_num(after); gv_puts("\n" as *u8) 136 // before==0 is the ANTI-SATURATION assertion: it proves the measure can read "nothing drawn". 137 ok = 0 138 if before==0 { if after>0 { if cur_get(base,e)==5 { ok=1 } } } 139 gv_check("T6 an empty cell measures 0 ink and placing a digit draws pixels in it" as *u8, ok, ctr) 140 141 // ---------- T7: ANTI-VACUITY TWIN -- a GIVEN is immutable, in state AND pixels ---------- 142 let g: i64 = first_given(base) 143 let gval: i64 = cur_get(base, g) 144 st_put(base, S_SEL, g) 145 render_impl(base) 146 let gink: i64 = cell_ink(base, g) 147 let ch: i64 = tick_impl(base, 9) 148 gv_puts(" given_ink=" as *u8); gv_num(gink); gv_puts(" (must be > 0, else this control is vacuous)\n" as *u8) 149 ok = 0 150 if gink>0 { if cur_get(base,g)==gval { if cell_ink(base,g)==gink { if ch==0 { ok=1 } } } } 151 gv_check("T7 neg-control: writing over a given changes nothing (state, pixels, or return)" as *u8, ok, ctr) 152 153 // ---------- T8: solved detection, both directions ---------- 154 i = 0 155 while i<81 { cur_put(base, i, soln(base,i)); i=i+1 } 156 check_solved(base) 157 let sv1: i64 = st_get(base,S_SOLVED) 158 let e2: i64 = first_empty(base) 159 var wrongv: i64 = soln(base,e2) + 1 160 if wrongv>9 { wrongv = 1 } 161 cur_put(base, e2, wrongv) 162 check_solved(base) 163 let sv2: i64 = st_get(base,S_SOLVED) 164 ok = 0 165 if sv1==1 { if sv2==0 { ok=1 } } 166 gv_check("T8 solved detection fires on a correct grid and clears on one wrong cell" as *u8, ok, ctr) 167 168 // ---------- T9: score counts only correct player entries ---------- 169 i = 0 170 while i<81 { if given(base,i)==0 { cur_put(base,i,0) } i=i+1 } 171 let s0: i64 = score_impl(base) 172 cur_put(base, e2, soln(base,e2)) 173 let s1: i64 = score_impl(base) 174 cur_put(base, e2, wrongv) 175 let s2: i64 = score_impl(base) 176 gv_puts(" score empty=" as *u8); gv_num(s0) 177 gv_puts(" correct=" as *u8); gv_num(s1) 178 gv_puts(" wrong=" as *u8); gv_num(s2); gv_puts("\n" as *u8) 179 ok = 0 180 if s0==0 { if s1==1 { if s2==0 { ok=1 } } } 181 gv_check("T9 score rises on a correct entry and not on a wrong one" as *u8, ok, ctr) 182 183 // ---------- T10: navigation wraps and stays in range ---------- 184 st_put(base, S_SEL, 0) 185 tick_impl(base, 10) 186 let selL: i64 = st_get(base,S_SEL) 187 st_put(base, S_SEL, 0) 188 tick_impl(base, 12) 189 let selU: i64 = st_get(base,S_SEL) 190 ok = 0 191 if selL==8 { if selU==72 { ok=1 } } 192 gv_check("T10 selection wraps left 0->8 and up 0->72" as *u8, ok, ctr) 193 194 // ---------- T11: DETERMINISM -- same seed, same puzzle ---------- 195 let b2buf: *u8 = sys_mmap(O_END + 4096) 196 let b2: i64 = b2buf as i64 197 init_impl(b2) 198 ok = 1 199 i = 0 200 while i<81 { 201 if sc_get(base+O_CORE,SC_SOL,i)!=sc_get(b2+O_CORE,SC_SOL,i) { ok=0; i=81 } else { i=i+1 } 202 } 203 gv_check("T11 the fixed init seed reproduces an identical puzzle" as *u8, ok, ctr) 204 205 // ---------- T12: a NEW puzzle is actually different, and still unique ---------- 206 let ss1: i64 = sc_get(b2+O_CORE,SC_SOL,0)*100 + sc_get(b2+O_CORE,SC_SOL,40) 207 tick_impl(b2, 20) 208 let ss2: i64 = sc_get(b2+O_CORE,SC_SOL,0)*100 + sc_get(b2+O_CORE,SC_SOL,40) 209 sc_copy(b2+O_CORE, SC_G, SC_WORK) 210 let n2: i64 = sc_count(b2+O_CORE, SC_WORK, 0, 0) 211 ok = 0 212 if ss1!=ss2 { if n2==1 { ok=1 } } 213 gv_check("T12 new-puzzle yields a different grid that is also uniquely solvable" as *u8, ok, ctr) 214 215 // ---------- T13: tap hit-testing, inside and outside the grid ---------- 216 // Touch is the primary input on a phone, so the module owns cell hit testing and the page never 217 // computes a cell. Both directions: a point inside a known cell selects exactly that cell, and a 218 // point outside selects nothing AND leaves the selection untouched. 219 let want: i64 = 4*9 + 6 220 let cx: i64 = MARGIN + 6*CELL + CELL/2 221 let cy: i64 = MARGIN + 4*CELL + CELL/2 222 let hit: i64 = tap_impl(base, cx, cy) 223 st_put(base, S_SEL, want) 224 let miss: i64 = tap_impl(base, 1, 1) 225 let selAfter: i64 = st_get(base, S_SEL) 226 gv_puts(" tap_center=" as *u8); gv_num(hit) 227 gv_puts(" want=" as *u8); gv_num(want) 228 gv_puts(" tap_outside=" as *u8); gv_num(miss) 229 gv_puts(" sel_preserved=" as *u8); gv_num(selAfter); gv_puts("\n" as *u8) 230 ok = 0 231 if hit==want { if miss==(0-1) { if selAfter==want { ok=1 } } } 232 gv_check("T13 tap selects the cell under the point and refuses points outside the grid" as *u8, ok, ctr) 233 234 return gv_verdict("NX-SUDOKU-WASM-GATE" as *u8, ctr, 235 "sovereign NishiLang sudoku: rules+solver+generator+renderer in one base-relative module, no JS logic, no float; uniqueness COUNTED via the shared nx_sudoku_core; pixel teeth include a poisoned-buffer control and an anti-saturation empty-cell assertion" as *u8) 236}