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}