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 &mdash; Sanctuary</h2><canvas id='c'></canvas><p id='hud'>CLICK to move &middot; walk into the fallen to strike them &middot; grab the gold &middot; 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}