code wiki / _hdl_build / nx_wasm_diablo_html.nx
nx_wasm_diablo_html.nx source
↩ module page · 60 lines · 5656 B
1// nx_wasm_diablo_html.nx -- SOVEREIGN packager: the isometric ARPG wasm -> a self-contained playable .html WITH
2// AUDIO. PRE-GENERATES 4 SFX natively (nx_audio_sfx: noise=hit, explosion=kill, coin=loot, tone=game-over ->
3// nx_audio_wav), embeds them base64, and plays them via Web Audio when the wasm core signals an event
4// (ex.sfx_event(): 1=hit 2=kill 3=coin 4=gameover). JS does ONLY blit + click + the rAF loop + sound playback;
5// all logic/render (iso + 2.5D walls + shaded sprites + torch lighting) + SFX synthesis are sovereign.
6// usage: nx_wasm_diablo_html <in.wasm> <out.html> license_tier: ORIGINAL
7import "nx_syscalls.nx"
8import "nx_base64.nx"
9import "nx_audio_sfx.nx"
10import "nx_audio_wav.nx"
11const K_MAGIC_8000: i64 = 8000
12const K_MAGIC_2654435761: i64 = 2654435761
13const K_MAGIC_9000: i64 = 9000
14const K_MAGIC_22050: i64 = 22050
15
16func hw(fd: i64, s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(fd, s, n); return 0 }
17
18// kind: 1=noise(hit) 2=explosion(kill) 3=coin 4=tone(game-over) -> WAV -> base64; returns b64 len, sets b64out[0]
19func sfx_b64(kind: i64, ms: i64, rate: i64, b64out: *i64) -> i64 {
20 let n: i64 = (rate * ms) / 1000
21 let samp: *i64 = sys_mmap(n * 8) as *i64
22 if kind == 1 { sfx_noise(samp, n, K_MAGIC_8000, K_MAGIC_2654435761) }
23 else { if kind == 2 { sfx_explosion(samp, n, rate) } else { if kind == 3 { sfx_coin(samp, n, rate) } else { sfx_tone(samp, n, 150, rate, K_MAGIC_9000) } } }
24 let wav: *u8 = sys_mmap(64 + n * 4)
25 let wl: i64 = wav_render_stereo(samp, samp, n, rate, wav)
26 let b64: *u8 = sys_mmap(wl * 2 + 64)
27 let bl: i64 = b64_encode(wav, wl, b64)
28 b64out[0] = b64 as i64
29 return bl
30}
31
32func main(argc: i64, argv: *i64) -> i64 {
33 if argc < 3 { hw(2, "usage: nx_wasm_diablo_html <in.wasm> <out.html>\n" as *u8); return 2 }
34 let wlen_p: *i64 = sys_mmap(8) as *i64
35 let wbytes: *u8 = sys_read_file(argv[1] as *u8, wlen_p)
36 if (wbytes as i64) == 0 { hw(2, "read wasm failed\n" as *u8); return 3 }
37 let wlen: i64 = wlen_p[0]
38 let b64: *u8 = sys_mmap(wlen * 2 + 64) as *u8
39 let b64len: i64 = b64_encode(wbytes, wlen, b64)
40
41 let rate: i64 = K_MAGIC_22050
42 let p1: *i64 = sys_mmap(8) as *i64; let l1: i64 = sfx_b64(1, 90, rate, p1) // hit = short noise
43 let p2: *i64 = sys_mmap(8) as *i64; let l2: i64 = sfx_b64(2, 340, rate, p2) // kill = explosion
44 let p3: *i64 = sys_mmap(8) as *i64; let l3: i64 = sfx_b64(3, 200, rate, p3) // coin = ding
45 let p4: *i64 = sys_mmap(8) as *i64; let l4: i64 = sfx_b64(4, 500, rate, p4) // over = low tone
46
47 let fd: i64 = sys_openat_wr(argv[2] as *u8, 0x1a4)
48 if fd < 0 { hw(2, "open html failed\n" as *u8); return 4 }
49 hw(fd, "<!doctype html><html><head><meta charset='utf-8'><title>Nishi ARPG (sovereign wasm + audio)</title><style>body{background:#05060a;margin:0;display:flex;flex-direction:column;justify-content:center;align-items:center;height:100vh;font-family:sans-serif;color:#cbd}canvas{image-rendering:pixelated;width:576px;height:384px;border:1px solid #2a3140;background:#05060a;cursor:crosshair}p{margin:8px}</style></head><body><h2>Nishi ARPG — Sanctuary</h2><canvas id='c'></canvas><p id='hud'>CLICK to move · walk into the fallen to strike them · grab the gold · sound on!</p><p style='color:#566;font-size:12px'>100% Nishi: isometric world + 2.5D walls + shaded sprites + torch-follow lighting + SFX synthesis, all sovereign WASM/native; the browser only blits, forwards clicks, and plays our pre-rendered WAVs on events</p><script>\n" as *u8)
50 hw(fd, "const B='" as *u8); sys_write(fd, b64, b64len); hw(fd, "';\n" as *u8)
51 hw(fd, "const SFX1='" as *u8); sys_write(fd, p1[0] as *u8, l1); hw(fd, "';\n" as *u8)
52 hw(fd, "const SFX2='" as *u8); sys_write(fd, p2[0] as *u8, l2); hw(fd, "';\n" as *u8)
53 hw(fd, "const SFX3='" as *u8); sys_write(fd, p3[0] as *u8, l3); hw(fd, "';\n" as *u8)
54 hw(fd, "const SFX4='" as *u8); sys_write(fd, p4[0] as *u8, l4); hw(fd, "';\n" as *u8)
55 hw(fd, "const bytes=Uint8Array.from(atob(B),c=>c.charCodeAt(0));\nlet actx=null;const SB={};\nfunction ab(b){return Uint8Array.from(atob(b),c=>c.charCodeAt(0)).buffer;}\nfunction ia(){if(actx)return;actx=new(window.AudioContext||window.webkitAudioContext)();[[1,SFX1],[2,SFX2],[3,SFX3],[4,SFX4]].forEach(function(p){actx.decodeAudioData(ab(p[1])).then(function(b){SB[p[0]]=b;});});}\nfunction ps(id){if(!actx)return;const b=SB[id];if(!b)return;const s=actx.createBufferSource();s.buffer=b;s.connect(actx.destination);s.start();}\nWebAssembly.instantiate(bytes).then(function(r){const ex=r.instance.exports;const W=Number(ex.ww()),H=Number(ex.hh()),off=Number(ex.fb_off());const cv=document.getElementById('c');cv.width=W;cv.height=H;const ctx=cv.getContext('2d');const img=ctx.createImageData(W,H);function blit(){const mem=new Uint8Array(ex.memory.buffer);for(let i=0;i<W*H;i++){const b=off+i*8;img.data[i*4]=mem[b];img.data[i*4+1]=mem[b+1];img.data[i*4+2]=mem[b+2];img.data[i*4+3]=255;}ctx.putImageData(img,0,0);}cv.addEventListener('click',function(e){ia();const r2=cv.getBoundingClientRect();const x=Math.floor((e.clientX-r2.left)*W/r2.width);const y=Math.floor((e.clientY-r2.top)*H/r2.height);ex.click(BigInt(x),BigInt(y));});ex.init();function loop(){ex.tick(0n);ex.render();blit();const ev=Number(ex.sfx_event());if(ev>0)ps(ev);requestAnimationFrame(loop);}requestAnimationFrame(loop);}).catch(function(e){document.body.innerHTML+='<pre>'+e.message+'</pre>';});\n" as *u8)
56 hw(fd, "</script></body></html>\n" as *u8)
57 sys_close(fd)
58 hw(1, "wrote arpg html (with sovereign SFX)\n" as *u8)
59 return 0
60}