nx_dungeon_alpha_wasm.nx source
↩ module page · 326 lines · 10045 B
1// nx_dungeon_alpha_wasm.nx -- Generates a dungeon crawler game with combat, movement, and rendering in WebAssembly.
2const K_MAGIC_1024: i64 = 1024
3const K_MAGIC_1103515245: i64 = 1103515245
4const K_MAGIC_12345: i64 = 12345
5const K_MAGIC_32767: i64 = 32767
6const K_MAGIC_4096: i64 = 4096
7const K_MAGIC_65536: i64 = 65536
8// nx_dungeon_alpha_wasm.nx -- PLAYABLE dungeon-crawler ALPHA, compiled to WASM and run
9// in the browser (nx -> wat -> wasm -> WebAssembly). This is the SOVEREIGN web-game
10// path: the whole game engine (world gen, movement, collision, combat, render to a
11// framebuffer in linear memory) is Nishi-emitted WASM. The browser only blits the
12// framebuffer to a canvas and forwards keystrokes (the thin shim -- same sovereignty
13// status as the crypto wasm already shipped in web_assets/).
14//
15// HONEST AUTHORSHIP (operator law: do not pass tutor work off as team work):
16// - The COMBAT state machine (g_dungenc_step below) is the TEAM's emitter-authored
17// FSM: it is the exact transition table nx_auto_builder emitted into
18// runtime/_hdl_build/_pe_dungenc.nx from the data-only spec build_dungenc.spec.
19// The game genuinely runs the Nishi-authored game system.
20// - The rest of this module (world gen, render, input handling) is TUTOR-authored
21// (Claude) game code, hand-written for the WAT target. It is NOT emitter output.
22// - The named rung to make the WHOLE game emitter-authored from a spec is
23// X-GAME-GEN-001 (GAMESPEC-emitted) + a GAME_BIND/compose-loop emitter shape.
24// This alpha is the playable proof that the target is reachable, not a claim that
25// the Builder authored it.
26//
27// WAT-target constraints honored (per nx_sha256_wasm.nx notes): imports NOTHING, works
28// in fixed linear-memory offsets via local base pointers, integer-only, <=6 args/func,
29// no &&/||. Exported memory layout:
30// 1024.. : game state, 13 i64 slots (gs[])
31// 4096.. : world grid, 16*12 bytes (cell types)
32// 65536..: framebuffer, 320*240 palette bytes (1 byte/pixel; shim maps to colors)
33// license_tier: ORIGINAL (combat FSM lineage: _pe_dungenc, emitter-authored)
34
35// ---- state accessors (fixed offsets; local base ptr = proven WAT-safe pattern) ----
36// gs slots: 0=px 1=py 2=hp 3=score 4=level 5=mode 6=fsm_state 7=enemy_hp
37// 8=rng 9=enemies_left 10=msg 11=enemy_x 12=enemy_y
38// mode: 0=explore 1=combat 2=level_clear 3=dead
39
40func g_rng() -> i64 {
41 let gs: *i64 = (K_MAGIC_1024 as i64) as *i64
42 var s: i64 = gs[8]
43 s = s * K_MAGIC_1103515245 + K_MAGIC_12345
44 gs[8] = s
45 return (s >> 16) & K_MAGIC_32767
46}
47
48func g_cell_get(x: i64, y: i64) -> i64 {
49 if x < 0 { return 1 }
50 if y < 0 { return 1 }
51 if x >= 16 { return 1 }
52 if y >= 12 { return 1 }
53 let w: *u8 = (K_MAGIC_4096 as i64) as *u8
54 return w[y * 16 + x] as i64
55}
56
57func g_cell_set(x: i64, y: i64, v: i64) -> i64 {
58 let w: *u8 = (K_MAGIC_4096 as i64) as *u8
59 w[y * 16 + x] = v as u8
60 return 0
61}
62
63// place n items of `kind` on random interior floor cells (spawn area kept clear)
64func g_place(kind: i64, n: i64) -> i64 {
65 var placed: i64 = 0
66 var tries: i64 = 0
67 while placed < n {
68 if tries > 300 { return placed }
69 tries = tries + 1
70 let rx: i64 = 1 + (g_rng() % 14)
71 let ry: i64 = 1 + (g_rng() % 10)
72 var ok: i64 = 1
73 if g_cell_get(rx, ry) != 0 { ok = 0 }
74 if rx < 3 { if ry < 3 { ok = 0 } }
75 if ok == 1 {
76 g_cell_set(rx, ry, kind)
77 placed = placed + 1
78 }
79 }
80 return placed
81}
82
83func g_build_level() -> i64 {
84 let gs: *i64 = (K_MAGIC_1024 as i64) as *i64
85 var y: i64 = 0
86 while y < 12 {
87 var x: i64 = 0
88 while x < 16 {
89 var c: i64 = 0
90 if x == 0 { c = 1 }
91 if y == 0 { c = 1 }
92 if x == 15 { c = 1 }
93 if y == 11 { c = 1 }
94 g_cell_set(x, y, c)
95 x = x + 1
96 }
97 y = y + 1
98 }
99 var i: i64 = 0
100 while i < 20 {
101 let rx: i64 = 1 + (g_rng() % 14)
102 let ry: i64 = 1 + (g_rng() % 10)
103 var ok: i64 = 1
104 if rx < 3 { if ry < 3 { ok = 0 } }
105 if ok == 1 { g_cell_set(rx, ry, 1) }
106 i = i + 1
107 }
108 g_cell_set(1, 1, 0)
109 let lvl: i64 = gs[4]
110 var ne: i64 = 3 + lvl
111 if ne > 10 { ne = 10 }
112 gs[9] = g_place(2, ne)
113 g_place(3, 1)
114 g_place(5, 2)
115 gs[0] = 1
116 gs[1] = 1
117 gs[5] = 0
118 gs[6] = 0
119 return 0
120}
121
122// ---- the TEAM's emitter-authored combat FSM (exact _pe_dungenc transition table) ----
123// states 0=ENC_START 1=PLAYER_TURN 2=ENEMY_TURN 3=PLAYER_WIN 4=FLEE 5=ENC_END
124// events 0=enter 1=attack 2=enemy_act 3=defeat 4=flee 5=resolve
125func g_dungenc_step(s: i64, e: i64) -> i64 {
126 if s == 0 { if e == 0 { return 1 } }
127 if s == 1 { if e == 1 { return 2 } }
128 if s == 1 { if e == 3 { return 3 } }
129 if s == 1 { if e == 4 { return 4 } }
130 if s == 2 { if e == 2 { return 1 } }
131 if s == 3 { if e == 5 { return 5 } }
132 if s == 4 { if e == 5 { return 5 } }
133 return 0 - 1
134}
135
136func gi_init(seed: i64) -> i64 {
137 let gs: *i64 = (K_MAGIC_1024 as i64) as *i64
138 gs[8] = seed
139 gs[2] = 100
140 gs[3] = 0
141 gs[4] = 1
142 gs[5] = 0
143 gs[6] = 0
144 gs[7] = 0
145 gs[10] = 0
146 g_build_level()
147 return 0
148}
149
150func gi_next() -> i64 {
151 let gs: *i64 = (K_MAGIC_1024 as i64) as *i64
152 gs[4] = gs[4] + 1
153 gs[3] = gs[3] + 100
154 gs[2] = gs[2] + 25
155 if gs[2] > 100 { gs[2] = 100 }
156 g_build_level()
157 return 0
158}
159
160func g_try_move(dx: i64, dy: i64) -> i64 {
161 let gs: *i64 = (K_MAGIC_1024 as i64) as *i64
162 let nx: i64 = gs[0] + dx
163 let ny: i64 = gs[1] + dy
164 let c: i64 = g_cell_get(nx, ny)
165 if c == 1 { return 0 }
166 if c == 2 {
167 gs[5] = 1
168 gs[6] = g_dungenc_step(0, 0)
169 gs[7] = 20 + gs[4] * 5
170 gs[11] = nx
171 gs[12] = ny
172 gs[10] = 1
173 return 1
174 }
175 gs[0] = nx
176 gs[1] = ny
177 if c == 3 { gs[5] = 2; gs[10] = 2; return 2 }
178 if c == 5 { g_cell_set(nx, ny, 0); gs[3] = gs[3] + 25; gs[10] = 3 }
179 return 0
180}
181
182func g_attack() -> i64 {
183 let gs: *i64 = (K_MAGIC_1024 as i64) as *i64
184 if gs[5] != 1 { return 0 }
185 let dmg: i64 = 8 + (g_rng() % 8)
186 gs[7] = gs[7] - dmg
187 if gs[7] <= 0 {
188 gs[6] = g_dungenc_step(1, 3)
189 gs[6] = g_dungenc_step(gs[6], 5)
190 g_cell_set(gs[11], gs[12], 0)
191 gs[3] = gs[3] + 50
192 gs[9] = gs[9] - 1
193 gs[5] = 0
194 gs[6] = 0
195 gs[10] = 4
196 return 1
197 }
198 gs[6] = g_dungenc_step(1, 1)
199 let edmg: i64 = 4 + (g_rng() % (6 + gs[4]))
200 gs[2] = gs[2] - edmg
201 gs[6] = g_dungenc_step(2, 2)
202 if gs[2] <= 0 {
203 gs[2] = 0
204 gs[5] = 3
205 gs[10] = 5
206 }
207 return 0
208}
209
210func g_flee() -> i64 {
211 let gs: *i64 = (K_MAGIC_1024 as i64) as *i64
212 if gs[5] != 1 { return 0 }
213 gs[6] = g_dungenc_step(1, 4)
214 gs[6] = g_dungenc_step(gs[6], 5)
215 gs[5] = 0
216 gs[6] = 0
217 gs[10] = 6
218 return 0
219}
220
221// key: 0=up 1=down 2=left 3=right 4=action 5=restart
222func gi_input(key: i64) -> i64 {
223 let gs: *i64 = (K_MAGIC_1024 as i64) as *i64
224 let mode: i64 = gs[5]
225 if mode == 3 {
226 if key == 4 { gi_init(gs[8] + 7) }
227 return 0
228 }
229 if mode == 2 {
230 if key == 4 { gi_next() }
231 return 0
232 }
233 if mode == 1 {
234 if key == 4 { g_attack() }
235 if key == 0 { g_flee() }
236 if key == 1 { g_flee() }
237 if key == 2 { g_flee() }
238 if key == 3 { g_flee() }
239 return 0
240 }
241 if key == 0 { g_try_move(0, 0 - 1) }
242 if key == 1 { g_try_move(0, 1) }
243 if key == 2 { g_try_move(0 - 1, 0) }
244 if key == 3 { g_try_move(1, 0) }
245 return 0
246}
247
248// ---- render to the framebuffer (palette bytes); shim maps palette -> RGBA ----
249func g_fill_rect(x0: i64, y0: i64, ww: i64, hh: i64, col: i64) -> i64 {
250 let fb: *u8 = (K_MAGIC_65536 as i64) as *u8
251 var y: i64 = y0
252 while y < y0 + hh {
253 if y >= 0 { if y < 240 {
254 var x: i64 = x0
255 while x < x0 + ww {
256 if x >= 0 { if x < 320 { fb[y * 320 + x] = col as u8 } }
257 x = x + 1
258 }
259 } }
260 y = y + 1
261 }
262 return 0
263}
264
265func g_render_world() -> i64 {
266 let gs: *i64 = (K_MAGIC_1024 as i64) as *i64
267 var cy: i64 = 0
268 while cy < 12 {
269 var cx: i64 = 0
270 while cx < 16 {
271 let c: i64 = g_cell_get(cx, cy)
272 if c == 1 {
273 g_fill_rect(cx * 20, cy * 20, 20, 20, 1)
274 } else {
275 g_fill_rect(cx * 20, cy * 20, 20, 20, 0)
276 if c == 2 { g_fill_rect(cx * 20 + 4, cy * 20 + 4, 12, 12, 3) }
277 if c == 3 { g_fill_rect(cx * 20 + 3, cy * 20 + 3, 14, 14, 4) }
278 if c == 5 { g_fill_rect(cx * 20 + 6, cy * 20 + 6, 8, 8, 5) }
279 }
280 cx = cx + 1
281 }
282 cy = cy + 1
283 }
284 g_fill_rect(gs[0] * 20 + 3, gs[1] * 20 + 3, 14, 14, 2)
285 return 0
286}
287
288func g_render_combat() -> i64 {
289 let gs: *i64 = (K_MAGIC_1024 as i64) as *i64
290 g_fill_rect(0, 0, 320, 240, 6)
291 g_fill_rect(40, 90, 64, 80, 2)
292 g_fill_rect(216, 90, 64, 80, 3)
293 let phw: i64 = gs[2] * 120 / 100
294 g_fill_rect(20, 24, 120, 14, 1)
295 g_fill_rect(20, 24, phw, 14, 7)
296 let emax: i64 = 20 + gs[4] * 5
297 var ehp: i64 = gs[7]
298 if ehp < 0 { ehp = 0 }
299 let ehw: i64 = ehp * 120 / emax
300 g_fill_rect(180, 24, 120, 14, 1)
301 g_fill_rect(180, 24, ehw, 14, 3)
302 return 0
303}
304
305func gi_render() -> i64 {
306 let gs: *i64 = (K_MAGIC_1024 as i64) as *i64
307 if gs[5] == 1 { g_render_combat(); return 0 }
308 g_render_world()
309 return 0
310}
311
312func gi_get(field: i64) -> i64 {
313 let gs: *i64 = (K_MAGIC_1024 as i64) as *i64
314 if field >= 0 { if field < 13 { return gs[field] } }
315 return 0
316}
317func gi_fb_offset() -> i64 { return K_MAGIC_65536 }
318func gi_fb_w() -> i64 { return 320 }
319func gi_fb_h() -> i64 { return 240 }
320
321// headless self-test entrypoint: init + render, return enemies placed (level 1 => up to 4)
322func main() -> i64 {
323 gi_init(K_MAGIC_12345)
324 gi_render()
325 return gi_get(9)
326}