code wiki / _hdl_build / nx_sim_page.nx

nx_sim_page.nx source

↩ module page · 41 lines · 3744 B

1// nx_sim_page.nx -- GENERIC sovereign packager: any base-relative Nishi sim wasm -> self-contained .html that runs 2// in any browser. Extracted at the 3rd sim (simlab/solarsim/gassim) per the DRY-at-3 rule: the blit JS is identical 3// across sims, only the title/description differ -> pass them as args (data-driven, not hardcoded). The emitted JS 4// does ONLY: BLIT our wasm framebuffer to the canvas via putImageData (copy bytes, NOT canvas drawing), drive the 5// sim each animation frame (ex.tick(0)), and forward R (reset = tick(3)) / space (pause). ALL physics + integer 6// rasterization live in the sovereign wasm (no JS logic, no canvas-draw, no float, no 3rd-party). Any sim that 7// exports init/tick/render/ww/hh/fb_off + treats tick(3) as reset works unchanged. 8// usage: nx_sim_page <in.wasm> <out.html> <title> <description>. license_tier: ORIGINAL 9import "nx_syscalls.nx" 10import "nx_base64.nx" 11 12func 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 } 13 14func main(argc: i64, argv: *i64) -> i64 { 15 if argc < 5 { hw(2, "usage: nx_sim_page <in.wasm> <out.html> <title> <description>\n" as *u8); return 2 } 16 let wpath: *u8 = argv[1] as *u8 17 let hpath: *u8 = argv[2] as *u8 18 let title: *u8 = argv[3] as *u8 19 let desc: *u8 = argv[4] as *u8 20 let wlen_p: *i64 = sys_mmap(8) as *i64 21 let wbytes: *u8 = sys_read_file(wpath, wlen_p) 22 if (wbytes as i64) == 0 { hw(2, "read wasm failed\n" as *u8); return 3 } 23 let wlen: i64 = wlen_p[0] 24 let b64: *u8 = sys_mmap(wlen * 2 + 64) as *u8 25 let b64len: i64 = b64_encode(wbytes, wlen, b64) 26 27 let fd: i64 = sys_openat_wr(hpath, 0x1a4) 28 if fd < 0 { hw(2, "open html failed\n" as *u8); return 4 } 29 hw(fd, "<!doctype html><html><head><meta charset='utf-8'><title>" as *u8); hw(fd, title) 30 hw(fd, "</title><style>body{background:#05070f;margin:0;display:flex;flex-direction:column;justify-content:center;align-items:center;height:100vh;font-family:sans-serif;color:#cdd}canvas{image-rendering:pixelated;width:512px;height:512px;border:1px solid #2a3140;background:#05070f}p{margin:6px}b{color:#8eb}</style></head><body><h2>" as *u8) 31 hw(fd, title) 32 hw(fd, "</h2><canvas id='c'></canvas><p id='hud'><b>R</b> reset &middot; <b>space</b> pause</p><p style='color:#778;font-size:12px;max-width:520px;text-align:center'>" as *u8) 33 hw(fd, desc) 34 hw(fd, " <br>100% Nishi: all physics + the integer rasterizer run in sovereign WASM; the browser only blits our framebuffer &amp; forwards keys (no JS logic, no float, no 3rd-party).</p><script>\n" as *u8) 35 hw(fd, "const B='" as *u8) 36 sys_write(fd, b64, b64len) 37 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);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);}let paused=false;ex.init();function frame(){if(!paused){ex.tick(BigInt(0));ex.render();blit();}requestAnimationFrame(frame);}ex.render();blit();requestAnimationFrame(frame);document.addEventListener('keydown',function(e){const k=e.key.toLowerCase();if(k=='r'){ex.tick(BigInt(3));}else if(k==' '){paused=!paused;e.preventDefault();}ex.render();blit();});}).catch(e=>{document.body.innerHTML+='<pre>'+e.message+'</pre>';});\n</script></body></html>\n" as *u8) 38 sys_close(fd) 39 hw(1, "wrote sim page\n" as *u8) 40 return 0 41}