code wiki / _hdl_build / nx_wasm_html_fps.nx

nx_wasm_html_fps.nx source

↩ module page · 28 lines · 5227 B

1// nx_wasm_html_fps.nx -- SOVEREIGN packager for the FIRST-PERSON wasm world: reads the .wasm, base64s it 2// (sovereign nx_base64), and emits a self-contained playable .html whose shim is pointer-lock mouse-look + WASD 3// movement (camera state in JS, owns trig; calls render(camx256,camy256,camz256,yc,ys,pc,ps) per frame and blits 4// the framebuffer). The JS is EMITTED OUTPUT (the browser last-mile), not a build piece. Single-quoted so no 5// escapes. usage: nx_wasm_html_fps <in.wasm> <out.html>. license_tier: ORIGINAL 6import "nx_syscalls.nx" 7import "nx_base64.nx" 8 9func 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 } 10 11func main(argc: i64, argv: *i64) -> i64 { 12 if argc < 3 { hw(2, "usage: nx_wasm_html_fps <in.wasm> <out.html>\n" as *u8); return 2 } 13 let wlen_p: *i64 = sys_mmap(8) as *i64 14 let wbytes: *u8 = sys_read_file(argv[1] as *u8, wlen_p) 15 if (wbytes as i64) == 0 { hw(2, "read wasm failed\n" as *u8); return 3 } 16 let wlen: i64 = wlen_p[0] 17 let b64: *u8 = sys_mmap(wlen * 2 + 64) as *u8 18 let b64len: i64 = b64_encode(wbytes, wlen, b64) 19 let fd: i64 = sys_openat_wr(argv[2] as *u8, 0x1a4) 20 if fd < 0 { hw(2, "open html failed\n" as *u8); return 4 } 21 hw(fd, "<!doctype html><html><head><meta charset='utf-8'><title>Nishi Voxel World</title><style>html,body{background:#0e1118;margin:0;height:100%;overflow:hidden;font-family:sans-serif;color:#cdd}#wrap{display:flex;flex-direction:column;align-items:center;justify-content:center;height:100vh}canvas{image-rendering:pixelated;width:760px;height:570px;background:#9ec3e6;border:1px solid #333;cursor:pointer}#hud{position:fixed;top:10px;left:0;right:0;text-align:center;font-size:13px;color:#aef;text-shadow:0 1px 2px #000;pointer-events:none}</style></head><body><div id='wrap'><canvas id='c'></canvas></div><div id='hud'>click to play &middot; WASD walk &middot; mouse look &middot; L-click dig &middot; R-click build &middot; Space jump &middot; F fly</div><script>\n" as *u8) 22 hw(fd, "const B='" as *u8) 23 sys_write(fd, b64, b64len) 24 hw(fd, "';\nconst bytes=Uint8Array.from(atob(B),c=>c.charCodeAt(0));\nWebAssembly.instantiate(bytes).then(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);const mem=new Uint8Array(ex.memory.buffer);let cam={x:48.5,y:8,z:10.5},yaw=0,pitch=-0.18,vy=0,fly=false,onground=false;function gy(){let gx=Math.floor(cam.x),gz=Math.floor(cam.z),h=2;try{h=Number(ex.terrain_at(BigInt(gx),BigInt(gz)));}catch(e){}return h+2.6;}function solid(x,z){let h=0;try{h=Number(ex.terrain_at(BigInt(Math.floor(x)),BigInt(Math.floor(z))));}catch(e){}return h;}cam.y=gy();const keys={};addEventListener('keydown',e=>{keys[e.code]=1;if(e.code=='Space')e.preventDefault();if(e.code=='KeyF')fly=!fly;});addEventListener('keyup',e=>{keys[e.code]=0;});cv.addEventListener('mousedown',e=>{if(document.pointerLockElement!==cv){cv.requestPointerLock();return;}e.preventDefault();const ddx=Math.sin(yaw)*Math.cos(pitch),ddy=Math.sin(pitch),ddz=Math.cos(yaw)*Math.cos(pitch);const p=Number(ex.pick(BigInt(Math.round(cam.x*256)),BigInt(Math.round(cam.y*256)),BigInt(Math.round(cam.z*256)),BigInt(Math.round(ddx*1000)),BigInt(Math.round(ddy*1000)),BigInt(Math.round(ddz*1000))));if(p>=0){const ggx=Math.floor(p/65536),ggz=p%65536;if(e.button===0)ex.break_block(BigInt(ggx),BigInt(ggz));else if(e.button===2)ex.place_block(BigInt(ggx),BigInt(ggz));}});cv.addEventListener('contextmenu',e=>e.preventDefault());addEventListener('mousemove',e=>{if(document.pointerLockElement===cv){yaw+=e.movementX*0.0032;pitch-=e.movementY*0.0032;if(pitch>1.4)pitch=1.4;if(pitch<-1.4)pitch=-1.4;}});function frame(){const sp=0.28;const fx=Math.sin(yaw),fz=Math.cos(yaw);let dx=0,dz=0;if(keys['KeyW']){dx+=fx;dz+=fz;}if(keys['KeyS']){dx-=fx;dz-=fz;}if(keys['KeyD']){dx+=fz;dz-=fx;}if(keys['KeyA']){dx-=fz;dz+=fx;}const tx=cam.x+dx*sp,tz=cam.z+dz*sp;if(fly){cam.x=tx;cam.z=tz;}else{const feet=cam.y-2.6;if(solid(tx,cam.z)<=feet+1.2)cam.x=tx;if(solid(cam.x,tz)<=feet+1.2)cam.z=tz;}if(fly){if(keys['Space'])cam.y+=sp;if(keys['ShiftLeft']||keys['ShiftRight'])cam.y-=sp;vy=0;}else{const g=gy();vy-=0.045;cam.y+=vy;if(cam.y<=g){cam.y=g;vy=0;onground=true;}else{onground=false;}if(keys['Space']&&onground)vy=0.72;}const yc=Math.round(Math.cos(yaw)*1000),ys=Math.round(Math.sin(yaw)*1000),pc=Math.round(Math.cos(pitch)*1000),ps=Math.round(Math.sin(pitch)*1000);ex.render(BigInt(Math.round(cam.x*256)),BigInt(Math.round(cam.y*256)),BigInt(Math.round(cam.z*256)),BigInt(yc),BigInt(ys),BigInt(pc),BigInt(ps));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);ctx.fillStyle='rgba(255,255,255,0.75)';ctx.fillRect((W>>1)-1,(H>>1)-5,2,11);ctx.fillRect((W>>1)-5,(H>>1)-1,11,2);requestAnimationFrame(frame);}frame();}).catch(e=>{document.body.innerHTML+='<pre style=color:#f99>'+e+'</pre>';});\n</script></body></html>\n" as *u8) 25 sys_close(fd) 26 hw(1, "nx_wasm_html_fps: wrote html\n" as *u8) 27 return 0 28}