← Zombie FPS results

Zombie FPS

GPT 6 Luna · full

Initial view of Zombie FPS

Reviews & generation details

Reviews

No human review yet. Unreviewed does not mean unsuccessful.

Generation context

Imported historical artifact. Its exact original instructions and lineage are not known.

Generation prompt · exact text unavailable

See the current prompt for reference. It may differ from the instructions used to generate this artifact.

Inspect original source 37,895 bytes · SHA-256 b6ff0e1a4352
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no">
<title>Last Call at the Starlite</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=Barlow+Condensed:wght@400;600;700;900&family=Special+Elite&display=swap');
:root{--amber:#ffbd62;--cream:#f1e7d2;--red:#e35343;--cyan:#9bd5d3}*{box-sizing:border-box}html,body{margin:0;width:100%;height:100%;overflow:hidden;background:#05080c;color:var(--cream);font-family:'Barlow Condensed',sans-serif}canvas{display:block;width:100%;height:100%;cursor:none}#hud{position:absolute;inset:0;pointer-events:none;text-shadow:0 2px 5px #000;user-select:none}.topbar{position:absolute;top:22px;left:28px;right:28px;display:flex;justify-content:space-between;align-items:flex-start}.brand{font-size:14px;letter-spacing:4px;color:#d0b894;opacity:.85}.wavebox{text-align:center;background:linear-gradient(90deg,transparent,#080b0dcc,transparent);padding:0 36px 8px}.wave{font-size:30px;font-weight:900;letter-spacing:3px;color:#f6dfb8}.remaining{font-size:13px;letter-spacing:2px;color:#d0c7b7}.clock{font-family:'Special Elite',serif;color:#dfbb84;font-size:16px;letter-spacing:1px}.bottom{position:absolute;bottom:24px;left:28px;right:28px;display:flex;align-items:flex-end;justify-content:space-between}.healthbox{width:225px}.label{font-size:12px;letter-spacing:3px;color:#c6b79f}.healthline{height:8px;background:#161716;border:1px solid #776958;margin:6px 0 5px;transform:skewX(-12deg)}#healthbar{height:100%;background:linear-gradient(90deg,#9d2823,#f06a46);width:100%;transition:width .2s}.healthnum{font-size:19px;font-weight:700}.ammo{text-align:right;min-width:160px}.ammo-main{font-size:38px;font-weight:900;line-height:1;color:#fff0d6}.ammo-main small{font-size:18px;color:#b6a58f}.weaponname{font-size:12px;letter-spacing:3px;color:#e0b46f;margin-top:5px}.crosshair{position:absolute;left:50%;top:50%;width:22px;height:22px;transform:translate(-50%,-50%);opacity:.92}.crosshair:before,.crosshair:after{content:'';position:absolute;background:#f5e9d0;box-shadow:0 0 4px #000}.crosshair:before{width:2px;height:16px;left:10px;top:3px}.crosshair:after{height:2px;width:16px;top:10px;left:3px}.crossdot{position:absolute;top:9px;left:9px;width:4px;height:4px;border-radius:50%;background:#f4a663}.killcount{position:absolute;right:30px;top:80px;text-align:right;font-size:14px;letter-spacing:2px;color:#ddd0b9}.message{position:absolute;top:27%;left:50%;transform:translateX(-50%);font-size:42px;font-weight:900;letter-spacing:7px;color:#f4d7a3;text-align:center;text-shadow:0 0 22px #e88743;opacity:0;transition:opacity .35s;white-space:nowrap}.message small{display:block;font-size:15px;letter-spacing:4px;color:#d7c6a7;margin-top:6px}.breaktime{position:absolute;top:38%;left:50%;transform:translateX(-50%);font-size:14px;letter-spacing:4px;color:#e6c890;opacity:0}.vignette{position:absolute;inset:0;box-shadow:inset 0 0 140px 24px #000a;opacity:.85;transition:box-shadow .15s}.hurt{position:absolute;inset:0;background:radial-gradient(ellipse,transparent 35%,#a40000 100%);opacity:0;transition:opacity .15s}.viewgun{position:absolute;right:9%;bottom:-25px;width:225px;height:205px;filter:drop-shadow(0 10px 8px #000);transform-origin:75% 100%;transition:transform .08s}.gun-grip{position:absolute;bottom:0;right:34px;width:68px;height:115px;background:linear-gradient(90deg,#221c1b,#55483b 40%,#151414);clip-path:polygon(12% 0,100% 0,90% 100%,25% 100%,0 18%);border:2px solid #827363}.gun-body{position:absolute;bottom:87px;right:12px;width:175px;height:59px;background:linear-gradient(#6f716e,#252a2a 55%,#101313);clip-path:polygon(0 12%,100% 12%,100% 72%,78% 72%,78% 100%,18% 100%,12% 78%,0 78%)}.gun-barrel{position:absolute;right:-31px;bottom:119px;width:82px;height:19px;background:linear-gradient(#555b59,#111);border-radius:2px}.gun-sight{position:absolute;right:76px;bottom:145px;width:15px;height:10px;background:#222;border-top:3px solid #87847a}.gunlabel{display:none}.flash{position:absolute;left:50%;top:50%;width:100px;height:100px;transform:translate(-50%,-50%);background:radial-gradient(circle,#fff9bf 0,#ffae46aa 24%,transparent 72%);opacity:0;pointer-events:none}.hint{position:absolute;bottom:94px;left:50%;transform:translateX(-50%);color:#e3d2ad;font-size:13px;letter-spacing:2px;text-align:center;opacity:.85}.overlay{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;background:radial-gradient(ellipse at 50% 45%,#111a20d9,#030609f5 75%);z-index:5}.panel{text-align:center;width:min(650px,92vw);padding:38px 45px 35px;border:1px solid #9d805544;background:linear-gradient(145deg,#151714ee,#090b0bee);box-shadow:0 20px 80px #000}.eyebrow{font-size:13px;letter-spacing:7px;color:#e0a65d}.title{font-size:clamp(44px,8vw,78px);line-height:.9;font-weight:900;letter-spacing:1px;color:#f1dfc2;margin:14px 0 13px;text-shadow:0 4px 0 #60402d,0 0 30px #ed9e4233}.title span{color:#edaf61}.subtitle{font-family:'Special Elite',serif;font-size:15px;color:#c5bba9;letter-spacing:1px}.story{max-width:475px;margin:23px auto 22px;color:#a9a69e;line-height:1.5;font-size:15px}.controls{font-size:12px;line-height:2.1;letter-spacing:1px;color:#c6b99e;border-top:1px solid #59493277;border-bottom:1px solid #59493277;padding:10px}.controls b{color:#f4ce8d}.start{margin-top:25px;color:#ffcf83;font-weight:700;font-size:15px;letter-spacing:5px;animation:pulse 1.6s infinite}.tiny{font-family:'Special Elite',serif;color:#746b5c;font-size:11px;margin-top:14px}@keyframes pulse{50%{opacity:.4}}.hidden{display:none!important}
</style></head>
<body><canvas id="game"></canvas><div id="hud" class="hidden"><div class="vignette"></div><div class="hurt" id="hurt"></div><div class="topbar"><div><div class="brand">THE STARLITE MOTOR COURT</div><div class="clock">3:10 A.M. &nbsp;·&nbsp; ROUTE 9</div></div><div class="wavebox"><div class="wave" id="waveLabel">WAVE 1 / 10</div><div class="remaining" id="remaining">THREATS REMAINING: 0</div></div><div class="brand">NIGHT SHIFT &nbsp; / &nbsp; <span id="score">0</span> KILLS</div></div><div class="killcount" id="statusLine">FOG: HEAVY<br>DAWN: 00%</div><div class="crosshair"><span class="crossdot"></span></div><div class="message" id="message">WAVE 1<small>THEY'RE COMING THROUGH THE TREES</small></div><div class="breaktime" id="breaktime"></div><div class="bottom"><div class="healthbox"><div class="label">VITALS</div><div class="healthline"><div id="healthbar"></div></div><div class="healthnum"><span id="healthnum">100</span> <small style="font-size:11px;color:#ab9e8c;letter-spacing:2px">HEALTH</small></div></div><div class="ammo"><div class="ammo-main"><span id="mag">12</span><small> / <span id="reserve">72</span></small></div><div class="weaponname" id="weaponname">9MM SERVICE PISTOL</div></div></div><div class="hint" id="hint"></div><div class="viewgun" id="viewgun"><div class="gun-grip"></div><div class="gun-body"></div><div class="gun-barrel"></div><div class="gun-sight"></div></div><div class="flash" id="flash"></div></div>
<div class="overlay" id="overlay"><div class="panel"><div class="eyebrow">MILE 89 &nbsp; / &nbsp; 3:10 A.M.</div><div class="title">LAST CALL<br>AT THE <span>STARLITE</span></div><div class="subtitle">A MOTOR COURT IN THE MIDDLE OF NOWHERE</div><div class="story">The phones went dead at 2:47. The people didn't leave. Hold the lot until morning. Ten waves. One long night. Don't let them get close.</div><div class="controls"><b>WASD</b> MOVE &nbsp; <b>MOUSE</b> AIM &nbsp; <b>CLICK</b> FIRE &nbsp; <b>SHIFT</b> SPRINT<br><b>R</b> RELOAD &nbsp; <b>1 / 2 / 3</b> WEAPONS &nbsp; <b>WHEEL</b> SWITCH &nbsp; <b>ESC</b> PAUSE</div><div class="start" id="startText">CLICK TO BEGIN</div><div class="tiny">BEST PLAYED WITH SOUND · HEADSHOTS COUNT</div></div></div>
<script>
(()=>{'use strict';
const canvas=document.getElementById('game'),gl=canvas.getContext('webgl',{antialias:true});if(!gl){document.body.innerHTML='WebGL required';return}const hud=document.getElementById('hud');
const vs=`attribute vec3 p;attribute vec3 n;uniform mat4 mvp;uniform mat4 model;varying vec3 vN;varying vec3 vP;void main(){vec4 w=model*vec4(p,1.);vP=w.xyz;vN=mat3(model)*n;gl_Position=mvp*vec4(p,1.);}`;
const fs=`precision mediump float;uniform vec3 color;uniform vec3 eye;uniform float emissive;uniform float dawn;varying vec3 vN;varying vec3 vP;void main(){float d=length(vP-eye);float fog=smoothstep(19.,48.,d);vec3 N=normalize(vN);float light=.38+max(dot(N,normalize(vec3(-.4,.85,.25))),0.)*.42;vec3 c=color*(light+emissive);vec3 fogc=mix(vec3(.018,.025,.038),vec3(.23,.32,.39),dawn);gl_FragColor=vec4(mix(c,fogc,fog),1.);}`;
function shader(type,src){let s=gl.createShader(type);gl.shaderSource(s,src);gl.compileShader(s);if(!gl.getShaderParameter(s,gl.COMPILE_STATUS))console.error(gl.getShaderInfoLog(s));return s}const prog=gl.createProgram();gl.attachShader(prog,shader(gl.VERTEX_SHADER,vs));gl.attachShader(prog,shader(gl.FRAGMENT_SHADER,fs));gl.linkProgram(prog);gl.useProgram(prog);
const loc={p:gl.getAttribLocation(prog,'p'),n:gl.getAttribLocation(prog,'n'),mvp:gl.getUniformLocation(prog,'mvp'),model:gl.getUniformLocation(prog,'model'),color:gl.getUniformLocation(prog,'color'),eye:gl.getUniformLocation(prog,'eye'),em:gl.getUniformLocation(prog,'emissive'),dawn:gl.getUniformLocation(prog,'dawn')};
function meshData(type){let v=[],n=[],idx=[];function face(a,b,c,d,norm){let k=v.length/3;v.push(...a,...b,...c,...d);for(let i=0;i<4;i++)n.push(...norm);idx.push(k,k+1,k+2,k,k+2,k+3)}if(type==='cube'){face([-0.5,-.5,.5],[.5,-.5,.5],[.5,.5,.5],[-.5,.5,.5],[0,0,1]);face([.5,-.5,-.5],[-.5,-.5,-.5],[-.5,.5,-.5],[.5,.5,-.5],[0,0,-1]);face([-.5,-.5,-.5],[-.5,-.5,.5],[-.5,.5,.5],[-.5,.5,-.5],[-1,0,0]);face([.5,-.5,.5],[.5,-.5,-.5],[.5,.5,-.5],[.5,.5,.5],[1,0,0]);face([-.5,.5,.5],[.5,.5,.5],[.5,.5,-.5],[-.5,.5,-.5],[0,1,0]);face([-.5,-.5,-.5],[.5,-.5,-.5],[.5,-.5,.5],[-.5,-.5,.5],[0,-1,0])}else if(type==='cyl'){let seg=12;for(let i=0;i<seg;i++){let a=i*2*Math.PI/seg,b=(i+1)*2*Math.PI/seg,x=Math.cos(a)*.5,z=Math.sin(a)*.5,X=Math.cos(b)*.5,Z=Math.sin(b)*.5;face([x,-.5,z],[X,-.5,Z],[X,.5,Z],[x,.5,z],[Math.cos((a+b)/2),0,Math.sin((a+b)/2)]);face([0,.5,0],[x,.5,z],[X,.5,Z],[0,.5,0],[0,1,0])}}else{let rings=8,segs=12;for(let j=0;j<rings;j++){let lat1=-Math.PI/2+j*Math.PI/rings,lat2=-Math.PI/2+(j+1)*Math.PI/rings;for(let i=0;i<segs;i++){let a=i*2*Math.PI/segs,b=(i+1)*2*Math.PI/segs;let pt=(lat,lon)=>[Math.cos(lat)*Math.cos(lon)*.5,Math.sin(lat)*.5,Math.cos(lat)*Math.sin(lon)*.5];let A=pt(lat1,a),B=pt(lat1,b),C=pt(lat2,b),D=pt(lat2,a);face(A,B,C,D,[0,1,0])}}}return {v:new Float32Array(v),n:new Float32Array(n),idx:new Uint16Array(idx)}}
const meshes={};for(const t of ['cube','cyl','sphere']){let d=meshData(t),m={};m.v=gl.createBuffer();gl.bindBuffer(gl.ARRAY_BUFFER,m.v);gl.bufferData(gl.ARRAY_BUFFER,d.v,gl.STATIC_DRAW);m.n=gl.createBuffer();gl.bindBuffer(gl.ARRAY_BUFFER,m.n);gl.bufferData(gl.ARRAY_BUFFER,d.n,gl.STATIC_DRAW);m.i=gl.createBuffer();gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER,m.i);gl.bufferData(gl.ELEMENT_ARRAY_BUFFER,d.idx,gl.STATIC_DRAW);m.count=d.idx.length;meshes[t]=m}
function perspective(fov,asp,near,far){let f=1/Math.tan(fov/2),nf=1/(near-far);return new Float32Array([f/asp,0,0,0,0,f,0,0,0,0,(far+near)*nf,-1,0,0,2*far*near*nf,0])}function lookAt(e,c,u){let z=norm(sub(e,c)),x=norm(cross(u,z)),y=cross(z,x);return new Float32Array([x[0],y[0],z[0],0,x[1],y[1],z[1],0,x[2],y[2],z[2],0,-dot(x,e),-dot(y,e),-dot(z,e),1])}function mul(a,b){let o=new Float32Array(16);for(let c=0;c<4;c++)for(let r=0;r<4;r++)o[c*4+r]=a[r]*b[c*4]+a[4+r]*b[c*4+1]+a[8+r]*b[c*4+2]+a[12+r]*b[c*4+3];return o}function modelMat(pos,sc,ry=0,rx=0){let cy=Math.cos(ry),sy=Math.sin(ry),cx=Math.cos(rx),sx=Math.sin(rx);return new Float32Array([cy*sc[0],sx*sy*sc[0],-cx*sy*sc[0],0,0,cx*sc[1],sx*sc[1],0,sy*sc[2],-sx*cy*sc[2],cx*cy*sc[2],0,pos[0],pos[1],pos[2],1])}function norm(a){let l=Math.hypot(...a)||1;return a.map(x=>x/l)}function sub(a,b){return a.map((x,i)=>x-b[i])}function cross(a,b){return[a[1]*b[2]-a[2]*b[1],a[2]*b[0]-a[0]*b[2],a[0]*b[1]-a[1]*b[0]]}function dot(a,b){return a.reduce((s,x,i)=>s+x*b[i],0)}
let W=innerWidth,H=innerHeight;function resize(){W=canvas.width=Math.floor(innerWidth*devicePixelRatio);H=canvas.height=Math.floor(innerHeight*devicePixelRatio);gl.viewport(0,0,W,H)}window.addEventListener('resize',resize);resize();gl.enable(gl.DEPTH_TEST);gl.enable(gl.CULL_FACE);gl.clearColor(.012,.018,.028,1);
let objects=[];function add(type,pos,scale,color,em=0,ry=0){objects.push({type,pos,scale,color,em,ry})}const C={asphalt:[.12,.13,.13],concrete:[.3,.29,.25],wall:[.38,.31,.24],trim:[.17,.16,.14],door:[.31,.16,.11],red:[.45,.08,.055],blue:[.12,.29,.34],yellow:[.73,.48,.18],glass:[.14,.34,.38],dark:[.025,.04,.05],leaf:[.35,.27,.13],green:[.12,.25,.16],white:[.73,.68,.55],neon:[.9,.12,.3],orange:[.95,.45,.13]};
// lot and highway
add('cube',[0,-.28,0],[48,.5,48],C.asphalt);add('cube',[0,-.05,22],[48,.12,8],[.2,.2,.19]);add('cube',[0,.015,22],[.22,.02,8],[.76,.67,.42],.1);for(let x=-22;x<=22;x+=3){add('cube',[x,-.015,4],[1.8,.025,.12],[.65,.53,.31],.05);}
// motel body, walkway
add('cube',[0,2,-13],[27,4,7],C.wall);add('cube',[0,4.05,-13],[28,.35,7.5],[.11,.12,.12]);add('cube',[0,.22,-9.32],[27,.3,1.1],[.38,.34,.28]);add('cube',[0,3.86,-9.15],[28,.18,1.2],[.21,.2,.17]);
for(let i=0;i<8;i++){let x=-11.2+i*3.2;add('cube',[x,1.2,-9.43],[1.45,2.35,.16],i===3?C.red:C.door);add('cube',[x,2.83,-9.4],[1.72,.14,.16],[.15,.14,.12]);add('cube',[x,3.37,-9.4],[1.7,.72,.13],[.13,.27,.29],.12);add('cube',[x,3.38,-9.31],[1.48,.52,.035],[.09,.19,.22],.13);add('cube',[x+.54,1.35,-9.31],[.08,.12,.04],C.yellow,.2);add('cube',[x,2.1,-9.31],[.45,.24,.035],[.72,.63,.46],.05);add('cube',[x,2.1,-9.285],[.36,.18,.02],[.1,.09,.08],.05)}
// open room and interior peek
add('cube',[-1.6,1.1,-9.4],[1.65,2.2,.08],[.04,.04,.035]);add('cube',[-1.6,1,-11.1],[4,2,.08],[.2,.16,.13]);add('cube',[-1.8,.25,-11],[2,.18,1],[.4,.24,.12]);add('cube',[-1.4,.42,-11],[1.7,.16,.65],[.58,.47,.36]);add('cube',[-2.6,2.2,-10.6],[.55,.5,.12],[.6,.34,.18],.1);
// motel room number / canopy pillars
for(let i=0;i<8;i++){let x=-11.2+i*3.2;add('cube',[x,2.43,-9.28],[.45,.17,.025],[.84,.76,.56],.08)}for(let x=-13;x<=13;x+=6.4)add('cube',[x,1.8,-8.85],[.18,3.5,.22],[.2,.19,.16]);
// office building left wing
add('cube',[-17,2,-12],[6.5,4,9],C.wall);add('cube',[-17,3.95,-7.35],[6.8,.22,1.3],[.18,.17,.15]);add('cube',[-17,2.55,-7.34],[3.6,1.25,.12],[.07,.22,.23],.34);add('cube',[-17,2.55,-7.24],[3.25,1.03,.04],[.04,.15,.2],.32);add('cube',[-17,1,-7.3],[1.4,2.1,.18],[.21,.12,.08]);
// office desk and key board TV
add('cube',[-17,1,-10],[4.4,1.1,1.1],[.3,.18,.09]);add('cube',[-17,1.62,-10],[4.5,.15,1.15],[.45,.3,.13]);add('cube',[-18.7,2.6,-15.8],[3.2,1.3,.12],[.26,.17,.1]);for(let i=0;i<8;i++)add('cube',[-19.8+(i%4)*.55,2.48-Math.floor(i/4)*.4,-15.68],[.07,.16,.045],[.84,.62,.24],.08);add('cube',[-15.9,1.8,-12.7],[1.2,.85,.18],[.03,.04,.045]);add('cube',[-15.8,1.8,-12.59],[.9,.62,.03],[.42,.48,.51],.25);add('cube',[-15.6,1.8,-12.55],[.4,.25,.02],[.7,.7,.72],.5);
// diner wing right and visible interior
add('cube',[17,2,-12],[7,4,10],C.wall);add('cube',[17,3.95,-7],[7.2,.22,1.4],[.16,.17,.15]);add('cube',[17,2.5,-7],[4.7,1.5,.12],[.07,.2,.22],.3);add('cube',[17,2.5,-6.9],[4.4,1.25,.04],[.1,.24,.26],.22);add('cube',[17,1,-7],[1.35,2,.18],C.door);
// counter, coffee and pie case visible through glass
add('cube',[17,1,-10],[5.6,1.1,.9],[.34,.17,.08]);add('cube',[17,1.62,-10],[5.8,.14,1],[.58,.4,.17]);add('cube',[16,2.08,-10],[1.2,.75,.6],[.15,.21,.2],.15);add('cube',[16,2.18,-10],[.9,.04,.42],[.78,.52,.24],.24);add('cyl',[18.2,1.95,-10.15],[.38,.32,.38],[.12,.09,.06],.1);add('cyl',[18.2,2.12,-10.15],[.3,.1,.3],[.4,.19,.07],.1);add('cube',[14,1.1,-12],[2,.2,1],[.23,.17,.12]);for(let x=13.4;x<15.2;x+=.8){add('cyl',[x,.65,-11.8],[.28,.75,.28],[.28,.17,.09]);add('cyl',[x,.3,-11.8],[.5,.08,.5],[.18,.12,.08])}add('cube',[19,1,-12.5],[2,.18,1.1],[.24,.15,.1]);add('cube',[19,.45,-12.5],[1.3,.72,.1],[.43,.3,.15]);
// pool: rim, deep basin, tiles and debris
add('cube',[8,.02,4],[10,.5,7],[.48,.43,.34]);add('cube',[8,-.12,4],[8.9,.3,5.9],[.08,.15,.17]);add('cube',[8,-.58,4],[8.1,.45,5.1],[.2,.23,.21]);add('cube',[8,-.34,1.05],[8.2,.1,.15],[.54,.52,.42]);add('cube',[8,-.34,6.95],[8.2,.1,.15],[.54,.52,.42]);add('cube',[3.95,-.34,4],[.15,.1,5.8],[.54,.52,.42]);add('cube',[12.05,-.34,4],[.15,.1,5.8],[.54,.52,.42]);for(let i=0;i<10;i++){let x=4.4+(i*1.61)%7.2,z=2+(i*1.37)%4;add('cube',[x,-.31,z],[.3+Math.random()*.4,.035,.16+Math.random()*.25],i%2?C.leaf:[.26,.24,.2],0,Math.random())}add('cube',[9,-.22,4],[1.5,.22,.65],[.42,.31,.19],0,.6);add('cube',[9.35,-.55,4],[.12,.65,.12],[.37,.31,.2],0,.6);add('cube',[9,-.55,4.4],[.12,.65,.12],[.37,.31,.2],0,.6);for(let j=0;j<5;j++)add('cube',[12.45,-.05+j*.2,5],[.22,.12,.16],[.63,.58,.44]);
// car station wagon body, wheels, windows, lights, open driver's door
add('cube',[-6,.78,5],[5.6,1.15,2.3],[.31,.25,.18],0,.18);add('cube',[-6,1.63,5.05],[3.3,.92,1.9],[.18,.23,.24],0,.18);add('cube',[-6,1.72,4.05],[1.25,.72,.06],[.13,.24,.28],.2,.18);add('cube',[-6,1.72,6.02],[1.2,.72,.06],[.12,.22,.26],.15,.18);for(let x of [-7.9,-4.2]){add('cyl',[x,.42,3.95],[.82,.45,.82],[.025,.03,.03],0,0);add('cyl',[x,.42,6.02],[.82,.45,.82],[.025,.03,.03])}add('cube',[-8.76,.92,5.1],[.12,.35,.55],[.95,.78,.44],.7);add('cube',[-8.8,.98,4.8],[.08,.25,.25],[.9,.9,.75],1);add('cube',[-8.8,.98,5.4],[.08,.25,.25],[.9,.9,.75],1);add('cube',[-6.7,1.2,6.35],[2.15,1.5,.12],[.2,.18,.13],0,.48);add('cube',[-6.7,1.9,6.37],[1.45,.65,.05],[.08,.12,.13],0,.48);
// vending and ice machine under walkway
add('cube',[4,1.2,-8.35],[1.2,2.5,.75],[.28,.29,.27]);add('cube',[4,1.26,-7.94],[.84,1.65,.06],[.1,.45,.57],.55);for(let y=.7;y<1.9;y+=.28)add('cube',[3.92,y,-7.89],[.6,.12,.025],[.2,.55,.64],.5);add('cube',[6,1.05,-8.32],[1.25,2.1,.8],[.48,.46,.39]);add('cube',[6,1.15,-7.89],[.75,1.1,.04],[.27,.3,.29]);add('cube',[6,1.85,-7.89],[.8,.3,.04],[.7,.72,.67],.1);
// payphone outside office
add('cube',[-13.5,1.15,-7.8],[.15,2.3,.18],[.1,.16,.15]);add('cube',[-13.5,2.35,-7.8],[.65,.32,.35],[.11,.22,.19]);add('cube',[-13.5,2.5,-7.59],[.32,.17,.03],[.5,.56,.47],.1);add('cyl',[-13.5,2.25,-7.56],[.11,.4,.1],[.07,.07,.06]);
// neon sign and NO vacancy
add('cube',[0,7,-17],[12,.35,.3],[.08,.12,.13]);for(let i=0;i<6;i++)add('cube',[-4.4+i*1.75,7.03,-16.78],[1.45,.16,.12],i===3?[.03,.02,.02]:[.88,.18,.39],i===3?0:.75);add('cube',[-17,5.5,-16.7],[3,.8,.2],[.06,.12,.12]);add('cube',[-17,5.7,-16.56],[1.15,.24,.04],[.05,.04,.03]);add('cube',[-16.15,5.7,-16.55],[1.3,.23,.04],[.9,.13,.08],.55);
// fence perimeter and tree silhouettes
for(let x=-23;x<=23;x+=2){add('cube',[x,1.5,18],[.08,3,.08],[.19,.22,.2]);add('cube',[x,1.5,-21],[.08,3,.08],[.19,.22,.2])}for(let z=-20;z<=18;z+=2){add('cube',[-23,1.5,z],[.08,3,.08],[.19,.22,.2]);add('cube',[23,1.5,z],[.08,3,.08],[.19,.22,.2])}for(let y=.5;y<=2.5;y+=.5){add('cube',[0,y,18],[46,.025,.025],[.25,.26,.24]);add('cube',[0,y,-21],[46,.025,.025],[.25,.26,.24]);add('cube',[-23,y,0],[.025,.025,39],[.25,.26,.24]);add('cube',[23,y,0],[.025,.025,39],[.25,.26,.24])}for(let i=0;i<38;i++){let x=(Math.random()-.5)*58,z=(Math.random()<.5?-1:1)*(21+Math.random()*8);add('cyl',[x,2+Math.random()*2,z],[.6+Math.random(),5+Math.random()*4,.6+Math.random()],[.035,.065,.055]);add('sphere',[x,5+Math.random()*2,z],[3+Math.random()*3,5+Math.random()*4,3+Math.random()*3],[.025,.06,.045])}for(let i=0;i<16;i++){let z=(Math.random()-.5)*42,x=(Math.random()<.5?-1:1)*(22+Math.random()*3);add('cyl',[x,2,z],[.7,5,.7],[.04,.07,.06]);add('sphere',[x,5,z],[4,6,4],[.025,.05,.038])}
// lamps, clutter: laundry, bike-ish silhouette, suitcase, cones, trash
for(let x of [-13,0,13]){add('cyl',[x,4.8,-8.5],[.12,4.7,.12],[.17,.16,.12]);add('cube',[x,7.1,-8.5],[.65,.18,.32],[.95,.58,.28],.8)}add('cube',[2,.52,-7.8],[1.25,.9,.75],[.47,.36,.21]);add('cube',[2,.15,-7.8],[1.4,.15,.85],[.2,.18,.14]);add('cyl',[10,.28,-7.5],[.7,.55,.7],[.16,.19,.17]);add('cube',[-10,.25,1],[.8,.35,.4],[.34,.26,.13],0,.4);add('cube',[1,.03,12],[.3,.02,.7],[.55,.49,.35],0,.7);for(let i=0;i<4;i++)add('cube',[14+i*.5,.04,8],[.45,.08,.35],[.2,.17,.12],0,i);
// interact pickups
const pickupBases=[{x:-5,z:7,type:'shotgun'},{x:-14,z:-5.8,type:'rifle'},{x:3,z:2,type:'ammo'},{x:-11,z:11,type:'med'},{x:14,z:-2,type:'ammo'}];let pickups=[];function makePickups(){pickups=pickupBases.map((p,i)=>({...p,active:true,baseY:.25}));}makePickups();
let player={x:0,y:1.67,z:11,yaw:0,pitch:0,health:100};let keys={},locked=false,started=false,paused=false,gameOver=false,won=false,shooting=false,lastShot=0,lastTime=performance.now(),bob=0,recoil=0,shake=0;let wave=0,phase='title',breakEnd=0,waveZombies=[],kills=0,score=0,day=0,damageFlash=0,weapon=0,unlocked=[true,false,false],mag=[12,6,24],reserve=[72,24,100],reloadUntil=0;const weapons=[{name:'9MM SERVICE PISTOL',rate:260,dmg:42,spread:.018,range:34,mag:12},{name:'12-GAUGE PUMP',rate:650,dmg:82,spread:.085,range:20,mag:6},{name:'HUNTING RIFLE',rate:430,dmg:105,spread:.009,range:44,mag:8}];mag[2]=8;reserve[2]=48;
const overlay=document.getElementById('overlay'),startText=document.getElementById('startText'),msg=document.getElementById('message'),breakEl=document.getElementById('breaktime');
// procedural audio
let audio=null,master=null;function initAudio(){if(audio)return;audio=new(window.AudioContext||window.webkitAudioContext)();master=audio.createGain();master.gain.value=.32;master.connect(audio.destination);ambient();}function tone(freq,dur,wave='sine',vol=.2,slide=0){if(!audio)return;let o=audio.createOscillator(),g=audio.createGain();o.type=wave;o.frequency.setValueAtTime(freq,audio.currentTime);if(slide)o.frequency.exponentialRampToValueAtTime(Math.max(25,freq+slide),audio.currentTime+dur);g.gain.setValueAtTime(vol,audio.currentTime);g.gain.exponentialRampToValueAtTime(.001,audio.currentTime+dur);o.connect(g);g.connect(master);o.start();o.stop(audio.currentTime+dur)}function noise(dur,vol=.12,low=300){if(!audio)return;let b=audio.createBuffer(1,audio.sampleRate*dur,audio.sampleRate),a=b.getChannelData(0);for(let i=0;i<a.length;i++)a[i]=Math.random()*2-1;let s=audio.createBufferSource();s.buffer=b;let f=audio.createBiquadFilter();f.type='lowpass';f.frequency.value=low;let g=audio.createGain();g.gain.setValueAtTime(vol,audio.currentTime);g.gain.exponentialRampToValueAtTime(.001,audio.currentTime+dur);s.connect(f);f.connect(g);g.connect(master);s.start()}function ambient(){if(!audio)return;let o=audio.createOscillator(),g=audio.createGain();o.type='sawtooth';o.frequency.value=52;g.gain.value=.025;o.connect(g);g.connect(master);o.start();let w=audio.createOscillator(),wg=audio.createGain();w.type='triangle';w.frequency.value=.22;wg.gain.value=.025;w.connect(wg);wg.connect(master);w.start();setInterval(()=>{if(started&&!gameOver){if(Math.random()<.33)tone(790+Math.random()*90,.12,'sine',.018);if(phase==='break'&&Math.random()<.65){tone(780,.3,'sine',.06);setTimeout(()=>tone(620,.35,'sine',.05),260)}let near=waveZombies.filter(z=>!z.dead).sort((a,b)=>Math.hypot(a.x-player.x,a.z-player.z)-Math.hypot(b.x-player.x,b.z-player.z))[0];if(near&&Math.hypot(near.x-player.x,near.z-player.z)<23&&Math.random()<.65){let d=Math.hypot(near.x-player.x,near.z-player.z);tone(near.type==='runner'?190:near.type==='brute'?62:105,.38,near.type==='runner'?'sawtooth':'triangle',.045*(1-d/28),near.type==='runner'?-100:-35);if(near.type==='brute')tone(48,.22,'sine',.08,-18)}}},1700)}function gunSound(){if(weapon===0){noise(.13,.28,1100);tone(105,.12,'triangle',.23,-68)}else if(weapon===1){noise(.27,.42,680);tone(68,.22,'sawtooth',.3,-40)}else{noise(.18,.35,1500);tone(145,.16,'triangle',.25,-110)}}function sting(){tone(110,.65,'sawtooth',.14,-45);setTimeout(()=>tone(165,.7,'triangle',.12,-55),170);noise(.5,.12,600)}function hurtSound(){tone(95,.3,'sawtooth',.24,-60);noise(.18,.12,500)}function hitSound(head){tone(head?125:82,.13,'triangle',head?.22:.12,head?-72:-30);noise(.1,head?.22:.1,head?500:800)}
function begin(){initAudio();if(audio.state==='suspended')audio.resume();started=true;paused=false;overlay.classList.add('hidden');hud.classList.remove('hidden');canvas.requestPointerLock?.();if(phase==='title')startWave()}overlay.addEventListener('click',()=>{if(gameOver||won){location.reload();return}if(!started){begin();return}if(paused){paused=false;overlay.classList.add('hidden');canvas.requestPointerLock?.()}});document.addEventListener('pointerlockchange',()=>{locked=document.pointerLockElement===canvas;if(started&&!gameOver&&!won&&!locked){paused=true;overlay.classList.remove('hidden');startText.textContent='CLICK TO RESUME'}});document.addEventListener('mousemove',e=>{if(locked&&!paused){player.yaw-=e.movementX*.0022;player.pitch=clamp(player.pitch-e.movementY*.0022,-1.15,1.15)}});document.addEventListener('keydown',e=>{keys[e.code]=true;if(e.code==='Escape'&&started&&!gameOver&&!won){paused=true;document.exitPointerLock?.();overlay.classList.remove('hidden');startText.textContent='CLICK TO RESUME'}if(!started)return;if(e.code==='KeyR')reload();if(e.code==='Digit1')switchWeapon(0);if(e.code==='Digit2')switchWeapon(1);if(e.code==='Digit3')switchWeapon(2)});document.addEventListener('keyup',e=>keys[e.code]=false);canvas.addEventListener('mousedown',e=>{if(e.button===0){if(!started){begin();return}if(paused)return;if(!locked){canvas.requestPointerLock?.();return}shooting=true;fire()}});document.addEventListener('mouseup',()=>shooting=false);canvas.addEventListener('wheel',e=>{if(!started)return;let n=weapon+(e.deltaY>0?1:-1);for(let k=0;k<3;k++){n=(n+3)%3;if(unlocked[n]){switchWeapon(n);break}}});
function clamp(x,a,b){return Math.max(a,Math.min(b,x))}function switchWeapon(i){if(unlocked[i]){weapon=i;document.getElementById('weaponname').textContent=weapons[i].name;updateHud()}}function reload(){let w=weapons[weapon];if(reloadUntil>performance.now()||mag[weapon]>=w.mag||reserve[weapon]<=0)return;reloadUntil=performance.now()+(weapon===1?1550:weapon===2?1750:1250);tone(240,.07,'square',.08);setTimeout(()=>tone(330,.08,'square',.07),400);setTimeout(()=>tone(430,.1,'square',.08),800)}function fire(){let now=performance.now();if(now-lastShot<weapons[weapon].rate||reloadUntil>now||phase==='title'||gameOver)return;lastShot=now;if(mag[weapon]<=0){tone(500,.04,'square',.1,-100);if(reserve[weapon]>0)reload();return}mag[weapon]--;gunSound();recoil=Math.min(.24,recoil+.17);document.getElementById('flash').style.opacity='1';setTimeout(()=>document.getElementById('flash').style.opacity='0',65);document.getElementById('viewgun').style.transform='translateY(12px) rotate(-3deg)';setTimeout(()=>document.getElementById('viewgun').style.transform='',85);let dir=aimDir(),best=null,bestT=weapons[weapon].range;for(let z of waveZombies){if(z.dead)continue;let to=[z.x-player.x,z.y+.65-(player.y),z.z-player.z],dist=Math.hypot(...to),nd=norm(to);let aimdot=dot(dir,nd);let spread=weapons[weapon].spread*(weapon===1?1.2:1)+(Math.random()-.5)*weapons[weapon].spread;if(aimdot>1-(spread*spread*.5)&&dist<bestT){best=z;bestT=dist}}if(best){let head=Math.abs((player.pitch)-Math.atan2(best.y+1.55-player.y,Math.hypot(best.x-player.x,best.z-player.z)))<.2;let dmg=weapons[weapon].dmg*(head?2.5:1);if(weapon===1)dmg*=Math.max(.35,1-bestT/27);best.hp-=dmg;best.hit=.2;let away=norm([best.x-player.x,0,best.z-player.z]);best.x+=away[0]*(head?.7:.25);best.z+=away[2]*(head?.7:.25);hitSound(head);if(head){document.getElementById('hint').textContent='HEADSHOT';setTimeout(()=>document.getElementById('hint').textContent='',650)}if(best.hp<=0){best.dead=true;best.death=0;kills++;score+=head?150:100}}updateHud();}
function aimDir(){return [Math.sin(player.yaw)*Math.cos(player.pitch),Math.sin(player.pitch),-Math.cos(player.yaw)*Math.cos(player.pitch)]}function updateHud(){document.getElementById('mag').textContent=mag[weapon];document.getElementById('reserve').textContent=reserve[weapon]===999?'∞':reserve[weapon];document.getElementById('healthnum').textContent=Math.ceil(player.health);document.getElementById('healthbar').style.width=player.health+'%';document.getElementById('score').textContent=kills;document.getElementById('waveLabel').textContent=`WAVE ${Math.max(wave,1)} / 10`;document.getElementById('remaining').textContent=`THREATS REMAINING: ${waveZombies.filter(z=>!z.dead).length}`}
function startBreak(secs=17){phase='break';breakEnd=performance.now()+secs*1000;waveZombies=[];makePickups();document.getElementById('hint').textContent='PAYPHONE RINGING · SCAVENGE THE LOT';setTimeout(()=>{if(phase==='break')document.getElementById('hint').textContent=''},4500);}function startWave(){wave++;if(wave>10){win();return}phase='wave';document.getElementById('hint').textContent='';msg.innerHTML=`WAVE ${wave}<small>${wave===1?'THEY’RE COMING THROUGH THE TREES':'NO PLACE LEFT TO RUN'}</small>`;msg.style.opacity=1;setTimeout(()=>msg.style.opacity=0,2600);sting();let count=5+wave*2+(wave>5?wave:0);for(let i=0;i<count;i++){let type='shambler';if(wave>=3&&Math.random()<Math.min(.45,.1+wave*.035))type='runner';if(wave>=6&&Math.random()<Math.min(.24,(wave-4)*.035))type='brute';let side=Math.floor(Math.random()*4),x,z;if(side===0){x=-19+Math.random()*38;z=-18.4}if(side===1){x=-19+Math.random()*38;z=15.7}if(side===2){x=-19.4;z=-17+Math.random()*32}if(side===3){x=19.4;z=-17+Math.random()*32}waveZombies.push({x,y:0,z,type,hp:type==='brute'?360:type==='runner'?75:135,max:type==='brute'?360:type==='runner'?75:135,speed:type==='brute'?.72:type==='runner'?2.05:1.02,hit:0,attack:Math.random()*1.2,dead:false,death:0,phase:Math.random()*7})}updateHud()}
function win(){won=true;phase='won';player.health=Math.max(player.health,1);overlay.classList.remove('hidden');overlay.querySelector('.eyebrow').textContent='6:02 A.M. / THE SUN IS UP';overlay.querySelector('.title').innerHTML='YOU MADE IT<br><span>TO DAWN</span>';overlay.querySelector('.subtitle').textContent='THE STARLITE IS QUIET AGAIN';overlay.querySelector('.story').textContent=`Ten waves passed. ${kills} of them won't see another sunrise. The coffee's gone cold, but the horizon is turning grey.`;overlay.querySelector('.controls').textContent='FINAL SCORE  '+score+'  ·  KILLS  '+kills;startText.textContent='CLICK TO PLAY AGAIN';document.exitPointerLock?.()}
function die(){gameOver=true;phase='dead';overlay.classList.remove('hidden');overlay.querySelector('.eyebrow').textContent='3:?? A.M. / NIGHT SHIFT ENDED';overlay.querySelector('.title').innerHTML='THE NIGHT<br><span>TAKES YOU</span>';overlay.querySelector('.subtitle').textContent='THE STARLITE MOTOR COURT';overlay.querySelector('.story').textContent=`You reached wave ${wave}. You put down ${kills} of them. The lot is still full.`;overlay.querySelector('.controls').textContent=`WAVE ${wave}  ·  ${kills} KILLS  ·  SCORE ${score}`;startText.textContent='CLICK TO TRY AGAIN';document.exitPointerLock?.()}
function collect(){for(let p of pickups){if(!p.active)continue;if(Math.hypot(player.x-p.x,player.z-p.z)<1.5){p.active=false;if(p.type==='shotgun'){unlocked[1]=true;weapon=1;mag[1]=6;reserve[1]+=12;document.getElementById('hint').textContent='PUMP SHOTGUN · FOUND BY THE WAGON'}else if(p.type==='rifle'){unlocked[2]=true;weapon=2;document.getElementById('hint').textContent='HUNTING RIFLE · OFFICE COUNTER'}else if(p.type==='med'){player.health=Math.min(100,player.health+38);document.getElementById('hint').textContent='FIRST AID · +38 VITALS'}else{reserve[weapon]+=weapon===1?6:weapon===2?16:24;document.getElementById('hint').textContent='AMMO SCOOPED'}tone(620,.1,'sine',.12);setTimeout(()=>document.getElementById('hint').textContent='',1900);updateHud()}}}
function hurt(amount){player.health-=amount;damageFlash=.8;shake=.22;hurtSound();if(player.health<=0)die();updateHud()}
function update(dt,now){if(!started||paused||gameOver||won)return;day=clamp((wave-1)/9,0,1);if(phase==='break'){let left=Math.max(0,Math.ceil((breakEnd-now)/1000));breakEl.textContent=`NEXT WAVE IN  00:${String(left).padStart(2,'0')}`;breakEl.style.opacity=1;if(left<=0)startWave()}else{breakEl.style.opacity=0}
let mx=(keys.KeyD?1:0)-(keys.KeyA?1:0),mz=(keys.KeyW?1:0)-(keys.KeyS?1:0),speed=(keys.ShiftLeft||keys.ShiftRight)?7:4.6;let l=Math.hypot(mx,mz)||1;mx/=l;mz/=l;let dx=(Math.cos(player.yaw)*mx+Math.sin(player.yaw)*-mz)*speed*dt,dz=(-Math.sin(player.yaw)*mx+Math.cos(player.yaw)*mz)*speed*dt;let nx=clamp(player.x+dx,-21.2,21.2),nz=clamp(player.z+dz,-18.2,16.4);if(!blocked(nx,nz)){player.x=nx;player.z=nz}else if(!blocked(nx,player.z)){player.x=nx}else if(!blocked(player.x,nz)){player.z=nz}if(mx||mz)bob+=dt*(keys.ShiftLeft?12:8);player.y=(player.x>4&&player.x<12&&player.z>1.4&&player.z<6.6)?.82:1.67;recoil=Math.max(0,recoil-dt*1.4);damageFlash=Math.max(0,damageFlash-dt*1.8);shake=Math.max(0,shake-dt*1.2);
if(shooting&&locked)fire();if(reloadUntil&&now>=reloadUntil){let take=Math.min(weapons[weapon].mag-mag[weapon],reserve[weapon]);mag[weapon]+=take;reserve[weapon]-=take;reloadUntil=0;tone(390,.08,'square',.08);updateHud()}collect();
for(let z of waveZombies){if(z.dead){z.death+=dt;continue}let ddx=player.x-z.x,ddz=player.z-z.z,d=Math.hypot(ddx,ddz)||1;let vx=ddx/d,vz=ddz/d;let steer=obstacleSteer(z);vx+=steer[0]*1.8;vz+=steer[1]*1.8;let vl=Math.hypot(vx,vz)||1;vx/=vl;vz/=vl;let sp=z.speed*(1+wave*.035);if(z.hit>0){z.hit-=dt;sp*=.24}let tx=z.x+vx*sp*dt,tz=z.z+vz*sp*dt;if(!blocked(tx,tz)){z.x=tx;z.z=tz}else{z.x+=-vz*sp*dt;z.z+=vx*sp*dt}z.phase+=dt*5;z.attack-=dt;if(d<1.25&&z.attack<=0){hurt(z.type==='brute'?17:z.type==='runner'?11:8);z.attack=z.type==='runner'?.8:1.15}}if(phase==='wave'&&waveZombies.every(z=>z.dead)){if(wave>=10){win()}else startBreak(17)}updateHud();document.getElementById('statusLine').innerHTML=`FOG: ${day>.7?'THINNING':'HEAVY'}<br>DAWN: ${Math.floor(day*100)}%`}
function blocked(x,z){if(x>3.6&&x<12.5&&z>1.3&&z<6.7&&!(x>11.1&&z>3.05&&z<4.95))return true;if(x<-20||x>20||z<-19||z>16.8)return true;if(Math.abs(x)<13.8&&z<-9.1&&z>-17)return true;if(x<-13.7&&z<-7.3&&z>-16.7)return true;if(x>13.2&&z<-7.1&&z>-17)return true;if(x>-9&&x<-3&&z>3.7&&z<6.5)return true;return false}function obstacleSteer(z){let sx=0,sz=0;for(let o of [[8,4,6],[-6,5,2.2],[0,-13,13.8],[-17,-12,3.5],[17,-12,3.5]]){let dx=z.x-o[0],dz=z.z-o[1],d=Math.hypot(dx,dz);if(d<o[2]+1.3){let f=(o[2]+1.3-d)/(o[2]+1.3);sx+=dx/(d||1)*f*2;sz+=dz/(d||1)*f*2}}return[sx,sz]}
function drawObj(o,viewProj){let m=modelMat(o.pos,o.scale,o.ry||0),mvp=mul(viewProj,m),me=meshes[o.type];gl.bindBuffer(gl.ARRAY_BUFFER,me.v);gl.enableVertexAttribArray(loc.p);gl.vertexAttribPointer(loc.p,3,gl.FLOAT,false,0,0);gl.bindBuffer(gl.ARRAY_BUFFER,me.n);gl.enableVertexAttribArray(loc.n);gl.vertexAttribPointer(loc.n,3,gl.FLOAT,false,0,0);gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER,me.i);gl.uniformMatrix4fv(loc.model,false,m);gl.uniformMatrix4fv(loc.mvp,false,mvp);gl.uniform3fv(loc.color,o.color);gl.uniform1f(loc.em,o.em||0);gl.drawElements(gl.TRIANGLES,me.count,gl.UNSIGNED_SHORT,0)}
function zombieDraw(z,vp){let p=z.death?Math.min(1,z.death*2):0,y=z.y+(p?-.75*p:0);let sway=Math.sin(z.phase)*.07,skin=z.type==='brute'?[.29,.32,.28]:z.type==='runner'?[.36,.42,.34]:[.38,.36,.29],body=z.type==='brute'?[.84,1.2,.52]:[.52,.83,.34],rot=p?Math.PI/2*p:sway;let ox=z.hit>0?.1:0;drawObj({type:'cube',pos:[z.x,y+body[1]*.58,z],scale:body,color:skin,ry:rot},vp);drawObj({type:'sphere',pos:[z.x+ox,y+body[1]+.22,z],scale:z.type==='brute'?[.48,.48,.43]:[.35,.38,.34],color:[.43,.39,.31],ry:rot},vp);drawObj({type:'cube',pos:[z.x-.12,y+body[1]+.25,z+.28],scale:[.075,.07,.04],color:[.92,.13,.08],em:.28},vp);drawObj({type:'cube',pos:[z.x+.12,y+body[1]+.25,z+.28],scale:[.075,.07,.04],color:[.92,.13,.08],em:.28},vp);for(let s of [-1,1]){let legX=z.x+s*(z.type==='brute'?.24:.15),legY=y+.35+(p?0:Math.sin(z.phase*1.3+(s===1?Math.PI:0))*.16);drawObj({type:'cube',pos:[legX,legY,z.z],scale:[z.type==='brute'?.28:.2,.7,.22],color:[.19,.18,.15],ry:rot},vp);drawObj({type:'cube',pos:[z.x+s*(body[0]*.55),y+body[1]*.65,z.z],scale:[.18,.7,.2],color:skin,ry:rot+s*.15},vp)}if(z.hit>0){drawObj({type:'sphere',pos:[z.x,y+1.7,z],scale:[.42,.48,.2],color:[.8,.08,.04],em:.3},vp)}}
function render(){let aspect=W/H;gl.clear(gl.COLOR_BUFFER_BIT|gl.DEPTH_BUFFER_BIT);let dir=aimDir();let eye=[player.x,player.y+(Math.sin(bob)*.035),player.z];if(shake){eye[0]+=(Math.random()-.5)*shake;eye[1]+=(Math.random()-.5)*shake}let center=[eye[0]+dir[0],eye[1]+dir[1],eye[2]+dir[2]],vp=mul(perspective(1.18,aspect,.1,100),lookAt(eye,center,[0,1,0]));gl.uniform3fv(loc.eye,eye);gl.uniform1f(loc.dawn,day);for(let o of objects)drawObj(o,vp);for(let z of waveZombies)zombieDraw(z,vp);for(let p of pickups)if(p.active){let col=p.type==='shotgun'?[.73,.51,.19]:p.type==='rifle'?[.3,.5,.53]:p.type==='med'?[.7,.14,.12]:[.24,.65,.48];drawObj({type:'cube',pos:[p.x,.28+Math.sin(performance.now()*.003+p.x)*.07,p.z],scale:[.48,.35,.4],color:col,em:.25},vp);drawObj({type:'cube',pos:[p.x,.49,p.z],scale:[.14,.06,.16],color:[.9,.79,.55],em:.25},vp)}document.querySelector('.hurt').style.opacity=damageFlash*.5;document.querySelector('.vignette').style.boxShadow=`inset 0 0 ${140+Math.sin(performance.now()*.001)*10}px 24px rgba(0,0,0,${.7-day*.32})`;let g=document.getElementById('viewgun');g.style.bottom=(-25+Math.sin(bob)*4+recoil*12)+'px';gl.clear(gl.DEPTH_BUFFER_BIT)}
function loop(now){let dt=Math.min(.04,(now-lastTime)/1000);lastTime=now;update(dt,now);render();requestAnimationFrame(loop)}requestAnimationFrame(loop);
})();
</script></body></html>
<!-- agent-meta {"model":"openai/gpt-6-luna","provider":"openrouter","persona":"full","sessionId":"6542eef0-1eb4-49e1-9d8b-0080b5686b25","tokensIn":1003032,"tokensOut":32374,"tokensTotal":1035406,"cost":0.031020315,"turns":35,"toolCalls":34,"failedToolCalls":3,"timestamp":"2026-09-23T02:27:27.753Z"} -->