@korial29/tiny-fps 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,11 +1,11 @@
1
- var e=.4,t=1.8,n=1.55,r=e=>{let t=(e,t,n,r)=>`${e},${t},${n},${r}`,n=/* @__PURE__ */ new Map;return e.sectors.forEach(({pts:e},r)=>e.forEach(([i,a],o)=>n.set(t(i,a,...e[(o+1)%e.length]),r))),{map:e,edges:e.sectors.flatMap(({pts:e},r)=>e.map(([i,a],o)=>{let[s,c]=e[(o+1)%e.length];return{ax:i,az:a,bx:s,bz:c,front:r,back:n.get(t(s,c,i,a))??-1}}))}},i=({pts:e},t,n)=>e.every(([r,i],a)=>{let[o,s]=e[(a+1)%e.length];return(o-r)*(n-i)-(s-i)*(t-r)>=0}),a=({map:e},t,n)=>e.sectors.findIndex(e=>i(e,t,n)),o=({map:e,edges:t},[n,r,i],[a,o,s])=>{let[c,l]=[a-n,s-i];return t.some(t=>{let[a,s]=[t.bx-t.ax,t.bz-t.az],u=c*s-l*a;if(!u)return!1;let d=((t.ax-n)*s-(t.az-i)*a)/u,f=((t.ax-n)*l-(t.az-i)*c)/u;if(d<=0||d>=1||f<0||f>1)return!1;if(t.back<0)return!0;let[p,m]=[e.sectors[t.front],e.sectors[t.back]],h=r+d*(o-r);return h<Math.max(p.floor,m.floor)||h>Math.min(p.ceil,m.ceil)})},s={FORWARD:1,BACK:2,LEFT:4,RIGHT:8,JUMP:16},c={BRICK:0,TILE:1,LAVA:2,STONE:3},l=(e,t,n,r,i)=>({pts:e,floor:t,ceil:n,ft:r,wt:c.BRICK,ct:c.STONE,light:i}),u={sectors:[l([[0,0],[16,0],[16,6],[16,10],[16,16],[0,16]],1,5,c.TILE),l([[16,6],[20,6],[20,10],[16,10]],1,3.5,c.TILE,.6),l([[20,0],[36,0],[36,6],[30,6],[26,6],[20,6]],1,7,c.TILE),l([[20,6],[26,6],[26,10],[20,10]],1,7,c.TILE),l([[30,6],[36,6],[36,10],[30,10]],1,7,c.TILE),l([[20,10],[26,10],[30,10],[36,10],[36,16],[32,16],[28,16],[20,16]],1,7,c.TILE),l([[26,6],[30,6],[30,10],[26,10]],0,7,c.LAVA),l([[28,16],[32,16],[32,17],[28,17]],1.4,7,c.STONE),l([[28,17],[32,17],[32,18],[28,18]],1.8,7,c.STONE),l([[28,18],[32,18],[32,19],[28,19]],2.2,7,c.STONE),l([[24,19],[28,19],[32,19],[36,19],[36,26],[24,26]],2.6,7,c.TILE,.8)],spawns:[[4,4,Math.PI*1.5],[4,12,Math.PI*1.5],[8,8,Math.PI*1.5],[12,4,Math.PI*1.5],[12,12,Math.PI*1.5],[2,8,Math.PI*1.5],[8,2,Math.PI*1.5],[8,14,Math.PI*1.5]]},d=65536/(2*Math.PI),f=e=>Math.round(e*d)&65535,p=e=>Math.max(-32768,Math.min(32767,Math.round(e))),m=e=>p(e*1e4),h=e=>e/1e4,g=e=>e/d,_=e=>({...e,yaw:g(f(e.yaw)),pitch:h(m(e.pitch))}),v=e=>{let t=/* @__PURE__ */ new DataView(/* @__PURE__ */ new ArrayBuffer(8));return t.setUint8(0,1),t.setUint16(1,e.seq&65535),t.setUint8(3,e.keys&255),t.setUint16(4,f(e.yaw)),t.setInt16(6,m(e.pitch)),t.buffer},y=e=>{if(e.byteLength<4)return null;let t=new DataView(e),n=t.getUint8(3);if(t.getUint8(0)!==2||e.byteLength!==4+24*n)return null;let r=[];for(let e=0;e<n;e++){let n=4+e*24;r.push({id:t.getUint8(n),x:t.getFloat32(n+1),y:t.getFloat32(n+5),z:t.getFloat32(n+9),vy:t.getFloat32(n+13),yaw:g(t.getUint16(n+17)),pitch:h(t.getInt16(n+19)),ack:t.getUint16(n+21),ground:(t.getUint8(n+23)&1)==1})}return{tick:t.getUint16(1),players:r}},b=e=>{try{let t=JSON.parse(e);return t&&typeof t.t==`string`?t:null}catch{return null}},x=.0025,S=Math.PI*2,C={KeyW:s.FORWARD,ArrowUp:s.FORWARD,KeyS:s.BACK,ArrowDown:s.BACK,KeyA:s.LEFT,ArrowLeft:s.LEFT,KeyD:s.RIGHT,ArrowRight:s.RIGHT,Space:s.JUMP},w=e=>C[e]??0,T=e=>{let t=new AbortController,r=(e,n,r)=>e.addEventListener(n,r,{signal:t.signal}),i={yaw:0,pitch:0,keys:0,locked:()=>e.getRootNode().pointerLockElement==e,destroy:()=>t.abort()};r(e,`click`,()=>{Promise.resolve(e.requestPointerLock()).catch(()=>{})}),r(document,`mousemove`,e=>{i.locked()&&(i.yaw=((i.yaw-e.movementX*x)%S+S)%S,i.pitch=Math.max(-1.55,Math.min(n,i.pitch-e.movementY*x)))}),r(document,`keydown`,e=>{let t=w(e.code);t&&i.locked()&&(i.keys|=t,e.preventDefault())}),r(document,`keyup`,e=>{i.keys&=~w(e.code)});let a=()=>{i.keys=0};return r(window,`blur`,a),r(document,`pointerlockchange`,()=>i.locked()||a()),i},E=1/60,D=Math.PI*2,O=1e-6,k=.01,A=e=>(e%D+D)%D,j=e=>[-Math.sin(e),-Math.cos(e)],M=Math.fround,N=(e,t,n)=>Math.min(Math.max(e,t),n),P=({map:e},t,n)=>{if(t.back<0)return!0;let r=e.sectors[t.front],i=e.sectors[t.back],a=Math.min(r.ceil,i.ceil);return i.floor>n+.5+k||a-Math.max(r.floor,i.floor)<1.8-k||a<n+1.8-k},ee=(t,n)=>{let r=(e-O)**2;for(let i=0;i<4;i++){let i=0,a=0,o=0;for(let s of n.edges){let c=s.bx-s.ax,l=s.bz-s.az,u=t.x-s.ax,d=t.z-s.az;if(c*d-l*u<0)continue;let f=N((u*c+d*l)/(c*c+l*l),0,1),p=u-f*c,m=d-f*l,h=p*p+m*m;if(h>=r)continue;let g=Math.sqrt(h);if(.4-g<=i||!P(n,s,t.y))continue;i=e-g;let _=g||Math.hypot(c,l);a=(g?p:-l)/_*i,o=(g?m:c)/_*i}if(!i)return;t.x+=a,t.z+=o}},te=(t,n,r,i)=>{let{map:o,edges:s}=t,c=Math.max(a(t,n,r),0),{floor:l,ceil:u}=o.sectors[c],d=(e-O)**2;for(let e of s){if(e.front!==c||e.back<0)continue;let a=e.bx-e.ax,s=e.bz-e.az,f=n-e.ax,p=r-e.az,m=N((f*a+p*s)/(a*a+s*s),0,1);if((f-m*a)**2+(p-m*s)**2<d&&!P(t,e,i)){let t=o.sectors[e.back];l=Math.max(l,t.floor),u=Math.min(u,t.ceil)}}return[l,u]},F=(e,r,i)=>{e.yaw=A(r.yaw),e.pitch=Math.max(-1.55,Math.min(n,r.pitch));let a=(r.keys&s.FORWARD?1:0)-(r.keys&s.BACK?1:0),o=(r.keys&s.RIGHT?1:0)-(r.keys&s.LEFT?1:0),c=6*E/(Math.hypot(a,o)||1),[l,u]=j(e.yaw);e.x+=(l*a-u*o)*c,e.z+=(u*a+l*o)*c,ee(e,i);let[d,f]=te(i,e.x,e.z,e.y);e.ground&&r.keys&s.JUMP&&(e.vy=7);let p=e.vy-20*E;e.y+=(e.vy+p)/2*E,e.vy=p,e.ground=!1,e.y+1.8>f&&(e.y=f-t,e.vy=Math.min(e.vy,0)),(e.vy<=0?e.y<=d+k:e.y<d)&&(e.y=d,e.vy<=0&&(e.vy=0,e.ground=!0)),e.x=M(e.x),e.y=M(e.y),e.z=M(e.z),e.vy=M(e.vy)},I=120,ne=16,L=({id:e,x:t,y:n,z:r,vy:i,yaw:a,pitch:o,ground:s})=>({id:e,x:t,y:n,z:r,vy:i,yaw:a,pitch:o,ground:s}),re=(e,t)=>(t-e&65535)<32768,R=(e,t,n)=>{let r=(e,t)=>e+(t-e)*n,i=((t.yaw-e.yaw+Math.PI)%D+D)%D-Math.PI;return{...t,x:r(e.x,t.x),y:r(e.y,t.y),z:r(e.z,t.z),yaw:A(e.yaw+i*n),pitch:r(e.pitch,t.pitch)}},z=e=>{let t,n=0,r=[],i,a,o=[];return{you:e=>{t=e},step:t=>{let o=_({...t,seq:n});return n=n+1&65535,r.push(o),r.length>I&&r.shift(),i&&(a=L(i),F(i,o,e)),o},snapshot:(n,s)=>{o.push({t:s,s:n}),o.length>ne&&o.shift();let c=n.players.find(e=>e.id===t);c&&(r=r.filter(e=>!re(e.seq,c.ack)),i=L(c),a=L(i),r.forEach((t,n)=>{n===r.length-1&&(a=L(i)),F(i,t,e)}))},me:e=>i&&a?R(a,i,Math.max(0,Math.min(1,e))):void 0,others:e=>{let n=o.at(-1);if(!n)return[];let r=e-100,i=0;for(;i+1<o.length&&o[i+1].t<=r;)i++;let a=o[i],s=o[i+1]??a,c=s.t>a.t?Math.max(0,Math.min(1,(r-a.t)/(s.t-a.t))):0;return n.s.players.filter(e=>e.id!==t).map(e=>{let t=a.s.players.find(t=>t.id===e.id),n=s.s.players.find(t=>t.id===e.id);return t&&n?R(L(t),L(n),c):L(n??t??e)})}}},B=Object.keys(c).length,V=(e,t,n)=>{let r=Math.imul(e,374761393)+Math.imul(t,668265263)+Math.imul(n,144665)|0;return r=Math.imul(r^r>>>13,1274126177),((r^r>>>16)>>>0)/4294967296},H=(e,t,n,r)=>{let[i,a]=[Math.floor(e),Math.floor(t)],[o,s]=[e-i,t-a].map(e=>e*e*(3-2*e)),c=(e,t)=>V((i+e)%n,(a+t)%n,r);return(c(0,0)*(1-o)+c(1,0)*o)*(1-s)+(c(0,1)*(1-o)+c(1,1)*o)*s},U=(e,t,n,r,i)=>{let[a,o]=[0,.5];for(let s=0;s<r;s++,o/=2)a+=o*H(e*(1<<s),t*(1<<s),n<<s,i+s);return a/(1-o*2)},W=(e,t,n)=>e.map((e,r)=>e+(t[r]-e)*n),G=(e,t)=>e.map(e=>e*t),K=[];K[c.BRICK]=(e,t)=>{let n=Math.floor(t*8),r=e*4+(n&1)*.5,[i,a,o]=[Math.floor(r)%4,r%1,t*8%1],s=.8+.4*H(e*16,t*16,16,5);return a<.04||o<.08?G([165,160,150],.7+.3*s):G([170,78,56],(.8+.4*V(i,n,1))*s*(.85+.15*o))},K[c.TILE]=(e,t)=>{let[n,r]=[Math.floor(e*4),Math.floor(t*4)],i=.9+.2*H(e*16,t*16,16,7);return e*4%1<.04||t*4%1<.04?G([85,85,80],i):G([196,190,172],i*(.88+.08*(n+r&1)+.1*V(n,r,2)))},K[c.LAVA]=(e,t)=>{let n=U(e*4,t*4,4,4,11),r=Math.max(0,1-Math.abs(n-.5)*7)**2;return W(W([55,8,4],[230,70,8],n),[255,215,90],r)},K[c.STONE]=(e,t)=>G([128,124,120],.45+.7*(U(e*8,t*8,8,4,3)*1.3+.1*V(Math.floor(e*128),Math.floor(t*128),4)));var q=()=>{let e=new Uint8ClampedArray(B*128*128*4);return K.forEach((t,n)=>{for(let r=0;r<16384;r++){let i=t(r%128/128,Math.floor(r/128)/128);e.set([...i.map(Math.round),255],(n*128*128+r)*4)}}),e},J=[.05,.045,.05],Y=10,ie=36,ae=3,oe=2,se=`#version 300 es
1
+ var e=.4,t=1.8,n=1.6,r=1.55,i=.02,a=e=>{let t=(e,t,n,r)=>`${e},${t},${n},${r}`,n=/* @__PURE__ */ new Map;return e.sectors.forEach(({pts:e},r)=>e.forEach(([i,a],o)=>n.set(t(i,a,...e[(o+1)%e.length]),r))),{map:e,edges:e.sectors.flatMap(({pts:e},r)=>e.map(([i,a],o)=>{let[s,c]=e[(o+1)%e.length];return{ax:i,az:a,bx:s,bz:c,front:r,back:n.get(t(s,c,i,a))??-1}}))}},o=({pts:e},t,n)=>e.every(([r,i],a)=>{let[o,s]=e[(a+1)%e.length];return(o-r)*(n-i)-(s-i)*(t-r)>=0}),s=({map:e},t,n)=>e.sectors.findIndex(e=>o(e,t,n)),c=({map:e,edges:t},[n,r,i],[a,o,s])=>{let[c,l]=[a-n,s-i];return t.some(t=>{let[a,s]=[t.bx-t.ax,t.bz-t.az],u=c*s-l*a;if(!u)return!1;let d=((t.ax-n)*s-(t.az-i)*a)/u,f=((t.ax-n)*l-(t.az-i)*c)/u;if(d<=0||d>=1||f<0||f>1)return!1;if(t.back<0)return!0;let[p,m]=[e.sectors[t.front],e.sectors[t.back]],h=r+d*(o-r);return h<Math.max(p.floor,m.floor)||h>Math.min(p.ceil,m.ceil)})},l={FORWARD:1,BACK:2,LEFT:4,RIGHT:8,JUMP:16,FIRE:32},u={BRICK:0,TILE:1,LAVA:2,STONE:3},d=(e,t,n,r,i)=>({pts:e,floor:t,ceil:n,ft:r,wt:u.BRICK,ct:u.STONE,light:i}),f={sectors:[d([[0,0],[16,0],[16,6],[16,10],[16,16],[0,16]],1,5,u.TILE),d([[16,6],[20,6],[20,10],[16,10]],1,3.5,u.TILE,.6),d([[20,0],[36,0],[36,6],[30,6],[26,6],[20,6]],1,7,u.TILE),d([[20,6],[26,6],[26,10],[20,10]],1,7,u.TILE),d([[30,6],[36,6],[36,10],[30,10]],1,7,u.TILE),d([[20,10],[26,10],[30,10],[36,10],[36,16],[32,16],[28,16],[20,16]],1,7,u.TILE),d([[26,6],[30,6],[30,10],[26,10]],0,7,u.LAVA),d([[28,16],[32,16],[32,17],[28,17]],1.4,7,u.STONE),d([[28,17],[32,17],[32,18],[28,18]],1.8,7,u.STONE),d([[28,18],[32,18],[32,19],[28,19]],2.2,7,u.STONE),d([[24,19],[28,19],[32,19],[36,19],[36,26],[24,26]],2.6,7,u.TILE,.8)],spawns:[[4,4,Math.PI*1.5],[4,12,Math.PI*1.5],[8,8,Math.PI*1.5],[12,4,Math.PI*1.5],[12,12,Math.PI*1.5],[2,8,Math.PI*1.5],[8,2,Math.PI*1.5],[8,14,Math.PI*1.5]]},p=65536/(2*Math.PI),m=e=>Math.round(e*p)&65535,h=e=>Math.max(-32768,Math.min(32767,Math.round(e))),g=e=>h(e*1e4),_=e=>e/1e4,v=e=>e/p,y=(e=0)=>Math.round(e*256)&16777215,b=e=>({...e,yaw:v(m(e.yaw)),pitch:_(g(e.pitch)),...e.view!==void 0&&{view:y(e.view)/256}}),x=e=>{let t=/* @__PURE__ */ new DataView(/* @__PURE__ */ new ArrayBuffer(11));t.setUint8(0,1),t.setUint16(1,e.seq&65535),t.setUint8(3,e.keys&255),t.setUint16(4,m(e.yaw)),t.setInt16(6,g(e.pitch));let n=y(e.view);return t.setUint16(8,n>>8),t.setUint8(10,n&255),t.buffer},S=e=>{if(e.byteLength<4)return null;let t=new DataView(e),n=t.getUint8(3),r=4+24*n;if(t.getUint8(0)!==2||e.byteLength<=r)return null;let i=t.getUint8(r);if(e.byteLength!==r+1+14*i)return null;let a=[];for(let e=0;e<n;e++){let n=4+e*24;a.push({id:t.getUint8(n),x:t.getFloat32(n+1),y:t.getFloat32(n+5),z:t.getFloat32(n+9),vy:t.getFloat32(n+13),yaw:v(t.getUint16(n+17)),pitch:_(t.getInt16(n+19)),ack:t.getUint16(n+21),cool:t.getUint8(n+23)>>1,ground:(t.getUint8(n+23)&1)==1})}let o=[];for(let e=0;e<i;e++){let n=r+1+e*14;o.push({id:t.getUint8(n),x:t.getFloat32(n+1),y:t.getFloat32(n+5),z:t.getFloat32(n+9),hit:t.getUint8(n+13)===255?-1:t.getUint8(n+13)})}return{tick:t.getUint16(1),players:a,shots:o}},C=e=>{try{let t=JSON.parse(e);return t&&typeof t.t==`string`?t:null}catch{return null}},w=1/60,T=Math.PI*2,E=1e-6,D=.01,O=e=>(e%T+T)%T,k=e=>[-Math.sin(e),-Math.cos(e)],A=Math.fround,j=(e,t,n)=>Math.min(Math.max(e,t),n),M=({map:e},t,n)=>{if(t.back<0)return!0;let r=e.sectors[t.front],i=e.sectors[t.back],a=Math.min(r.ceil,i.ceil);return i.floor>n+.5+D||a-Math.max(r.floor,i.floor)<1.8-D||a<n+1.8-D},N=(t,n)=>{let r=(e-E)**2;for(let i=0;i<4;i++){let i=0,a=0,o=0;for(let s of n.edges){let c=s.bx-s.ax,l=s.bz-s.az,u=t.x-s.ax,d=t.z-s.az;if(c*d-l*u<0)continue;let f=j((u*c+d*l)/(c*c+l*l),0,1),p=u-f*c,m=d-f*l,h=p*p+m*m;if(h>=r)continue;let g=Math.sqrt(h);if(.4-g<=i||!M(n,s,t.y))continue;i=e-g;let _=g||Math.hypot(c,l);a=(g?p:-l)/_*i,o=(g?m:c)/_*i}if(!i)return;t.x+=a,t.z+=o}},P=(t,n,r,i)=>{let{map:a,edges:o}=t,c=Math.max(s(t,n,r),0),{floor:l,ceil:u}=a.sectors[c],d=(e-E)**2;for(let e of o){if(e.front!==c||e.back<0)continue;let o=e.bx-e.ax,s=e.bz-e.az,f=n-e.ax,p=r-e.az,m=j((f*o+p*s)/(o*o+s*s),0,1);if((f-m*o)**2+(p-m*s)**2<d&&!M(t,e,i)){let t=a.sectors[e.back];l=Math.max(l,t.floor),u=Math.min(u,t.ceil)}}return[l,u]},F=(e,n,i)=>{let a=(n.keys&l.FIRE)!==0;e.cool=Math.max(0,e.cool-1),e.yaw=O(n.yaw),e.pitch=Math.max(-1.55,Math.min(r,n.pitch));let o=(n.keys&l.FORWARD?1:0)-(n.keys&l.BACK?1:0),s=(n.keys&l.RIGHT?1:0)-(n.keys&l.LEFT?1:0),c=6*w*(a?.5:1)/(Math.hypot(o,s)||1),[u,d]=k(e.yaw);e.x+=(u*o-d*s)*c,e.z+=(d*o+u*s)*c,N(e,i);let[f,p]=P(i,e.x,e.z,e.y);e.ground&&n.keys&l.JUMP&&(e.vy=7);let m=e.vy-20*w;return e.y+=(e.vy+m)/2*w,e.vy=m,e.ground=!1,e.y+1.8>p&&(e.y=p-t,e.vy=Math.min(e.vy,0)),(e.vy<=0?e.y<=f+D:e.y<f)&&(e.y=f,e.vy<=0&&(e.vy=0,e.ground=!0)),e.x=A(e.x),e.y=A(e.y),e.z=A(e.z),e.vy=A(e.vy),a&&!e.cool?(e.cool=5,!0):!1},I=.05,ee=.3,te=96,ne=4.4,re=.035,ie=.03,ae=30,oe=(e=Math.random)=>{let t=0,n=0,r=0,i=0,a,o=()=>e()*2-1,s={bob:0,roll:0,shakeYaw:0,shakePitch:0,kick:0,spin:0,flash:0,flashes:/* @__PURE__ */ new Map,sparks:[],update(e,c,u){let d=Math.min(1,u*10),f=e&&a?.ground&&e.ground?Math.hypot(e.x-a.x,e.z-a.z):0;f<1&&(t+=f*ne),n+=(+(f>.5*u&&f<1)-n)*d,a=e,s.bob=Math.sin(t)*re*n;let p=+!!(c&l.RIGHT)-!!(c&l.LEFT);s.roll+=(p*ie-s.roll)*Math.min(1,u*8),r*=Math.exp(-u*12),s.shakeYaw=o()*r,s.shakePitch=o()*r,s.kick*=Math.exp(-u*18),s.flash=Math.max(0,s.flash-u);for(let[e,t]of s.flashes)t>u?s.flashes.set(e,t-u):s.flashes.delete(e);i=Math.max(0,Math.min(ae,i+(c&l.FIRE?60:-25)*u)),s.spin=(s.spin+i*u)%T;for(let e of s.sparks)e.v[1]-=9.8*u,e.p=e.p.map((t,n)=>t+e.v[n]*u),e.life-=u;s.sparks=s.sparks.filter(e=>e.life>0)},shot(t,n,i,a=!0){i?(s.kick=1,s.flash=I,r=.012):s.flashes.set(t.id,I);let c=[t.x,t.y,t.z],l=n.map((e,t)=>e-c[t]),u=Math.hypot(...l);if(a&&u>0&&u<99.99)for(let t=0;t<4&&s.sparks.length<te;t++){let t=l.map(t=>t/u*(2+e()*2)+o());t[1]+=1,s.sparks.push({p:c,v:t,life:ee*(.6+e()*.4)})}}};return s},L=.0025,R=Math.PI*2,se={KeyW:l.FORWARD,ArrowUp:l.FORWARD,KeyS:l.BACK,ArrowDown:l.BACK,KeyA:l.LEFT,ArrowLeft:l.LEFT,KeyD:l.RIGHT,ArrowRight:l.RIGHT,Space:l.JUMP},z=e=>se[e]??0,ce=e=>{let t=new AbortController,n=(e,n,r)=>e.addEventListener(n,r,{signal:t.signal}),i={yaw:0,pitch:0,keys:0,locked:()=>e.getRootNode().pointerLockElement==e,destroy:()=>t.abort()};n(e,`click`,()=>{Promise.resolve(e.requestPointerLock()).catch(()=>{})}),n(document,`mousemove`,e=>{i.locked()&&(i.yaw=((i.yaw-e.movementX*L)%R+R)%R,i.pitch=Math.max(-1.55,Math.min(r,i.pitch-e.movementY*L)))}),n(document,`keydown`,e=>{let t=z(e.code);t&&i.locked()&&(i.keys|=t,e.preventDefault())}),n(document,`keyup`,e=>{i.keys&=~z(e.code)}),n(document,`mousedown`,e=>{e.button==0&&i.locked()&&(i.keys|=l.FIRE)}),n(document,`mouseup`,e=>{e.button==0&&(i.keys&=~l.FIRE)});let a=()=>{i.keys=0};return n(window,`blur`,a),n(document,`pointerlockchange`,()=>i.locked()||a()),i},le=e=>{for(let t=0;t<2;t++)e=Math.imul(e^e>>>16,73244475);return(e^e>>>16)>>>0},ue=(e,t,n)=>{let[r,a,o,s]=[Math.sin(e),Math.cos(e),Math.sin(t),Math.cos(t)],c=le(n+1),l=(c&65535)/65536*T,u=(c>>>16)/65535,[d,f]=[Math.cos(l)*u*i,Math.sin(l)*u*i],p=[-r*s+a*d+r*o*f,o+s*f,-a*s-r*d+a*o*f],m=Math.hypot(...p);return[p[0]/m,p[1]/m,p[2]/m]},de=(e,t,n,r)=>{let{map:i,edges:a}=e,o=Math.max(s(e,t[0],t[2]),0),c=0;for(let e=0;e<=i.sectors.length;e++){let{floor:e,ceil:s}=i.sectors[o],l=1/0,u;for(let e of a){if(e.front!==o)continue;let[r,i]=[e.bx-e.ax,e.bz-e.az],a=n[2]*r-n[0]*i;if(a>=0)continue;let s=Math.max(((t[2]-e.az)*r-(t[0]-e.ax)*i)/-a,c),d=((t[0]+s*n[0]-e.ax)*r+(t[2]+s*n[2]-e.az)*i)/(r*r+i*i);d>-1e-9&&d<1+1e-9&&(s<l-1e-9||s<l+1e-9&&e.back<0)&&([l,u]=[s,e])}let d=n[1]<0?(e-t[1])/n[1]:n[1]>0?(s-t[1])/n[1]:1/0;if(d<=l||!u)return Math.min(Math.max(d,c),r);if(l>=r)return r;let f=t[1]+l*n[1],p=u.back<0?void 0:i.sectors[u.back];if(!p||f<Math.max(e,p.floor)||f>Math.min(s,p.ceil))return l;[o,c]=[u.back,l]}return c},fe=(n,r,i)=>{let[a,o]=[n[0]-i.x,n[2]-i.z],s=r[0]*r[0]+r[2]*r[2],c=a*r[0]+o*r[2],l=a*a+o*o-e*e,[u,d]=[0,1/0];if(s>1e-12){let e=c*c-s*l;if(e<0)return 1/0;[u,d]=[Math.max(0,(-c-Math.sqrt(e))/s),(-c+Math.sqrt(e))/s]}else if(l>0)return 1/0;let[f,p]=[i.y-n[1],i.y+t-n[1]];if(r[1]){let[e,t]=[f/r[1],p/r[1]];u=Math.max(u,Math.min(e,t)),d=Math.min(d,Math.max(e,t))}else if(f>0||p<0)return 1/0;return u<=d?u:1/0},pe=(e,t,r,i)=>{let a=[t.x,t.y+n,t.z],o=ue(t.yaw,t.pitch,r),s=de(e,a,o,100),c=-1;for(let e of i){let n=e.id===t.id?1/0:fe(a,o,e);n<s&&([s,c]=[n,e.id])}return{id:t.id,x:a[0]+o[0]*s,y:a[1]+o[1]*s,z:a[2]+o[2]*s,hit:c}},me=120,he=16,B=({id:e,x:t,y:n,z:r,vy:i,yaw:a,pitch:o,ground:s,cool:c})=>({id:e,x:t,y:n,z:r,vy:i,yaw:a,pitch:o,ground:s,cool:c}),ge=(e,t)=>(t-e&65535)<32768,_e=(e,t,n)=>{let r=(e,t)=>e+(t-e)*n,i=((t.yaw-e.yaw+Math.PI)%T+T)%T-Math.PI;return{...t,x:r(e.x,t.x),y:r(e.y,t.y),z:r(e.z,t.z),yaw:O(e.yaw+i*n),pitch:r(e.pitch,t.pitch)}},ve=e=>{let t,n=0,r=[],i,a,o,s=[],c=e=>{let t=e-100,n=0;for(;n+1<s.length&&s[n+1].t<=t;)n++;let r=s[n],i=s[n+1]??r;return{a:r,b:i,u:i.t>r.t?Math.max(0,Math.min(1,(t-r.t)/(i.t-r.t))):0}},l=e=>{if(!s.length)return;let{a:t,b:n,u:r}=c(e);return(t.s.tick+(n.s.tick-t.s.tick&65535)*r)%65536},u=e=>{let n=s.at(-1);if(!n)return[];let{a:r,b:i,u:a}=c(e);return n.s.players.filter(e=>e.id!==t).map(e=>{let t=r.s.players.find(t=>t.id===e.id),n=i.s.players.find(t=>t.id===e.id);return t&&n?_e(B(t),B(n),a):B(n??t??e)})};return{you:e=>{t=e},step:(t,s)=>{let c=b({...t,seq:n,view:s===void 0?void 0:l(s)});return o=void 0,n=n+1&65535,r.push(c),r.length>me&&r.shift(),i&&(a=B(i),F(i,c,e)&&(o=pe(e,i,c.seq,u(s??1/0)))),c},snapshot:(n,o)=>{s.push({t:o,s:n}),s.length>he&&s.shift();let c=n.players.find(e=>e.id===t);c&&(r=r.filter(e=>!ge(e.seq,c.ack)),i=B(c),a=B(i),r.forEach((t,n)=>{n===r.length-1&&(a=B(i)),F(i,t,e)}))},shot:()=>o,me:e=>i&&a?_e(a,i,Math.max(0,Math.min(1,e))):void 0,others:u}},ye=.55,be=6,xe=(e,t,n=[0,0,0])=>{let r=[];for(let i=1;i<e.length;i++){let[[a,o],[s,c]]=[e[i-1],e[i]],l=Math.hypot(s-a,c-o);if(!l)continue;let[u,d]=[(c-o)/l,-(s-a)/l],f=(e,i,a)=>{let[o,s]=[Math.cos(e*T/t),Math.sin(e*T/t)];r.push(n[0]+i*o,n[1]+i*s,n[2]-a,u*o,u*s,-d)};for(let e=0;e<t;e++)for(let[t,n,r]of[[e,a,o],[e+1,a,o],[e+1,s,c],[e,a,o],[e+1,s,c],[e,s,c]])f(t,n,r)}return r},V=e=>{let t=[];for(let n=0;n<be;n++){let r=n*T/be;t.push(...xe([[0,0],[.02,0],[.02,.45],[.014,.45],[.014,.55],[0,.55]],e,[Math.cos(r)*.05,Math.sin(r)*.05,0]))}return{barrels:t,body:xe([[0,-.3],[.08,-.3],[.09,-.26],[.09,0],[.075,0],[.075,.1],[.03,.1],[.03,.4],[0,.4]],e)}},Se=Object.keys(u).length,H=(e,t,n)=>{let r=Math.imul(e,374761393)+Math.imul(t,668265263)+Math.imul(n,144665)|0;return r=Math.imul(r^r>>>13,1274126177),((r^r>>>16)>>>0)/4294967296},U=(e,t,n,r)=>{let[i,a]=[Math.floor(e),Math.floor(t)],[o,s]=[e-i,t-a].map(e=>e*e*(3-2*e)),c=(e,t)=>H((i+e)%n,(a+t)%n,r);return(c(0,0)*(1-o)+c(1,0)*o)*(1-s)+(c(0,1)*(1-o)+c(1,1)*o)*s},Ce=(e,t,n,r,i)=>{let[a,o]=[0,.5];for(let s=0;s<r;s++,o/=2)a+=o*U(e*(1<<s),t*(1<<s),n<<s,i+s);return a/(1-o*2)},we=(e,t,n)=>e.map((e,r)=>e+(t[r]-e)*n),W=(e,t)=>e.map(e=>e*t),G=[];G[u.BRICK]=(e,t)=>{let n=Math.floor(t*8),r=e*4+(n&1)*.5,[i,a,o]=[Math.floor(r)%4,r%1,t*8%1],s=.8+.4*U(e*16,t*16,16,5);return a<.04||o<.08?W([165,160,150],.7+.3*s):W([170,78,56],(.8+.4*H(i,n,1))*s*(.85+.15*o))},G[u.TILE]=(e,t)=>{let[n,r]=[Math.floor(e*4),Math.floor(t*4)],i=.9+.2*U(e*16,t*16,16,7);return e*4%1<.04||t*4%1<.04?W([85,85,80],i):W([196,190,172],i*(.88+.08*(n+r&1)+.1*H(n,r,2)))},G[u.LAVA]=(e,t)=>{let n=Ce(e*4,t*4,4,4,11),r=Math.max(0,1-Math.abs(n-.5)*7)**2;return we(we([55,8,4],[230,70,8],n),[255,215,90],r)},G[u.STONE]=(e,t)=>W([128,124,120],.45+.7*(Ce(e*8,t*8,8,4,3)*1.3+.1*H(Math.floor(e*128),Math.floor(t*128),4)));var Te=()=>{let e=new Uint8ClampedArray(Se*128*128*4);return G.forEach((t,n)=>{for(let r=0;r<16384;r++){let i=t(r%128/128,Math.floor(r/128)/128);e.set([...i.map(Math.round),255],(n*128*128+r)*4)}}),e},K=[.05,.045,.05],q=10,Ee=1<<18,De=2,Oe=`#version 300 es
2
2
  layout(location=0)in vec3 p;layout(location=1)in vec3 n;layout(location=2)in vec4 c;
3
3
  uniform mat4 m;out vec3 P,N;out vec4 C;
4
- void main(){P=p;N=n;C=c;gl_Position=m*vec4(p,1);}`,ce=`#version 300 es
4
+ void main(){P=p;N=n;C=c;gl_Position=m*vec4(p,1);}`,ke=`#version 300 es
5
5
  precision highp float;precision highp sampler2DArray;
6
6
  in vec3 P,N;in vec4 C;uniform vec3 E;uniform float T;uniform sampler2DArray S;out vec4 o;
7
7
  void main(){
8
- float L=C.z,s=step(0.,L),g=step(abs(L-${c.LAVA}.),.5);
9
- vec3 c=mix(vec3(C.xy,C.w),texture(S,vec3(C.xy/${oe}.+vec2(g*T*.01,0),max(L,0.))).rgb,s);
8
+ float L=C.z,s=step(0.,L),g=step(abs(L-${u.LAVA}.),.5)+step(L,-1.5);
9
+ vec3 c=mix(vec3(C.xy,C.w),texture(S,vec3(C.xy/${De}.+vec2(g*T*.01,0),max(L,0.))).rgb,s);
10
10
  c*=mix(mix(1.,C.w,s)*(.45+.55*max(dot(normalize(N),normalize(vec3(.4,.8,.3))),0.)),1.4,g);
11
- o=vec4(mix(c,vec3(${J}),1.-exp(-.02*length(P-E))),1);}`,le=(e,t,n,r)=>{let[i,a,o,s]=[Math.sin(t),Math.cos(t),Math.sin(n),Math.cos(n)],[c,l,u]=[.1,200,Math.tan(70*Math.PI/360)],d=[a,0,-i],f=[i*o,s,a*o],p=[-i*s,o,-a*s],m=(l+c)/(l-c),h=[d.map(e=>e/(u*r)),f.map(e=>e/u),p.map(e=>e*m),p].map(t=>[...t,-t[0]*e[0]-t[1]*e[1]-t[2]*e[2]]);return h[2][3]-=2*l*c/(l-c),Float32Array.from({length:16},(e,t)=>h[t&3][t>>2])},X=(e,t,n,r)=>{let i=e[3]*t+e[7]*n+e[11]*r+e[15];if(i<=0)return;let a=e[0]*t+e[4]*n+e[8]*r+e[12],o=e[1]*t+e[5]*n+e[9]*r+e[13];return[.5+a/(2*i),.5-o/(2*i)]},Z=(e,[t,n,r],[i,a,o],s,c)=>{let[l,u]=[Math.sin(s),Math.cos(s)],d=(e,t)=>[e*u+t*l,-e*l+t*u];for(let s=0;s<3;s++)for(let l of[-1,1]){let[u,f]=[(s+1)%3,(s+2)%3],p=[0,0,0];p[s]=l;let[m,h]=d(p[0],p[2]);for(let[g,_]of[[-1,-1],[1,-1],[1,1],[-1,-1],[1,1],[-1,1]]){let v=[0,0,0];[v[s],v[u],v[f]]=[l,g,_];let[y,b]=d(v[0]*i,v[2]*o);e.push(t+y,n+v[1]*a,r+b,m,p[1],h,c[0],c[1],-1,c[2])}}},ue=({map:e},{front:t,back:n})=>{let r=e.sectors[t];if(n<0)return[[r.floor,r.ceil]];let i=e.sectors[n];return[[r.floor,i.floor],[i.ceil,r.ceil]].filter(([e,t])=>t>e)},de=e=>{let t=[];for(let{pts:n,floor:r,ceil:i,ft:a,ct:o,light:s=1}of e.map.sectors)for(let[e,c,l]of[[r,1,a],[i,-1,o]])for(let r=1;r<n.length-1;r++)for(let[i,a]of[n[0],n[r],n[r+1]])t.push(i,e,a,0,c,0,i,a,l,s);for(let n of e.edges){let{ax:r,az:i,bx:a,bz:o,front:s}=n,{wt:c,light:l=1}=e.map.sectors[s],u=Math.hypot(a-r,o-i),[d,f]=[(a-r)/u,(o-i)/u];for(let[s,u]of ue(e,n))for(let[e,n,p]of[[r,i,s],[a,o,s],[a,o,u],[r,i,s],[a,o,u],[r,i,u]])t.push(e,p,n,-f,0,d,e*d+n*f,p,c,l)}return t},fe=(n,r)=>{let i=n.getContext(`webgl2`,{antialias:!0});if(!i)throw Error(`WebGL2 is not available`);let a=document.createElement(`canvas`).getContext(`2d`),o=/* @__PURE__ */ new Map,s=e=>{let t=o.get(e);if(!t){a.fillStyle=`#888`,a.fillStyle=e;let n=parseInt(a.fillStyle.slice(1),16),r=n>=0?n:8947848;o.set(e,t=[r>>16,r>>8&255,r&255].map(e=>e/255))}return t},c=de(r),l=q(),u=c.length*4,d=[],f=!1,p=/* @__PURE__ */ new Float32Array(16),m=()=>{let e=i.createProgram();for(let[t,n]of[[i.VERTEX_SHADER,se],[i.FRAGMENT_SHADER,ce]]){let r=i.createShader(t);i.shaderSource(r,n),i.compileShader(r),i.attachShader(e,r)}i.linkProgram(e),i.useProgram(e),d=[`m`,`E`,`T`].map(t=>i.getUniformLocation(e,t)),i.bindBuffer(i.ARRAY_BUFFER,i.createBuffer()),i.bufferData(i.ARRAY_BUFFER,u+16*ae*ie*Y*4,i.DYNAMIC_DRAW),i.bufferSubData(i.ARRAY_BUFFER,0,new Float32Array(c)),[3,3,4].reduce((e,t,n)=>(i.enableVertexAttribArray(n),i.vertexAttribPointer(n,t,i.FLOAT,!1,40,e*4),e+t),0),i.bindTexture(i.TEXTURE_2D_ARRAY,i.createTexture()),i.texImage3D(i.TEXTURE_2D_ARRAY,0,i.RGBA8,128,128,B,0,i.RGBA,i.UNSIGNED_BYTE,l),i.generateMipmap(i.TEXTURE_2D_ARRAY),i.texParameteri(i.TEXTURE_2D_ARRAY,i.TEXTURE_MIN_FILTER,i.LINEAR_MIPMAP_LINEAR);let t=i.getExtension(`EXT_texture_filter_anisotropic`);t&&i.texParameterf(i.TEXTURE_2D_ARRAY,t.TEXTURE_MAX_ANISOTROPY_EXT,8),i.enable(i.DEPTH_TEST),i.clearColor(J[0],J[1],J[2],1)};m();let h=e=>{e.preventDefault(),f=!0},g=()=>{m(),f=!1};return n.addEventListener(`webglcontextlost`,h),n.addEventListener(`webglcontextrestored`,g),{frame(r,a,o,l,m){let h=Math.min(devicePixelRatio,2),g=Math.round(n.clientWidth*h),_=Math.round(n.clientHeight*h);if(f||!g||!_)return;(n.width!=g||n.height!=_)&&(n.width=g,n.height=_),i.viewport(0,0,g,_),p=le(r,a,o,g/_);let v=[];for(let n of l.slice(0,16)){let[r,i]=j(n.yaw),a=e*.8,o=1.4500000000000002;Z(v,[n.x,n.y+o/2,n.z],[a,o/2,a],n.yaw,[...s(m.get(n.id)??`#888`)]);let c=[n.x,n.y+t-.17,n.z];Z(v,c,[.17,.17,.17],n.yaw,[.95,.8,.65]),Z(v,[c[0]+r*.17,c[1]+.03,c[2]+i*.17],[.13,.05,.03],n.yaw,[.1,.1,.12])}i.bufferSubData(i.ARRAY_BUFFER,u,new Float32Array(v)),i.uniformMatrix4fv(d[0],!1,p),i.uniform3fv(d[1],r),i.uniform1f(d[2],performance.now()/1e3),i.clear(i.COLOR_BUFFER_BIT|i.DEPTH_BUFFER_BIT),i.drawArrays(i.TRIANGLES,0,c.length/Y+v.length/Y)},project:(e,t,n)=>X(p,e,t,n),destroy(){n.removeEventListener(`webglcontextlost`,h),n.removeEventListener(`webglcontextrestored`,g),i.getExtension(`WEBGL_lose_context`)?.loseContext()}}},Q=r(u),$=1/60,pe=5,me=(e,n)=>{let r=document.createElement(`div`),i=r.attachShadow({mode:`open`});i.innerHTML=`<style>:host{display:block;width:100%;height:100%}div{position:relative;width:100%;height:100%;overflow:hidden;background:#000;font:600 14px system-ui,sans-serif;color:#fff;text-shadow:0 1px 2px #000}canvas{display:block;width:100%;height:100%;cursor:pointer}i,p,b,em{position:absolute;pointer-events:none;user-select:none;margin:0}i{left:50%;top:50%;width:6px;height:6px;margin:-3px;border-radius:50%;background:#fff;box-shadow:0 0 0 1px #0008}p{top:8px;left:12px}em{left:50%;top:60%;transform:translateX(-50%);font-size:18px;font-style:normal}b{transform:translate(-50%,-100%);font-size:12px;white-space:nowrap}[hidden]{display:none}</style><div><canvas></canvas><i></i><p></p><em></em></div>`,e.append(r);let a=i.querySelector(`canvas`),s=i.querySelector(`p`),c=i.querySelector(`em`),l=i.querySelector(`div`),u={roster:[],open:[],close:[]},d=e=>u[e].forEach(e=>e()),f=z(Q),p=T(a),m;try{m=fe(a,Q)}catch{}let h=[],g=/* @__PURE__ */ new Map,_=/* @__PURE__ */ new Map,x=/* @__PURE__ */ new Map,S=!1,C=m?``:`WebGL2 is not available`,w=!1,E=performance.now(),D=0,O=0,k=!1,A=n.socket??new WebSocket(n.url);A.binaryType=`arraybuffer`,A.onopen=()=>{S=!0,A.send(JSON.stringify({t:`join`,name:n.name})),d(`open`)},A.onclose=()=>{S=!1,C||=`Connection closed`,d(`close`)},A.onmessage=({data:e})=>{if(typeof e!=`string`){let t=y(e);t&&f.snapshot(t,performance.now());return}let t=b(e);t?.t==`roster`?(h=t.players,g=new Map(h.map(e=>[e.id,e.color])),_=new Map(h.map(e=>[e.id,e.name])),f.you(t.you),s.textContent=`Players: ${h.length}`,d(`roster`)):t?.t==`full`&&(C=`Room is full`)};let j=e=>{let t=x.get(e);return t||(x.set(e,t=document.createElement(`b`)),l.append(t)),t},M=()=>{O=requestAnimationFrame(M);let e=performance.now();if(D=Math.min(D+(e-E)/1e3,pe*$),E=e,!w){let e=f.me(0);e&&([p.yaw,p.pitch,w]=[e.yaw,e.pitch,!0]),D=0}for(;w&&D>=$;D-=$){let e=f.step({keys:p.keys,yaw:p.yaw,pitch:p.pitch});S&&A.send(v(e))}let n=f.me(D/$),r=f.others(e),i=n?[n.x,n.y+1.6,n.z]:[0,0,0];n&&m&&m.frame(i,p.yaw,p.pitch,r,g);let a=/* @__PURE__ */ new Set;for(let e of r){let r=[e.x,e.y+t+.3,e.z],s=n&&!o(Q,i,r)?m?.project(...r):void 0,c=j(e.id);a.add(e.id),c.hidden=!s,s&&(c.style.left=s[0]*100+`%`,c.style.top=s[1]*100+`%`);let l=_.get(e.id)??``;c.textContent!=l&&(c.textContent=l)}for(let[e,t]of x)a.has(e)||(t.remove(),x.delete(e));let s=C||(S?w?p.locked()?``:`Click to play`:`Joining…`:`Connecting…`);c.textContent!=s&&(c.textContent=s)};return O=requestAnimationFrame(M),{on:(e,t)=>void u[e].push(t),players:()=>h,destroy(){k||(k=!0,cancelAnimationFrame(O),p.destroy(),m?.destroy(),A.onopen=A.onclose=A.onmessage=null,A.close(),document.exitPointerLock(),r.remove())}}};export{me as tinyFps};
11
+ o=vec4(mix(c,vec3(${K}),1.-exp(-.02*length(P-E))),1);}`,J=(e,t,n=0)=>{let[r,i,a,o,s,c]=[e,t,n].flatMap(e=>[Math.sin(e),Math.cos(e)]),l=[i,0,-r],u=[r*a,o,i*a];return[l.map((e,t)=>e*c-u[t]*s),u.map((e,t)=>e*c+l[t]*s),[-r*o,a,-i*o]]},Ae=(e,t,n,r,i=0)=>{let[a,o,s]=J(t,n,i),[c,l,u]=[.1,200,Math.tan(70*Math.PI/360)],d=(l+c)/(l-c),f=[a.map(e=>e/(u*r)),o.map(e=>e/u),s.map(e=>e*d),s].map(t=>[...t,-t[0]*e[0]-t[1]*e[1]-t[2]*e[2]]);return f[2][3]-=2*l*c/(l-c),Float32Array.from({length:16},(e,t)=>f[t&3][t>>2])},je=(e,t,n,r)=>{let i=e[3]*t+e[7]*n+e[11]*r+e[15];if(i<=0)return;let a=e[0]*t+e[4]*n+e[8]*r+e[12],o=e[1]*t+e[5]*n+e[9]*r+e[13];return[.5+a/(2*i),.5-o/(2*i)]},Y=(e,[t,n,r],[i,a,o],s,c,l=-1)=>{let[u,d]=[Math.sin(s),Math.cos(s)],f=(e,t)=>[e*d+t*u,-e*u+t*d];for(let s=0;s<3;s++)for(let u of[-1,1]){let[d,p]=[(s+1)%3,(s+2)%3],m=[0,0,0];m[s]=u;let[h,g]=f(m[0],m[2]);for(let[_,v]of[[-1,-1],[1,-1],[1,1],[-1,-1],[1,1],[-1,1]]){let y=[0,0,0];[y[s],y[d],y[p]]=[u,_,v];let[b,x]=f(y[0]*i,y[2]*o);e.push(t+b,n+y[1]*a,r+x,h,m[1],g,c[0],c[1],l,c[2])}}},Me=({map:e},{front:t,back:n})=>{let r=e.sectors[t];if(n<0)return[[r.floor,r.ceil]];let i=e.sectors[n];return[[r.floor,i.floor],[i.ceil,r.ceil]].filter(([e,t])=>t>e)},Ne=e=>{let t=[];for(let{pts:n,floor:r,ceil:i,ft:a,ct:o,light:s=1}of e.map.sectors)for(let[e,c,l]of[[r,1,a],[i,-1,o]])for(let r=1;r<n.length-1;r++)for(let[i,a]of[n[0],n[r],n[r+1]])t.push(i,e,a,0,c,0,i,a,l,s);for(let n of e.edges){let{ax:r,az:i,bx:a,bz:o,front:s}=n,{wt:c,light:l=1}=e.map.sectors[s],u=Math.hypot(a-r,o-i),[d,f]=[(a-r)/u,(o-i)/u];for(let[s,u]of Me(e,n))for(let[e,n,p]of[[r,i,s],[a,o,s],[a,o,u],[r,i,s],[a,o,u],[r,i,u]])t.push(e,p,n,-f,0,d,e*d+n*f,p,c,l)}return t},X=(e,[t,n,r])=>Array.from({length:e.length*(q/6)},(i,a)=>{let[o,s]=[Math.floor(a/q),a%q];return s<6?e[o*6+s]:[t,n,-1,r][s-6]}),Z=(e,t,n,[r,i,a],o,s,c=0)=>{let[l,u]=[Math.sin(c),Math.cos(c)],d=(e,t,n)=>[0,1,2].map(o=>r[o]*(e*u-t*l)+i[o]*(e*l+t*u)-a[o]*n),f=[0,1,2].map(e=>n[e]+r[e]*o[0]+i[e]*o[1]+a[e]*o[2]);for(let n=0;n<t.length;n+=q){let r=d(t[n]*s,t[n+1]*s,t[n+2]*s);e.push(f[0]+r[0],f[1]+r[1],f[2]+r[2],...d(t[n+3],t[n+4],t[n+5])),e.push(...t.slice(n+6,n+q))}},Pe=(n,r)=>{let i=n.getContext(`webgl2`,{antialias:!0});if(!i)throw Error(`WebGL2 is not available`);let a=document.createElement(`canvas`).getContext(`2d`),o=/* @__PURE__ */ new Map,s=e=>{let t=o.get(e);if(!t){a.fillStyle=`#888`,a.fillStyle=e;let n=parseInt(a.fillStyle.slice(1),16),r=n>=0?n:8947848;o.set(e,t=[r>>16,r>>8&255,r&255].map(e=>e/255))}return t},c=Ne(r),l=Te(),u=c.length*4,d=u+Ee,[f,p]=[[.55,.55,.6],[.2,.2,.23]],m={barrels:X(V(10).barrels,f),body:X(V(10).body,p)},h={barrels:X(V(5).barrels,f),body:X(V(5).body,p)},g=[],_=[];for(let e of[[.1,.01,.03],[.01,.1,.03],[.03,.03,.05]])Y(g,[0,0,-ye-.04],e,0,[1,.85,.4],-2),Y(_,[0,0,-ye-.12],e.map(e=>e*3),0,[1,.85,.4],-2);let v=[],y=!1,b=/* @__PURE__ */ new Float32Array(16),x=()=>{i.bufferData(i.ARRAY_BUFFER,d,i.DYNAMIC_DRAW),i.bufferSubData(i.ARRAY_BUFFER,0,new Float32Array(c))},S=()=>{let e=i.createProgram();for(let[t,n]of[[i.VERTEX_SHADER,Oe],[i.FRAGMENT_SHADER,ke]]){let r=i.createShader(t);i.shaderSource(r,n),i.compileShader(r),i.attachShader(e,r)}i.linkProgram(e),i.useProgram(e),v=[`m`,`E`,`T`].map(t=>i.getUniformLocation(e,t)),i.bindBuffer(i.ARRAY_BUFFER,i.createBuffer()),x(),[3,3,4].reduce((e,t,n)=>(i.enableVertexAttribArray(n),i.vertexAttribPointer(n,t,i.FLOAT,!1,40,e*4),e+t),0),i.bindTexture(i.TEXTURE_2D_ARRAY,i.createTexture()),i.texImage3D(i.TEXTURE_2D_ARRAY,0,i.RGBA8,128,128,Se,0,i.RGBA,i.UNSIGNED_BYTE,l),i.generateMipmap(i.TEXTURE_2D_ARRAY),i.texParameteri(i.TEXTURE_2D_ARRAY,i.TEXTURE_MIN_FILTER,i.LINEAR_MIPMAP_LINEAR);let t=i.getExtension(`EXT_texture_filter_anisotropic`);t&&i.texParameterf(i.TEXTURE_2D_ARRAY,t.TEXTURE_MAX_ANISOTROPY_EXT,8),i.enable(i.DEPTH_TEST),i.clearColor(K[0],K[1],K[2],1)};S();let C=e=>{e.preventDefault(),y=!0},w=()=>{S(),y=!1};return n.addEventListener(`webglcontextlost`,C),n.addEventListener(`webglcontextrestored`,w),{frame(r,a,o,l,f,p){let S=Math.min(devicePixelRatio,2),C=Math.round(n.clientWidth*S),w=Math.round(n.clientHeight*S);if(y||!C||!w)return;(n.width!=C||n.height!=w)&&(n.width=C,n.height=w),i.viewport(0,0,C,w),b=Ae(r,a,o,C/w,p.roll);let T=[];for(let n of l){let[r,i]=k(n.yaw),a=e*.8,o=1.4500000000000002;Y(T,[n.x,n.y+o/2,n.z],[a,o/2,a],n.yaw,[...s(f.get(n.id)??`#888`)]);let c=[n.x,n.y+t-.17,n.z];Y(T,c,[.17,.17,.17],n.yaw,[.95,.8,.65]),Y(T,[c[0]+r*.17,c[1]+.03,c[2]+i*.17],[.13,.05,.03],n.yaw,[.1,.1,.12]);let l=[n.x,n.y+1.25,n.z],u=J(n.yaw,n.pitch);for(let e of[h.barrels,h.body,p.flashes.has(n.id)?_:[]])Z(T,e,l,u,[.36,-.12,.3],.8)}for(let{p:e}of p.sparks)Y(T,e,[.025,.025,.025],0,[1,.8,.3],-2);let E=T.length,D=J(a,o,p.roll),O=[.26,-.24+p.kick*.01,.6-p.kick*.06];Z(T,m.body,r,D,O,.8),Z(T,m.barrels,r,D,O,.8,p.spin),p.flash>0&&Z(T,g,r,D,O,.8,p.spin*3),u+T.length*4>d&&(d=u+T.length*8,x()),i.bufferSubData(i.ARRAY_BUFFER,u,new Float32Array(T)),i.uniformMatrix4fv(v[0],!1,b),i.uniform3fv(v[1],r),i.uniform1f(v[2],performance.now()/1e3),i.clear(i.COLOR_BUFFER_BIT|i.DEPTH_BUFFER_BIT);let A=c.length/q;i.drawArrays(i.TRIANGLES,0,A+E/q),i.clear(i.DEPTH_BUFFER_BIT),i.drawArrays(i.TRIANGLES,A+E/q,(T.length-E)/q)},project:(e,t,n)=>je(b,e,t,n),destroy(){n.removeEventListener(`webglcontextlost`,C),n.removeEventListener(`webglcontextrestored`,w),i.getExtension(`WEBGL_lose_context`)?.loseContext()}}},Q=a(f),$=1/60,Fe=5,Ie=(e,i)=>{let a=document.createElement(`div`),o=a.attachShadow({mode:`open`});o.innerHTML=`<style>:host{display:block;width:100%;height:100%}div{position:relative;width:100%;height:100%;overflow:hidden;background:#000;font:600 14px system-ui,sans-serif;color:#fff;text-shadow:0 1px 2px #000}canvas{display:block;width:100%;height:100%;cursor:pointer}i,p,b,em{position:absolute;pointer-events:none;user-select:none;margin:0}i{left:50%;top:50%;width:6px;height:6px;margin:-3px;border-radius:50%;background:#fff;box-shadow:0 0 0 1px #0008}p{top:8px;left:12px}em{left:50%;top:60%;transform:translateX(-50%);font-size:18px;font-style:normal}b{transform:translate(-50%,-100%);font-size:12px;white-space:nowrap}[hidden]{display:none}</style><div><canvas></canvas><i></i><p></p><em></em></div>`,e.append(a);let s=o.querySelector(`canvas`),l=o.querySelector(`p`),u=o.querySelector(`em`),d=o.querySelector(`div`),f={roster:[],open:[],close:[]},p=e=>f[e].forEach(e=>e()),m=ve(Q),h=ce(s),g=oe(),_=[],v=-1,y;try{y=Pe(s,Q)}catch{}let b=[],w=/* @__PURE__ */ new Map,T=/* @__PURE__ */ new Map,E=/* @__PURE__ */ new Map,D=!1,O=y?``:`WebGL2 is not available`,k=!1,A=performance.now(),j=0,M=0,N=!1,P=i.socket??new WebSocket(i.url);P.binaryType=`arraybuffer`,P.onopen=()=>{D=!0,P.send(JSON.stringify({t:`join`,name:i.name})),p(`open`)},P.onclose=()=>{D=!1,O||=`Connection closed`,p(`close`)},P.onmessage=({data:e})=>{if(typeof e!=`string`){let t=S(e);if(t){let e=performance.now();m.snapshot(t,e),_.push(...t.shots.filter(e=>e.id!=v).map(t=>({t:e,s:t})))}return}let t=C(e);t?.t==`roster`?(b=t.players,w=new Map(b.map(e=>[e.id,e.color])),T=new Map(b.map(e=>[e.id,e.name])),m.you(v=t.you),l.textContent=`Players: ${b.length}`,p(`roster`)):t?.t==`full`&&(O=`Room is full`)};let F=e=>{let t=E.get(e);return t||(E.set(e,t=document.createElement(`b`)),d.append(t)),t},I=()=>{M=requestAnimationFrame(I);let e=performance.now(),i=A;if(j=Math.min(j+(e-A)/1e3,Fe*$),A=e,!k){let e=m.me(0);e&&([h.yaw,h.pitch,k]=[e.yaw,e.pitch,!0]),j=0}for(;k&&j>=$;j-=$){let t=m.step({keys:h.keys,yaw:h.yaw,pitch:h.pitch},e);D&&P.send(x(t));let r=m.shot(),i=r&&m.me(1);r&&i&&g.shot(r,[i.x,i.y+n,i.z],!0)}let a=m.me(j/$),o=m.others(e),s=a?[a.x,a.y+n,a.z]:[0,0,0];for(;_.length&&_[0].t+100<=e;_.shift()){let e=o.find(e=>e.id==_[0].s.id);e&&g.shot(_[0].s,[e.x,e.y+n,e.z],!1,_[0].s.hit!=v)}if(g.update(a,h.keys,Math.min((e-i)/1e3,.1)),a&&y){let e=Math.max(-1.55,Math.min(r,h.pitch+g.shakePitch));y.frame([s[0],s[1]+g.bob,s[2]],h.yaw+g.shakeYaw,e,o,w,g)}let l=/* @__PURE__ */ new Set;for(let e of o){let n=[e.x,e.y+t+.3,e.z],r=a&&!c(Q,s,n)?y?.project(...n):void 0,i=F(e.id);l.add(e.id),i.hidden=!r,r&&(i.style.left=r[0]*100+`%`,i.style.top=r[1]*100+`%`);let o=T.get(e.id)??``;i.textContent!=o&&(i.textContent=o)}for(let[e,t]of E)l.has(e)||(t.remove(),E.delete(e));let d=O||(D?k?h.locked()?``:`Click to play`:`Joining…`:`Connecting…`);u.textContent!=d&&(u.textContent=d)};return M=requestAnimationFrame(I),{on:(e,t)=>void f[e].push(t),players:()=>b,destroy(){N||(N=!0,cancelAnimationFrame(M),h.destroy(),y?.destroy(),P.onopen=P.onclose=P.onmessage=null,P.close(),document.exitPointerLock(),a.remove())}}};export{Ie as tinyFps};
package/dist/local.js CHANGED
@@ -1 +1 @@
1
- var e=65536/(2*Math.PI),t=t=>Math.round(t*e)&65535,n=e=>Math.max(-32768,Math.min(32767,Math.round(e))),r=e=>n(e*1e4),i=e=>e/1e4,a=t=>t/e,o=e=>{if(e.byteLength!==8)return null;let t=new DataView(e);return t.getUint8(0)===1?{seq:t.getUint16(1),keys:t.getUint8(3),yaw:a(t.getUint16(4)),pitch:i(t.getInt16(6))}:null},s=e=>{let n=/* @__PURE__ */ new DataView(/* @__PURE__ */ new ArrayBuffer(4+24*e.players.length));return n.setUint8(0,2),n.setUint16(1,e.tick&65535),n.setUint8(3,e.players.length),e.players.forEach((e,i)=>{let a=4+i*24;n.setUint8(a,e.id),n.setFloat32(a+1,e.x),n.setFloat32(a+5,e.y),n.setFloat32(a+9,e.z),n.setFloat32(a+13,e.vy),n.setUint16(a+17,t(e.yaw)),n.setInt16(a+19,r(e.pitch)),n.setUint16(a+21,e.ack&65535),n.setUint8(a+23,+e.ground)}),n.buffer},c=e=>{try{let t=JSON.parse(e);return t&&typeof t.t==`string`?t:null}catch{return null}},l=.4,u=1.8,d=1.55,f={maxPlayers:16},p=e=>{let t=(e,t,n,r)=>`${e},${t},${n},${r}`,n=/* @__PURE__ */ new Map;return e.sectors.forEach(({pts:e},r)=>e.forEach(([i,a],o)=>n.set(t(i,a,...e[(o+1)%e.length]),r))),{map:e,edges:e.sectors.flatMap(({pts:e},r)=>e.map(([i,a],o)=>{let[s,c]=e[(o+1)%e.length];return{ax:i,az:a,bx:s,bz:c,front:r,back:n.get(t(s,c,i,a))??-1}}))}},m=({pts:e},t,n)=>e.every(([r,i],a)=>{let[o,s]=e[(a+1)%e.length];return(o-r)*(n-i)-(s-i)*(t-r)>=0}),h=({map:e},t,n)=>e.sectors.findIndex(e=>m(e,t,n)),g={FORWARD:1,BACK:2,LEFT:4,RIGHT:8,JUMP:16},_={BRICK:0,TILE:1,LAVA:2,STONE:3},v=(e,t,n,r,i)=>({pts:e,floor:t,ceil:n,ft:r,wt:_.BRICK,ct:_.STONE,light:i}),y={sectors:[v([[0,0],[16,0],[16,6],[16,10],[16,16],[0,16]],1,5,_.TILE),v([[16,6],[20,6],[20,10],[16,10]],1,3.5,_.TILE,.6),v([[20,0],[36,0],[36,6],[30,6],[26,6],[20,6]],1,7,_.TILE),v([[20,6],[26,6],[26,10],[20,10]],1,7,_.TILE),v([[30,6],[36,6],[36,10],[30,10]],1,7,_.TILE),v([[20,10],[26,10],[30,10],[36,10],[36,16],[32,16],[28,16],[20,16]],1,7,_.TILE),v([[26,6],[30,6],[30,10],[26,10]],0,7,_.LAVA),v([[28,16],[32,16],[32,17],[28,17]],1.4,7,_.STONE),v([[28,17],[32,17],[32,18],[28,18]],1.8,7,_.STONE),v([[28,18],[32,18],[32,19],[28,19]],2.2,7,_.STONE),v([[24,19],[28,19],[32,19],[36,19],[36,26],[24,26]],2.6,7,_.TILE,.8)],spawns:[[4,4,Math.PI*1.5],[4,12,Math.PI*1.5],[8,8,Math.PI*1.5],[12,4,Math.PI*1.5],[12,12,Math.PI*1.5],[2,8,Math.PI*1.5],[8,2,Math.PI*1.5],[8,14,Math.PI*1.5]]},b=1/60,x=Math.PI*2,S=1e-6,C=.01,w=e=>(e%x+x)%x,T=e=>[-Math.sin(e),-Math.cos(e)],E=Math.fround,D=(e,t,n)=>Math.min(Math.max(e,t),n),O=({map:e},t,n)=>{if(t.back<0)return!0;let r=e.sectors[t.front],i=e.sectors[t.back],a=Math.min(r.ceil,i.ceil);return i.floor>n+.5+C||a-Math.max(r.floor,i.floor)<1.8-C||a<n+1.8-C},k=(e,t)=>{let n=(l-S)**2;for(let r=0;r<4;r++){let r=0,i=0,a=0;for(let o of t.edges){let s=o.bx-o.ax,c=o.bz-o.az,u=e.x-o.ax,d=e.z-o.az;if(s*d-c*u<0)continue;let f=D((u*s+d*c)/(s*s+c*c),0,1),p=u-f*s,m=d-f*c,h=p*p+m*m;if(h>=n)continue;let g=Math.sqrt(h);if(.4-g<=r||!O(t,o,e.y))continue;r=l-g;let _=g||Math.hypot(s,c);i=(g?p:-c)/_*r,a=(g?m:s)/_*r}if(!r)return;e.x+=i,e.z+=a}},A=(e,t,n,r)=>{let{map:i,edges:a}=e,o=Math.max(h(e,t,n),0),{floor:s,ceil:c}=i.sectors[o],u=(l-S)**2;for(let l of a){if(l.front!==o||l.back<0)continue;let a=l.bx-l.ax,d=l.bz-l.az,f=t-l.ax,p=n-l.az,m=D((f*a+p*d)/(a*a+d*d),0,1);if((f-m*a)**2+(p-m*d)**2<u&&!O(e,l,r)){let e=i.sectors[l.back];s=Math.max(s,e.floor),c=Math.min(c,e.ceil)}}return[s,c]},j=(e,t,n)=>{e.yaw=w(t.yaw),e.pitch=Math.max(-d,Math.min(d,t.pitch));let r=(t.keys&g.FORWARD?1:0)-(t.keys&g.BACK?1:0),i=(t.keys&g.RIGHT?1:0)-(t.keys&g.LEFT?1:0),a=6*b/(Math.hypot(r,i)||1),[o,s]=T(e.yaw);e.x+=(o*r-s*i)*a,e.z+=(s*r+o*i)*a,k(e,n);let[c,l]=A(n,e.x,e.z,e.y);e.ground&&t.keys&g.JUMP&&(e.vy=7);let f=e.vy-20*b;e.y+=(e.vy+f)/2*b,e.vy=f,e.ground=!1,e.y+1.8>l&&(e.y=l-u,e.vy=Math.min(e.vy,0)),(e.vy<=0?e.y<=c+C:e.y<c)&&(e.y=c,e.vy<=0&&(e.vy=0,e.ground=!0)),e.x=E(e.x),e.y=E(e.y),e.z=E(e.z),e.vy=E(e.vy)},M=(e,t,n)=>{let{spawns:r,sectors:i}=t.map,[a,o,s]=r[(n%r.length+r.length)%r.length],c=i[Math.max(h(t,a,o),0)].floor;return{id:e,x:E(a),y:E(c),z:E(o),vy:0,yaw:w(s),pitch:0,ground:!0}},N=20,P=p(y),F=2,I=[`#e6194b`,`#3cb44b`,`#ffe119`,`#4363d8`,`#f58231`,`#911eb4`,`#42d4f4`,`#f032e6`,`#bfef45`,`#fabed4`,`#469990`,`#dcbeff`,`#9a6324`,`#800000`,`#aaffc3`,`#808000`],L=(e,t)=>Array.from(e).slice(0,t).join(``).trim(),R=e=>L((typeof e==`string`?e:``).replace(/\s+/g,` `).replace(/\p{Cc}/gu,``).trim(),16),z=(e,t)=>{let n=e;for(let r=2;t.includes(n);r++)n=`${L(e,15-`${r}`.length)} ${r}`;return n},B=(e,t)=>{let n=setInterval(e,t);return()=>clearInterval(n)},V=class{peers=/* @__PURE__ */ new Map;ticks=0;halt;rules;rr=0;constructor(e={}){this.rules={...f,...e}}get size(){return this.players().length}connect(e){this.peers.set(e,null)}receive(e,t){let n=this.peers.get(e);if(typeof t==`string`){let r=c(t);n===null&&r?.t===`join`&&this.join(e,r.name);return}let r=n&&o(t);r&&n.bank>0&&(n.bank--,j(n.state,r,P),n.ack=r.seq)}disconnect(e){let t=this.peers.get(e);this.peers.delete(e),t&&this.roster()}tick(){let e=this.players();if(e.length&&(e.forEach(e=>e.bank=Math.min(10,e.bank+1)),++this.ticks%2==0)){let t=s({tick:this.ticks,players:e.map(e=>({...e.state,ack:e.ack}))});e.forEach(e=>e.conn.send(t))}}start(e=B){if(this.halt)return;let t=1e3/60,n=performance.now(),r=0;this.halt=e(()=>{let e=performance.now();r+=e-n,n=e;for(let e=0;r>=t&&e<N;e++)this.tick(),r-=t;r%=t},t)}stop(){this.halt?.(),this.halt=void 0}players(){return[...this.peers.values()].filter(e=>e!==null)}join(e,t){let n=this.players();if(n.length>=this.rules.maxPlayers){e.send(JSON.stringify({t:`full`}));return}let r=0;for(;n.some(e=>e.info.id===r);)r++;let i=I.find(e=>!n.some(t=>t.info.color===e))??I[r%I.length],a=e=>{let{x:t,z:i}=M(r,P,e);return n.some(e=>Math.hypot(e.state.x-t,e.state.z-i)<F)},o=0;for(;o<y.spawns.length&&a(o);)o++;let s={conn:e,info:{id:r,name:z(R(t)||`Player ${r}`,n.map(e=>e.info.name)),color:i},state:M(r,P,o<y.spawns.length?o:this.rr++),bank:10,ack:65535};this.peers.set(e,s),this.roster()}roster(){let e=this.players(),t=e.map(e=>e.info).sort((e,t)=>e.id-t.id);e.forEach(e=>e.conn.send(JSON.stringify({t:`roster`,you:e.info.id,players:t})))}},H=(e,t)=>{let n=()=>{},r=()=>{n();let r=setInterval(e,t);n=()=>clearInterval(r)};try{let i=URL.createObjectURL(new Blob([`setInterval(()=>postMessage(0),${t})`])),a=new Worker(i);a.onmessage=e,a.onerror=r,n=()=>{a.terminate(),URL.revokeObjectURL(i)}}catch{r()}return()=>n()},U=(e,t)=>{let n=`tiny-fps:`+e,r=Math.random().toString(36).slice(2),i=new BroadcastChannel(n),a=new AbortController,o=globalThis.navigator?.locks,s,l,u,d,f=[],p,m=!1,h=e=>i.postMessage({from:r,...e}),g=e=>!m&&S.onmessage?.({data:e}),_={send:e=>queueMicrotask(()=>g(e))},v=()=>{let e=f.length?f:p?[p]:[];return f=[],e},y=e=>{if(m)return e();u=e,d=r,l=/* @__PURE__ */ new Map,s=new V(t),s.start(H),s.connect(_),v().forEach(e=>s.receive(_,e)),h({kind:`h`})};i.onmessage=({data:e})=>{if(!m){if(s&&l){let{from:t,kind:n}=e,r=l.get(t);n===`q`?h({to:t,kind:`h`}):n===`m`&&!e.to?(r||(r={send:e=>!m&&h({to:t,kind:`m`,data:e})},l.set(t,r),s.connect(r)),s.receive(r,e.data)):n===`b`&&r&&(l.delete(t),s.disconnect(r))}else e.to!==r&&e.to||(e.kind===`m`&&e.to?g(e.data):e.kind===`h`&&e.from!==d&&(d=e.from,v().forEach(e=>h({kind:`m`,data:e}))))}};let b=e=>{m||(typeof e==`string`&&c(e)?.t===`join`&&(p=e),s?s.receive(_,e):d?h({kind:`m`,data:e}):f.push(e))},x=()=>{m||(m=!0,globalThis.removeEventListener?.(`pagehide`,x),s?(s.stop(),u()):(h({kind:`b`}),a.abort()),i.close(),setTimeout(()=>S.onclose?.()))},S={binaryType:`arraybuffer`,onopen:null,onclose:null,onmessage:null,send:b,close:x};return o?o.request(n,{signal:a.signal},()=>new Promise(y)).catch(()=>{}):y(()=>{}),h({kind:`q`}),globalThis.addEventListener?.(`pagehide`,x),setTimeout(()=>!m&&S.onopen?.()),S};export{U as localSocket};
1
+ var e=65536/(2*Math.PI),t=t=>Math.round(t*e)&65535,n=e=>Math.max(-32768,Math.min(32767,Math.round(e))),r=e=>n(e*1e4),i=e=>e/1e4,a=t=>t/e,o=e=>{if(e.byteLength!==11)return null;let t=new DataView(e);return t.getUint8(0)===1?{seq:t.getUint16(1),keys:t.getUint8(3),yaw:a(t.getUint16(4)),pitch:i(t.getInt16(6)),view:(t.getUint16(8)*256+t.getUint8(10))/256}:null},s=e=>{let n=4+24*e.players.length,i=new DataView(new ArrayBuffer(n+1+14*e.shots.length));return i.setUint8(0,2),i.setUint16(1,e.tick&65535),i.setUint8(3,e.players.length),e.players.forEach((e,n)=>{let a=4+n*24;i.setUint8(a,e.id),i.setFloat32(a+1,e.x),i.setFloat32(a+5,e.y),i.setFloat32(a+9,e.z),i.setFloat32(a+13,e.vy),i.setUint16(a+17,t(e.yaw)),i.setInt16(a+19,r(e.pitch)),i.setUint16(a+21,e.ack&65535),i.setUint8(a+23,+e.ground|e.cool<<1)}),i.setUint8(n,e.shots.length),e.shots.forEach((e,t)=>{let r=n+1+t*14;i.setUint8(r,e.id),i.setFloat32(r+1,e.x),i.setFloat32(r+5,e.y),i.setFloat32(r+9,e.z),i.setUint8(r+13,e.hit&255)}),i.buffer},c=e=>{try{let t=JSON.parse(e);return t&&typeof t.t==`string`?t:null}catch{return null}},l=.4,u=1.8,d=1.6,f=1.55,p=.5,m=.02,h={maxPlayers:16},g=e=>{let t=(e,t,n,r)=>`${e},${t},${n},${r}`,n=/* @__PURE__ */ new Map;return e.sectors.forEach(({pts:e},r)=>e.forEach(([i,a],o)=>n.set(t(i,a,...e[(o+1)%e.length]),r))),{map:e,edges:e.sectors.flatMap(({pts:e},r)=>e.map(([i,a],o)=>{let[s,c]=e[(o+1)%e.length];return{ax:i,az:a,bx:s,bz:c,front:r,back:n.get(t(s,c,i,a))??-1}}))}},_=({pts:e},t,n)=>e.every(([r,i],a)=>{let[o,s]=e[(a+1)%e.length];return(o-r)*(n-i)-(s-i)*(t-r)>=0}),v=({map:e},t,n)=>e.sectors.findIndex(e=>_(e,t,n)),y={FORWARD:1,BACK:2,LEFT:4,RIGHT:8,JUMP:16,FIRE:32},b={BRICK:0,TILE:1,LAVA:2,STONE:3},x=(e,t,n,r,i)=>({pts:e,floor:t,ceil:n,ft:r,wt:b.BRICK,ct:b.STONE,light:i}),S={sectors:[x([[0,0],[16,0],[16,6],[16,10],[16,16],[0,16]],1,5,b.TILE),x([[16,6],[20,6],[20,10],[16,10]],1,3.5,b.TILE,.6),x([[20,0],[36,0],[36,6],[30,6],[26,6],[20,6]],1,7,b.TILE),x([[20,6],[26,6],[26,10],[20,10]],1,7,b.TILE),x([[30,6],[36,6],[36,10],[30,10]],1,7,b.TILE),x([[20,10],[26,10],[30,10],[36,10],[36,16],[32,16],[28,16],[20,16]],1,7,b.TILE),x([[26,6],[30,6],[30,10],[26,10]],0,7,b.LAVA),x([[28,16],[32,16],[32,17],[28,17]],1.4,7,b.STONE),x([[28,17],[32,17],[32,18],[28,18]],1.8,7,b.STONE),x([[28,18],[32,18],[32,19],[28,19]],2.2,7,b.STONE),x([[24,19],[28,19],[32,19],[36,19],[36,26],[24,26]],2.6,7,b.TILE,.8)],spawns:[[4,4,Math.PI*1.5],[4,12,Math.PI*1.5],[8,8,Math.PI*1.5],[12,4,Math.PI*1.5],[12,12,Math.PI*1.5],[2,8,Math.PI*1.5],[8,2,Math.PI*1.5],[8,14,Math.PI*1.5]]},C=1/60,w=Math.PI*2,T=1e-6,E=.01,D=e=>(e%w+w)%w,O=e=>[-Math.sin(e),-Math.cos(e)],k=Math.fround,A=(e,t,n)=>Math.min(Math.max(e,t),n),j=({map:e},t,n)=>{if(t.back<0)return!0;let r=e.sectors[t.front],i=e.sectors[t.back],a=Math.min(r.ceil,i.ceil);return i.floor>n+.5+E||a-Math.max(r.floor,i.floor)<1.8-E||a<n+1.8-E},M=(e,t)=>{let n=(l-T)**2;for(let r=0;r<4;r++){let r=0,i=0,a=0;for(let o of t.edges){let s=o.bx-o.ax,c=o.bz-o.az,u=e.x-o.ax,d=e.z-o.az;if(s*d-c*u<0)continue;let f=A((u*s+d*c)/(s*s+c*c),0,1),p=u-f*s,m=d-f*c,h=p*p+m*m;if(h>=n)continue;let g=Math.sqrt(h);if(.4-g<=r||!j(t,o,e.y))continue;r=l-g;let _=g||Math.hypot(s,c);i=(g?p:-c)/_*r,a=(g?m:s)/_*r}if(!r)return;e.x+=i,e.z+=a}},N=(e,t,n,r)=>{let{map:i,edges:a}=e,o=Math.max(v(e,t,n),0),{floor:s,ceil:c}=i.sectors[o],u=(l-T)**2;for(let l of a){if(l.front!==o||l.back<0)continue;let a=l.bx-l.ax,d=l.bz-l.az,f=t-l.ax,p=n-l.az,m=A((f*a+p*d)/(a*a+d*d),0,1);if((f-m*a)**2+(p-m*d)**2<u&&!j(e,l,r)){let e=i.sectors[l.back];s=Math.max(s,e.floor),c=Math.min(c,e.ceil)}}return[s,c]},P=(e,t,n)=>{let r=(t.keys&y.FIRE)!==0;e.cool=Math.max(0,e.cool-1),e.yaw=D(t.yaw),e.pitch=Math.max(-f,Math.min(f,t.pitch));let i=(t.keys&y.FORWARD?1:0)-(t.keys&y.BACK?1:0),a=(t.keys&y.RIGHT?1:0)-(t.keys&y.LEFT?1:0),o=6*C*(r?p:1)/(Math.hypot(i,a)||1),[s,c]=O(e.yaw);e.x+=(s*i-c*a)*o,e.z+=(c*i+s*a)*o,M(e,n);let[l,d]=N(n,e.x,e.z,e.y);e.ground&&t.keys&y.JUMP&&(e.vy=7);let m=e.vy-20*C;return e.y+=(e.vy+m)/2*C,e.vy=m,e.ground=!1,e.y+1.8>d&&(e.y=d-u,e.vy=Math.min(e.vy,0)),(e.vy<=0?e.y<=l+E:e.y<l)&&(e.y=l,e.vy<=0&&(e.vy=0,e.ground=!0)),e.x=k(e.x),e.y=k(e.y),e.z=k(e.z),e.vy=k(e.vy),r&&!e.cool?(e.cool=5,!0):!1},F=(e,t,n)=>{let{spawns:r,sectors:i}=t.map,[a,o,s]=r[(n%r.length+r.length)%r.length],c=i[Math.max(v(t,a,o),0)].floor;return{id:e,x:k(a),y:k(c),z:k(o),vy:0,yaw:D(s),pitch:0,ground:!0,cool:0}},I=e=>{for(let t=0;t<2;t++)e=Math.imul(e^e>>>16,73244475);return(e^e>>>16)>>>0},L=(e,t,n)=>{let[r,i,a,o]=[Math.sin(e),Math.cos(e),Math.sin(t),Math.cos(t)],s=I(n+1),c=(s&65535)/65536*w,l=(s>>>16)/65535,[u,d]=[Math.cos(c)*l*m,Math.sin(c)*l*m],f=[-r*o+i*u+r*a*d,a+o*d,-i*o-r*u+i*a*d],p=Math.hypot(...f);return[f[0]/p,f[1]/p,f[2]/p]},R=(e,t,n,r)=>{let{map:i,edges:a}=e,o=Math.max(v(e,t[0],t[2]),0),s=0;for(let e=0;e<=i.sectors.length;e++){let{floor:e,ceil:c}=i.sectors[o],l=1/0,u;for(let e of a){if(e.front!==o)continue;let[r,i]=[e.bx-e.ax,e.bz-e.az],a=n[2]*r-n[0]*i;if(a>=0)continue;let c=Math.max(((t[2]-e.az)*r-(t[0]-e.ax)*i)/-a,s),d=((t[0]+c*n[0]-e.ax)*r+(t[2]+c*n[2]-e.az)*i)/(r*r+i*i);d>-1e-9&&d<1+1e-9&&(c<l-1e-9||c<l+1e-9&&e.back<0)&&([l,u]=[c,e])}let d=n[1]<0?(e-t[1])/n[1]:n[1]>0?(c-t[1])/n[1]:1/0;if(d<=l||!u)return Math.min(Math.max(d,s),r);if(l>=r)return r;let f=t[1]+l*n[1],p=u.back<0?void 0:i.sectors[u.back];if(!p||f<Math.max(e,p.floor)||f>Math.min(c,p.ceil))return l;[o,s]=[u.back,l]}return s},z=(e,t,n)=>{let[r,i]=[e[0]-n.x,e[2]-n.z],a=t[0]*t[0]+t[2]*t[2],o=r*t[0]+i*t[2],s=r*r+i*i-l*l,[c,d]=[0,1/0];if(a>1e-12){let e=o*o-a*s;if(e<0)return 1/0;[c,d]=[Math.max(0,(-o-Math.sqrt(e))/a),(-o+Math.sqrt(e))/a]}else if(s>0)return 1/0;let[f,p]=[n.y-e[1],n.y+u-e[1]];if(t[1]){let[e,n]=[f/t[1],p/t[1]];c=Math.max(c,Math.min(e,n)),d=Math.min(d,Math.max(e,n))}else if(f>0||p<0)return 1/0;return c<=d?c:1/0},B=(e,t,n,r)=>{let i=[t.x,t.y+d,t.z],a=L(t.yaw,t.pitch,n),o=R(e,i,a,100),s=-1;for(let e of r){let n=e.id===t.id?1/0:z(i,a,e);n<o&&([o,s]=[n,e.id])}return{id:t.id,x:i[0]+a[0]*o,y:i[1]+a[1]*o,z:i[2]+a[2]*o,hit:s}},V=20,H=g(S),U=2,W=[`#e6194b`,`#3cb44b`,`#ffe119`,`#4363d8`,`#f58231`,`#911eb4`,`#42d4f4`,`#f032e6`,`#bfef45`,`#fabed4`,`#469990`,`#dcbeff`,`#9a6324`,`#800000`,`#aaffc3`,`#808000`],G=(e,t)=>Array.from(e).slice(0,t).join(``).trim(),K=e=>G((typeof e==`string`?e:``).replace(/\s+/g,` `).replace(/\p{Cc}/gu,``).trim(),16),q=(e,t)=>{let n=e;for(let r=2;t.includes(n);r++)n=`${G(e,15-`${r}`.length)} ${r}`;return n},J=(e,t)=>{let n=setInterval(e,t);return()=>clearInterval(n)},Y=class{peers=/* @__PURE__ */ new Map;ticks=0;history=[];shots=[];halt;rules;rr=0;constructor(e={}){this.rules={...h,...e}}get size(){return this.players().length}connect(e){this.peers.set(e,null)}receive(e,t){let n=this.peers.get(e);if(typeof t==`string`){let r=c(t);n===null&&r?.t===`join`&&this.join(e,r.name);return}let r=n&&o(t);r&&n.bank>0&&(n.bank--,P(n.state,r,H)&&this.shots.push(B(H,n.state,r.seq,this.rewind(n,r.view))),n.ack=r.seq)}disconnect(e){let t=this.peers.get(e);this.peers.delete(e),t&&this.roster()}tick(){let e=this.players();if(e.length&&(e.forEach(e=>e.bank=Math.min(10,e.bank+1)),++this.ticks%2==0)){for(this.history.push({tick:this.ticks,players:e.map(e=>({...e.state}))});this.history[0].tick<this.ticks-30;)this.history.shift();let t=s({tick:this.ticks,players:e.map(e=>({...e.state,ack:e.ack})),shots:this.shots});this.shots=[],e.forEach(e=>e.conn.send(t))}}start(e=J){if(this.halt)return;let t=1e3/60,n=performance.now(),r=0;this.halt=e(()=>{let e=performance.now();r+=e-n,n=e;for(let e=0;r>=t&&e<V;e++)this.tick(),r-=t;r%=t},t)}stop(){this.halt?.(),this.halt=void 0}rewind(e,t=0){let n=this.players().filter(t=>t!==e).map(e=>e.state),[r,i]=[this.history[0],this.history.at(-1)];if(!r||!i)return n;let a=((this.ticks-t+32768)%65536+65536)%65536-32768,o=Math.min(Math.max(this.ticks-a,r.tick),i.tick),s=0;for(;s+1<this.history.length&&this.history[s+1].tick<=o;)s++;let[c,l]=[this.history[s],this.history[s+1]??this.history[s]],u=l.tick>c.tick?(o-c.tick)/(l.tick-c.tick):0;return n.map(e=>{let t=c.players.find(t=>t.id===e.id),n=l.players.find(t=>t.id===e.id),r=(e,t)=>e+(t-e)*u;return t&&n?{...n,x:r(t.x,n.x),y:r(t.y,n.y),z:r(t.z,n.z)}:n??t??e})}players(){return[...this.peers.values()].filter(e=>e!==null)}join(e,t){let n=this.players();if(n.length>=this.rules.maxPlayers){e.send(JSON.stringify({t:`full`}));return}let r=0;for(;n.some(e=>e.info.id===r);)r++;let i=W.find(e=>!n.some(t=>t.info.color===e))??W[r%W.length],a=e=>{let{x:t,z:i}=F(r,H,e);return n.some(e=>Math.hypot(e.state.x-t,e.state.z-i)<U)},o=0;for(;o<S.spawns.length&&a(o);)o++;let s={conn:e,info:{id:r,name:q(K(t)||`Player ${r}`,n.map(e=>e.info.name)),color:i},state:F(r,H,o<S.spawns.length?o:this.rr++),bank:10,ack:65535};this.peers.set(e,s),this.roster()}roster(){let e=this.players(),t=e.map(e=>e.info).sort((e,t)=>e.id-t.id);e.forEach(e=>e.conn.send(JSON.stringify({t:`roster`,you:e.info.id,players:t})))}},X=(e,t)=>{let n=()=>{},r=()=>{n();let r=setInterval(e,t);n=()=>clearInterval(r)};try{let i=URL.createObjectURL(new Blob([`setInterval(()=>postMessage(0),${t})`])),a=new Worker(i);a.onmessage=e,a.onerror=r,n=()=>{a.terminate(),URL.revokeObjectURL(i)}}catch{r()}return()=>n()},Z=(e,t)=>{let n=`tiny-fps:`+e,r=Math.random().toString(36).slice(2),i=new BroadcastChannel(n),a=new AbortController,o=globalThis.navigator?.locks,s,l,u,d,f=[],p,m=!1,h=e=>i.postMessage({from:r,...e}),g=e=>!m&&S.onmessage?.({data:e}),_={send:e=>queueMicrotask(()=>g(e))},v=()=>{let e=f.length?f:p?[p]:[];return f=[],e},y=e=>{if(m)return e();u=e,d=r,l=/* @__PURE__ */ new Map,s=new Y(t),s.start(X),s.connect(_),v().forEach(e=>s.receive(_,e)),h({kind:`h`})};i.onmessage=({data:e})=>{if(!m){if(s&&l){let{from:t,kind:n}=e,r=l.get(t);n===`q`?h({to:t,kind:`h`}):n===`m`&&!e.to?(r||(r={send:e=>!m&&h({to:t,kind:`m`,data:e})},l.set(t,r),s.connect(r)),s.receive(r,e.data)):n===`b`&&r&&(l.delete(t),s.disconnect(r))}else e.to!==r&&e.to||(e.kind===`m`&&e.to?g(e.data):e.kind===`h`&&e.from!==d&&(d=e.from,v().forEach(e=>h({kind:`m`,data:e}))))}};let b=e=>{m||(typeof e==`string`&&c(e)?.t===`join`&&(p=e),s?s.receive(_,e):d?h({kind:`m`,data:e}):f.push(e))},x=()=>{m||(m=!0,globalThis.removeEventListener?.(`pagehide`,x),s?(s.stop(),u()):(h({kind:`b`}),a.abort()),i.close(),setTimeout(()=>S.onclose?.()))},S={binaryType:`arraybuffer`,onopen:null,onclose:null,onmessage:null,send:b,close:x};return o?o.request(n,{signal:a.signal},()=>new Promise(y)).catch(()=>{}):y(()=>{}),h({kind:`q`}),globalThis.addEventListener?.(`pagehide`,x),setTimeout(()=>!m&&S.onopen?.()),S};export{Z as localSocket};
package/dist/server.js CHANGED
@@ -3,8 +3,14 @@ import { WebSocketServer } from "ws";
3
3
  /** Player cylinder. */
4
4
  var PLAYER_R = .4;
5
5
  var PLAYER_H = 1.8;
6
+ /** Camera height above the feet. */
7
+ var EYE = 1.6;
6
8
  /** Pitch is clamped to ±MAX_PITCH radians, just short of straight up or down. */
7
9
  var MAX_PITCH = 1.55;
10
+ /** Speed factor while the trigger is held. */
11
+ var FIRE_SLOW = .5;
12
+ /** Largest angle, in radians, between a shot and the view direction. */
13
+ var SPREAD = .02;
8
14
  var RULES = { maxPlayers: 16 };
9
15
  // src/shared/level.ts
10
16
  /** Joins every edge a→b with the sector that has b→a, if any: that edge is a portal. */
@@ -41,7 +47,9 @@ var KEY = {
41
47
  BACK: 2,
42
48
  LEFT: 4,
43
49
  RIGHT: 8,
44
- JUMP: 16
50
+ JUMP: 16,
51
+ /** Left mouse button: the chaingun fires every FIRE_EVERY steps while held. */
52
+ FIRE: 32
45
53
  };
46
54
  /** Procedural textures, generated by the client. A sector names one per surface. */
47
55
  var TEX = {
@@ -289,13 +297,18 @@ var span = (lvl, x, z, y) => {
289
297
  * it slides along shut edges (see shut and push), stands on the highest floor under the circle
290
298
  * (a step up to STEP_H is climbed at once, a higher one needs a jump, a ledge is a fall), and
291
299
  * bumps its head on the lowest ceiling.
300
+ * The chaingun: `cool` counts down one per step, RUN_SPEED is scaled by FIRE_SLOW while KEY.FIRE
301
+ * is held, and the step that finds it held with `cool` at 0 re-arms it to FIRE_EVERY and returns
302
+ * true: the caller resolves a shot (one every FIRE_EVERY steps, the first at once).
292
303
  */
293
304
  var move = (p, i, lvl) => {
305
+ const fire = (i.keys & KEY.FIRE) !== 0;
306
+ p.cool = Math.max(0, p.cool - 1);
294
307
  p.yaw = norm(i.yaw);
295
308
  p.pitch = Math.max(-MAX_PITCH, Math.min(MAX_PITCH, i.pitch));
296
309
  const f = (i.keys & KEY.FORWARD ? 1 : 0) - (i.keys & KEY.BACK ? 1 : 0);
297
310
  const s = (i.keys & KEY.RIGHT ? 1 : 0) - (i.keys & KEY.LEFT ? 1 : 0);
298
- const d = 6 * DT / (Math.hypot(f, s) || 1);
311
+ const d = 6 * DT * (fire ? FIRE_SLOW : 1) / (Math.hypot(f, s) || 1);
299
312
  const [fx, fz] = forward(p.yaw);
300
313
  p.x += (fx * f - fz * s) * d;
301
314
  p.z += (fz * f + fx * s) * d;
@@ -321,6 +334,11 @@ var move = (p, i, lvl) => {
321
334
  p.y = f32(p.y);
322
335
  p.z = f32(p.z);
323
336
  p.vy = f32(p.vy);
337
+ if (fire && !p.cool) {
338
+ p.cool = 5;
339
+ return true;
340
+ }
341
+ return false;
324
342
  };
325
343
  /** A player standing on the floor at spawn point n of the map (modulo the count). */
326
344
  var spawn = (id, lvl, n) => {
@@ -335,7 +353,8 @@ var spawn = (id, lvl, n) => {
335
353
  vy: 0,
336
354
  yaw: norm(yaw),
337
355
  pitch: 0,
338
- ground: true
356
+ ground: true,
357
+ cool: 0
339
358
  };
340
359
  };
341
360
  var TURN = 65536 / (2 * Math.PI);
@@ -348,18 +367,20 @@ var fromPitch = (w) => w / 1e4;
348
367
  var fromTurn = (w) => w / TURN;
349
368
  /** Null on anything that is not a well-formed input frame. */
350
369
  var decodeInput = (buf) => {
351
- if (buf.byteLength !== 8) return null;
370
+ if (buf.byteLength !== 11) return null;
352
371
  const v = new DataView(buf);
353
372
  if (v.getUint8(0) !== 1) return null;
354
373
  return {
355
374
  seq: v.getUint16(1),
356
375
  keys: v.getUint8(3),
357
376
  yaw: fromTurn(v.getUint16(4)),
358
- pitch: fromPitch(v.getInt16(6))
377
+ pitch: fromPitch(v.getInt16(6)),
378
+ view: (v.getUint16(8) * 256 + v.getUint8(10)) / 256
359
379
  };
360
380
  };
361
381
  var encodeSnapshot = (s) => {
362
- const v = /* @__PURE__ */ new DataView(/* @__PURE__ */ new ArrayBuffer(4 + 24 * s.players.length));
382
+ const shots = 4 + 24 * s.players.length;
383
+ const v = new DataView(new ArrayBuffer(shots + 1 + 14 * s.shots.length));
363
384
  v.setUint8(0, 2);
364
385
  v.setUint16(1, s.tick & 65535);
365
386
  v.setUint8(3, s.players.length);
@@ -373,7 +394,16 @@ var encodeSnapshot = (s) => {
373
394
  v.setUint16(o + 17, toTurn(p.yaw));
374
395
  v.setInt16(o + 19, toPitch(p.pitch));
375
396
  v.setUint16(o + 21, p.ack & 65535);
376
- v.setUint8(o + 23, +p.ground);
397
+ v.setUint8(o + 23, +p.ground | p.cool << 1);
398
+ });
399
+ v.setUint8(shots, s.shots.length);
400
+ s.shots.forEach((h, i) => {
401
+ const o = shots + 1 + i * 14;
402
+ v.setUint8(o, h.id);
403
+ v.setFloat32(o + 1, h.x);
404
+ v.setFloat32(o + 5, h.y);
405
+ v.setFloat32(o + 9, h.z);
406
+ v.setUint8(o + 13, h.hit & 255);
377
407
  });
378
408
  return v.buffer;
379
409
  };
@@ -386,6 +416,124 @@ var parseText = (raw) => {
386
416
  return null;
387
417
  }
388
418
  };
419
+ // src/shared/shoot.ts
420
+ /** 32-bit integer hash (a xorshift-multiply round, twice). */
421
+ var hash = (n) => {
422
+ for (let k = 0; k < 2; k++) n = Math.imul(n ^ n >>> 16, 73244475);
423
+ return (n ^ n >>> 16) >>> 0;
424
+ };
425
+ /**
426
+ * Unit direction of a shot: the view direction at yaw and pitch (yaw 0, pitch 0 is -z; pitch > 0
427
+ * looks up), turned by at most SPREAD in a direction hashed from seq, so the host and the
428
+ * shooter's client agree on it.
429
+ */
430
+ var aim = (yaw, pitch, seq) => {
431
+ const [sy, cy, sp, cp] = [
432
+ Math.sin(yaw),
433
+ Math.cos(yaw),
434
+ Math.sin(pitch),
435
+ Math.cos(pitch)
436
+ ];
437
+ const h = hash(seq + 1);
438
+ const a = (h & 65535) / 65536 * TAU;
439
+ const r = (h >>> 16) / 65535;
440
+ const [x, y] = [Math.cos(a) * r * SPREAD, Math.sin(a) * r * SPREAD];
441
+ const d = [
442
+ -sy * cp + cy * x + sy * sp * y,
443
+ sp + cp * y,
444
+ -cy * cp - sy * x + cy * sp * y
445
+ ];
446
+ const n = Math.hypot(...d);
447
+ return [
448
+ d[0] / n,
449
+ d[1] / n,
450
+ d[2] / n
451
+ ];
452
+ };
453
+ /**
454
+ * Distance along the ray from o (inside the map) in the unit direction d to the first wall, step
455
+ * face, floor or ceiling, at most max.
456
+ *
457
+ * Walks the sectors: in the current convex one the ray leaves through the edge with the least
458
+ * crossing distance among the edges it goes outward through, or ends earlier on the floor or the
459
+ * ceiling. A solid edge, or a portal whose opening does not hold the crossing height, ends it.
460
+ */
461
+ var trace = (lvl, o, d, max) => {
462
+ const { map, edges } = lvl;
463
+ let s = Math.max(sectorAt(lvl, o[0], o[2]), 0);
464
+ let t = 0;
465
+ for (let hop = 0; hop <= map.sectors.length; hop++) {
466
+ const { floor, ceil } = map.sectors[s];
467
+ let exit = Infinity;
468
+ let through;
469
+ for (const e of edges) {
470
+ if (e.front !== s) continue;
471
+ const [ex, ez] = [e.bx - e.ax, e.bz - e.az];
472
+ const out = d[2] * ex - d[0] * ez;
473
+ if (out >= 0) continue;
474
+ const x = Math.max(((o[2] - e.az) * ex - (o[0] - e.ax) * ez) / -out, t);
475
+ const u = ((o[0] + x * d[0] - e.ax) * ex + (o[2] + x * d[2] - e.az) * ez) / (ex * ex + ez * ez);
476
+ if (u > -1e-9 && u < 1 + 1e-9 && (x < exit - 1e-9 || x < exit + 1e-9 && e.back < 0)) [exit, through] = [x, e];
477
+ }
478
+ const flat = d[1] < 0 ? (floor - o[1]) / d[1] : d[1] > 0 ? (ceil - o[1]) / d[1] : Infinity;
479
+ if (flat <= exit || !through) return Math.min(Math.max(flat, t), max);
480
+ if (exit >= max) return max;
481
+ const y = o[1] + exit * d[1];
482
+ const k = through.back < 0 ? void 0 : map.sectors[through.back];
483
+ if (!k || y < Math.max(floor, k.floor) || y > Math.min(ceil, k.ceil)) return exit;
484
+ [s, t] = [through.back, exit];
485
+ }
486
+ return t;
487
+ };
488
+ /**
489
+ * Distance at which the ray enters the upright cylinder of t (sides and caps), Infinity on a miss:
490
+ * the span of the ray inside the infinite cylinder, cut to the span between the cap planes.
491
+ */
492
+ var hit = (o, d, t) => {
493
+ const [px, pz] = [o[0] - t.x, o[2] - t.z];
494
+ const a = d[0] * d[0] + d[2] * d[2];
495
+ const b = px * d[0] + pz * d[2];
496
+ const c = px * px + pz * pz - PLAYER_R * PLAYER_R;
497
+ let [lo, hi] = [0, Infinity];
498
+ if (a > 1e-12) {
499
+ const q = b * b - a * c;
500
+ if (q < 0) return Infinity;
501
+ [lo, hi] = [Math.max(0, (-b - Math.sqrt(q)) / a), (-b + Math.sqrt(q)) / a];
502
+ } else if (c > 0) return Infinity;
503
+ const [y0, y1] = [t.y - o[1], t.y + PLAYER_H - o[1]];
504
+ if (d[1]) {
505
+ const [u, v] = [y0 / d[1], y1 / d[1]];
506
+ lo = Math.max(lo, Math.min(u, v));
507
+ hi = Math.min(hi, Math.max(u, v));
508
+ } else if (y0 > 0 || y1 < 0) return Infinity;
509
+ return lo <= hi ? lo : Infinity;
510
+ };
511
+ /**
512
+ * Resolves one shot of `shooter` (state after the step that fired) from its eye (feet + EYE)
513
+ * along aim(yaw, pitch, seq): the nearest of the level (trace) and the upright cylinders
514
+ * (PLAYER_R, PLAYER_H, from the feet) of `targets` other than the shooter, within RANGE.
515
+ */
516
+ var fire = (lvl, shooter, seq, targets) => {
517
+ const o = [
518
+ shooter.x,
519
+ shooter.y + EYE,
520
+ shooter.z
521
+ ];
522
+ const d = aim(shooter.yaw, shooter.pitch, seq);
523
+ let t = trace(lvl, o, d, 100);
524
+ let id = -1;
525
+ for (const p of targets) {
526
+ const u = p.id === shooter.id ? Infinity : hit(o, d, p);
527
+ if (u < t) [t, id] = [u, p.id];
528
+ }
529
+ return {
530
+ id: shooter.id,
531
+ x: o[0] + d[0] * t,
532
+ y: o[1] + d[1] * t,
533
+ z: o[2] + d[2] * t,
534
+ hit: id
535
+ };
536
+ };
389
537
  // src/shared/room.ts
390
538
  /** Ticks run at most this many times per timer callback, so a stalled host does not spiral. */
391
539
  var MAX_CATCH_UP = 20;
@@ -432,12 +580,19 @@ var setEvery = (cb, ms) => {
432
580
  * taken by another player; 'full' beyond rules.maxPlayers. Everyone gets a fresh 'roster' on
433
581
  * every join and leave.
434
582
  * - Each input frame moves its player at once with move(); the INPUT_BURST bank drops the excess.
435
- * - Every SNAP_EVERY ticks of TICK_HZ, one snapshot with every player goes to every peer.
583
+ * A step that fires resolves its shot at once with fire(), the other players rewound to the
584
+ * view tick of the input (lag compensation, at most MAX_REWIND ticks back).
585
+ * - Every SNAP_EVERY ticks of TICK_HZ, one snapshot with every player and the shots resolved
586
+ * since the last one goes to every peer.
436
587
  */
437
588
  var Room = class {
438
589
  /** Every connected peer; null until it joins. */
439
590
  peers = /* @__PURE__ */ new Map();
440
591
  ticks = 0;
592
+ /** The players as each snapshot of the last MAX_REWIND ticks sent them. */
593
+ history = [];
594
+ /** Shots resolved since the last snapshot. */
595
+ shots = [];
441
596
  halt;
442
597
  rules;
443
598
  /** Round-robin cursor over the spawn points, used once they are all taken. */
@@ -468,7 +623,7 @@ var Room = class {
468
623
  const input = p && decodeInput(data);
469
624
  if (input && p.bank > 0) {
470
625
  p.bank--;
471
- move(p.state, input, LEVEL);
626
+ if (move(p.state, input, LEVEL)) this.shots.push(fire(LEVEL, p.state, input.seq, this.rewind(p, input.view)));
472
627
  p.ack = input.seq;
473
628
  }
474
629
  }
@@ -484,13 +639,20 @@ var Room = class {
484
639
  if (!players.length) return;
485
640
  players.forEach((p) => p.bank = Math.min(10, p.bank + 1));
486
641
  if (++this.ticks % 2 === 0) {
642
+ this.history.push({
643
+ tick: this.ticks,
644
+ players: players.map((p) => ({ ...p.state }))
645
+ });
646
+ while (this.history[0].tick < this.ticks - 30) this.history.shift();
487
647
  const snap = encodeSnapshot({
488
648
  tick: this.ticks,
489
649
  players: players.map((p) => ({
490
650
  ...p.state,
491
651
  ack: p.ack
492
- }))
652
+ })),
653
+ shots: this.shots
493
654
  });
655
+ this.shots = [];
494
656
  players.forEach((p) => p.conn.send(snap));
495
657
  }
496
658
  }
@@ -521,6 +683,34 @@ var Room = class {
521
683
  this.halt?.();
522
684
  this.halt = void 0;
523
685
  }
686
+ /**
687
+ * The players other than `shooter` as the shooter drew them at `view` (a u16 tick, fractional,
688
+ * read against the current tick and clamped to the kept history): x, y, z interpolated between
689
+ * the two snapshots around it like the client does. A player the history does not hold stands
690
+ * where it is.
691
+ */
692
+ rewind(shooter, view = 0) {
693
+ const now = this.players().filter((p) => p !== shooter).map((p) => p.state);
694
+ const [first, last] = [this.history[0], this.history.at(-1)];
695
+ if (!first || !last) return now;
696
+ const back = ((this.ticks - view + 32768) % 65536 + 65536) % 65536 - 32768;
697
+ const t = Math.min(Math.max(this.ticks - back, first.tick), last.tick);
698
+ let k = 0;
699
+ while (k + 1 < this.history.length && this.history[k + 1].tick <= t) k++;
700
+ const [a, b] = [this.history[k], this.history[k + 1] ?? this.history[k]];
701
+ const u = b.tick > a.tick ? (t - a.tick) / (b.tick - a.tick) : 0;
702
+ return now.map((p) => {
703
+ const pa = a.players.find((q) => q.id === p.id);
704
+ const pb = b.players.find((q) => q.id === p.id);
705
+ const l = (m, n) => m + (n - m) * u;
706
+ return pa && pb ? {
707
+ ...pb,
708
+ x: l(pa.x, pb.x),
709
+ y: l(pa.y, pb.y),
710
+ z: l(pa.z, pb.z)
711
+ } : pb ?? pa ?? p;
712
+ });
713
+ }
524
714
  players() {
525
715
  return [...this.peers.values()].filter((p) => p !== null);
526
716
  }
@@ -0,0 +1,37 @@
1
+ import type { PlayerState, Shot } from '../shared/types';
2
+ type Vec3 = [number, number, number];
3
+ export interface Spark {
4
+ p: Vec3;
5
+ v: Vec3;
6
+ /** Seconds left. */
7
+ life: number;
8
+ }
9
+ /** What the renderer and the camera read; update and shot move it. */
10
+ export interface Effects {
11
+ /** Vertical eye offset, metres. */
12
+ bob: number;
13
+ /** Radians, positive leans right (strafing right). */
14
+ roll: number;
15
+ /** Camera yaw and pitch jitter, radians. */
16
+ shakeYaw: number;
17
+ shakePitch: number;
18
+ /** Recoil, 1 at a shot down to 0. */
19
+ kick: number;
20
+ /** Barrel angle, radians. */
21
+ spin: number;
22
+ /** Seconds of own muzzle flash left. */
23
+ flash: number;
24
+ /** Seconds of flash left at each other shooter, by player id. */
25
+ flashes: Map<number, number>;
26
+ sparks: Spark[];
27
+ /** Advances dt seconds: the own player (eased, undefined before the first snapshot) and the held keys. */
28
+ update(me: PlayerState | undefined, keys: number, dt: number): void;
29
+ /**
30
+ * A shot, from the shooter's eye; own ones kick the camera and the gun. No sparks with
31
+ * `sparks` false: a shot that hit the viewer would burst them inside the camera.
32
+ */
33
+ shot(s: Shot, from: Vec3, own: boolean, sparks?: boolean): void;
34
+ }
35
+ /** rand in [0, 1): injectable so the tests are exact. */
36
+ export declare const createEffects: (rand?: () => number) => Effects;
37
+ export {};
@@ -0,0 +1,19 @@
1
+ type Vec3 = [number, number, number];
2
+ /** Where the barrels end, on -z: the muzzle flash sits just past it. */
3
+ export declare const MUZZLE = 0.55;
4
+ /**
5
+ * Revolves a profile of [radius, length] points around the axis through `at` along -z: `length`
6
+ * runs forward from `at`. Returns triangles as x, y, z, nx, ny, nz per vertex, `sides` quads of 2
7
+ * triangles per profile segment. The profile goes from back to front and closes on the axis, so
8
+ * the normals (smooth around the ring, flat along the profile) point outward.
9
+ */
10
+ export declare const lathe: (profile: [number, number][], sides: number, at?: Vec3) => number[];
11
+ /**
12
+ * The chaingun, built once per level of detail: `barrels` turn around the central axis (the caller
13
+ * spins them), the `body` stays. Vertices as in lathe.
14
+ */
15
+ export declare const chaingun: (sides: number) => {
16
+ barrels: number[];
17
+ body: number[];
18
+ };
19
+ export {};
@@ -13,5 +13,5 @@ export interface Controls {
13
13
  /** Removes every listener. */
14
14
  destroy(): void;
15
15
  }
16
- /** Click the canvas to lock the pointer; while locked the mouse looks and the keys move. */
16
+ /** Click the canvas to lock the pointer; while locked the mouse looks, the keys move, the left button fires. */
17
17
  export declare const createInput: (canvas: HTMLCanvasElement) => Controls;
@@ -1,4 +1,4 @@
1
- import type { Input, Level, PlayerState, Snapshot } from '../shared/types';
1
+ import type { Input, Level, PlayerState, Shot, Snapshot } from '../shared/types';
2
2
  export interface Predictor {
3
3
  /** The own player id, from the roster. Snapshots before it are only used for the others. */
4
4
  you(id: number): void;
@@ -7,8 +7,14 @@ export interface Predictor {
7
7
  * own player with it, keeps it for replay, and returns it for the caller to send (the very
8
8
  * values predicted with, which the host reads back). Until the own player is known (no snapshot
9
9
  * shows it yet) it only numbers and keeps the input: the first snapshot replays it.
10
+ * With `now` (ms, performance.now()) the input carries the view tick the others are drawn at.
10
11
  */
11
- step(i: Omit<Input, 'seq'>): Input;
12
+ step(i: Omit<Input, 'seq' | 'view'>, now?: number): Input;
13
+ /**
14
+ * The shot the last step fired, resolved with fire() against others(now) of that step; undefined
15
+ * when it did not fire. Replays on a snapshot never fire again.
16
+ */
17
+ shot(): Shot | undefined;
12
18
  /** A decoded snapshot, received at `now` (ms, performance.now()). */
13
19
  snapshot(s: Snapshot, now: number): void;
14
20
  /**
@@ -1,24 +1,30 @@
1
1
  import type { Edge, Level, PlayerState } from '../shared/types';
2
+ import type { Effects } from './effects';
2
3
  type Vec3 = [number, number, number];
3
4
  /**
4
- * Column-major view-projection matrix: camera at eye looking along (-sin yaw cos pitch, sin pitch,
5
- * -cos yaw cos pitch), y up, 70° vertical field of view.
5
+ * Camera axes [right, up, forward]: forward is (-sin yaw cos pitch, sin pitch, -cos yaw cos pitch),
6
+ * y up at roll 0. A positive roll leans the camera right: up tilts toward right.
6
7
  */
7
- export declare const viewProj: (eye: Vec3, yaw: number, pitch: number, aspect: number) => Float32Array<ArrayBuffer>;
8
+ export declare const basis: (yaw: number, pitch: number, roll?: number) => [Vec3, Vec3, Vec3];
9
+ /** Column-major view-projection matrix of basis(yaw, pitch, roll) at eye, 70° vertical field of view. */
10
+ export declare const viewProj: (eye: Vec3, yaw: number, pitch: number, aspect: number, roll?: number) => Float32Array<ArrayBuffer>;
8
11
  /** A world point on screen, in [0, 1] from the top left; undefined behind the camera. */
9
12
  export declare const project: (m: Float32Array, x: number, y: number, z: number) => [number, number] | undefined;
10
13
  /**
11
14
  * Appends the 36 vertices of a box centred on (cx, cy, cz) with half sizes (hx, hy, hz), turned
12
15
  * by yaw about its own vertical axis (local forward, -z, ends up along forward(yaw)).
13
16
  */
14
- export declare const pushBox: (out: number[], [cx, cy, cz]: Vec3, [hx, hy, hz]: Vec3, yaw: number, color: number[]) => void;
17
+ export declare const pushBox: (out: number[], [cx, cy, cz]: Vec3, [hx, hy, hz]: Vec3, yaw: number, color: number[], layer?: number) => void;
15
18
  /** Vertical spans [y0, y1] an edge draws on its own side: the wall, or the steps of a portal. */
16
19
  export declare const spans: ({ map }: Level, { front, back }: Edge) => [number, number][];
17
20
  /** Floors and ceilings as fans, then every edge's walls as quads, in the vertex format above. */
18
21
  export declare const levelVerts: (lvl: Level) => number[];
19
22
  export interface Renderer {
20
- /** Draws one frame: the map and the others as seen from eye. colors maps player id to CSS. */
21
- frame(eye: Vec3, yaw: number, pitch: number, others: PlayerState[], colors: ReadonlyMap<number, string>): void;
23
+ /**
24
+ * Draws one frame: the map, the others and the effects as seen from eye, with the view model on
25
+ * top. colors maps player id to CSS.
26
+ */
27
+ frame(eye: Vec3, yaw: number, pitch: number, others: PlayerState[], colors: ReadonlyMap<number, string>, fx: Effects): void;
22
28
  /** A world point on screen as of the last frame, in [0, 1] from the top left. */
23
29
  project(x: number, y: number, z: number): [number, number] | undefined;
24
30
  destroy(): void;
@@ -22,6 +22,19 @@ export declare const GRAVITY = 20;
22
22
  export declare const MAX_PITCH = 1.55;
23
23
  /** A floor at most this much higher is walked onto; a higher one needs a jump. */
24
24
  export declare const STEP_H = 0.5;
25
+ /** Steps between two chaingun shots while the trigger is held: 60 / 5 = 12 shots per second. */
26
+ export declare const FIRE_EVERY = 5;
27
+ /** Speed factor while the trigger is held. */
28
+ export declare const FIRE_SLOW = 0.5;
29
+ /** Metres a shot reaches. */
30
+ export declare const RANGE = 100;
31
+ /** Largest angle, in radians, between a shot and the view direction. */
32
+ export declare const SPREAD = 0.02;
33
+ /**
34
+ * Host ticks a shot may look back (0.5 s): the host rewinds the targets to the view tick of the
35
+ * input that fired, clamped to this.
36
+ */
37
+ export declare const MAX_REWIND = 30;
25
38
  /**
26
39
  * Host-side speed-hack guard: each player earns one input per host tick and may bank up to
27
40
  * INPUT_BURST of them; inputs beyond the bank are dropped (and not acked).
@@ -11,7 +11,10 @@ export declare const forward: (yaw: number) => [x: number, z: number];
11
11
  * it slides along shut edges (see shut and push), stands on the highest floor under the circle
12
12
  * (a step up to STEP_H is climbed at once, a higher one needs a jump, a ledge is a fall), and
13
13
  * bumps its head on the lowest ceiling.
14
+ * The chaingun: `cool` counts down one per step, RUN_SPEED is scaled by FIRE_SLOW while KEY.FIRE
15
+ * is held, and the step that finds it held with `cool` at 0 re-arms it to FIRE_EVERY and returns
16
+ * true: the caller resolves a shot (one every FIRE_EVERY steps, the first at once).
14
17
  */
15
- export declare const move: (p: PlayerState, i: Input, lvl: Level) => void;
18
+ export declare const move: (p: PlayerState, i: Input, lvl: Level) => boolean;
16
19
  /** A player standing on the floor at spawn point n of the map (modulo the count). */
17
20
  export declare const spawn: (id: number, lvl: Level, n: number) => PlayerState;
@@ -12,12 +12,14 @@ export type ServerText = {
12
12
  };
13
13
  export declare const MSG_INPUT = 1;
14
14
  export declare const MSG_SNAPSHOT = 2;
15
- export declare const INPUT_BYTES = 8;
15
+ export declare const INPUT_BYTES = 11;
16
16
  export declare const SNAP_HEAD = 4;
17
17
  export declare const SNAP_PLAYER = 24;
18
+ export declare const SNAP_SHOT = 14;
18
19
  /**
19
- * The input as the host will read it after encodeInput and decodeInput: yaw and pitch rounded to
20
- * the wire. The client predicts with this, so both sides move with the same values.
20
+ * The input as the host will read it after encodeInput and decodeInput: yaw, pitch and view
21
+ * rounded to the wire (an absent view stays absent here, and reads 0 on the host). The client
22
+ * predicts with this, so both sides move with the same values.
21
23
  */
22
24
  export declare const onWire: (i: Input) => Input;
23
25
  export declare const encodeInput: (i: Input) => ArrayBuffer;
@@ -7,12 +7,19 @@ import type { Conn, Every, Rules } from './types';
7
7
  * taken by another player; 'full' beyond rules.maxPlayers. Everyone gets a fresh 'roster' on
8
8
  * every join and leave.
9
9
  * - Each input frame moves its player at once with move(); the INPUT_BURST bank drops the excess.
10
- * - Every SNAP_EVERY ticks of TICK_HZ, one snapshot with every player goes to every peer.
10
+ * A step that fires resolves its shot at once with fire(), the other players rewound to the
11
+ * view tick of the input (lag compensation, at most MAX_REWIND ticks back).
12
+ * - Every SNAP_EVERY ticks of TICK_HZ, one snapshot with every player and the shots resolved
13
+ * since the last one goes to every peer.
11
14
  */
12
15
  export declare class Room {
13
16
  /** Every connected peer; null until it joins. */
14
17
  private peers;
15
18
  private ticks;
19
+ /** The players as each snapshot of the last MAX_REWIND ticks sent them. */
20
+ private history;
21
+ /** Shots resolved since the last snapshot. */
22
+ private shots;
16
23
  private halt;
17
24
  private rules;
18
25
  /** Round-robin cursor over the spawn points, used once they are all taken. */
@@ -38,6 +45,13 @@ export declare class Room {
38
45
  */
39
46
  start(every?: Every): void;
40
47
  stop(): void;
48
+ /**
49
+ * The players other than `shooter` as the shooter drew them at `view` (a u16 tick, fractional,
50
+ * read against the current tick and clamped to the kept history): x, y, z interpolated between
51
+ * the two snapshots around it like the client does. A player the history does not hold stands
52
+ * where it is.
53
+ */
54
+ private rewind;
41
55
  private players;
42
56
  private join;
43
57
  /** Each joined peer gets the full roster with its own id. */
@@ -0,0 +1,24 @@
1
+ import type { Level, PlayerState, Shot } from './types';
2
+ type Vec3 = [number, number, number];
3
+ /**
4
+ * Unit direction of a shot: the view direction at yaw and pitch (yaw 0, pitch 0 is -z; pitch > 0
5
+ * looks up), turned by at most SPREAD in a direction hashed from seq, so the host and the
6
+ * shooter's client agree on it.
7
+ */
8
+ export declare const aim: (yaw: number, pitch: number, seq: number) => Vec3;
9
+ /**
10
+ * Distance along the ray from o (inside the map) in the unit direction d to the first wall, step
11
+ * face, floor or ceiling, at most max.
12
+ *
13
+ * Walks the sectors: in the current convex one the ray leaves through the edge with the least
14
+ * crossing distance among the edges it goes outward through, or ends earlier on the floor or the
15
+ * ceiling. A solid edge, or a portal whose opening does not hold the crossing height, ends it.
16
+ */
17
+ export declare const trace: (lvl: Level, o: Vec3, d: Vec3, max: number) => number;
18
+ /**
19
+ * Resolves one shot of `shooter` (state after the step that fired) from its eye (feet + EYE)
20
+ * along aim(yaw, pitch, seq): the nearest of the level (trace) and the upright cylinders
21
+ * (PLAYER_R, PLAYER_H, from the feet) of `targets` other than the shooter, within RANGE.
22
+ */
23
+ export declare const fire: (lvl: Level, shooter: PlayerState, seq: number, targets: PlayerState[]) => Shot;
24
+ export {};
@@ -11,6 +11,8 @@ export declare const KEY: {
11
11
  readonly LEFT: 4;
12
12
  readonly RIGHT: 8;
13
13
  readonly JUMP: 16;
14
+ /** Left mouse button: the chaingun fires every FIRE_EVERY steps while held. */
15
+ readonly FIRE: 32;
14
16
  };
15
17
  /** One client step: what the player held during 1 / TICK_HZ s, and where they looked. */
16
18
  export interface Input {
@@ -22,6 +24,13 @@ export interface Input {
22
24
  yaw: number;
23
25
  /** Radians, [-MAX_PITCH, MAX_PITCH]. */
24
26
  pitch: number;
27
+ /**
28
+ * Host tick the client drew the others at when it sent this input, fractional (interpolation
29
+ * between two snapshots), in [0, 65536): the host rewinds the targets of a shot to it (lag
30
+ * compensation). The wire keeps 1/256 tick. Absent (no snapshot seen yet) goes out as 0: the host
31
+ * clamps it to the oldest tick it keeps.
32
+ */
33
+ view?: number;
25
34
  }
26
35
  export interface PlayerState {
27
36
  id: number;
@@ -35,6 +44,19 @@ export interface PlayerState {
35
44
  pitch: number;
36
45
  /** Standing on a floor: a jump is allowed. */
37
46
  ground: boolean;
47
+ /** Steps until the chaingun may fire again, 0 to FIRE_EVERY. */
48
+ cool: number;
49
+ }
50
+ /** One shot, as the host resolved it. */
51
+ export interface Shot {
52
+ /** The shooter. */
53
+ id: number;
54
+ /** Where the shot stopped: a wall, a floor, a ceiling, a target, or RANGE away. */
55
+ x: number;
56
+ y: number;
57
+ z: number;
58
+ /** Id of the player hit, -1 for the level or nothing. */
59
+ hit: number;
38
60
  }
39
61
  /** Procedural textures, generated by the client. A sector names one per surface. */
40
62
  export declare const TEX: {
@@ -97,6 +119,7 @@ export interface SnapPlayer {
97
119
  yaw: number;
98
120
  pitch: number;
99
121
  ground: boolean;
122
+ cool: number;
100
123
  /** Seq of the last input of this player the host applied: the client replays the ones after. */
101
124
  ack: number;
102
125
  }
@@ -104,6 +127,8 @@ export interface Snapshot {
104
127
  /** Host tick, u16, wraps. */
105
128
  tick: number;
106
129
  players: SnapPlayer[];
130
+ /** Shots fired since the previous snapshot, in order. */
131
+ shots: Shot[];
107
132
  }
108
133
  /** Room rules, set by the host. Defaults in constants.ts (RULES). */
109
134
  export interface Rules {
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@korial29/tiny-fps",
3
3
  "description": "Tiny multiplayer first-person shooter for the browser: raw WebGL, zero runtime dependencies, WebSocket or same-browser multi-tab play.",
4
4
  "license": "Apache-2.0",
5
- "version": "0.1.0",
5
+ "version": "0.2.0",
6
6
  "type": "module",
7
7
  "files": [
8
8
  "dist"