← Worms results

Worms

GPT 6 Luna · full

Initial view of Worms

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 41,148 bytes · SHA-256 fb7d23eefa7f
<!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>Worms — Pocket Planet</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=DM+Mono:wght@400;500&family=Nunito:wght@600;700;800;900;1000&display=swap');
:root{--ink:#19334b;--cream:#fff7dc;--coral:#ff705d;--gold:#ffd166;--mint:#a6e8c6;--blue:#79d5ef}*{box-sizing:border-box}html,body{margin:0;width:100%;height:100%;overflow:hidden;background:#84d8ee;color:var(--ink);font-family:Nunito,system-ui,sans-serif}canvas{display:block;width:100vw;height:100vh;cursor:crosshair}button,select{font:inherit}#hud{position:fixed;inset:0;pointer-events:none;display:none} .panel{background:rgba(255,249,226,.94);border:2px solid rgba(25,51,75,.13);box-shadow:0 5px 0 rgba(25,51,75,.11),0 12px 32px #18456122;border-radius:17px;backdrop-filter:blur(8px)}
#top{position:absolute;left:22px;right:22px;top:18px;height:62px;display:flex;align-items:center;justify-content:space-between;padding:8px 15px;gap:12px}.brand{font-size:16px;font-weight:1000;letter-spacing:.04em;display:flex;align-items:center;gap:9px}.brand .logo{width:34px;height:34px;border-radius:12px;background:#ff735d;display:grid;place-items:center;color:white;font-size:19px;box-shadow:inset 0 -3px #d74e47}.round{font-family:'DM Mono',monospace;font-size:11px;color:#688096;letter-spacing:.14em;text-transform:uppercase}.wind{font-size:13px;font-weight:900;background:#e4f7f5;border-radius:10px;padding:8px 12px;color:#358497}.turnLabel{font-weight:1000;font-size:15px}.turnLabel small{display:block;font:10px 'DM Mono',monospace;color:#8293a1;letter-spacing:.13em;margin-bottom:2px}.timerWrap{display:flex;align-items:center;gap:10px}.timer{font:1000 25px 'DM Mono',monospace;color:#23516c;min-width:46px;text-align:center}.timer.urgent{color:#ef594e;transform:scale(1.13)}.timerbar{height:8px;width:100px;border-radius:8px;background:#e7dfc7;overflow:hidden}.timerbar i{display:block;height:100%;width:100%;background:#72c9a0;border-radius:inherit;transition:width .2s,background .2s}.teams{display:flex;align-items:center;gap:7px}.teamPip{width:10px;height:10px;border-radius:50%;display:inline-block;margin-right:4px}.teamScore{font-size:12px;font-weight:900;color:#547087;padding:6px 9px;border-radius:9px;background:#f5efd9}.teamScore.active{outline:2px solid #ffb34d;color:#213d51;background:#fff4cf}
#leftInfo{position:absolute;top:96px;left:22px;padding:11px 14px;width:176px}.activeName{font-size:15px;font-weight:1000}.activeMeta{font:10px 'DM Mono',monospace;color:#8293a1;margin-top:3px;letter-spacing:.08em}.healthbar{height:9px;border-radius:8px;background:#e8dfcb;margin-top:9px;overflow:hidden}.healthbar i{height:100%;display:block;background:#72cb91;border-radius:8px;transition:width .3s}.unitCount{font:10px 'DM Mono',monospace;color:#788b9a;margin-top:6px}#help{position:absolute;right:22px;top:96px;padding:10px 13px;font-size:10px;color:#738698;line-height:1.7;text-align:right}#help b{color:#34546b}
#bottom{position:absolute;left:50%;transform:translateX(-50%);bottom:16px;display:flex;gap:7px;padding:9px;border-radius:17px;pointer-events:auto;max-width:94vw;overflow-x:auto} .weapon{width:89px;min-width:75px;border:0;background:#fff9e8;border-radius:12px;padding:7px 5px 6px;cursor:pointer;color:#35536b;transition:.14s;box-shadow:0 3px 0 #e8ddc3;position:relative}.weapon:hover{transform:translateY(-4px);background:white}.weapon.selected{background:#ffdf91;box-shadow:0 3px 0 #e0ab4e;transform:translateY(-4px)}.weapon .icon{font-size:22px;line-height:25px}.weapon .wname{font-weight:1000;font-size:10px;white-space:nowrap}.weapon .wcat{font:8px 'DM Mono',monospace;color:#91a0a5;text-transform:uppercase;letter-spacing:.05em;margin-top:2px}.weapon .key{position:absolute;top:4px;right:6px;font:9px 'DM Mono',monospace;color:#a4a79c}.weapon.disabled{opacity:.4;filter:grayscale(1)}#toast{position:absolute;left:50%;top:115px;transform:translateX(-50%);padding:9px 16px;background:#183950;color:#fff7dc;border-radius:20px;font-size:12px;font-weight:900;opacity:0;transition:opacity .2s;white-space:nowrap}#toast.show{opacity:1}#turnBanner{position:absolute;left:50%;top:34%;transform:translate(-50%,-50%) scale(.8);opacity:0;font-size:40px;font-weight:1000;color:white;text-shadow:0 4px 0 #32617a,0 7px 22px #22485b;transition:.3s;pointer-events:none;white-space:nowrap}#turnBanner.show{opacity:1;transform:translate(-50%,-50%) scale(1)}#mute{position:absolute;right:23px;bottom:111px;pointer-events:auto;border:0;background:#fff8df;color:#49647a;border-radius:11px;padding:8px 11px;font-weight:900;cursor:pointer;box-shadow:0 3px 0 #d8ceba}#pauseBtn{position:absolute;left:23px;bottom:111px;pointer-events:auto;border:0;background:#fff8df;color:#49647a;border-radius:11px;padding:8px 11px;font-weight:900;cursor:pointer;box-shadow:0 3px 0 #d8ceba}
#screen{position:fixed;inset:0;display:flex;align-items:center;justify-content:center;background:linear-gradient(180deg,#95e5ef 0%,#d0f1df 100%);z-index:4}.menuCard{width:min(920px,94vw);max-height:95vh;overflow:auto;border-radius:30px;background:#fff8e2;box-shadow:0 12px 0 #dfd2ae,0 30px 90px #24526a40;padding:30px 36px;display:grid;grid-template-columns:1fr 1fr;gap:24px;position:relative;border:3px solid #fffdf0}.menuCard:before{content:'✦';position:absolute;right:35px;top:25px;font-size:30px;color:#f6b94c;transform:rotate(12deg)}.eyebrow{font:11px 'DM Mono',monospace;color:#72939b;letter-spacing:.19em;text-transform:uppercase}.title{font-size:clamp(35px,6vw,62px);line-height:.92;font-weight:1000;letter-spacing:-.055em;margin:12px 0 12px;color:#20445a}.title span{color:#ff715e}.subtitle{font-size:14px;color:#738894;line-height:1.55;max-width:380px}.wormArt{font-size:78px;filter:drop-shadow(0 8px 0 #e6c98d);display:block;margin:10px 0 3px;animation:bob 2.3s ease-in-out infinite}@keyframes bob{50%{transform:translateY(-9px) rotate(3deg)}}.menuRight{background:#fffdf3;border-radius:20px;padding:19px 21px;border:1px solid #f0e7ca}.menuRight h3{margin:0 0 13px;font-size:16px}.field{display:flex;align-items:center;justify-content:space-between;margin:10px 0;font-size:12px;font-weight:900;color:#526d7d}.field select{border:1px solid #ded6bd;background:#fff9e9;padding:8px 10px;border-radius:9px;color:#34546a;font-weight:800;max-width:165px}.startBtn{width:100%;border:0;border-radius:13px;background:#ff715e;color:white;font-weight:1000;font-size:16px;padding:14px;cursor:pointer;box-shadow:0 5px 0 #d94e48;margin-top:13px;transition:.15s}.startBtn:hover{transform:translateY(-2px);filter:brightness(1.04)}.featureRow{display:flex;gap:7px;flex-wrap:wrap;margin-top:15px}.tag{font-size:10px;font-weight:900;background:#e8f4e5;color:#5d947b;border-radius:9px;padding:7px 9px}.footnote{font:10px 'DM Mono',monospace;color:#99a5a1;margin-top:14px}.endCard{text-align:center;width:min(480px,92vw);padding:35px;background:#fff8e2;border-radius:28px;box-shadow:0 12px 0 #dfd2ae}.endCard h2{font-size:38px;margin:9px;color:#20445a}.endCard p{color:#718895}.mobileHint{display:none}
@media(max-width:700px){#top{left:8px;right:8px;top:8px;height:52px;padding:6px 8px}.brand{font-size:12px}.brand .logo{width:28px;height:28px}.round{display:none}.teamScore{font-size:10px;padding:5px}.timerbar{display:none}#leftInfo{left:8px;top:70px;width:145px;padding:8px 10px}.activeName{font-size:13px}#help{right:8px;top:70px;padding:7px 9px;font-size:9px}#bottom{bottom:9px;gap:4px;padding:6px;max-width:98vw}.weapon{min-width:60px;width:64px;padding:5px 2px}.weapon .icon{font-size:18px}.weapon .wname{font-size:9px}.weapon .wcat{font-size:7px}#mute{right:9px;bottom:95px}#pauseBtn{left:9px;bottom:95px}.menuCard{grid-template-columns:1fr;padding:23px;gap:13px}.menuLeft{text-align:center}.subtitle{margin:auto}.wormArt{font-size:58px}.menuRight{padding:14px}.mobileHint{display:block;font-size:10px;color:#8b9a9b;margin-top:8px}}
</style></head><body>
<canvas id="game"></canvas>
<div id="hud"><div id="top" class="panel"><div class="brand"><span class="logo">〰</span> POCKET PLANET <span class="round" id="roundLabel">ROUND 01</span></div><div class="teams" id="teamScores"></div><div class="wind" id="windLabel">↗ WIND 0</div><div class="timerWrap"><div class="timerbar"><i id="timerBar"></i></div><div class="timer" id="timer">30</div></div></div>
<div id="leftInfo" class="panel"><div class="activeName" id="activeName">Mochi</div><div class="activeMeta" id="activeMeta">TEAM MINT · UNIT 1 / 4</div><div class="healthbar"><i id="healthFill"></i></div><div class="unitCount" id="healthLabel">100 / 100 ♥</div></div><div id="help" class="panel"><b>A / D</b> move &nbsp; <b>W / SPACE</b> jump<br><b>MOUSE</b> aim &nbsp; <b>CLICK</b> fire<br><b>1–8</b> weapon &nbsp; <b>Q</b> utility</div>
<div id="bottom" class="panel"></div><button id="pauseBtn">Ⅱ &nbsp; Pause</button><button id="mute">♫ &nbsp; Sound on</button><div id="toast"></div><div id="turnBanner"></div></div>
<div id="screen"><div class="menuCard" id="menuCard"><div class="menuLeft"><div class="eyebrow">A tiny turn-based artillery adventure</div><div class="title">POCKET<br><span>PLANET</span></div><div class="subtitle">Four brave little worms. One extremely breakable world. Take your shot, make a mess, and try not to fall in the sea.</div><span class="wormArt">🐛</span><div class="featureRow"><span class="tag">✦ DESTRUCTIBLE WORLDS</span><span class="tag">↗ WIND & WATER</span><span class="tag">☄ HOT-SEAT / CPU</span></div></div><div class="menuRight"><h3>Set up your skirmish</h3><label class="field">Teams <select id="teamSelect"><option value="2">2 teams · classic duel</option><option value="3">3 teams · free-for-all</option><option value="4">4 teams · total chaos</option></select></label><label class="field">Opponent <select id="cpuSelect"><option value="cpu">You vs CPU</option><option value="hotseat">Hot-seat friends</option><option value="2cpu">You + CPU teams</option></select></label><label class="field">Landscape <select id="mapSelect"><option value="random">Surprise me!</option><option value="cave">Cave of Wonders</option><option value="island">Lonely Island</option><option value="twins">Twin Isles</option></select></label><label class="field">Match pace <select id="paceSelect"><option value="30">30 sec · lively</option><option value="45">45 sec · thoughtful</option><option value="20">20 sec · frantic</option></select></label><button class="startBtn" id="startBtn">LET'S GET WORMY &nbsp; →</button><div class="footnote">WASD to scoot · mouse to aim · 1–8 choose chaos</div></div></div></div>
<script>
(()=>{'use strict';
const canvas=document.getElementById('game'),ctx=canvas.getContext('2d');const W=1280,H=720;let dpr=1;function resize(){dpr=Math.min(devicePixelRatio||1,2);canvas.width=innerWidth*dpr;canvas.height=innerHeight*dpr}addEventListener('resize',resize);resize();
const $=s=>document.querySelector(s);const COLORS=['#71cfa5','#ff7864','#74b9ef','#f3bd51'];const TEAMNAMES=['MINT','CORAL','SKY','HONEY'];const NAMES=[['Mochi','Pickle','Noodle','Bean'],['Peaches','Biscuit','Gumdrop','Pip'],['Bluey','Comet','Miso','Sprout'],['Crumpet','Jelly','Waffles','Dot']];
const weapons=[{name:'Bazooka',icon:'🚀',cat:'indirect',key:'1',power:48,rad:68,speed:420,gravity:360,ammo:99,desc:'Classic boom'},{name:'Cluster',icon:'🎆',cat:'indirect',key:'2',power:27,rad:44,speed:390,gravity:340,ammo:5,cluster:5,desc:'Five little surprises'},{name:'Bouncy',icon:'🏐',cat:'indirect',key:'3',power:42,rad:58,speed:390,gravity:420,ammo:6,bounce:2,desc:'Bounces, then bursts'},{name:'Dynamite',icon:'🧨',cat:'map-altering',key:'4',power:72,rad:105,speed:0,gravity:0,ammo:4,place:true,desc:'Plant. Run. Pray.'},{name:'Drill',icon:'⛏️',cat:'map-altering',key:'5',power:36,rad:53,speed:330,gravity:250,ammo:5,drill:true,desc:'Cuts through earth'},{name:'Laser',icon:'⚡',cat:'direct',key:'6',power:39,rad:37,speed:0,gravity:0,ammo:5,laser:true,desc:'Instant zap'},{name:'Airstrike',icon:'🛩️',cat:'map-altering',key:'7',power:31,rad:47,speed:0,gravity:0,ammo:3,strike:true,desc:'Rain from above'},{name:'Sheep',icon:'🐑',cat:'indirect',key:'8',power:55,rad:67,speed:290,gravity:330,ammo:3,sheep:true,desc:'BAAA-d news'}];
const utility=[{name:'Jetpack',icon:'🎒'},{name:'Teleport',icon:'🌀'},{name:'Shield',icon:'🫧'}];let state='menu',terrain=[],worldW=2400,sea=574,units=[],teams=2,turnTeam=0,activeIndex=0,round=1,timeMax=30,timeLeft=30,wind=0,weaponIndex=0,utilIndex=0,projectiles=[],particles=[],floaters=[],debris=[],effects=[],camera={x:0,y:0,z:1},shake=0,freeze=0,turnLock=0,mouse={x:640,y:360,down:false},keys={},lastTime=0,toastTimer=0,muted=false,audio=null,windPhase=0,aimPower=1,aiBusy=false,matchMode='cpu',paused=false,usedUtility={};
function sound(freq=240,dur=.08,type='sine',vol=.035,slide=0){if(muted)return;try{audio=audio||new(window.AudioContext||window.webkitAudioContext)();let o=audio.createOscillator(),g=audio.createGain();o.type=type;o.frequency.setValueAtTime(freq,audio.currentTime);if(slide)o.frequency.exponentialRampToValueAtTime(Math.max(30,freq+slide),audio.currentTime+dur);g.gain.setValueAtTime(vol,audio.currentTime);g.gain.exponentialRampToValueAtTime(.001,audio.currentTime+dur);o.connect(g);g.connect(audio.destination);o.start();o.stop(audio.currentTime+dur)}catch(e){}}
function toast(t){let el=$('#toast');el.textContent=t;el.classList.add('show');clearTimeout(toastTimer);toastTimer=setTimeout(()=>el.classList.remove('show'),1900)}
function rng(a,b){return a+Math.random()*(b-a)}function clamp(x,a,b){return Math.max(a,Math.min(b,x))}function dist(a,b){return Math.hypot(a.x-b.x,a.y-b.y)}
function makeTerrain(type){worldW=2400;terrain=new Array(worldW+1);let mode=type==='random'?['cave','island','twins'][Math.floor(Math.random()*3)]:type;sea=574;for(let x=0;x<=worldW;x++){let y=700;if(mode==='island'){let u=(x-worldW*.5)/(worldW*.48);if(Math.abs(u)<1){y=492+Math.pow(Math.abs(u),1.7)*158+Math.sin(x*.016)*12+Math.sin(x*.042)*5}}else if(mode==='twins'){for(let c of [.27,.73]){let u=(x-worldW*c)/(worldW*.235);if(Math.abs(u)<1)y=Math.min(y,485+Math.pow(Math.abs(u),1.6)*155+Math.sin(x*.017)*11+Math.sin(x*.045)*5)}}else{y=452+Math.sin(x*.003)*37+Math.sin(x*.012)*18+Math.sin(x*.039)*9+Math.sin(x*.0013)*20;let cave=Math.sin(x*.0021)*23;y+=cave; if(x<35||x>worldW-35)y=625+Math.sin(x*.04)*12;}terrain[x]=clamp(y,415,715)}
// gentle crags and one cave roof variation
for(let x=2;x<worldW;x++){let smooth=(terrain[x-1]+terrain[x]*2+terrain[x+1])/4;terrain[x]=smooth}return mode}
function groundAt(x){let ix=Math.floor(clamp(x,0,worldW-1));let f=clamp(x-ix,0,1);return terrain[ix]*(1-f)+terrain[ix+1]*f}
function newMatch(){teams=+$('#teamSelect').value;matchMode=$('#cpuSelect').value;timeMax=+$('#paceSelect').value;makeTerrain($('#mapSelect').value);units=[];for(let t=0;t<teams;t++){for(let i=0;i<4;i++){let pos;if(teams===2)pos=(t===0?330:worldW-330)+rng(-105,105);else pos=worldW*(t+.5)/teams+rng(-95,95);pos=clamp(pos,30,worldW-30);let y=groundAt(pos)-13;units.push({x:pos,y,vx:0,vy:0,r:13,team:t,slot:i,name:NAMES[t][i],hp:100,alive:true,ground:false,shield:0,jet:0,fall:0,aim:.48,face:t===0?1:-1,hat:['✿','★','◉','♦'][i]})}}projectiles=[];particles=[];floaters=[];debris=[];effects=[];turnTeam=0;activeIndex=0;round=1;wind=rng(-13,13);timeLeft=timeMax;weaponIndex=0;usedUtility={};camera.x=units[0].x;camera.y=0;camera.z=1;state='play';paused=false;$('#screen').style.display='none';$('#hud').style.display='block';buildWeapons();beginTurn(true);sound(480,.13,'triangle',.05,260)}
function buildWeapons(){let b=$('#bottom');b.innerHTML='';weapons.forEach((w,i)=>{let el=document.createElement('button');el.className='weapon'+(i===weaponIndex?' selected':'');el.innerHTML=`<span class="key">${w.key}</span><div class="icon">${w.icon}</div><div class="wname">${w.name}</div><div class="wcat">${w.cat}</div>`;el.onclick=()=>selectWeapon(i);b.appendChild(el)})}
function selectWeapon(i){weaponIndex=(i+weapons.length)%weapons.length;[...document.querySelectorAll('.weapon')].forEach((e,n)=>e.classList.toggle('selected',n===weaponIndex));sound(570,.045,'square',.018,100)}
function current(){return units[activeIndex]}function aliveTeam(t){return units.filter(u=>u.team===t&&u.alive)}function teamsAlive(){return Array.from({length:teams},(_,t)=>t).filter(t=>aliveTeam(t).length)}
function beginTurn(first=false){let guard=0;while(guard++<teams*5){if(!first){turnTeam=(turnTeam+1)%teams;if(turnTeam===0)round++}let candidates=units.map((u,i)=>({u,i})).filter(q=>q.u.team===turnTeam&&q.u.alive);if(candidates.length){let last=activeIndex;activeIndex=candidates[0].i; // rotate per team
let same=units.map((u,i)=>({u,i})).filter(q=>q.u.team===turnTeam&&q.u.alive);if(!first){let after=same.find(q=>q.i>last);activeIndex=after?after.i:same[0].i}break}}timeLeft=timeMax;turnLock=0;usedUtility={};let u=current();if(!u)return;u.jet=0;wind=rng(-16,16);updateHud();let banner=$('#turnBanner');banner.textContent=matchMode!=='hotseat'&&turnTeam>0?'CPU TURN':'TEAM '+TEAMNAMES[turnTeam]+' TURN';banner.classList.add('show');setTimeout(()=>banner.classList.remove('show'),1000);sound(360,.12,'triangle',.03,120);if(matchMode!=='hotseat'&&turnTeam>0){aiBusy=true;setTimeout(cpuTurn,850)}else aiBusy=false;}
function updateHud(){let u=current();if(!u)return;$('#activeName').textContent=u.name+' '+u.hat;$('#activeMeta').textContent='TEAM '+TEAMNAMES[u.team]+' · UNIT '+(u.slot+1)+' / 4';$('#healthFill').style.width=u.hp+'%';$('#healthLabel').textContent=`${Math.ceil(u.hp)} / 100 ♥${u.shield?'  · SHIELD':''}`;$('#healthFill').style.background=u.hp<30?'#f36b5b':'#72cb91';$('#roundLabel').textContent='ROUND '+String(round).padStart(2,'0');$('#windLabel').textContent=`${wind<0?'↙':'↗'} WIND ${Math.abs(Math.round(wind))}`;let s=$('#teamScores');s.innerHTML='';for(let t=0;t<teams;t++){let el=document.createElement('div');el.className='teamScore'+(t===turnTeam?' active':'');el.innerHTML=`<i class="teamPip" style="background:${COLORS[t]}"></i>${TEAMNAMES[t]} <span>${aliveTeam(t).length}</span>`;s.appendChild(el)}}
function screenToWorld(sx,sy){let sxn=sx/innerWidth*W,syn=sy/innerHeight*H;return{x:(sxn-W/2)/camera.z+camera.x,y:(syn-H/2)/camera.z+camera.y}}
function worldToScreen(x,y){return{x:(x-camera.x)*camera.z+W/2,y:(y-camera.y)*camera.z+H/2}}
function aimAt(u){let p=screenToWorld(mouse.x,mouse.y);let dx=p.x-u.x,dy=p.y-(u.y-10);let ang=Math.atan2(dy,dx);return{ang,dx,dy}}
function shoot(){if(state!=='play'||paused||aiBusy||turnLock>0)return;let u=current();if(!u||!u.alive)return;let w=weapons[weaponIndex];if(w.ammo===0){toast('All out! Try another toy.');sound(100,.1,'square');return}turnLock=1.1;timeLeft=Math.min(timeLeft,2.2);let {ang}=aimAt(u);u.face=Math.cos(ang)>=0?1:-1;sound(180,.18,'sawtooth',.04,500);if(w.laser){if(w.ammo<99)w.ammo--;let end=rayEnd(u.x,u.y-9,ang,worldW);effects.push({type:'laser',x:u.x,y:u.y-9,x2:end.x,y2:end.y,life:.24,max:.24,color:COLORS[u.team]});let hit=units.filter(v=>v.alive&&v.team!==u.team).map(v=>({v,d:lineDistance(v.x,v.y,u.x,u.y-9,end.x,end.y)})).filter(a=>a.d<22).sort((a,b)=>a.d-b.d)[0];if(hit)damage(hit.v,w.power,'ZAP!');explode(end.x,end.y,w.rad,w.power,u.team,false);finishShot();return}
if(w.strike){if(w.ammo<99)w.ammo--;let target=screenToWorld(mouse.x,mouse.y).x;for(let j=0;j<5;j++)projectiles.push({x:target+rng(-90,90),y:-80-rng(0,100),vx:rng(-8,8),vy:90+j*20,r:8,team:u.team,weapon:w,life:8,strike:true,damage:w.power});effects.push({type:'plane',x:target,y:105,life:1,max:1});finishShot();return}
if(w.place){let px=u.x+Math.cos(ang)*34,py=groundAt(px)-8;effects.push({type:'dynamite',x:px,y:py,life:2.4,max:2.4,team:u.team});finishShot();return}
if(w.ammo<99)w.ammo--;projectiles.push({x:u.x+Math.cos(ang)*20,y:u.y-10+Math.sin(ang)*14,vx:Math.cos(ang)*w.speed,vy:Math.sin(ang)*w.speed,r:w.sheep?14:7,team:u.team,weapon:w,life:7,bounces:w.bounce||0,damage:w.power,trail:[],drill:w.drill,sheep:w.sheep,cluster:w.cluster});finishShot()}
function lineDistance(px,py,x1,y1,x2,y2){let dx=x2-x1,dy=y2-y1,l=dx*dx+dy*dy,t=l?clamp(((px-x1)*dx+(py-y1)*dy)/l,0,1):0;return Math.hypot(px-(x1+t*dx),py-(y1+t*dy))}
function rayEnd(x,y,a,max){let dx=Math.cos(a),dy=Math.sin(a);for(let k=8;k<1200;k+=8){let xx=x+dx*k,yy=y+dy*k;if(xx<0||xx>worldW||yy>groundAt(xx)||yy>H+100)return{x:xx,y:yy}}return{x:x+dx*1200,y:y+dy*1200}}
function finishShot(){updateHud();setTimeout(()=>{if(state==='play')nextTurn()},850)}
function nextTurn(){turnLock=0;if(teamsAlive().length<=1){endGame();return}beginTurn()}
function damage(u,n,label='BOOM!'){if(!u.alive)return;let actual=n;if(u.shield){actual*=.42;u.shield=0;effects.push({type:'shield',x:u.x,y:u.y,life:.5,max:.5});sound(700,.13,'sine',.05,-350)}u.hp=Math.max(0,u.hp-actual);floaters.push({x:u.x,y:u.y-22,text:`-${Math.round(actual)} ${label}`,life:1.1,max:1.1,color:actual>35?'#ff6254':'#fff4bd'});u.vy-=Math.min(210,n*2.3);u.vx+=rng(-90,90);sound(250,.08,'triangle',.025,-100);if(u.hp<=0){u.alive=false;floaters.push({x:u.x,y:u.y-36,text:'SPLAT!',life:1.4,max:1.4,color:'#fff'});for(let i=0;i<22;i++)particles.push({x:u.x,y:u.y,vx:rng(-150,150),vy:rng(-230,20),life:rng(.4,1),max:1,r:rng(2,5),color:COLORS[u.team],kind:'goo'});sound(110,.35,'sawtooth',.06,-75);updateHud()}}
function explode(x,y,r,power,team,terrainHit=true){if(terrainHit)carve(x,y,r);let heavy=r>75;freeze=heavy?.045:0;shake=Math.max(shake,heavy?9:4);for(let i=0;i<Math.ceil(r*.7);i++){let a=rng(0,Math.PI*2),s=rng(25,300)*(1-Math.random()*.25);particles.push({x:x+rng(-8,8),y:y+rng(-8,8),vx:Math.cos(a)*s,vy:Math.sin(a)*s-rng(20,90),life:rng(.35,1.1),max:1.1,r:rng(2,7),color:['#ffda76','#ff795f','#fff2c3','#d6a878'][Math.floor(Math.random()*4)],kind:'spark'})}for(let i=0;i<Math.floor(r/9);i++){let xx=x+rng(-r*.8,r*.8);debris.push({x:xx,y:Math.min(y,groundAt(xx))-rng(1,12),vx:rng(-100,100),vy:rng(-180,-30),r:rng(3,8),life:5,color:['#8d704d','#af8c5c','#66896a'][Math.floor(Math.random()*3)]})}for(let u of units){if(!u.alive)continue;let d=Math.hypot(u.x-x,u.y-y);if(d<r+u.r){let amount=power*(1-d/(r+u.r));if(u.team===team)amount*=.8;if(amount>3)damage(u,amount,amount>35?'CRITICAL':'OUCH');let dx=u.x-x,dy=u.y-y,len=Math.max(1,d);u.vx+=dx/len*(1-d/(r+30))*300;u.vy+=dy/len*(1-d/(r+30))*240-60}}sound(heavy?92:135,heavy?.28:.16,'sawtooth',heavy?.07:.05,heavy?340:220);effects.push({type:'boom',x,y,r,life:.35,max:.35})}
function carve(x,y,r){let lo=Math.max(0,Math.floor(x-r)),hi=Math.min(worldW,Math.ceil(x+r));let removed=[];for(let xx=lo;xx<=hi;xx++){let dx=xx-x,dy=Math.sqrt(Math.max(0,r*r-dx*dx));let bottom=y+dy,top=y-dy;if(bottom>terrain[xx])continue;if(top<terrain[xx]){removed.push({x:xx,y:terrain[xx],depth:Math.min(40,bottom-terrain[xx])});terrain[xx]=Math.min(718,Math.max(terrain[xx],bottom))}}// physical falling chunks represented by clustered debris
for(let j=0;j<Math.min(14,Math.floor(removed.length/5));j++){let q=removed[Math.floor(Math.random()*removed.length)];if(q)debris.push({x:q.x,y:y+rng(-r*.45,r*.45),vx:rng(-85,85),vy:rng(-155,-40),r:rng(5,10),life:7,color:'#997d59'})}}
function update(dt){if(state!=='play'||paused)return;if(freeze>0){freeze-=dt;dt*=.22}if(shake>0)shake=Math.max(0,shake-dt*22);windPhase+=dt;
for(let u of units){if(!u.alive)continue;let oldY=u.y;u.vx+=wind*dt*.65;u.vx*=Math.pow(.91,dt*60);u.vy+=650*dt;u.x+=u.vx*dt;u.y+=u.vy*dt;u.x=clamp(u.x,8,worldW-8);let g=groundAt(u.x)-u.r;if(u.y>=g){if(u.vy>280){let fall=(u.vy-260)*.045;if(fall>4)damage(u,Math.min(45,fall),'FALL');for(let k=0;k<5;k++)particles.push({x:u.x+rng(-8,8),y:g,vx:rng(-60,60),vy:rng(-80,-15),life:.35,max:.35,r:rng(2,4),color:'#cbb98b',kind:'dust'})}u.y=g;u.vy=0;u.ground=true}else u.ground=false;if(u.y>sea+95){u.hp=0;u.alive=false;floaters.push({x:u.x,y:sea,text:'GLUB GLUB!',life:1.4,max:1.4,color:'#fff'});for(let k=0;k<12;k++)particles.push({x:u.x+rng(-8,8),y:sea,vx:rng(-30,30),vy:rng(-120,-30),life:1,max:1,r:rng(2,5),color:'#e8ffff',kind:'bubble'});sound(180,.2,'sine',.04,-100);updateHud()}
if(state==='play'&&u===current()&&!aiBusy&&!turnLock){let speed=keys.ArrowLeft||keys.KeyA?-175:keys.ArrowRight||keys.KeyD?175:0;if(speed){u.vx+=speed*dt*4;u.face=Math.sign(speed);if(Math.random()<dt*10&&u.ground)particles.push({x:u.x-u.face*8,y:u.y+10,vx:rng(-25,25),vy:rng(-40,-5),life:.35,max:.35,r:rng(2,4),color:'#d4c49b',kind:'dust'})}if((keys.Space||keys.ArrowUp||keys.KeyW)&&u.ground){u.vy=-285;u.ground=false;keys.Space=keys.ArrowUp=keys.KeyW=false;sound(350,.1,'triangle',.03,170)}}}
for(let p of projectiles){p.trail?.push({x:p.x,y:p.y});if(p.trail&&p.trail.length>8)p.trail.shift();if(p.strike){p.vy+=75*dt;p.y+=p.vy*dt;p.x+=p.vx*dt}else{p.vx+=wind*10*dt;p.vy+=(p.weapon.gravity||360)*dt;p.x+=p.vx*dt;p.y+=p.vy*dt}p.life-=dt;
if(p.drill&&p.x>=0&&p.x<worldW&&p.y>groundAt(p.x)-10){carve(p.x,p.y,19);if(Math.random()<.22)particles.push({x:p.x,y:p.y,vx:rng(-80,80),vy:rng(-100,20),life:.4,max:.4,r:3,color:'#b38a58',kind:'dust'});p.life=0;explode(p.x,p.y,53,p.damage,p.team,false);continue}
let hitGround=p.x<0||p.x>worldW||p.y>groundAt(clamp(p.x,0,worldW));let hitUnit=units.find(u=>u.alive&&Math.hypot(u.x-p.x,u.y-p.y)<u.r+p.r);if((hitGround||hitUnit||p.y>H+100||p.life<=0)&&!p.strike){if(p.bounces>0&&hitGround){p.bounces--;p.vy=-Math.abs(p.vy)*.67;p.vx*=.78;p.y=groundAt(clamp(p.x,0,worldW))-p.r; sound(280,.08,'triangle',.03,100)}else if(p.cluster){let n=p.weapon.cluster;for(let k=0;k<n;k++){let a=rng(-Math.PI*.92,-Math.PI*.08);projectiles.push({x:p.x,y:p.y,vx:Math.cos(a)*rng(140,260),vy:Math.sin(a)*rng(130,260),r:5,team:p.team,weapon:{gravity:400},life:2,damage:p.damage*.6,child:true})}explode(p.x,p.y,30,8,p.team,false);p.life=-1}else if(p.sheep){p.vy=-220;p.vx*=.8;p.sheep=false;p.life=Math.min(p.life,2)}else{explode(p.x,p.y,p.weapon.rad,p.damage,p.team,true);p.life=-1}}}
projectiles=projectiles.filter(p=>p.life>0);for(let e of effects){e.life-=dt;if(e.type==='dynamite'&&e.life<.04){explode(e.x,e.y,105,72,e.team,true);e.life=-1}}effects=effects.filter(e=>e.life>0);
for(let p of particles){p.vy+=380*dt;p.x+=p.vx*dt;p.y+=p.vy*dt;p.life-=dt;if(p.y>sea&&p.kind!=='bubble'&&p.kind!=='goo'){p.y=sea;p.vy*=-.25;p.vx*=.7}}particles=particles.filter(p=>p.life>0);
for(let d of debris){d.vy+=480*dt;d.x+=d.vx*dt;d.y+=d.vy*dt;d.vx*=Math.pow(.985,dt*60);d.life-=dt;if(d.x>=0&&d.x<worldW&&d.y>=groundAt(d.x)-d.r){d.y=groundAt(d.x)-d.r;d.vy*=-.28;d.vx*=.65;if(Math.abs(d.vy)<35)d.life=Math.min(d.life,1)}}debris=debris.filter(d=>d.life>0);
for(let f of floaters){f.y-=28*dt;f.life-=dt}floaters=floaters.filter(f=>f.life>0);
if(!aiBusy){timeLeft-=dt;if(timeLeft<=0&&turnLock<=0){toast('Time! Your worm gets a move on.');nextTurn()}if(turnLock>0)turnLock-=dt}
let u=current();if(u){let targetX=u.x,targetY=u.y-110;camera.x+=(targetX-camera.x)*Math.min(1,dt*2.5);camera.y+=(targetY-camera.y)*Math.min(1,dt*2);let targetZ=clamp(1.04-(Math.abs(u.vx)/900),.84,1.04);camera.z+=(targetZ-camera.z)*dt*.7}updateHud();if(teamsAlive().length<=1)endGame()}
function cpuTurn(){if(state!=='play'||turnTeam===0||paused){aiBusy=false;return}let u=current();if(!u){aiBusy=false;return}let foes=units.filter(v=>v.alive&&v.team!==u.team);if(!foes.length){aiBusy=false;return}let target=foes.map(v=>({v,score:(100-v.hp)*.8+Math.max(0,230-Math.abs(v.x-u.x))*.25+(v.x<120||v.x>worldW-120?38:0)+(v.y>sea-80?30:0)})).sort((a,b)=>b.score-a.score)[0].v;
// Risk assessment: favor safe firing ground; move away from edge/water if exposed
let edge=Math.min(u.x,worldW-u.x),danger=edge<180||u.y>sea-100; if(danger&&u.ground){u.vx+=(u.x<worldW/2?1:-1)*200;toast('CPU scrambles for safer ground');setTimeout(()=>{if(state==='play'){aiAimAndFire(u,target)}},700)}else setTimeout(()=>aiAimAndFire(u,target),550)}
function aiAimAndFire(u,target){if(state!=='play')return;let dx=target.x-u.x;let power=420,grav=360,distx=Math.abs(dx);let best={err:Infinity,ang:dx>0?.65:Math.PI-.65};for(let a=.22;a<1.35;a+=.035){let ang=dx>0?a:Math.PI-a;let vx=Math.cos(ang)*power,vy=Math.sin(ang)*power;for(let t=.12;t<2.4;t+=.06){let xx=u.x+vx*t+wind*5*t*t,yy=u.y-10+vy*t+grav*.5*t*t;let err=Math.hypot((xx-target.x)*.7,yy-(target.y-10));if(err<best.err)best={err,ang};if((xx>=0&&xx<=worldW&&yy>groundAt(clamp(xx,0,worldW)))||xx<0||xx>worldW)break}}let wIndex=best.err>190?2:0;if(Math.random()<.15)wIndex=5;weaponIndex=wIndex;selectWeapon(wIndex);let ang=best.ang;mouse.x=clamp((u.x+Math.cos(ang)*420-camera.x)*camera.z+W/2,10,W-10)/W*innerWidth;mouse.y=clamp((u.y-10+Math.sin(ang)*250-camera.y)*camera.z+H/2,10,H-10)/H*innerHeight;aiBusy=false;shoot()}
function endGame(){if(state!=='play')return;state='end';let win=teamsAlive()[0];$('#screen').style.display='flex';$('#screen').innerHTML=`<div class="endCard"><div style="font-size:55px">${win===undefined?'🌊':['🌿','🍑','☁️','🍯'][win]}</div><div class="eyebrow">THE PLANET HAS SPOKEN</div><h2>${win===undefined?'DRAW!':TEAMNAMES[win]+' WINS!'}</h2><p>${win===undefined?'Everyone took the scenic route to the sea.':'A legendary display of worm-based bravery.'}</p><button class="startBtn" id="again">ONE MORE ROUND &nbsp; ↻</button></div>`;$('#again').onclick=()=>location.reload();sound(440,.5,'triangle',.06,300)}
function draw(){let sx=canvas.width/W,sy=canvas.height/H;ctx.setTransform(dpr*innerWidth/W,0,0,dpr*innerHeight/H,0,0);ctx.clearRect(0,0,W,H);let sky=ctx.createLinearGradient(0,0,0,H);sky.addColorStop(0,'#88d9ee');sky.addColorStop(.59,'#d4f1db');sky.addColorStop(1,'#f9e6b4');ctx.fillStyle=sky;ctx.fillRect(0,0,W,H);drawSky();let shakeX=shake?rng(-shake,shake):0,shakeY=shake?rng(-shake,shake):0;ctx.save();ctx.translate(W/2+shakeX,H/2+shakeY);ctx.scale(camera.z,camera.z);ctx.translate(-camera.x,-camera.y);drawDistant();drawTerrain();drawWater();drawWorldEffects();for(let d of debris)drawDebris(d);for(let p of projectiles)drawProjectile(p);for(let u of units)if(u.alive)drawWorm(u);drawAim();drawParticles();ctx.restore();drawForeground();requestAnimationFrame(draw)}
function drawSky(){ctx.save();for(let i=0;i<38;i++){let x=((i*179+37)%W),y=(i*71+24)%340;ctx.fillStyle=`rgba(255,255,255,${.2+(i%4)*.07})`;ctx.beginPath();ctx.arc(x,y,1.2+(i%3),0,7);ctx.fill()}ctx.fillStyle='#fff6cf';ctx.beginPath();ctx.arc(1080,120,43,0,7);ctx.fill();ctx.fillStyle='#fff9e8';ctx.beginPath();ctx.arc(1066,105,39,0,7);ctx.fill();for(let i=0;i<4;i++){let x=(i*370+130-camera.x*.05)%1500-100,y=130+(i%3)*55;ctx.fillStyle='rgba(255,255,255,.3)';ctx.beginPath();ctx.ellipse(x,y,43,9,0,0,7);ctx.ellipse(x+21,y-7,21,12,0,0,7);ctx.ellipse(x+43,y,30,8,0,0,7);ctx.fill()}ctx.restore()}
function drawDistant(){ctx.fillStyle='#9bc9ad';ctx.beginPath();ctx.moveTo(-200,540);for(let x=-200;x<W+220;x+=22){let wx=(x-W/2)/camera.z+camera.x;ctx.lineTo(x,450+Math.sin(wx*.003)*24+Math.sin(wx*.008)*19)}ctx.lineTo(W+200,H);ctx.lineTo(-200,H);ctx.fill();ctx.fillStyle='#b1d9b7';ctx.beginPath();ctx.moveTo(-100,580);for(let x=-100;x<W+180;x+=30){let wx=(x-W/2)/camera.z+camera.x;ctx.lineTo(x,520+Math.sin(wx*.004+1)*19)}ctx.lineTo(W+180,H);ctx.lineTo(-100,H);ctx.fill()}
function drawTerrain(){let left=Math.max(0,Math.floor(camera.x-W/(2*camera.z)-20)),right=Math.min(worldW,Math.ceil(camera.x+W/(2*camera.z)+20));let grad=ctx.createLinearGradient(0,390,0,710);grad.addColorStop(0,'#87bd78');grad.addColorStop(.13,'#bb9b68');grad.addColorStop(1,'#75583f');ctx.fillStyle=grad;ctx.beginPath();ctx.moveTo(left,760);for(let x=left;x<=right;x+=3)ctx.lineTo(x,terrain[x]);ctx.lineTo(right,760);ctx.closePath();ctx.fill();ctx.beginPath();for(let x=left;x<=right;x+=2){let y=terrain[x];if(x===left)ctx.moveTo(x,y);else ctx.lineTo(x,y)}ctx.strokeStyle='#527c50';ctx.lineWidth=7;ctx.lineJoin='round';ctx.stroke();ctx.strokeStyle='#c3d887';ctx.lineWidth=2;ctx.stroke(); // layered strata
ctx.save();ctx.globalAlpha=.19;ctx.strokeStyle='#4e4939';ctx.lineWidth=1;for(let y=585;y<750;y+=24){ctx.beginPath();for(let x=left;x<=right;x+=24){let yy=Math.max(y,terrain[x]+15);if(x===left)ctx.moveTo(x,yy);else ctx.lineTo(x,yy)}ctx.stroke()}ctx.restore();for(let x=left;x<right;x+=43){let y=terrain[x];if(y<sea+35&&y>390){ctx.fillStyle='#4d8359';ctx.beginPath();ctx.ellipse(x,y-3,6,3,0,0,7);ctx.fill();if(x%3<1){ctx.strokeStyle='#598456';ctx.lineWidth=2;ctx.beginPath();ctx.moveTo(x,y);ctx.lineTo(x-2,y-12);ctx.moveTo(x,y-4);ctx.lineTo(x+5,y-11);ctx.stroke()}}}}
function drawWater(){let wave=sea+Math.sin(windPhase*2)*3;ctx.fillStyle='rgba(61,184,214,.72)';ctx.fillRect(camera.x-W/(2*camera.z)-100,wave, W/camera.z+200,220);ctx.strokeStyle='rgba(225,255,248,.72)';ctx.lineWidth=2;ctx.beginPath();let l=camera.x-W/(2*camera.z)-100,r=camera.x+W/(2*camera.z)+100;for(let x=l;x<r;x+=7){let y=wave+Math.sin(x*.045+windPhase*2)*3;if(x===l)ctx.moveTo(x,y);else ctx.lineTo(x,y)}ctx.stroke();ctx.save();ctx.globalAlpha=.23;for(let i=0;i<14;i++){let x=((i*173+windPhase*30)%worldW);ctx.fillStyle='white';ctx.beginPath();ctx.ellipse(x,wave+18+(i%4)*19,15+(i%3)*8,2,0,0,7);ctx.fill()}ctx.restore()}
function drawWorm(u){ctx.save();ctx.translate(u.x,u.y);let wob=Math.sin(windPhase*7+u.slot)*1.5;ctx.rotate(clamp(u.vx/600,-.18,.18));ctx.fillStyle='#0002';ctx.beginPath();ctx.ellipse(0,13,15,4,0,0,7);ctx.fill();ctx.fillStyle=COLORS[u.team];ctx.beginPath();ctx.ellipse(0,1+wob,14,12,0,0,7);ctx.fill();ctx.beginPath();ctx.ellipse(2,-8+wob,11,11,0,0,7);ctx.fill();ctx.fillStyle='#fff';ctx.beginPath();ctx.ellipse(6,-10,3.7,4.8,0,0,7);ctx.ellipse(-1,-10,3.4,4.4,0,0,7);ctx.fill();ctx.fillStyle='#19334b';ctx.beginPath();ctx.arc(7,-10,1.6,0,7);ctx.arc(0,-10,1.5,0,7);ctx.fill();ctx.strokeStyle='#764e48';ctx.lineWidth=1.3;ctx.beginPath();ctx.arc(3,-4,3,.15,1.5);ctx.stroke();ctx.font='12px sans-serif';ctx.textAlign='center';ctx.fillText(u.hat,1,-21);if(u.shield){ctx.strokeStyle='rgba(100,222,255,.85)';ctx.lineWidth=3;ctx.beginPath();ctx.ellipse(0,0,20,23,0,0,7);ctx.stroke()}if(u===current()){ctx.strokeStyle='#fff6c9';ctx.lineWidth=2;ctx.setLineDash([3,4]);ctx.beginPath();ctx.ellipse(0,1,21,18,0,0,7);ctx.stroke();ctx.setLineDash([]);ctx.fillStyle='#173a50';ctx.font='900 11px Nunito';ctx.fillText('▼',0,-38)}ctx.restore();if(u.hp<100){ctx.fillStyle='#264a56';ctx.fillRect(u.x-15,u.y-34,30,4);ctx.fillStyle=u.hp<30?'#ff634f':'#e7efbc';ctx.fillRect(u.x-15,u.y-34,30*u.hp/100,4)}}
function drawAim(){if(state!=='play'||aiBusy||paused||turnLock>0)return;let u=current();if(!u||!u.alive||matchMode!=='hotseat'&&turnTeam>0)return;let {ang}=aimAt(u),w=weapons[weaponIndex];if(w.laser){let end=rayEnd(u.x,u.y-9,ang,worldW);ctx.strokeStyle='#ffed8a88';ctx.lineWidth=2;ctx.setLineDash([5,7]);ctx.beginPath();ctx.moveTo(u.x,u.y-9);ctx.lineTo(end.x,end.y);ctx.stroke();ctx.setLineDash([]);return}if(w.place)return;let speed=w.speed||400,vx=Math.cos(ang)*speed,vy=Math.sin(ang)*speed,x=u.x+Math.cos(ang)*20,y=u.y-10+Math.sin(ang)*14;ctx.fillStyle='rgba(255,255,255,.8)';for(let t=0;t<2.2;t+=.11){let xx=x+vx*t+wind*5*t*t,yy=y+vy*t+(w.gravity||360)*.5*t*t;if(xx<0||xx>worldW||yy>groundAt(clamp(xx,0,worldW)))break;ctx.beginPath();ctx.arc(xx,yy,Math.max(1.2,3-t),0,7);ctx.fill()}}
function drawProjectile(p){ctx.save();for(let i=0;i<(p.trail||[]).length;i++){let q=p.trail[i];ctx.globalAlpha=i/(p.trail.length||1)*.4;ctx.fillStyle=p.sheep?'#fff':p.strike?'#ff765f':'#ffe3a0';ctx.beginPath();ctx.arc(q.x,q.y,Math.max(1,p.r*(i/(p.trail.length||1))*.45),0,7);ctx.fill()}ctx.globalAlpha=1;if(p.sheep){ctx.font='25px sans-serif';ctx.textAlign='center';ctx.fillText('🐑',p.x,p.y+8)}else{ctx.fillStyle=p.strike?'#ff775c':p.child?'#ffcf6c':'#ffedbd';ctx.beginPath();ctx.arc(p.x,p.y,p.r,0,7);ctx.fill();ctx.fillStyle='#fff';ctx.beginPath();ctx.arc(p.x-p.r*.3,p.y-p.r*.35,Math.max(2,p.r*.32),0,7);ctx.fill()}ctx.restore()}
function drawWorldEffects(){for(let e of effects){if(e.type==='boom'){let p=1-e.life/e.max;ctx.globalAlpha=1-p;let g=ctx.createRadialGradient(e.x,e.y,2,e.x,e.y,e.r*(.35+p*.7));g.addColorStop(0,'#fff8cf');g.addColorStop(.35,'#ffdc78');g.addColorStop(1,'#ff765300');ctx.fillStyle=g;ctx.beginPath();ctx.arc(e.x,e.y,e.r*(.35+p*.7),0,7);ctx.fill();ctx.globalAlpha=1}else if(e.type==='laser'){ctx.strokeStyle='#fff1a0';ctx.lineWidth=8;ctx.beginPath();ctx.moveTo(e.x,e.y);ctx.lineTo(e.x2,e.y2);ctx.stroke();ctx.strokeStyle='#fff';ctx.lineWidth=2;ctx.stroke();ctx.globalAlpha=1}else if(e.type==='dynamite'){ctx.font='24px sans-serif';ctx.textAlign='center';ctx.fillText('🧨',e.x,e.y);ctx.fillStyle='#fff';ctx.font='bold 14px DM Mono';ctx.fillText((Math.ceil(e.life*10)/10).toFixed(1),e.x,e.y-22)}else if(e.type==='shield'){ctx.strokeStyle='#a5f3ff';ctx.lineWidth=4;ctx.beginPath();ctx.arc(e.x,e.y,23*(1-e.life/e.max)+18,0,7);ctx.stroke()}else if(e.type==='plane'){ctx.font='30px sans-serif';ctx.fillText('🛩️',e.x,e.y)}}}
function drawParticles(){for(let p of particles){ctx.globalAlpha=clamp(p.life/p.max,0,1);ctx.fillStyle=p.color;ctx.beginPath();ctx.arc(p.x,p.y,p.r*(p.kind==='spark'?clamp(p.life*2,.4,1):1),0,7);ctx.fill()}ctx.globalAlpha=1;for(let f of floaters){ctx.globalAlpha=clamp(f.life/f.max,0,1);ctx.fillStyle='rgba(20,43,57,.7)';ctx.font='1000 14px Nunito';ctx.textAlign='center';ctx.fillText(f.text,f.x+1,f.y+2);ctx.fillStyle=f.color;ctx.fillText(f.text,f.x,f.y)}ctx.globalAlpha=1}
function drawDebris(d){ctx.fillStyle=d.color;ctx.beginPath();ctx.moveTo(d.x-d.r,d.y-d.r*.4);ctx.lineTo(d.x+d.r*.7,d.y-d.r);ctx.lineTo(d.x+d.r,d.y+d.r*.5);ctx.lineTo(d.x-d.r*.4,d.y+d.r);ctx.closePath();ctx.fill()}
function drawForeground(){// subtle vignette and horizon edge
let g=ctx.createLinearGradient(0,H-100,0,H);g.addColorStop(0,'#183b4c00');g.addColorStop(1,'#183b4c15');ctx.fillStyle=g;ctx.fillRect(0,H-100,W,100)}
function tick(now){let dt=Math.min(.04,(now-lastTime)/1000||.016);lastTime=now;if(state==='play')update(dt);let urgency=state==='play'&&!aiBusy?clamp(1-timeLeft/8,0,1):0;let timer=$('#timer');timer.textContent=Math.ceil(timeLeft);timer.classList.toggle('urgent',urgency>.15);timer.style.transform=`scale(${1+urgency*.22+Math.sin(now/90)*urgency*.025})`;$('#timerBar').style.width=`${clamp(timeLeft/timeMax*100,0,100)}%`;$('#timerBar').style.background=urgency>.3?'#f46b58':'#72c9a0';if(urgency>.6&&Math.floor(now/430)!==Math.floor((now-dt*1000)/430))sound(urgentFreq(urgency),.045,'square',.018,20);requestAnimationFrame(tick)}function urgentFreq(u){return 300+u*220}
function jump(){let u=current();if(u&&u.ground){u.vy=-295;u.ground=false;sound(350,.1,'triangle',.03,170)}}function useUtility(){if(state!=='play'||aiBusy||turnLock)return;let u=current();if(usedUtility[u.team]){toast('One pocket trick per turn!');return}usedUtility[u.team]=true;let util=utility[utilIndex];if(utilIndex===0){u.jet=1;u.vy=-330;u.vx+=u.face*130;toast('Jetpack! Squeee!');for(let i=0;i<10;i++)particles.push({x:u.x,y:u.y+8,vx:rng(-30,30),vy:rng(30,130),life:.5,max:.5,r:rng(2,5),color:'#ffb35c',kind:'spark'});sound(180,.3,'sawtooth',.04,500)}else if(utilIndex===1){let p=screenToWorld(mouse.x,mouse.y);let xx=clamp(p.x,30,worldW-30);if(Math.min(xx,worldW-xx)<30||p.y>sea-40){toast('No teleporting into soup!');usedUtility[u.team]=false;return}for(let i=0;i<14;i++)particles.push({x:u.x,y:u.y,vx:rng(-100,100),vy:rng(-130,70),life:.5,max:.5,r:4,color:'#b68aff',kind:'spark'});u.x=xx;u.y=groundAt(xx)-u.r;u.vx=u.vy=0;toast('Whoosh! A strategic relocation.');sound(650,.18,'sine',.04,-430)}else{u.shield=1;toast('Bubble shield activated!');sound(500,.2,'sine',.04,300)}updateHud()}
canvas.addEventListener('mousemove',e=>{mouse.x=e.clientX;mouse.y=e.clientY});canvas.addEventListener('mousedown',e=>{if(e.button===0){mouse.down=true;shoot()}});canvas.addEventListener('mouseup',()=>mouse.down=false);canvas.addEventListener('contextmenu',e=>e.preventDefault());addEventListener('keydown',e=>{keys[e.code]=true;if(['Space','ArrowUp','ArrowLeft','ArrowRight'].includes(e.code))e.preventDefault();if(e.repeat)return;if(/^Digit[1-8]$/.test(e.code))selectWeapon(+e.code.slice(5)-1);if(e.code==='KeyQ')useUtility();if(e.code==='KeyE'){utilIndex=(utilIndex+1)%utility.length;toast(`Utility: ${utility[utilIndex].icon} ${utility[utilIndex].name}`);sound(500,.04,'square',.02)}if(e.code==='Enter'&&state==='menu')newMatch();if(e.code==='Escape'&&state==='play')togglePause()});addEventListener('keyup',e=>keys[e.code]=false);
function togglePause(){if(state!=='play')return;paused=!paused;$('#pauseBtn').textContent=paused?'▶  Resume':'Ⅱ  Pause';toast(paused?'Paused. Stretch your worm.':'Back in the dirt!');sound(paused?300:500,.08,'triangle')}
$('#startBtn').onclick=newMatch;$('#pauseBtn').onclick=togglePause;$('#mute').onclick=()=>{muted=!muted;$('#mute').innerHTML=muted?'♫ &nbsp; Sound off':'♫ &nbsp; Sound on';sound(480,.08)};
// utility button via Q/E help + click weapon strip cycling
$('#bottom').addEventListener('dblclick',()=>useUtility());$('#bottom').addEventListener('contextmenu',e=>{e.preventDefault();useUtility()});
requestAnimationFrame(draw);requestAnimationFrame(tick);
})();
</script></body></html>
<!-- agent-meta {"model":"openai/gpt-6-luna","provider":"openrouter","persona":"full","sessionId":"63b50522-77f7-4495-94c6-75186bb5cbde","tokensIn":185144,"tokensOut":21560,"tokensTotal":206704,"cost":0.015486905000000002,"turns":9,"toolCalls":8,"failedToolCalls":0,"timestamp":"2026-09-23T02:31:12.595Z"} -->