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}