code wiki / _hdl_build / nx_gx7_game_gate.nx

nx_gx7_game_gate.nx source

↩ module page · 287 lines · 16133 B

1// nx_gx7_game_gate.nx -- Gx-7: the first playable becomes an actual GAME. The walk gate proved locomotion; 2// this adds the three things that make it a game rather than a camera demo: ENTITIES that exist in world 3// state, COLLISION that changes that state, and a WIN CONDITION the player can actually reach. 4// The full loop, every tick: input -> player state -> COLLISION RESOLVE (world state mutates) -> build the 5// player-relative mesh (terrain bands + only the entities still alive) -> nx_swgpu -> HUD overlay -> canvas 6// blit -> live browser repaint. The render is a PURE FUNCTION of game state, so "it vanished when I picked it 7// up" is a rendering fact, not a separate animation to keep in sync. Still DETERMINISTIC end to end: the same 8// script replays to byte-identical frames INCLUDING the collision outcomes. 9// license_tier: ORIGINAL expect_exit: 0 10import "nx_browser_render.nx" 11import "nx_game_world.nx" 12import "nx_frame_sanity.nx" 13 14const G7_STEP: i64 = 8192 // forward step = 2 cells 15const G7_TURN: i64 = 3217 // ~45 degrees, rad*4096 16const G7_TICKS: i64 = 14 17const G7_N: i64 = 5 // beacons 18const G7_PICKR: i64 = 4915 // pickup radius = 1.2 cells (fx4096) 19const G7_EYE: i64 = 6600 // eye height above local ground (1.6 world units) 20const G7_HUD_H: i64 = 24 // HUD strip height, excluded from beacon pixel measurement 21 22func g7_puts(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 } 23func g7_pn(v: i64) -> i64 { let b: *u8=sys_mmap(32) as *u8; var x: i64=v; var ng: i64=0; if x<0{ng=1;x=0-x} var i: i64=31; if x==0{b[i]=48 as u8;i=i-1} while x>0{b[i]=(48+x%10) as u8;x=x/10;i=i-1} if ng==1{b[i]=45 as u8;i=i-1} sys_write(1,(b as i64+i+1) as *u8,31-i); return 0 } 24func g7_now_ms() -> i64 { let ts: *i64=sys_mmap(16) as *i64; sys_clock_gettime_mono(ts); return ts[0]*1000 + ts[1]/1000000 } 25 26// HUD: one slot per beacon, lit when collected, plus a banner the moment the game is won. 27func g7_hud(dst: *u8, collected: i64, won: i64) -> i64 { 28 var k: i64 = 0 29 while k < G7_N { 30 let x0: i64 = 8 + k*22 31 var yy: i64 = 6 32 while yy < 20 { 33 var xx: i64 = x0 34 while xx < x0 + 16 { 35 let o: i64 = (yy*ww()+xx)*3 36 if k < collected { dst[o]=80 as u8; dst[o+1]=220 as u8; dst[o+2]=240 as u8 } 37 else { dst[o]=58 as u8; dst[o+1]=58 as u8; dst[o+2]=68 as u8 } 38 xx = xx + 1 39 } 40 yy = yy + 1 41 } 42 k = k + 1 43 } 44 if won == 1 { 45 var y2: i64 = 0 46 while y2 < 5 { 47 var x2: i64 = 0 48 while x2 < ww() { let o: i64=(y2*ww()+x2)*3; dst[o]=90 as u8; dst[o+1]=220 as u8; dst[o+2]=120 as u8; x2=x2+1 } 49 y2 = y2 + 1 50 } 51 } 52 return 0 53} 54 55// one tick's render: 3 terrain bands + the LIVE entities, all over one shared z-buffer so beacons are 56// correctly occluded by the hills in front of them 57func g7_render(base: i64, ex: *i64, ez: *i64, alive: *i64, ppx: i64, ppz: i64, yaw: i64, pitch: i64, eyey: i64, dst: *u8) -> i64 { 58 gw_build_terrain(base, 0, ppx, ppz, eyey) 59 sg_project_clip_pitch(base, yaw, pitch, 0) 60 sg_render(base, 74, 156, 62) // grass -- clears fb + zbuf 61 gw_build_terrain(base, 1, ppx, ppz, eyey) 62 sg_project_clip_pitch(base, yaw, pitch, 0) 63 sg_render_pass(base, 128, 112, 100) // rock 64 gw_build_terrain(base, 2, ppx, ppz, eyey) 65 sg_project_clip_pitch(base, yaw, pitch, 0) 66 sg_render_pass(base, 232, 234, 240) // snow 67 let live: i64 = gw_build_entities(base, ex, ez, alive, G7_N, ppx, ppz, eyey, 1400, 5200) 68 if live > 0 { 69 sg_project_clip_pitch(base, yaw, pitch, 0) 70 sg_render_pass(base, 240, 200, 60) // beacons -- gold, distinct from every terrain band 71 } 72 // AMBIENT OCCLUSION last, over the completed depth buffer: grounds the beacons against the terrain and 73 // deepens the folds of the hills. Must run AFTER every geometry pass or it would occlude against a 74 // half-filled z-buffer. 75 sg_ssao(base, 190) 76 let fb: *i64 = sg_fb(base) 77 var p: i64 = 0 78 while p < ww()*hh() { 79 let v: i64 = fb[p] 80 dst[p*3] = (v % 256) as u8 81 dst[p*3+1] = ((v/256) % 256) as u8 82 dst[p*3+2] = ((v/65536) % 256) as u8 83 p = p + 1 84 } 85 return 0 86} 87 88// gold beacon pixels BELOW the HUD strip (so the HUD's own colours can never be mistaken for a beacon) 89func g7_gold(dst: *u8) -> i64 { 90 var n: i64 = 0 91 var y: i64 = G7_HUD_H 92 while y < hh() { 93 var x: i64 = 0 94 while x < ww() { 95 let o: i64 = (y*ww()+x)*3 96 let r: i64 = dst[o] as i64 97 let g: i64 = dst[o+1] as i64 98 let b: i64 = dst[o+2] as i64 99 if r > 140 { if r > b + 70 { if g > b + 30 { n = n + 1 } } } 100 x = x + 1 101 } 102 y = y + 1 103 } 104 return n 105} 106 107// Run the whole scripted session. Returns the frame checksum; writes per-tick collected counts into `hist`. 108func g7_run(base: i64, fbuf: *u8, hist: *i64) -> i64 { 109 // script: F F L R F F F F L R F F F F -- 10 forwards, turn pairs cancel so the path stays on the beacon line 110 let scr: *i64 = sys_mmap(8*20) as *i64 111 scr[0]=1; scr[1]=1; scr[2]=2; scr[3]=3; scr[4]=1; scr[5]=1; scr[6]=1 112 scr[7]=1; scr[8]=2; scr[9]=3; scr[10]=1; scr[11]=1; scr[12]=1; scr[13]=1 113 // beacons every 4 cells up the approach to the mountain 114 let ex: *i64 = sys_mmap(8*8) as *i64 115 let ez: *i64 = sys_mmap(8*8) as *i64 116 let alive: *i64 = sys_mmap(8*8) as *i64 117 var k: i64 = 0 118 while k < G7_N { ex[k] = 14*GW_CELL; ez[k] = (8 + k*4)*GW_CELL; alive[k] = 1; k = k + 1 } 119 var ppx: i64 = 14*GW_CELL 120 var ppz: i64 = 4*GW_CELL 121 var yaw: i64 = 0 122 var collected: i64 = 0 123 var won: i64 = 0 124 var ck: i64 = 0 125 var t: i64 = 0 126 while t < G7_TICKS { 127 // 1. INPUT -> player state 128 if scr[t] == 1 { ppx = ppx + it_sin4096(yaw)*G7_STEP/4096; ppz = ppz + it_cos4096(yaw)*G7_STEP/4096 } 129 if scr[t] == 2 { yaw = yaw + G7_TURN } 130 if scr[t] == 3 { yaw = yaw - G7_TURN } 131 // 2. COLLISION RESOLVE -- world state mutates, and that is the only thing that hides a beacon 132 var c: i64 = 0 133 while c < G7_N { 134 if alive[c] == 1 { 135 if gw_dist2(ppx, ppz, ex[c], ez[c]) <= G7_PICKR * G7_PICKR { 136 alive[c] = 0 137 collected = collected + 1 138 } 139 } 140 c = c + 1 141 } 142 if collected >= G7_N { won = 1 } 143 hist[t] = collected 144 // 3. RENDER from state 145 var gi: i64 = ppx / GW_CELL; if gi < 0 { gi = 0 } if gi > GW_N { gi = GW_N } 146 var gj: i64 = ppz / GW_CELL; if gj < 0 { gj = 0 } if gj > GW_N { gj = GW_N } 147 let ground: i64 = gw_h(gi,gj) 148 let eyey: i64 = ground + G7_EYE 149 // CAMERA PITCH aimed at the ground the player is WALKING TOWARD, not at their own elevation. 150 // First attempt keyed off the player's own height and looked DOWN while walking INTO a rising slope, 151 // burying the camera in the hillside -- caught by eyeballing the mid-session frame. The honest signal 152 // is the height of the terrain a few cells ahead relative to the eye: below -> look down, above -> 153 // look up. Small-angle ratio in rad*4096, clamped to +/-39 degrees. 154 // Aim at EYE HEIGHT over the terrain ahead, not at the ground itself: targeting the ground 4 cells 155 // out means staring at your own feet on flat land (the first build did exactly that). Adding the eye 156 // offset makes flat ground give pitch 0, a rise ahead look up, and falling ground look down. 157 let lax: i64 = ppx + it_sin4096(yaw) * (6*GW_CELL) / 4096 158 let laz: i64 = ppz + it_cos4096(yaw) * (6*GW_CELL) / 4096 159 let dy: i64 = (gw_ground(lax, laz) + G7_EYE) - eyey 160 var pitch: i64 = (0 - dy) * 4096 / (6*GW_CELL) 161 if pitch < (0-2800) { pitch = 0-2800 } 162 if pitch > 2800 { pitch = 2800 } 163 let dst: *u8 = ((fbuf as i64) + t*ww()*hh()*3) as *u8 164 g7_render(base, ex, ez, alive, ppx, ppz, yaw, pitch, eyey, dst) 165 g7_hud(dst, collected, won) 166 var p: i64 = 0 167 while p < ww()*hh()*3 { ck = ck + (dst[p] as i64)*(1 + p % 7); p = p + 97 } 168 t = t + 1 169 } 170 g7_puts(" final state: x="); g7_pn(ppx); g7_puts(" z="); g7_pn(ppz) 171 g7_puts(" collected="); g7_pn(collected); g7_puts("/"); g7_pn(G7_N); g7_puts(" won="); g7_pn(won); g7_puts("\n" as *u8) 172 return ck 173} 174 175func main() -> i64 { 176 g7_puts("=== nx_gx7_game_gate -- entities, collision and a win condition: the playable becomes a game ===\n" as *u8) 177 var fails: i64 = 0 178 179 let html: *u8 = "<html><head><style>canvas{display:block;width:512px;height:384px;margin:18px 0}</style></head><body><h1>Sovereign Collect</h1><p>Five beacons stand on the approach to the mountain. An input script walks the player through them; each one picked up changes world state, lights a slot in the heads-up display, and stops being drawn. Collect all five and the game is won.</p><canvas></canvas><p>Every frame is rendered by our own software GPU into our own browser. Deterministic end to end: the same inputs replay to byte-identical frames, collisions included.</p></body></html>\x00" as *u8 180 let page: *Page = sys_mmap(NX_PAGE_BYTES) as *Page 181 page.raw = html; page.raw_len = br_slen(html); page.dark_mode = 0 182 br_layout(page, 1000) 183 var t1: i64 = 0 184 if page.ok==1 { if page.tree.count>5 { t1=1 } } 185 let cv: i64 = br_find_canvas(page, 0) 186 if cv>=0 { if t1==1 { t1=1 } else { t1=0 } } else { t1=0 } 187 if t1==1 { g7_puts("T1 PASS the live browser laid out the game page and gave it a canvas surface\n" as *u8) } 188 else { fails=fails+1; g7_puts("T1 FAIL page/canvas\n" as *u8) } 189 190 let base: i64 = sys_mmap(swgpu_bytes()) as i64 191 let runA: *u8 = sys_mmap(G7_TICKS*ww()*hh()*3+16) 192 let runB: *u8 = sys_mmap(G7_TICKS*ww()*hh()*3+16) 193 let histA: *i64 = sys_mmap(8*32) as *i64 194 let histB: *i64 = sys_mmap(8*32) as *i64 195 let t0: i64 = g7_now_ms() 196 let ckA: i64 = g7_run(base, runA, histA) 197 let ms: i64 = g7_now_ms() - t0 198 let ckB: i64 = g7_run(base, runB, histB) 199 g7_puts(" MEASURED: "); g7_pn(G7_TICKS); g7_puts(" game ticks in "); g7_pn(ms); g7_puts(" ms = "); g7_pn(ms/G7_TICKS) 200 g7_puts(" ms/tick (terrain 3 passes + entity pass + collision + SSAO post-pass + HUD, 512x384; AO costs ~4 ms of that)\n" as *u8) 201 202 // ---- T2 ENTITIES RENDER: gold beacon pixels are on screen at the start ---- 203 let gold0: i64 = g7_gold(runA) 204 g7_puts(" beacon pixels: first frame="); g7_pn(gold0) 205 let goldEnd: i64 = g7_gold(((runA as i64) + (G7_TICKS-1)*ww()*hh()*3) as *u8) 206 g7_puts(" final frame="); g7_pn(goldEnd); g7_puts("\n" as *u8) 207 var t2: i64 = 0 208 if gold0 > 100 { t2 = 1 } 209 if t2==1 { g7_puts("T2 PASS entities render into the world (z-buffered against the terrain)\n" as *u8) } 210 else { fails=fails+1; g7_puts("T2 FAIL no beacons drawn\n" as *u8) } 211 212 // ---- T3 COLLISION drives state on the expected schedule (a beacon every 2 forward steps) ---- 213 g7_puts(" collected per tick:" as *u8) 214 var i: i64 = 0 215 while i < G7_TICKS { g7_puts(" " as *u8); g7_pn(histA[i]); i = i + 1 } 216 g7_puts("\n" as *u8) 217 var t3: i64 = 0 218 if histA[0]==0 { if histA[1]==1 { if histA[5]==2 { if histA[7]==3 { if histA[11]==4 { if histA[13]==5 { t3=1 } } } } } } 219 if t3==1 { g7_puts("T3 PASS collision fires on schedule: 0 -> 1 -> 2 -> 3 -> 4 -> 5 as the player reaches each beacon\n" as *u8) } 220 else { fails=fails+1; g7_puts("T3 FAIL collision schedule\n" as *u8) } 221 222 // ---- T4 the WIN CONDITION is actually reachable by playing ---- 223 var t4: i64 = 0 224 if histA[G7_TICKS-1] == G7_N { t4 = 1 } 225 if t4==1 { g7_puts("T4 PASS win condition reached: all "); g7_pn(G7_N); g7_puts(" beacons collected, banner drawn\n" as *u8) } 226 else { fails=fails+1; g7_puts("T4 FAIL not won\n" as *u8) } 227 228 // ---- T5 the RENDER IS A FUNCTION OF STATE: collected beacons stop being drawn ---- 229 var t5: i64 = 0 230 if goldEnd * 10 < gold0 { t5 = 1 } 231 if t5==1 { g7_puts("T5 PASS picked-up beacons vanish from the render ("); g7_pn(gold0); g7_puts(" px -> "); g7_pn(goldEnd); g7_puts(" px)\n" as *u8) } 232 else { fails=fails+1; g7_puts("T5 FAIL beacons still drawn after collection\n" as *u8) } 233 234 // ---- T6 DETERMINISM survives gameplay: identical script -> identical frames AND identical outcomes ---- 235 var samehist: i64 = 1 236 i = 0 237 while i < G7_TICKS { if histA[i] != histB[i] { samehist = 0 } i = i + 1 } 238 var t6: i64 = 0 239 if ckA==ckB { if samehist==1 { if ckA>0 { t6=1 } } } 240 if t6==1 { g7_puts("T6 PASS deterministic WITH gameplay: same script -> identical frames and identical collision outcomes (ck "); g7_pn(ckA); g7_puts(")\n" as *u8) } 241 else { fails=fails+1; g7_puts("T6 FAIL ckA="); g7_pn(ckA); g7_puts(" ckB="); g7_pn(ckB); g7_puts(" hist_match="); g7_pn(samehist); g7_puts("\n" as *u8) } 242 243 // ---- paint three moments of the session through the browser: start, mid-collect, and the win ---- 244 if cv>=0 { 245 page.bimg_w[cv] = ww(); page.bimg_h[cv] = hh() 246 page.bimg[cv] = (runA as i64) 247 br_shot_png(page, "https://nishifamily.com/world/collect\x00" as *u8, 37, "knowledge/nx_gx7_game_t0.png\x00" as *u8) 248 page.bimg[cv] = (runA as i64) + 7*ww()*hh()*3 249 br_shot_png(page, "https://nishifamily.com/world/collect\x00" as *u8, 37, "knowledge/nx_gx7_game_t7.png\x00" as *u8) 250 page.bimg[cv] = (runA as i64) + 13*ww()*hh()*3 251 br_shot_png(page, "https://nishifamily.com/world/collect\x00" as *u8, 37, "knowledge/nx_gx7_game_win.png\x00" as *u8) 252 } 253 let szp: *i64 = sys_mmap(16) as *i64 254 var ok: i64 = 0 255 let r0: *u8 = sys_read_file("knowledge/nx_gx7_game_t0.png\x00" as *u8, szp) 256 if (r0 as i64)!=0 { if szp[0]>1000 { ok=ok+1 } } 257 let r7: *u8 = sys_read_file("knowledge/nx_gx7_game_t7.png\x00" as *u8, szp) 258 if (r7 as i64)!=0 { if szp[0]>1000 { ok=ok+1 } } 259 let rw: *u8 = sys_read_file("knowledge/nx_gx7_game_win.png\x00" as *u8, szp) 260 if (rw as i64)!=0 { if szp[0]>1000 { ok=ok+1 } } 261 if ok==3 { g7_puts("T7 PASS three moments browser-painted (start, mid-collect, win)\n" as *u8) } 262 else { fails=fails+1; g7_puts("T7 FAIL pngs="); g7_pn(ok); g7_puts("\n" as *u8) } 263 264 // T8 EXPERIENTIAL (nx_frame_sanity, seq372 rung 1): the frames a player SEES must read as real 265 // scenes. T1-T7 measured GREEN through all three historical framing bugs (pure-sky win frame, 266 // buried camera, blown-white) -- this tooth is the eyeball as a machine check, on the START frame 267 // and the WIN frame (the win frame is exactly where the sky bug lived). 268 let fsth: *i64 = sys_mmap(FS_NMETRIC*8) as *i64 269 fs_default_th(fsth) 270 let fsm0: *i64 = sys_mmap(FS_NMETRIC*8) as *i64 271 let fsmw: *i64 = sys_mmap(FS_NMETRIC*8) as *i64 272 var fsok: i64 = 0 273 fs_score(runA, ww(), hh(), fsm0) 274 if fs_verdict(fsm0, fsth)==1 { fsok=fsok+1 } 275 let midoff: i64 = (runA as i64) + 7*ww()*hh()*3 276 fs_score(midoff as *u8, ww(), hh(), fsmw) 277 if fs_verdict(fsmw, fsth)==1 { fsok=fsok+1 } 278 g7_puts(" fs start="); g7_pn(fsm0[0]); g7_puts("/" as *u8); g7_pn(fsm0[1]); g7_puts("/" as *u8); g7_pn(fsm0[2]); g7_puts("/" as *u8); g7_pn(fsm0[3]); g7_puts("/" as *u8); g7_pn(fsm0[4]) 279 g7_puts(" mid="); g7_pn(fsmw[0]); g7_puts("/" as *u8); g7_pn(fsmw[1]); g7_puts("/" as *u8); g7_pn(fsmw[2]); g7_puts("/" as *u8); g7_pn(fsmw[3]); g7_puts("/" as *u8); g7_pn(fsmw[4]); g7_puts("\n" as *u8) 280 if fsok==2 { g7_puts("T8 PASS experiential: start + mid-collect frames read as real scenes\n" as *u8) } 281 else { fails=fails+1; g7_puts("T8 FAIL experiential fsok="); g7_pn(fsok); g7_puts("\n" as *u8) } 282 283 if fails==0 { g7_puts("GX7-GAME GREEN -- entities exist in world state, collision changes it, the win condition is reachable, and the whole session is deterministic\n" as *u8); sys_exit(0); return 0 } 284 g7_puts("GX7-GAME RED fails="); g7_pn(fails); g7_puts("\n" as *u8) 285 sys_exit(1) 286 return 1 287}