← Worms results

Worms

Qwen 3.6 35b a3b · composer

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 73,087 bytes · SHA-256 6ec8a8704105
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Worm Wars — Artillery Showdown</title>
<style>
/* ── css: layout ── */
* { margin: 0; padding: 0; box-sizing: border-box; }
html, body { width: 100%; height: 100%; overflow: hidden; background: #0a0a1a; font-family: 'Segoe UI', system-ui, sans-serif; }
#game { display: block; width: 100%; height: 100%; }
#hud { position: absolute; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none; z-index: 10; }
.overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; display: flex; flex-direction: column; align-items: center; justify-content: center; pointer-events: none; z-index: 20; }
#menu-overlay { background: rgba(5, 5, 25, 0.92); pointer-events: auto; }
#menu-overlay.hidden { display: none; }
#turn-overlay { background: rgba(0, 0, 0, 0.35); }
#turn-overlay.visible { pointer-events: none; }
/* ── css: hud ── */
.hud-top { position: absolute; top: 0; left: 0; right: 0; display: flex; justify-content: space-between; align-items: flex-start; padding: 12px 20px; pointer-events: none; z-index: 11; }
.hud-team { background: rgba(0,0,0,0.55); backdrop-filter: blur(6px); border-radius: 10px; padding: 10px 16px; border: 1px solid rgba(255,255,255,0.08); min-width: 180px; }
.hud-team-name { font-size: 13px; font-weight: 700; text-transform: uppercase; letter-spacing: 1.5px; margin-bottom: 6px; }
.hud-unit { display: flex; align-items: center; gap: 8px; padding: 3px 0; font-size: 12px; color: rgba(255,255,255,0.7); }
.hud-unit.active { color: #fff; font-weight: 600; }
.hud-unit.dead { color: rgba(255,255,255,0.25); text-decoration: line-through; }
.hud-hp-bar { width: 80px; height: 6px; background: rgba(255,255,255,0.1); border-radius: 3px; overflow: hidden; }
.hud-hp-fill { height: 100%; border-radius: 3px; transition: width 0.4s ease; }
.hud-center { display: flex; flex-direction: column; align-items: center; gap: 4px; }
.hud-turn-info { font-size: 14px; font-weight: 600; color: rgba(255,255,255,0.8); text-shadow: 0 1px 4px rgba(0,0,0,0.5); }
.hud-timer { font-size: 32px; font-weight: 800; color: #fff; text-shadow: 0 2px 8px rgba(0,0,0,0.5); transition: transform 0.15s; }
.hud-timer.urgent { color: #ff4444; animation: timer-pulse 0.5s infinite; }
@keyframes timer-pulse { 0%,100% { transform: scale(1); } 50% { transform: scale(1.15); } }
.hud-power { display: flex; align-items: center; gap: 8px; margin-top: 8px; }
.hud-power-bar { width: 200px; height: 10px; background: rgba(255,255,255,0.1); border-radius: 5px; overflow: hidden; }
.hud-power-fill { height: 100%; border-radius: 5px; transition: width 0.05s; }
.hud-weapon-name { font-size: 13px; font-weight: 600; color: rgba(255,255,255,0.9); text-shadow: 0 1px 3px rgba(0,0,0,0.4); }
.hud-bottom { position: absolute; bottom: 12px; left: 50%; transform: translateX(-50%); display: flex; gap: 6px; align-items: center; }
.hud-item-btn { pointer-events: auto; background: rgba(0,0,0,0.5); backdrop-filter: blur(6px); border: 1px solid rgba(255,255,255,0.1); border-radius: 8px; padding: 8px 12px; color: rgba(255,255,255,0.8); font-size: 11px; cursor: pointer; transition: all 0.15s; text-align: center; min-width: 56px; position: relative; }
.hud-item-btn:hover { background: rgba(255,255,255,0.1); border-color: rgba(255,255,255,0.25); transform: translateY(-2px); }
.hud-item-btn.selected { background: rgba(100,150,255,0.2); border-color: rgba(100,150,255,0.5); color: #fff; }
.hud-item-btn .icon { font-size: 18px; display: block; margin-bottom: 2px; }
.hud-item-btn:hover::after { content: attr(title); position: absolute; bottom: 110%; left: 50%; transform: translateX(-50%); background: rgba(0,0,0,0.9); color: #fff; padding: 4px 8px; border-radius: 4px; font-size: 11px; white-space: nowrap; pointer-events: none; z-index: 100; }
.hud-wind { position: absolute; top: 50%; right: 20px; transform: translateY(-50%); display: flex; flex-direction: column; align-items: center; gap: 4px; }
.hud-wind-arrow { font-size: 28px; color: rgba(255,255,255,0.6); transition: transform 0.3s; }
.hud-wind-val { font-size: 11px; color: rgba(255,255,255,0.5); }
.hud-team-2 { position: absolute; top: 50px; left: 50%; transform: translateX(-50%); }
/* ── css: menu ── */
.menu-title { font-size: 56px; font-weight: 900; color: #fff; text-shadow: 0 4px 20px rgba(100,100,255,0.4), 0 0 60px rgba(100,100,255,0.1); margin-bottom: 8px; letter-spacing: -1px; }
.menu-subtitle { font-size: 16px; color: rgba(255,255,255,0.5); margin-bottom: 36px; letter-spacing: 3px; text-transform: uppercase; }
.menu-section { margin-bottom: 28px; text-align: center; }
.menu-section-title { font-size: 12px; font-weight: 700; text-transform: uppercase; letter-spacing: 2px; color: rgba(255,255,255,0.4); margin-bottom: 10px; }
.menu-btn { display: inline-block; padding: 12px 28px; margin: 4px 6px; background: rgba(255,255,255,0.08); border: 1px solid rgba(255,255,255,0.12); border-radius: 10px; color: rgba(255,255,255,0.8); font-size: 15px; font-weight: 600; cursor: pointer; transition: all 0.2s; }
.menu-btn:hover { background: rgba(255,255,255,0.15); border-color: rgba(255,255,255,0.3); color: #fff; transform: translateY(-2px); box-shadow: 0 4px 16px rgba(0,0,0,0.3); }
.menu-btn.selected { background: rgba(100,150,255,0.2); border-color: rgba(100,150,255,0.5); color: #fff; }
.menu-row { display: flex; gap: 8px; justify-content: center; align-items: center; flex-wrap: wrap; }
.menu-btn-small { padding: 8px 16px; font-size: 13px; background: rgba(255,255,255,0.05); border: 1px solid rgba(255,255,255,0.08); border-radius: 8px; color: rgba(255,255,255,0.7); cursor: pointer; transition: all 0.15s; }
.menu-btn-small:hover { background: rgba(255,255,255,0.12); color: #fff; }
.menu-btn-small.selected { background: rgba(100,200,100,0.2); border-color: rgba(100,200,100,0.5); color: #aaffaa; }
.turn-announce { font-size: 42px; font-weight: 900; color: #fff; text-shadow: 0 4px 24px rgba(0,0,0,0.6); opacity: 0; transform: scale(0.8); transition: all 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275); }
.turn-announce.show { opacity: 1; transform: scale(1); }
.turn-worm-name { font-size: 24px; color: rgba(255,255,255,0.7); margin-top: 4px; }
.menu-bg-canvas { position: absolute; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none; z-index: -1; }
</style>
</head>
<body>
<div id="app"></div>
<canvas id="game"></canvas>
<div id="hud"></div>
<div id="menu-overlay" class="overlay"></div>
<div id="turn-overlay" class="overlay"></div>
<div id="weapon-bar" class="hud-bottom"></div>
<script>
'use strict';

// ── const: W ──
const W = 1600;

// ── const: H ──
const H = 900;

// ── const: MAX_HP ──
const MAX_HP = 100;

// ── const: TURN_TIME ──
const TURN_TIME = 30;

// ── const: GRAVITY ──
const GRAVITY = 0.15;

// ── const: WATER_LEVEL ──
const WATER_LEVEL = 0.65;

// ── const: MAX_WIND ──
const MAX_WIND = 0.04;

// ── const: TERRAIN_W ──
const TERRAIN_W = 2400;

// ── const: TERRAIN_H ──
const TERRAIN_H = 600;

// ── const: TEAM_COLORS ──
const TEAM_COLORS = ['#ff6b6b','#4ecdc4','#ffe66d','#a8e6cf'];

// ── const: TEAM_NAMES ──
const TEAM_NAMES = ['Red','Cyan','Yellow','Green'];

// ── const: WORM_COLORS ──
const WORM_COLORS = ['#ff4444','#44cccc','#cccc44','#44cc44'];

// ── const: WORM_NAMES ──
const WORM_NAMES = ['Wormy','Slith','Bulgy','Gloop','Wriggle','Bloop','Noodle','Puff'];

// ── const: WEAPONS ──
const WEAPONS = [
  { id:'rocket', name:'Rocket', icon:'🚀', type:'indirect', damage:35, radius:80, power:14, speed:1, fallDmg:15, desc:'High explosive' },
  { id:'grenade', name:'Grenade', icon:'💣', type:'indirect', damage:28, radius:60, power:11, speed:0.9, fallDmg:12, desc:'Medium blast' },
  { id:'shotgun', name:'Shotgun', icon:'🔫', type:'direct', damage:22, radius:30, power:13, speed:1.2, fallDmg:8, desc:'Close range' },
  { id:'homing', name:'Homing', icon:'🎯', type:'indirect', damage:25, radius:50, power:10, speed:0.85, fallDmg:10, desc:'Tracks target' },
  { id:'nuke', name:'Nuke', icon:'☢️', type:'aoe', damage:55, radius:150, power:16, speed:1.1, fallDmg:25, desc:'Map altering' },
  { id:'bazooka', name:'Bazooka', icon:'🔥', type:'indirect', damage:30, radius:70, power:15, speed:1.15, fallDmg:14, desc:'Bigger boom' },
  { id:'laser', name:'Laser', icon:'⚡', type:'direct', damage:40, radius:10, power:0, speed:1.5, fallDmg:0, desc:'Instant hit' },
  { id:'super-grenade', name:'Super G', icon:'💥', type:'aoe', damage:45, radius:100, power:12, speed:0.95, fallDmg:20, desc:'Massive blast' },
];

// ── const: ITEMS ──
const ITEMS = [
  { id:'medkit', name:'Medkit', icon:'❤️', desc:'Heal 30 HP' },
  { id:'shield', name:'Shield', icon:'🛡️', desc:'Block next hit' },
  { id:'banana', name:'Banana', icon:'🍌', desc:'Slip enemy' },
  { id:'dynamite', name:'Dynamite', icon:'🧨', desc:'Place & explode' },
  { id:'jetpack', name:'Jetpack', icon:'🪂', desc:'Fly 2s' },
  { id:'parachute', name:'Parachute', icon:'🪂', desc:'Slow fall' },
];

// ── function: generateTerrain ──
function generateTerrain(shape) {
  const data = new Float32Array(TERRAIN_W);
  const half = TERRAIN_W / 2;
  if (shape === 'single-island') {
    for (let x = 0; x < TERRAIN_W; x++) {
      const dx = (x - half) / half;
      const base = Math.cos(dx * Math.PI * 0.5);
      const h = base * base * TERRAIN_H * 0.7;
      const noise = fbm(x * 0.008) * 40 + fbm(x * 0.02) * 15;
      data[x] = H * 0.65 + TERRAIN_H * 0.35 - h + noise;
    }
  } else if (shape === 'two-islands') {
    for (let x = 0; x < TERRAIN_W; x++) {
      const cx1 = half * 0.4, cx2 = half * 1.6;
      const d1 = (x - cx1) / (half * 0.5);
      const d2 = (x - cx2) / (half * 0.5);
      const h1 = Math.max(0, Math.cos(d1 * Math.PI * 0.5));
      const h2 = Math.max(0, Math.cos(d2 * Math.PI * 0.5));
      const base = (h1 * h1 + h2 * h2) * 0.5;
      const noise = fbm(x * 0.008) * 35 + fbm(x * 0.025) * 12;
      data[x] = H * 0.65 + TERRAIN_H * 0.35 - base * TERRAIN_H * 0.55 + noise;
    }
  } else {
    for (let x = 0; x < TERRAIN_W; x++) {
      const base = H * 0.55 + fbm(x * 0.005) * 120 + fbm(x * 0.015) * 50;
      const cave = Math.sin(x * 0.008) * 30 + Math.sin(x * 0.023) * 15;
      data[x] = base + cave;
    }
  }
  return data;
}

// ── function: fbm ──
function fbm(x) {
  let v = 0, a = 1, f = 1, total = 0;
  for (let i = 0; i < 5; i++) {
    v += Math.sin(x * f) * a + Math.sin(x * f * 1.7 + 3.0) * a * 0.5;
    total += a;
    a *= 0.5;
    f *= 2.1;
  }
  return v / total;
}

// ── function: destroyTerrain ──
function destroyTerrain(terrain, cx, cy, radius) {
  const r2 = radius * radius;
  for (let x = Math.max(0, Math.floor(cx - radius)); x < Math.min(TERRAIN_W, Math.ceil(cx + radius)); x++) {
    const dx = x - cx;
    for (let y = Math.max(0, Math.floor(cy - radius)); y < Math.min(TERRAIN_H, Math.ceil(cy + radius)); y++) {
      const dy = y - cy;
      if (dx * dx + dy * dy < r2) {
        terrain[x] = Math.max(terrain[x], y);
      }
    }
  }
}

// ── function: getTerrainHeight ──
function getTerrainHeight(terrain, x) {
  const ix = Math.floor(x);
  if (ix < 0 || ix >= TERRAIN_W) return H + 100;
  return terrain[ix];
}

// ── function: isUnderWater ──
function isUnderWater(terrain, x, y) {
  return y > getTerrainHeight(terrain, x) && y > H * WATER_LEVEL;
}

// ── function: getWaterY ──
function getWaterY(x) {
  return H * WATER_LEVEL;
}

// ── class: Particle ──
class Particle {
  constructor(x, y, vx, vy, life, color, size, type='circle') {
    this.x = x; this.y = y; this.vx = vx; this.vy = vy;
    this.life = this.maxLife = life; this.color = color;
    this.size = size; this.type = type; this.rotation = Math.random() * Math.PI * 2;
    this.rotSpeed = (Math.random() - 0.5) * 0.2;
  }
  update(dt) {
    this.vy += 0.08 * dt;
    this.x += this.vx * dt;
    this.y += this.vy * dt;
    this.life -= dt;
    this.rotation += this.rotSpeed * dt;
    if (this.type === 'debris') {
      this.vx *= 0.99;
    }
    return this.life > 0;
  }
}

// ── class: FloatingText ──
class FloatingText {
  constructor(x, y, text, color, size=18) {
    this.x = x; this.y = y; this.text = text; this.color = color;
    this.size = size; this.life = 90; this.maxLife = 90; this.vy = -1.5;
  }
  update(dt) {
    this.y += this.vy * dt;
    this.vy *= 0.97;
    this.life -= dt;
    return this.life > 0;
  }
}

// ── function: spawnExplosion ──
function spawnExplosion(cx, cy, radius, color, particles) {
  const count = Math.floor(radius * 2);
  for (let i = 0; i < count; i++) {
    const angle = Math.random() * Math.PI * 2;
    const speed = Math.random() * radius * 0.15 + 1;
    const r = Math.random() * radius * 0.9;
    const px = cx + Math.cos(angle) * r;
    const py = cy + Math.sin(angle) * r;
    const colors = ['#ff6b35','#ffaa33','#ff4444','#ffdd44','#ffffff','#ff8800','#ffcc00'];
    const c = color || colors[Math.floor(Math.random()*colors.length)];
    particles.push(new Particle(px, py, Math.cos(angle)*speed, Math.sin(angle)*speed - 2, 40+Math.random()*50, c, 2+Math.random()*5, 'circle'));
  }
  // Fire ring
  for (let i = 0; i < radius * 0.6; i++) {
    const angle = Math.random() * Math.PI * 2;
    const speed = radius * 0.08 + Math.random() * 2;
    const r = radius * 0.5 + Math.random() * radius * 0.3;
    particles.push(new Particle(cx + Math.cos(angle)*r, cy + Math.sin(angle)*r, Math.cos(angle)*speed, Math.sin(angle)*speed, 30+Math.random()*30, '#ff4400', 3+Math.random()*3, 'circle'));
  }
  // Debris
  for (let i = 0; i < radius * 0.6; i++) {
    const angle = -Math.PI * 0.15 + Math.random() * Math.PI * 0.3;
    const speed = Math.random() * radius * 0.1 + 2;
    const px = cx + (Math.random()-0.5) * radius;
    const py = cy;
    particles.push(new Particle(px, py, Math.cos(angle)*speed, Math.sin(angle)*speed - 3, 60+Math.random()*60, '#8B7355', 3+Math.random()*6, 'debris'));
  }
  // Smoke
  for (let i = 0; i < radius * 0.3; i++) {
    const angle = -Math.PI * 0.5 + (Math.random()-0.5) * Math.PI * 0.5;
    const speed = Math.random() * 1.5 + 0.5;
    const px = cx + (Math.random()-0.5) * radius * 0.5;
    const py = cy - Math.random() * 10;
    particles.push(new Particle(px, py, Math.cos(angle)*speed, Math.sin(angle)*speed - 1, 50+Math.random()*40, 'rgba(100,100,100,0.4)', 4+Math.random()*6, 'circle'));
  }
}

// ── function: spawnDust ──
function spawnDust(x, y, count, particles) {
  for (let i = 0; i < count; i++) {
    const angle = -Math.PI * 0.5 + (Math.random()-0.5) * Math.PI * 0.8;
    const speed = Math.random() * 2 + 0.5;
    particles.push(new Particle(x, y, Math.cos(angle)*speed, Math.sin(angle)*speed, 20+Math.random()*20, 'rgba(200,180,150,0.6)', 2+Math.random()*3, 'circle'));
  }
}

// ── function: spawnWaterSplash ──
function spawnWaterSplash(x, y, particles) {
  for (let i = 0; i < 15; i++) {
    const angle = -Math.PI * 0.3 + Math.random() * Math.PI * 0.6;
    const speed = Math.random() * 3 + 1;
    particles.push(new Particle(x, y, Math.cos(angle)*speed, Math.sin(angle)*speed, 30+Math.random()*20, 'rgba(100,180,255,0.7)', 2+Math.random()*3, 'circle'));
  }
}

// ── class: AudioSystem ──
class AudioSystem {
  constructor() {
    this.ctx = null;
    this.enabled = true;
  }
  init() {
    try {
      this.ctx = new (window.AudioContext || window.webkitAudioContext)();
    } catch(e) { this.enabled = false; }
  }
  play(type, vol=0.3) {
    if (!this.enabled || !this.ctx) return;
    try {
      const now = this.ctx.currentTime;
      const g = this.ctx.createGain();
      g.connect(this.ctx.destination);
      g.gain.setValueAtTime(vol, now);
      if (type === 'rocket') this._explosion(g, now);
      else if (type === 'shotgun') this._shotgun(g, now);
      else if (type === 'laser') this._laser(g, now);
      else if (type === 'homing') this._homing(g, now);
      else if (type === 'nuke') this._nuke(g, now);
      else if (type === 'explode') this._smallExplode(g, now);
      else if (type === 'click') this._click(g, now);
      else if (type === 'select') this._select(g, now);
      else if (type === 'turn') this._turn(g, now);
      else if (type === 'damage') this._damage(g, now);
      else if (type === 'item') this._item(g, now);
      else if (type === 'drown') this._drown(g, now);
      else if (type === 'death') this._death(g, now);
      else if (type === 'wind') this._wind(g, now);
    } catch(e) {}
  }
  _explosion(g, now) {
    const buf = this.ctx.createBuffer(1, this.ctx.sampleRate*0.5, this.ctx.sampleRate);
    const d = buf.getChannelData(0);
    for (let i=0;i<d.length;i++) d[i]=(Math.random()*2-1)*Math.exp(-i/(this.ctx.sampleRate*0.08));
    const n=this.ctx.createBufferSource(); n.buffer=buf;
    const f=this.ctx.createBiquadFilter();
    f.type='lowpass'; f.frequency.setValueAtTime(800,now); f.frequency.exponentialRampToValueAtTime(100,now+0.5);
    n.connect(f); f.connect(g); n.start(now);
    const o=this.ctx.createOscillator();
    o.type='sine'; o.frequency.setValueAtTime(80,now); o.frequency.exponentialRampToValueAtTime(30,now+0.4);
    const g2=this.ctx.createGain(); g2.gain.setValueAtTime(0.6,now); g2.gain.exponentialRampToValueAtTime(0.001,now+0.5);
    o.connect(g2); g2.connect(this.ctx.destination); o.start(now); o.stop(now+0.5);
    g.gain.exponentialRampToValueAtTime(0.001,now+0.6);
  }
  _shotgun(g, now) {
    const buf=this.ctx.createBuffer(1,this.ctx.sampleRate*0.15,this.ctx.sampleRate);
    const d=buf.getChannelData(0);
    for(let i=0;i<d.length;i++) d[i]=(Math.random()*2-1)*Math.exp(-i/(this.ctx.sampleRate*0.02));
    const n=this.ctx.createBufferSource(); n.buffer=buf;
    const f=this.ctx.createBiquadFilter(); f.type='bandpass'; f.frequency.value=2000; f.Q.value=1;
    n.connect(f); f.connect(g); n.start(now);
    g.gain.exponentialRampToValueAtTime(0.001,now+0.2);
  }
  _laser(g, now) {
    const o=this.ctx.createOscillator(); o.type='sawtooth';
    o.frequency.setValueAtTime(1200,now); o.frequency.exponentialRampToValueAtTime(200,now+0.2);
    const f=this.ctx.createBiquadFilter(); f.type='highpass'; f.frequency.value=400;
    o.connect(f); f.connect(g); o.start(now); o.stop(now+0.25);
    g.gain.setValueAtTime(0.15,now); g.gain.exponentialRampToValueAtTime(0.001,now+0.25);
  }
  _homing(g, now) {
    const o=this.ctx.createOscillator(); o.type='sine';
    o.frequency.setValueAtTime(400,now); o.frequency.linearRampToValueAtTime(600,now+0.3);
    o.connect(g); o.start(now); o.stop(now+0.4);
    g.gain.setValueAtTime(0.15,now); g.gain.exponentialRampToValueAtTime(0.001,now+0.4);
  }
  _nuke(g, now) {
    const buf=this.ctx.createBuffer(1,this.ctx.sampleRate*1.5,this.ctx.sampleRate);
    const d=buf.getChannelData(0);
    for(let i=0;i<d.length;i++) d[i]=(Math.random()*2-1)*Math.exp(-i/(this.ctx.sampleRate*0.15));
    const n=this.ctx.createBufferSource(); n.buffer=buf;
    const f=this.ctx.createBiquadFilter(); f.type='lowpass';
    f.frequency.setValueAtTime(400,now); f.frequency.exponentialRampToValueAtTime(50,now+1.5);
    n.connect(f); f.connect(g); n.start(now);
    g.gain.setValueAtTime(0.7,now); g.gain.exponentialRampToValueAtTime(0.001,now+1.6);
  }
  _smallExplode(g, now) {
    const buf=this.ctx.createBuffer(1,this.ctx.sampleRate*0.3,this.ctx.sampleRate);
    const d=buf.getChannelData(0);
    for(let i=0;i<d.length;i++) d[i]=(Math.random()*2-1)*Math.exp(-i/(this.ctx.sampleRate*0.04));
    const n=this.ctx.createBufferSource(); n.buffer=buf;
    const f=this.ctx.createBiquadFilter(); f.type='lowpass'; f.frequency.value=600;
    n.connect(f); f.connect(g); n.start(now);
    g.gain.exponentialRampToValueAtTime(0.001,now+0.35);
  }
  _click(g, now) {
    const o=this.ctx.createOscillator(); o.type='square'; o.frequency.value=800;
    o.connect(g); o.start(now); o.stop(now+0.03);
    g.gain.setValueAtTime(0.08,now); g.gain.exponentialRampToValueAtTime(0.001,now+0.03);
  }
  _select(g, now) {
    const o=this.ctx.createOscillator(); o.type='sine';
    o.frequency.setValueAtTime(600,now); o.frequency.linearRampToValueAtTime(900,now+0.08);
    o.connect(g); o.start(now); o.stop(now+0.1);
    g.gain.setValueAtTime(0.1,now); g.gain.exponentialRampToValueAtTime(0.001,now+0.1);
  }
  _turn(g, now) {
    const o=this.ctx.createOscillator(); o.type='sine';
    o.frequency.setValueAtTime(440,now); o.frequency.linearRampToValueAtTime(880,now+0.15);
    o.connect(g); o.start(now); o.stop(now+0.2);
    g.gain.setValueAtTime(0.12,now); g.gain.exponentialRampToValueAtTime(0.001,now+0.2);
  }
  _damage(g, now) {
    const o=this.ctx.createOscillator(); o.type='sawtooth';
    o.frequency.setValueAtTime(300,now); o.frequency.exponentialRampToValueAtTime(100,now+0.1);
    const f=this.ctx.createBiquadFilter(); f.type='lowpass'; f.frequency.value=500;
    o.connect(f); f.connect(g); o.start(now); o.stop(now+0.12);
    g.gain.setValueAtTime(0.1,now); g.gain.exponentialRampToValueAtTime(0.001,now+0.12);
  }
  _item(g, now) {
    const o=this.ctx.createOscillator(); o.type='sine';
    o.frequency.setValueAtTime(523,now); o.frequency.setValueAtTime(659,now+0.08);
    o.frequency.setValueAtTime(784,now+0.16);
    o.connect(g); o.start(now); o.stop(now+0.25);
    g.gain.setValueAtTime(0.1,now); g.gain.exponentialRampToValueAtTime(0.001,now+0.25);
  }
  _drown(g, now) {
    const o=this.ctx.createOscillator(); o.type='sine';
    o.frequency.setValueAtTime(200,now); o.frequency.linearRampToValueAtTime(100,now+0.5);
    o.connect(g); o.start(now); o.stop(now+0.6);
    g.gain.setValueAtTime(0.08,now); g.gain.exponentialRampToValueAtTime(0.001,now+0.6);
  }
  _death(g, now) {
    const o=this.ctx.createOscillator(); o.type='sawtooth';
    o.frequency.setValueAtTime(400,now); o.frequency.exponentialRampToValueAtTime(50,now+0.8);
    const f=this.ctx.createBiquadFilter(); f.type='lowpass'; f.frequency.value=600;
    o.connect(f); f.connect(g); o.start(now); o.stop(now+0.9);
    g.gain.setValueAtTime(0.15,now); g.gain.exponentialRampToValueAtTime(0.001,now+0.9);
  }
  _wind(g, now) {
    const buf=this.ctx.createBuffer(1,this.ctx.sampleRate*0.4,this.ctx.sampleRate);
    const d=buf.getChannelData(0);
    for(let i=0;i<d.length;i++) d[i]=Math.sin(i*0.01)*0.5*Math.random()*Math.exp(-i/(this.ctx.sampleRate*0.15));
    const n=this.ctx.createBufferSource(); n.buffer=buf;
    const f=this.ctx.createBiquadFilter(); f.type='bandpass'; f.frequency.value=300; f.Q.value=2;
    n.connect(f); f.connect(g); n.start(now);
    g.gain.setValueAtTime(0.06,now); g.gain.exponentialRampToValueAtTime(0.001,now+0.4);
  }
}

// ── class: Worm ──
class Worm {
  constructor(id, team, x, name) {
    this.id = id; this.team = team; this.name = name;
    this.x = x; this.y = 0; this.vx = 0; this.vy = 0;
    this.hp = MAX_HP; this.maxHp = MAX_HP;
    this.alive = true; this.angle = 0; this.facing = 1;
    this.shielded = false; this.slipped = false;
    this.jetpackActive = false; this.jetpackTimer = 0;
    this.parachute = false; this.inWater = false;
    this.drownTimer = 0; this.dying = false; this.deathTimer = 0;
    this.hasDynamite = false; this.dynamitePlaced = false;
    this.weaponIndex = 0; this.itemIndex = -1;
    this.xp = 0; this.kills = 0; this.damageDealt = 0;
  }
  getOnTerrain(terrain) {
    this.y = getTerrainHeight(terrain, this.x) - 14;
    this.inWater = this.y > getWaterY(this.x) - 5;
  }
  takeDamage(dmg, attacker) {
    if (this.shielded) { this.shielded = false; return; }
    this.hp -= dmg;
    if (attacker) { attacker.xp += Math.floor(dmg * 2); attacker.damageDealt += dmg; }
    if (this.hp <= 0) { this.hp = 0; this.alive = false; this.dying = true; this.deathTimer = 60; this.kills = attacker ? attacker.kills : this.kills; }
  }
}

// ── class: Projectile ──
class Projectile {
  constructor(x, y, angle, power, weapon, owner) {
    this.x = x; this.y = y; this.angle = angle; this.power = power;
    this.weapon = weapon; this.owner = owner; this.active = true;
    this.vx = Math.cos(angle) * power * weapon.speed;
    this.vy = Math.sin(angle) * power * weapon.speed;
    this.trail = []; this.age = 0; this.homingTarget = null;
  }
  update(terrain, wind, particles) {
    this.age++;
    if (this.weapon.id === 'homing' && !this.homingTarget) {
      this.homingTarget = this._findNearestEnemy();
    }
    if (this.homingTarget && this.homingTarget.alive) {
      const dx = this.homingTarget.x - this.x;
      const dy = (this.homingTarget.y - 14) - this.y;
      const dist = Math.sqrt(dx*dx+dy*dy);
      if (dist > 0) {
        const homingStr = 0.015;
        this.vx += (dx/dist) * homingStr;
        this.vy += (dy/dist) * homingStr;
        const spd = Math.sqrt(this.vx*this.vx+this.vy*this.vy);
        const maxSpd = this.weapon.speed * this.weapon.power;
        if (spd > maxSpd) { this.vx = (this.vx/spd)*maxSpd; this.vy = (this.vy/spd)*maxSpd; }
      }
    }
    this.vx += wind * 0.01;
    this.vy += GRAVITY;
    this.x += this.vx;
    this.y += this.vy;
    this.trail.push({x:this.x,y:this.y,a:1});
    if (this.trail.length > 40) this.trail.shift();
    for (let t of this.trail) t.a *= 0.95;
    if (this.x < 0 || this.x > TERRAIN_W || this.y > H + 100) { this.active = false; }
    const th = getTerrainHeight(terrain, this.x);
    if (this.y >= th) {
      this.active = false;
      return { hit: true, cx: this.x, cy: th, damage: this.weapon.damage, radius: this.weapon.radius, fallDmg: this.weapon.fallDmg };
    }
    return null;
  }
  _findNearestEnemy() {
    let best = null, bestD = Infinity;
    for (const w of game.worms) {
      if (w.team !== this.owner.team && w.alive) {
        const d = Math.abs(w.x - this.x) + Math.abs(w.y - this.y);
        if (d < bestD) { bestD = d; best = w; }
      }
    }
    return best;
  }
}

// ── function: predictTrajectory ──
function predictTrajectory(x, y, angle, power, weapon, wind) {
  const pts = [];
  let px = x, py = y, pvx = Math.cos(angle)*power*weapon.speed, pvy = Math.sin(angle)*power*weapon.speed;
  for (let i = 0; i < 120; i++) {
    pts.push({x:px, y:py});
    if (weapon.id === 'homing') {
      // Simplified homing for prediction
    }
    pvx += wind * 0.01;
    pvy += GRAVITY;
    px += pvx; py += pvy;
    const th = getTerrainHeight(terrainGen, px);
    if (py >= th || px < 0 || px > TERRAIN_W) break;
  }
  return pts;
}

// ── function: calculateAimLine ──
function calculateAimLine(worm, angle, power) {
  const weapon = WEAPONS[worm.weaponIndex];
  const startX = worm.x + Math.cos(angle) * 20 * worm.facing;
  const startY = worm.y - 10;
  return predictTrajectory(startX, startY, angle, power, weapon, windAngle);
}

// ── function: aiDecide ──
function aiDecide(worm, enemies, terrain) {
  const weapon = WEAPONS[worm.weaponIndex];
  let bestAngle = -0.6 + Math.random() * 0.3;
  let bestPower = weapon.power * (0.85 + Math.random() * 0.3);
  let target = null;

  const waterY = getWaterY(worm.x);
  const terrainY = getTerrainHeight(terrain, worm.x);
  const inWater = worm.y > waterY - 5;
  if (inWater) {
    bestAngle = -0.3;
    bestPower = 5;
    return { angle: bestAngle, power: bestPower, fire: true, move: 1 };
  }

  let bestScore = -Infinity;
  for (const e of enemies) {
    if (!e.alive) continue;
    const dx = e.x - worm.x;
    const dy = (e.y - 14) - (worm.y - 10);
    const dist = Math.sqrt(dx*dx + dy*dy);
    const risk = dist < 30 ? 5 : (dist > 500 ? 3 : 0);
    const hpScore = (1 - e.hp / MAX_HP) * 2;
    const score = -dist * 0.02 + hpScore - risk;
    if (score > bestScore) { bestScore = score; target = e; }
  }

  if (target) {
    const dx = target.x - worm.x;
    const dy = (target.y - 14) - (worm.y - 10);
    const dist = Math.sqrt(dx*dx + dy*dy);
    bestAngle = -Math.PI/4 - (dist * 0.0003) + (Math.random()-0.5)*0.15;
    bestPower = Math.sqrt(dist * GRAVITY) * (0.8 + Math.random()*0.4);
    bestPower = Math.max(3, Math.min(weapon.power * 1.3, bestPower));

    const testPts = predictTrajectory(worm.x, worm.y - 10, bestAngle, bestPower, weapon, windAngle);
    let hitsWater = false;
    for (const p of testPts) {
      if (p.y > getWaterY(p.x) && p.y > terrainY) { hitsWater = true; break; }
    }
    if (hitsWater) { bestAngle -= 0.15; bestPower *= 0.9; }

    const aheadX = worm.x + Math.cos(bestAngle) * 50;
    const aheadTerrain = getTerrainHeight(terrain, aheadX);
    if (aheadTerrain < worm.y - 40) { bestAngle += 0.1; bestPower *= 0.85; }
  }

  const fire = Math.random() > 0.15;
  const move = Math.random() > 0.5 ? (Math.random() > 0.5 ? 1 : -1) : 0;
  return { angle: bestAngle, power: bestPower, fire, move, target };
}

// ── function: drawTerrain ──
function drawTerrain(ctx, terrain, camX, camY, zoom) {
  ctx.save();
  ctx.scale(zoom, zoom);
  ctx.translate(-camX, -camY);

  // Sky gradient
  const skyGrad = ctx.createLinearGradient(0, -camY, 0, 1000-camY);
  skyGrad.addColorStop(0, '#050520');
  skyGrad.addColorStop(0.25, '#0a0a3a');
  skyGrad.addColorStop(0.5, '#1a2a5a');
  skyGrad.addColorStop(0.75, '#3a5a8a');
  skyGrad.addColorStop(1, '#5a8aba');
  ctx.fillStyle = skyGrad;
  ctx.fillRect(0, -camY, W/zoom + 100, H/zoom + 100);

  // Stars
  const time = Date.now() * 0.001;
  for (let i = 0; i < 100; i++) {
    const sx = ((i * 137.508 + 50) % W);
    const sy = ((i * 97.31 + 20) % (H * 0.45));
    const ss = 0.5 + (i % 3) * 0.6;
    const twinkle = 0.3 + 0.7 * Math.abs(Math.sin(time * (1 + i * 0.1) + i));
    ctx.fillStyle = `rgba(255,255,255,${twinkle * 0.5})`;
    ctx.beginPath();
    ctx.arc(sx, sy, ss, 0, Math.PI*2);
    ctx.fill();
  }

  // Sun/Moon
  const sunX = W * 0.75;
  const sunY = H * 0.15;
  const sunGrad = ctx.createRadialGradient(sunX, sunY, 0, sunX, sunY, 60);
  sunGrad.addColorStop(0, 'rgba(255,220,150,0.8)');
  sunGrad.addColorStop(0.3, 'rgba(255,180,100,0.3)');
  sunGrad.addColorStop(1, 'rgba(255,150,50,0)');
  ctx.fillStyle = sunGrad;
  ctx.beginPath();
  ctx.arc(sunX, sunY, 60, 0, Math.PI*2);
  ctx.fill();
  ctx.fillStyle = 'rgba(255,230,180,0.9)';
  ctx.beginPath();
  ctx.arc(sunX, sunY, 15, 0, Math.PI*2);
  ctx.fill();

  // Water
  const waterBaseY = getWaterY(0);
  const waterGrad = ctx.createLinearGradient(0, waterBaseY, 0, waterBaseY + 200);
  waterGrad.addColorStop(0, 'rgba(30,100,200,0.35)');
  waterGrad.addColorStop(0.3, 'rgba(20,80,180,0.45)');
  waterGrad.addColorStop(1, 'rgba(10,40,100,0.7)');
  ctx.fillStyle = waterGrad;
  ctx.beginPath();
  ctx.moveTo(0, waterBaseY);
  for (let x = 0; x <= W/zoom + 100; x += 3) {
    const wx = x + camX;
    const wy = waterBaseY + Math.sin(x*0.03 + time*1.5) * 4 + Math.sin(x*0.07 + time*2.3) * 2;
    ctx.lineTo(x, wy);
  }
  ctx.lineTo(W/zoom + 100, H/zoom + 100);
  ctx.lineTo(0, H/zoom + 100);
  ctx.closePath();
  ctx.fill();

  // Water surface shimmer
  ctx.strokeStyle = 'rgba(150,200,255,0.15)';
  ctx.lineWidth = 1;
  for (let i = 0; i < 15; i++) {
    const sx = (i * 100 + time * 30) % (W/zoom + 100);
    const sy = waterBaseY + Math.sin(sx*0.03 + time*1.5) * 4 + 5 + i * 8;
    ctx.beginPath();
    ctx.moveTo(sx, sy);
    ctx.lineTo(sx + 20, sy);
    ctx.stroke();
  }

  // Terrain fill with gradient
  const tGrad = ctx.createLinearGradient(0, 0, 0, TERRAIN_H);
  tGrad.addColorStop(0, '#6a9a3a');
  tGrad.addColorStop(0.05, '#5a8a2a');
  tGrad.addColorStop(0.15, '#4a7a2a');
  tGrad.addColorStop(0.3, '#6a5a3a');
  tGrad.addColorStop(0.6, '#5a4a2a');
  tGrad.addColorStop(1, '#2a1a0a');
  ctx.fillStyle = tGrad;
  ctx.beginPath();
  let started = false;
  for (let x = -10; x <= W/zoom + 100; x += 2) {
    const wx = x + camX;
    if (wx < 0 || wx > TERRAIN_W) continue;
    const ty = getTerrainHeight(terrain, wx);
    if (!started) { ctx.moveTo(x, ty); started = true; }
    else ctx.lineTo(x, ty);
  }
  ctx.lineTo(W/zoom + 100, H/zoom + 100);
  ctx.lineTo(-10, H/zoom + 100);
  ctx.closePath();
  ctx.fill();

  // Grass on top
  ctx.strokeStyle = '#8aba4a';
  ctx.lineWidth = 1.5;
  ctx.beginPath();
  started = false;
  for (let x = -10; x <= W/zoom + 100; x += 2) {
    const wx = x + camX;
    if (wx < 0 || wx > TERRAIN_W) continue;
    const ty = getTerrainHeight(terrain, wx);
    if (!started) { ctx.moveTo(x, ty); started = true; }
    else ctx.lineTo(x, ty);
  }
  ctx.stroke();

  // Grass tufts
  ctx.strokeStyle = '#7aaa3a';
  ctx.lineWidth = 1;
  for (let x = 0; x < W/zoom + 100; x += 8) {
    const wx = x + camX;
    if (wx < 0 || wx > TERRAIN_W) continue;
    const ty = getTerrainHeight(terrain, wx);
    if (ty > H * 0.5 && ty < H * 0.85) {
      const sway = Math.sin(time * 2 + wx * 0.1) * 3;
      ctx.beginPath();
      ctx.moveTo(x, ty);
      ctx.quadraticCurveTo(x + sway, ty - 8, x + sway * 1.5, ty - 12);
      ctx.stroke();
    }
  }

  ctx.restore();
}

// ── function: drawWorm ──
function drawWorm(ctx, worm, camX, camY, zoom, isActive) {
  ctx.save();
  ctx.scale(zoom, zoom);
  ctx.translate(-camX, -camY);

  const x = worm.x, y = worm.y;

  // Shadow
  ctx.fillStyle = 'rgba(0,0,0,0.15)';
  ctx.beginPath();
  ctx.ellipse(x, y + 14, 12, 4, 0, 0, Math.PI*2);
  ctx.fill();

  // Body glow
  const glowGrad = ctx.createRadialGradient(x, y, 5, x, y, 20);
  glowGrad.addColorStop(0, TEAM_COLORS[worm.team] + '40');
  glowGrad.addColorStop(1, 'transparent');
  ctx.fillStyle = glowGrad;
  ctx.beginPath();
  ctx.arc(x, y, 20, 0, Math.PI*2);
  ctx.fill();

  // Body
  const bodyGrad = ctx.createRadialGradient(x-3, y-5, 2, x, y, 14);
  bodyGrad.addColorStop(0, WORM_COLORS[worm.team]);
  bodyGrad.addColorStop(1, TEAM_COLORS[worm.team]);
  ctx.fillStyle = bodyGrad;
  ctx.beginPath();
  ctx.ellipse(x, y, 11, 13, 0, 0, Math.PI*2);
  ctx.fill();

  // Outline
  ctx.strokeStyle = 'rgba(0,0,0,0.3)';
  ctx.lineWidth = 1.5;
  ctx.stroke();

  // Eyes
  const eyeOffX = worm.facing * 4;
  // Eye whites
  ctx.fillStyle = '#fff';
  ctx.beginPath();
  ctx.ellipse(x + eyeOffX - 3, y - 5, 4, 4.5, 0, 0, Math.PI*2);
  ctx.fill();
  ctx.beginPath();
  ctx.ellipse(x + eyeOffX + 3, y - 5, 4, 4.5, 0, 0, Math.PI*2);
  ctx.fill();
  // Eye pupils
  ctx.fillStyle = '#111';
  ctx.beginPath();
  ctx.arc(x + eyeOffX - 2 + worm.facing * 1.5, y - 5, 2, 0, Math.PI*2);
  ctx.fill();
  ctx.beginPath();
  ctx.arc(x + eyeOffX + 4 + worm.facing * 1.5, y - 5, 2, 0, Math.PI*2);
  ctx.fill();
  // Eye shine
  ctx.fillStyle = 'rgba(255,255,255,0.8)';
  ctx.beginPath();
  ctx.arc(x + eyeOffX - 2.5 + worm.facing, y - 6, 0.8, 0, Math.PI*2);
  ctx.fill();
  ctx.beginPath();
  ctx.arc(x + eyeOffX + 3.5 + worm.facing, y - 6, 0.8, 0, Math.PI*2);
  ctx.fill();

  // Mouth based on HP
  ctx.strokeStyle = '#333';
  ctx.lineWidth = 1.5;
  ctx.beginPath();
  if (worm.hp > 70) {
    ctx.arc(x + eyeOffX, y + 2, 3, 0.1, Math.PI-0.1);
  } else if (worm.hp > 40) {
    ctx.moveTo(x + eyeOffX - 3, y + 2);
    ctx.lineTo(x + eyeOffX + 3, y + 2);
  } else if (worm.hp > 15) {
    ctx.arc(x + eyeOffX, y + 4, 3, Math.PI+0.3, -0.3);
  } else {
    // X eyes when dying
    ctx.moveTo(x + eyeOffX - 3, y + 1);
    ctx.lineTo(x + eyeOffX + 3, y + 3);
    ctx.moveTo(x + eyeOffX + 3, y + 1);
    ctx.lineTo(x + eyeOffX - 3, y + 3);
  }
  ctx.stroke();

  // Shield indicator
  if (worm.shielded) {
    ctx.strokeStyle = 'rgba(100,200,255,0.6)';
    ctx.lineWidth = 2;
    ctx.beginPath();
    ctx.arc(x, y, 16, 0, Math.PI*2);
    ctx.stroke();
    ctx.strokeStyle = 'rgba(100,200,255,0.3)';
    ctx.lineWidth = 4;
    ctx.beginPath();
    ctx.arc(x, y, 17, -0.5, 0.5);
    ctx.stroke();
  }

  // Active indicator
  if (isActive) {
    ctx.strokeStyle = 'rgba(255,255,100,0.5)';
    ctx.lineWidth = 2;
    ctx.setLineDash([4,4]);
    ctx.beginPath();
    ctx.arc(x, y, 20, 0, Math.PI*2);
    ctx.stroke();
    ctx.setLineDash([]);
  }

  // HP bar
  if (worm.alive) {
    const barW = 24, barH = 4;
    const hpRatio = worm.hp / MAX_HP;
    ctx.fillStyle = 'rgba(0,0,0,0.5)';
    ctx.fillRect(x - barW/2, y - 22, barW, barH);
    const hpColor = hpRatio > 0.6 ? '#44ff44' : (hpRatio > 0.3 ? '#ffaa00' : '#ff4444');
    ctx.fillStyle = hpColor;
    ctx.fillRect(x - barW/2, y - 22, barW * hpRatio, barH);
    ctx.strokeStyle = 'rgba(0,0,0,0.3)';
    ctx.lineWidth = 0.5;
    ctx.strokeRect(x - barW/2, y - 22, barW, barH);
  }

  // Name tag
  ctx.fillStyle = isActive ? '#fff' : 'rgba(255,255,255,0.6)';
  ctx.font = `${isActive ? 'bold ' : ''}9px sans-serif`;
  ctx.textAlign = 'center';
  ctx.shadowColor = 'rgba(0,0,0,0.5)';
  ctx.shadowBlur = 2;
  ctx.fillText(worm.name, x, y - 26);
  ctx.shadowBlur = 0;

  ctx.restore();
}

// ── function: drawProjectile ──
function drawProjectile(ctx, proj, camX, camY, zoom) {
  if (!proj.active) return;
  ctx.save();
  ctx.scale(zoom, zoom);
  ctx.translate(-camX, -camY);

  // Trail
  if (proj.trail.length > 1) {
    ctx.strokeStyle = proj.weapon.id === 'nuke' ? 'rgba(100,255,100,0.3)' :
      proj.weapon.id === 'homing' ? 'rgba(255,100,255,0.3)' : 'rgba(255,200,100,0.3)';
    ctx.lineWidth = 2;
    ctx.beginPath();
    for (let i = 0; i < proj.trail.length; i++) {
      const t = proj.trail[i];
      if (i === 0) ctx.moveTo(t.x, t.y);
      else ctx.lineTo(t.x, t.y);
    }
    ctx.stroke();
  }

  // Projectile
  const pColor = proj.weapon.id === 'nuke' ? '#44ff44' :
    proj.weapon.id === 'laser' ? '#ffff44' :
    proj.weapon.id === 'homing' ? '#ff88ff' : '#ffcc44';
  ctx.fillStyle = pColor;
  ctx.shadowColor = pColor;
  ctx.shadowBlur = 10;
  ctx.beginPath();
  ctx.arc(proj.x, proj.y, proj.weapon.id === 'nuke' ? 6 : 4, 0, Math.PI*2);
  ctx.fill();
  ctx.shadowBlur = 0;

  ctx.restore();
}

// ── function: drawTrajectory ──
function drawTrajectory(ctx, pts, camX, camY, zoom) {
  if (pts.length < 2) return;
  ctx.save();
  ctx.scale(zoom, zoom);
  ctx.translate(-camX, -camY);
  ctx.strokeStyle = 'rgba(255,255,100,0.4)';
  ctx.lineWidth = 1.5;
  ctx.setLineDash([4,4]);
  ctx.beginPath();
  for (let i = 0; i < pts.length; i++) {
    if (i === 0) ctx.moveTo(pts[i].x, pts[i].y);
    else ctx.lineTo(pts[i].x, pts[i].y);
  }
  ctx.stroke();
  ctx.setLineDash([]);

  // Impact indicator
  const last = pts[pts.length-1];
  ctx.strokeStyle = 'rgba(255,100,100,0.6)';
  ctx.lineWidth = 2;
  ctx.beginPath();
  ctx.arc(last.x, last.y, 8, 0, Math.PI*2);
  ctx.stroke();
  ctx.beginPath();
  ctx.moveTo(last.x-10, last.y); ctx.lineTo(last.x+10, last.y);
  ctx.moveTo(last.x, last.y-10); ctx.lineTo(last.x, last.y+10);
  ctx.stroke();

  ctx.restore();
}

// ── function: drawParticles ──
function drawParticles(ctx, particles, camX, camY, zoom) {
  ctx.save();
  ctx.scale(zoom, zoom);
  ctx.translate(-camX, -camY);
  for (const p of particles) {
    const alpha = Math.max(0, p.life / p.maxLife);
    if (p.type === 'debris') {
      ctx.save();
      ctx.translate(p.x, p.y);
      ctx.rotate(p.rotation);
      ctx.fillStyle = p.color;
      ctx.globalAlpha = alpha;
      ctx.fillRect(-p.size/2, -p.size/2, p.size, p.size);
      ctx.restore();
    } else {
      ctx.fillStyle = p.color;
      ctx.globalAlpha = alpha;
      ctx.beginPath();
      ctx.arc(p.x, p.y, p.size * alpha, 0, Math.PI*2);
      ctx.fill();
    }
  }
  ctx.globalAlpha = 1;
  ctx.restore();
}

// ── function: drawFloatingTexts ──
function drawFloatingTexts(ctx, texts, camX, camY, zoom) {
  ctx.save();
  ctx.scale(zoom, zoom);
  ctx.translate(-camX, -camY);
  for (const t of texts) {
    const alpha = Math.max(0, t.life / t.maxLife);
    ctx.globalAlpha = alpha;
    ctx.fillStyle = t.color;
    ctx.font = `bold ${t.size}px sans-serif`;
    ctx.textAlign = 'center';
    ctx.strokeStyle = 'rgba(0,0,0,0.5)';
    ctx.lineWidth = 3;
    ctx.strokeText(t.text, t.x, t.y);
    ctx.fillText(t.text, t.x, t.y);
  }
  ctx.globalAlpha = 1;
  ctx.restore();
}

// ── function: createGame ──
function createGame(teamCount, cpuCount, shape) {
  terrainGen = generateTerrain(shape);
  windAngle = (Math.random()-0.5) * MAX_WIND * 2;
  const game = {
    teams: teamCount,
    cpuCount: cpuCount,
    shape: shape,
    worms: [],
    currentTeam: 0,
    currentWormIndex: 0,
    phase: 'menu',
    turnTimer: TURN_TIME,
    turnTimerMax: TURN_TIME,
    power: 5,
    angle: -Math.PI/4,
    aimAngle: -Math.PI/4,
    aimPower: 5,
    aiming: false,
    projectiles: [],
    particles: [],
    floatingTexts: [],
    camera: { x: 0, y: 0, targetX: 0, targetY: 0, zoom: 1, targetZoom: 1 },
    shake: { x: 0, y: 0, intensity: 0, duration: 0 },
    freezeTimer: 0,
    selectedWeapon: 0,
    selectedItem: -1,
    selectedTeam: teamCount,
    selectedCpu: cpuCount,
    selectedShape: shape,
    menuPhase: 'setup',
    winner: null,
    turnWormName: '',
    turnShowTimer: 0,
    dynamiteTimer: -1,
    dynamiteX: 0,
    dynamiteY: 0,
    dynamiteActive: false,
    windChangeTimer: 0,
    totalTurns: 0,
    allDead: false,
  };
  const teamSize = 4;
  let id = 0;
  for (let t = 0; t < teamCount; t++) {
    const baseX = (TERRAIN_W / (teamCount + 1)) * (t + 1);
    for (let i = 0; i < teamSize; i++) {
      const wx = baseX + (Math.random()-0.5) * 100;
      const wy = getTerrainHeight(terrainGen, wx) - 14;
      const nameIdx = (t * teamSize + i) % WORM_NAMES.length;
      const w = new Worm(id++, t, wx, WORM_NAMES[nameIdx]);
      w.y = wy;
      game.worms.push(w);
    }
  }
  game.currentWormIndex = game.worms.findIndex(w => w.team === 0 && w.alive);
  game.worms[game.currentWormIndex].getOnTerrain(terrainGen);
  return game;
}

// ── function: nextTurn ──
function nextTurn(game) {
  // Find next alive worm
  let attempts = 0;
  do {
    game.currentWormIndex++;
    if (game.currentWormIndex >= game.worms.length) game.currentWormIndex = 0;
    attempts++;
  } while ((!game.worms[game.currentWormIndex].alive || game.worms[game.currentWormIndex].dying) && attempts < game.worms.length);

  const w = game.worms[game.currentWormIndex];
  if (!w.alive) { game.phase = 'gameover'; return; }

  game.phase = 'aiming';
  game.turnTimer = game.turnTimerMax;
  game.power = 5;
  game.aimAngle = -Math.PI/4;
  game.aimPower = 5;
  game.aiming = false;
  game.selectedWeapon = w.weaponIndex;
  game.selectedItem = w.itemIndex;
  game.shielded = false;

  // Wind change
  game.windChangeTimer = 300 + Math.random() * 300;
  const windDelta = (Math.random()-0.5) * 0.008;
  windAngle = Math.max(-MAX_WIND, Math.min(MAX_WIND, windAngle + windDelta));

  // Turn announcement
  game.turnWormName = w.name;
  game.turnShowTimer = 90;

  const audio = game.audio;
  if (audio) audio.play('turn', 0.2);
}

// ── function: allTeamsEliminated ──
function allTeamsEliminated(game) {
  const aliveTeams = new Set();
  for (const w of game.worms) {
    if (w.alive && !w.dying) aliveTeams.add(w.team);
  }
  if (aliveTeams.size <= 1) {
    game.winner = aliveTeams.size === 1 ? aliveTeams.values().next().value : -1;
    game.phase = 'gameover';
  }
}

// ── function: fireWeapon ──
function fireWeapon(game) {
  const w = game.worms[game.currentWormIndex];
  if (!w.alive || game.phase !== 'aiming') return;
  const weapon = WEAPONS[game.selectedWeapon];
  if (weapon.id === 'dynamite') {
    if (game.dynamiteActive) {
      game.dynamiteActive = false;
      const dx = game.dynamiteX, dy = game.dynamiteY;
      destroyTerrain(terrainGen, dx, dy, 60);
      spawnExplosion(dx, dy, 60, '#ff4444', game.particles);
      game.shake.intensity = 8; game.shake.duration = 20;
      game.freezeTimer = 8;
      const dmg = 30;
      for (const worm of game.worms) {
        if (worm.alive && !worm.dying) {
          const d = Math.sqrt((worm.x-dx)**2 + (worm.y-dy)**2);
          if (d < 60) {
            worm.takeDamage(dmg, w);
            game.floatingTexts.push(new FloatingText(worm.x, worm.y - 30, `-${dmg}`, '#ff4444', 20));
            if (!worm.alive) game.audio.play('death', 0.3);
          }
        }
      }
      game.floatingTexts.push(new FloatingText(dx, dy - 20, 'BOOM!', '#ff8844', 24));
      game.audio.play('explode', 0.4);
      game.dynamiteTimer = -1;
      w.hasDynamite = false;
      nextTurn(game);
    } else {
      game.dynamiteActive = true;
      game.dynamiteX = w.x + w.facing * 40;
      game.dynamiteY = w.y - 20;
      game.phase = 'place';
      game.audio.play('item', 0.2);
    }
    return;
  }
  if (weapon.id === 'jetpack') {
    w.jetpackActive = true;
    w.jetpackTimer = 120;
    game.phase = 'aiming';
    game.audio.play('item', 0.2);
    return;
  }
  if (weapon.id === 'parachute') {
    w.parachute = true;
    game.phase = 'aiming';
    game.audio.play('item', 0.2);
    return;
  }
  if (weapon.id === 'medkit') {
    w.hp = Math.min(MAX_HP, w.hp + 30);
    game.floatingTexts.push(new FloatingText(w.x, w.y - 30, '+30 HP', '#44ff44', 20));
    game.audio.play('item', 0.2);
    game.phase = 'aiming';
    return;
  }
  if (weapon.id === 'shield') {
    w.shielded = true;
    game.floatingTexts.push(new FloatingText(w.x, w.y - 30, 'Shielded!', '#88ccff', 18));
    game.audio.play('item', 0.2);
    game.phase = 'aiming';
    return;
  }
  if (weapon.id === 'banana') {
    const enemies = game.worms.filter(e => e.team !== w.team && e.alive);
    if (enemies.length > 0) {
      const target = enemies[Math.floor(Math.random()*enemies.length)];
      target.slipped = true;
      game.floatingTexts.push(new FloatingText(target.x, target.y - 30, 'Slipped!', '#ffcc44', 18));
    }
    game.audio.play('click', 0.15);
    game.phase = 'aiming';
    return;
  }

  // Regular weapon
  const startX = w.x + Math.cos(game.aimAngle) * 20 * w.facing;
  const startY = w.y - 10;
  const proj = new Projectile(startX, startY, game.aimAngle, game.aimPower, weapon, w);
  game.projectiles.push(proj);
  game.phase = 'projectile';
  game.audio.play(weapon.id === 'nuke' ? 'nuke' : weapon.id === 'laser' ? 'laser' : 'rocket', 0.3);
}

// ── function: applyExplosion ──
function applyExplosion(game, cx, cy, radius, fallDmg, weaponDmg) {
  destroyTerrain(terrainGen, cx, cy, radius);
  spawnExplosion(cx, cy, radius, null, game.particles);
  game.shake.intensity = radius * 0.08;
  game.shake.duration = radius * 0.15;
  game.freezeTimer = Math.floor(radius * 0.1);
  game.audio.play('explode', Math.min(0.5, radius * 0.005));

  const dmg = weaponDmg || fallDmg;
  for (const worm of game.worms) {
    if (!worm.alive || worm.dying) continue;
    const d = Math.sqrt((worm.x-cx)**2 + (worm.y-cy)**2);
    if (d < radius) {
      const falloff = 1 - d/radius;
      const actualDmg = Math.floor(dmg * falloff);
      if (actualDmg > 0) {
        const attacker = game.worms[game.currentWormIndex];
        worm.takeDamage(actualDmg, attacker);
        game.floatingTexts.push(new FloatingText(worm.x, worm.y - 30, `-${actualDmg}`, '#ff4444', 20));
        if (!worm.alive) {
          game.audio.play('death', 0.3);
          game.floatingTexts.push(new FloatingText(worm.x, worm.y - 50, '💀', '#ff0000', 28));
        }
      }
    }
  }
}

// ── function: applyFallDamage ──
function applyFallDamage(game, worm) {
  if (worm.parachute) return;
  const prevY = worm._prevY || worm.y;
  const fallDist = worm.y - prevY;
  if (fallDist > 80) {
    const dmg = Math.floor((fallDist - 80) * 0.1);
    if (dmg > 0) {
      worm.takeDamage(dmg, null);
      game.floatingTexts.push(new FloatingText(worm.x, worm.y - 30, `Fall: -${dmg}`, '#ff8844', 16));
    }
  }
}

// ── function: updateGame ──
function updateGame(game, dt) {
  const w = game.worms[game.currentWormIndex];
  if (!w || !w.alive) return;

  if (game.phase !== 'gameover') {
    game.turnTimer -= dt / 60;
    if (game.turnTimer <= 0) {
      game.turnTimer = 0;
      nextTurn(game);
      return;
    }
  }

  if (game.turnShowTimer > 0) game.turnShowTimer -= dt;
  if (game.windChangeTimer > 0) game.windChangeTimer -= dt;

  const target = w.alive ? w : null;
  if (target && target.alive) {
    game.camera.targetX = target.x - W/(2*game.camera.targetZoom);
    game.camera.targetY = target.y - H/(2*game.camera.targetZoom) - 50;
  }
  game.camera.x += (game.camera.targetX - game.camera.x) * 0.08;
  game.camera.y += (game.camera.targetY - game.camera.y) * 0.08;
  game.camera.zoom += (game.camera.targetZoom - game.camera.zoom) * 0.1;

  if (game.shake.duration > 0) {
    game.shake.x = (Math.random()-0.5) * game.shake.intensity;
    game.shake.y = (Math.random()-0.5) * game.shake.intensity;
    game.shake.duration -= dt;
    game.shake.intensity *= 0.95;
  } else {
    game.shake.x = 0; game.shake.y = 0;
  }

  if (game.freezeTimer > 0) { game.freezeTimer -= dt; return; }

  if (game.phase === 'aiming') {
    const isCPU = game.cpuCount > 0 && w.team < game.cpuCount;
    if (isCPU) {
      const enemies = game.worms.filter(e => e.team !== w.team && e.alive);
      if (enemies.length > 0) {
        const ai = aiDecide(w, enemies, terrainGen);
        game.aimAngle = ai.angle;
        game.aimPower = ai.power;
        if (ai.fire && Math.random() < 0.03) { fireWeapon(game); }
        if (ai.move !== 0) {
          w.x += ai.move * 0.3;
          w.getOnTerrain(terrainGen);
          w.facing = ai.move > 0 ? 1 : -1;
        }
      }
    }
  }

  if (game.phase === 'projectile') {
    let anyHit = false;
    for (const proj of game.projectiles) {
      if (!proj.active) continue;
      const result = proj.update(terrainGen, windAngle, game.particles);
      if (result && result.hit) {
        game.projectiles = game.projectiles.filter(p => p !== proj);
        applyExplosion(game, result.cx, result.cy, result.radius, result.fallDmg, result.damage);
        anyHit = true;
      }
    }
    if (game.projectiles.length === 0) {
      game.phase = 'aiming';
      if (anyHit) { setTimeout(() => nextTurn(game), 500); }
      else { nextTurn(game); }
    }
  }

  for (const worm of game.worms) {
    if (!worm.alive || worm.dying) continue;
    worm._prevY = worm.y;
    if (worm.jetpackActive) {
      worm.vy -= 0.4;
      worm.jetpackTimer -= dt;
      if (worm.jetpackTimer <= 0) {
        worm.jetpackActive = false;
        game.floatingTexts.push(new FloatingText(worm.x, worm.y - 30, 'Jetpack off!', '#aaa', 14));
      }
      game.particles.push(new Particle(worm.x+(Math.random()-0.5)*8, worm.y+10, (Math.random()-0.5)*2, Math.random()*2+1, 15+Math.random()*10, 'rgba(255,150,50,0.6)', 2+Math.random()*2, 'circle'));
    }
    if (worm.slipped) {
      worm.vx += (Math.random()-0.5) * 0.3;
      if (Math.random() < 0.02) worm.slipped = false;
    }
    worm.vy += GRAVITY;
    worm.vx *= 0.95;
    if (worm.inWater) {
      worm.vy *= 0.92;
      worm.vx *= 0.85;
      worm.drownTimer += dt;
      if (worm.drownTimer > 180) {
        worm.takeDamage(1, null);
        game.floatingTexts.push(new FloatingText(worm.x, worm.y - 30, 'Drowning!', '#4488ff', 16));
      }
    } else { worm.drownTimer = 0; }
    worm.x += worm.vx;
    worm.y += worm.vy;
    const terrainY = getTerrainHeight(terrainGen, worm.x);
    if (worm.y >= terrainY - 14) {
      worm.y = terrainY - 14;
      if (worm.vy > 5) {
        const fallDmg = Math.floor((worm.vy - 5) * 2);
        if (fallDmg > 0) {
          worm.takeDamage(fallDmg, null);
          game.floatingTexts.push(new FloatingText(worm.x, worm.y - 30, `-${fallDmg}`, '#ff8844', 18));
          spawnDust(worm.x, worm.y + 14, 8, game.particles);
        }
      }
      worm.vy = 0;
      worm.vx *= 0.8;
      worm.getOnTerrain(terrainGen);
    }
    if (worm.x < 10) worm.x = 10;
    if (worm.x > TERRAIN_W - 10) worm.x = TERRAIN_W - 10;
  }

  game.particles = game.particles.filter(p => p.update(dt));
  game.floatingTexts = game.floatingTexts.filter(t => t.update(dt));
  allTeamsEliminated(game);
}

// ── function: updateHUD ──
function updateHUD(game) {
  const hud = document.getElementById('hud');
  if (!hud) return;
  const w = game.worms[game.currentWormIndex];

  // Build team panel
  let teamHTML = '';
  if (w && w.alive) {
    const teamWorms = game.worms.filter(wm => wm.team === w.team);
    const teamAlive = teamWorms.filter(wm => wm.alive).length;
    teamHTML = `<div class="hud-team" style="border-left: 3px solid ${TEAM_COLORS[w.team]}">
      <div class="hud-team-name" style="color:${TEAM_COLORS[w.team]}">${TEAM_NAMES[w.team]}</div>`;
    for (const tw of teamWorms) {
      const isActive = tw === w;
      teamHTML += `<div class="hud-unit ${isActive?'active':''} ${!tw.alive?'dead':''}">
        <span style="color:${WORM_COLORS[tw.team]}">●</span>
        ${tw.name}
        <div class="hud-hp-bar"><div class="hud-hp-fill" style="width:${tw.hp}%;background:${tw.hp>60?'#44ff44':tw.hp>30?'#ffaa00':'#ff4444'}"></div></div>
      </div>`;
    }
    teamHTML += `<div style="font-size:10px;color:rgba(255,255,255,0.4);margin-top:4px">${teamAlive}/${teamWorms.length} alive</div></div>`;
  }
  let leftDiv = hud.querySelector('.hud-team-left');
  if (leftDiv) leftDiv.innerHTML = teamHTML;
  else { leftDiv = document.createElement('div'); leftDiv.className = 'hud-team-left'; leftDiv.innerHTML = teamHTML; hud.appendChild(leftDiv); }

  // Timer
  let timerContainer = hud.querySelector('.hud-timer-container');
  if (!timerContainer) {
    timerContainer = document.createElement('div');
    timerContainer.className = 'hud-timer-container';
    timerContainer.style.cssText = 'position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);display:flex;flex-direction:column;align-items:center;';
    hud.appendChild(timerContainer);
  }
  let timerEl = timerContainer.querySelector('.hud-timer');
  if (!timerEl) {
    timerEl = document.createElement('div');
    timerEl.className = 'hud-timer';
    timerContainer.appendChild(timerEl);
  }
  const pct = game.turnTimer / game.turnTimerMax;
  timerEl.textContent = Math.ceil(game.turnTimer);
  timerEl.style.fontSize = (32 + (1-pct) * 16) + 'px';
  timerEl.className = 'hud-timer' + (pct < 0.25 ? ' urgent' : '');
  timerContainer.style.transform = `translate(-50%,-50%) scale(${1 + (1-pct)*0.15})`;

  // Power bar
  let powerContainer = hud.querySelector('.hud-power-container');
  if (!powerContainer) {
    powerContainer = document.createElement('div');
    powerContainer.className = 'hud-power-container';
    powerContainer.style.cssText = 'position:absolute;bottom:60px;left:50%;transform:translateX(-50%);display:flex;flex-direction:column;align-items:center;gap:4px;';
    hud.appendChild(powerContainer);
  }
  let powerEl = powerContainer.querySelector('.hud-power-bar');
  if (!powerEl) {
    powerEl = document.createElement('div');
    powerEl.className = 'hud-power-bar';
    powerContainer.appendChild(powerEl);
  }
  let powerFill = powerEl.querySelector('.hud-power-fill');
  if (!powerFill) {
    powerFill = document.createElement('div');
    powerFill.className = 'hud-power-fill';
    powerEl.appendChild(powerFill);
  }
  let powerLabel = powerContainer.querySelector('.hud-power-label');
  if (!powerLabel) {
    powerLabel = document.createElement('div');
    powerLabel.className = 'hud-power-label';
    powerLabel.style.cssText = 'font-size:12px;color:rgba(255,255,255,0.6);';
    powerContainer.appendChild(powerLabel);
  }
  powerFill.style.width = (game.aimPower / 16 * 100) + '%';
  const pr = game.aimPower / 16;
  powerFill.style.background = `linear-gradient(90deg, #44ff44, #ffaa00 ${pr*50}%, #ff4444)`;
  powerLabel.textContent = Math.floor(game.aimPower * 10);

  // Weapon name
  let weaponNameEl = hud.querySelector('.hud-weapon-name');
  if (!weaponNameEl) {
    weaponNameEl = document.createElement('div');
    weaponNameEl.className = 'hud-weapon-name';
    weaponNameEl.style.cssText = 'position:absolute;bottom:100px;left:50%;transform:translateX(-50%);font-size:13px;font-weight:600;color:rgba(255,255,255,0.9);text-shadow:0 1px 3px rgba(0,0,0,0.4);';
    hud.appendChild(weaponNameEl);
  }
  weaponNameEl.textContent = WEAPONS[game.selectedWeapon].icon + ' ' + WEAPONS[game.selectedWeapon].name;

  // Wind
  let windContainer = hud.querySelector('.hud-wind');
  if (!windContainer) {
    windContainer = document.createElement('div');
    windContainer.className = 'hud-wind';
    windContainer.style.cssText = 'position:absolute;top:50%;right:20px;transform:translateY(-50%);display:flex;flex-direction:column;align-items:center;gap:4px;';
    hud.appendChild(windContainer);
  }
  let windArrow = windContainer.querySelector('.hud-wind-arrow');
  if (!windArrow) {
    windArrow = document.createElement('div');
    windArrow.className = 'hud-wind-arrow';
    windContainer.appendChild(windArrow);
  }
  let windVal = windContainer.querySelector('.hud-wind-val');
  if (!windVal) {
    windVal = document.createElement('div');
    windVal.className = 'hud-wind-val';
    windContainer.appendChild(windVal);
  }
  const absWind = Math.abs(windAngle) * 1000;
  windArrow.style.transform = `rotate(${windAngle > 0 ? 0 : 180}deg)`;
  windArrow.textContent = absWind > 2 ? (windAngle > 0 ? '➤' : '◀') : '—';
  windArrow.style.fontSize = (20 + absWind) + 'px';
  windVal.textContent = Math.abs(windAngle * 1000).toFixed(1);
}

// ── function: updateWeaponBar ──
function updateWeaponBar(game) {
  const bar = document.getElementById('weapon-bar');
  if (!bar) return;
  const w = game.worms[game.currentWormIndex];
  if (!w) return;
  let html = '';
  html += '<div style="font-size:10px;color:rgba(255,255,255,0.4);margin-right:8px">Weapons</div>';
  for (let i = 0; i < WEAPONS.length; i++) {
    const wp = WEAPONS[i];
    const selected = game.selectedWeapon === i;
    html += `<div class="hud-item-btn ${selected?'selected':''}" data-weapon="${i}" style="pointer-events:auto;cursor:pointer;position:relative" title="${wp.desc} | DMG:${wp.damage} | R:${wp.radius}">
      <span class="icon">${wp.icon}</span>${wp.name}</div>`;
  }
  html += '<div style="font-size:10px;color:rgba(255,255,255,0.4);margin:0 8px">Items</div>';
  for (let i = 0; i < ITEMS.length; i++) {
    const it = ITEMS[i];
    const hasItem = w.itemIndex === i;
    html += `<div class="hud-item-btn ${hasItem?'selected':''}" data-item="${i}" style="pointer-events:auto;cursor:pointer" title="${it.desc}">
      <span class="icon">${it.icon}</span>${it.name}</div>`;
  }
  bar.innerHTML = html;
}

// ── function: buildMenu ──
function buildMenu(menu) {
  menu.innerHTML = `
    <canvas class="menu-bg-canvas" id="menu-bg"></canvas>
    <div style="position:relative;z-index:1;text-align:center">
      <div class="menu-title">Worm Wars</div>
      <div class="menu-subtitle">Artillery Showdown</div>
      <div class="menu-section">
        <div class="menu-section-title">Teams</div>
        <div class="menu-row">
          ${[2,3,4].map(n => `<div class="menu-btn ${game && game.selectedTeam===n?'selected':''}" data-teams="${n}">${n} Teams</div>`).join('')}
        </div>
      </div>
      <div class="menu-section">
        <div class="menu-section-title">CPU Opponents</div>
        <div class="menu-row">
          ${[0,1,2,3].map(n => `<div class="menu-btn ${game && game.selectedCpu===n?'selected':''}" data-cpu="${n}">${n} CPU</div>`).join('')}
        </div>
      </div>
      <div class="menu-section">
        <div class="menu-section-title">Terrain</div>
        <div class="menu-row">
          <div class="menu-btn ${game && game.selectedShape==='single-island'?'selected':''}" data-shape="single-island">🏝️ Island</div>
          <div class="menu-btn ${game && game.selectedShape==='two-islands'?'selected':''}" data-shape="two-islands">🏝️🏝️ Two Islands</div>
          <div class="menu-btn ${game && game.selectedShape==='cave'?'selected':''}" data-shape="cave">🕳️ Cave</div>
        </div>
      </div>
      <div class="menu-section">
        <div class="menu-btn" id="start-btn" style="font-size:20px;padding:16px 48px;background:rgba(100,150,255,0.2);border-color:rgba(100,150,255,0.5)">▶ START GAME</div>
      </div>
      <div class="menu-section" style="margin-top:12px">
        <div style="font-size:11px;color:rgba(255,255,255,0.3)">A/D: Move | W: Jump | Mouse: Aim | Click: Fire | R/T: Angle | P/O: Power</div>
      </div>
    </div>
  `;
  startMenuBg();
}

// ── function: startMenuBg ──
function startMenuBg() {
  const canvas = document.getElementById('menu-bg');
  if (!canvas) return;
  canvas.width = window.innerWidth;
  canvas.height = window.innerHeight;
  const ctx = canvas.getContext('2d');
  const particles = [];
  for (let i = 0; i < 60; i++) {
    particles.push({
      x: Math.random() * canvas.width,
      y: Math.random() * canvas.height,
      vx: (Math.random() - 0.5) * 0.5,
      vy: -Math.random() * 0.5 - 0.1,
      size: Math.random() * 3 + 1,
      alpha: Math.random() * 0.3 + 0.05,
      color: ['#ff6b6b','#4ecdc4','#ffe66d','#a8e6cf'][Math.floor(Math.random()*4)]
    });
  }
  let t = 0;
  function draw() {
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    t += 0.016;
    for (const p of particles) {
      p.x += p.vx;
      p.y += p.vy;
      if (p.y < -10) { p.y = canvas.height + 10; p.x = Math.random() * canvas.width; }
      if (p.x < -10) p.x = canvas.width + 10;
      if (p.x > canvas.width + 10) p.x = -10;
      ctx.fillStyle = p.color;
      ctx.globalAlpha = p.alpha * (0.5 + 0.5 * Math.sin(t * 2 + p.x * 0.01));
      ctx.beginPath();
      ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2);
      ctx.fill();
    }
    ctx.globalAlpha = 1;
    if (document.getElementById('menu-bg')) requestAnimationFrame(draw);
  }
  draw();
}

// ── function: showMenu ──
function showMenu() {
  const menu = document.getElementById('menu-overlay');
  if (menu) {
    menu.style.display = 'flex';
    buildMenu(menu);
  }
}

// ── function: startGame ──
function startGame() {
  audio.play('select', 0.2);
  const teamCount = game ? game.selectedTeam : 2;
  const cpuCount = game ? game.selectedCpu : 0;
  const shape = game ? game.selectedShape : 'single-island';
  game = createGame(teamCount, cpuCount, shape);
  game.audio = audio;
  game.phase = 'aiming';
  game.camera.targetZoom = 0.85;
  nextTurn(game);
  const menu = document.getElementById('menu-overlay');
  if (menu) menu.style.display = 'none';
  lastTime = performance.now();
  if (animFrame) cancelAnimationFrame(animFrame);
  gameLoop(performance.now());
}

// ── function: drawGameOver ──
function drawGameOver(ctx) {
  if (!game || game.phase !== 'gameover') return;
  
  ctx.save();
  ctx.fillStyle = 'rgba(0,0,0,0.6)';
  ctx.fillRect(0, 0, W/ctx.getImageData(1,1,1,1).width, H/ctx.getImageData(1,1,1,1).height);
  
  // Actually, let's just draw on the canvas directly
  const cx = W/2, cy = H/2;
  
  ctx.fillStyle = 'rgba(0,0,0,0.5)';
  ctx.fillRect(0, 0, W, H);
  
  ctx.fillStyle = '#fff';
  ctx.font = 'bold 48px sans-serif';
  ctx.textAlign = 'center';
  ctx.shadowColor = 'rgba(0,0,0,0.8)';
  ctx.shadowBlur = 10;
  
  if (game.winner >= 0) {
    ctx.fillStyle = TEAM_COLORS[game.winner];
    ctx.fillText(TEAM_NAMES[game.winner] + ' Wins!', cx, cy - 40);
  } else {
    ctx.fillText('Draw!', cx, cy - 40);
  }
  
  ctx.shadowBlur = 0;
  ctx.fillStyle = 'rgba(255,255,255,0.7)';
  ctx.font = '16px sans-serif';
  ctx.fillText('Press ESC for menu', cx, cy + 10);
  
  ctx.restore();
}

// ── main ──
let game = null;
let terrainGen = null;
let windAngle = 0;
let animFrame = null;
let lastTime = 0;
let audio = null;

function init() {
  audio = new AudioSystem();
  audio.init();
  const canvas = document.getElementById('game');
  canvas.width = window.innerWidth;
  canvas.height = window.innerHeight;
  const ctx = canvas.getContext('2d');

  // Menu overlay
  const menuOverlay = document.getElementById('menu-overlay');
  buildMenu(menuOverlay);

  // Event listeners
  canvas.addEventListener('mousemove', onCanvasMouseMove);
  canvas.addEventListener('click', onCanvasClick);
  canvas.addEventListener('contextmenu', e => e.preventDefault());
  window.addEventListener('keydown', onKeyDown);
  window.addEventListener('resize', () => {
    canvas.width = window.innerWidth;
    canvas.height = window.innerHeight;
  });

  menuOverlay.addEventListener('click', onMenuClick);
  document.getElementById('weapon-bar').addEventListener('click', onWeaponBarClick);
}

function startGame() {
  audio.play('select', 0.2);
  const teamCount = game ? game.selectedTeam : 2;
  const cpuCount = game ? game.selectedCpu : 0;
  const shape = game ? game.selectedShape : 'single-island';
  game = createGame(teamCount, cpuCount, shape);
  game.audio = audio;
  game.phase = 'aiming';
  game.camera.targetZoom = 0.85;
  nextTurn(game);
  hideMenu();
  lastTime = performance.now();
  if (animFrame) cancelAnimationFrame(animFrame);
  gameLoop(performance.now());
}

function buildMenu(menu) {
  menu.innerHTML = `
    <div class="menu-title">Worm Wars</div>
    <div class="menu-subtitle">Artillery Showdown</div>
    <div class="menu-section">
      <div class="menu-section-title">Teams</div>
      <div class="menu-row">
        ${[2,3,4].map(n => `<div class="menu-btn ${game && game.selectedTeam===n?'selected':''}" data-teams="${n}">${n} Teams</div>`).join('')}
      </div>
    </div>
    <div class="menu-section">
      <div class="menu-section-title">CPU Opponents</div>
      <div class="menu-row">
        ${[0,1,2,3].map(n => `<div class="menu-btn ${game && game.selectedCpu===n?'selected':''}" data-cpu="${n}">${n} CPU</div>`).join('')}
      </div>
    </div>
    <div class="menu-section">
      <div class="menu-section-title">Terrain</div>
      <div class="menu-row">
        <div class="menu-btn ${game && game.selectedShape==='single-island'?'selected':''}" data-shape="single-island">Island</div>
        <div class="menu-btn ${game && game.selectedShape==='two-islands'?'selected':''}" data-shape="two-islands">Two Islands</div>
        <div class="menu-btn ${game && game.selectedShape==='cave'?'selected':''}" data-shape="cave">Cave</div>
      </div>
    </div>
    <div class="menu-section">
      <div class="menu-btn" id="start-btn" style="font-size:20px;padding:16px 48px;background:rgba(100,150,255,0.2);border-color:rgba(100,150,255,0.5)">▶ START GAME</div>
    </div>
  `;
}

function hideMenu() {
  document.getElementById('menu-overlay').style.display = 'none';
}

function showMenu() {
  document.getElementById('menu-overlay').style.display = 'flex';
  buildMenu(document.getElementById('menu-overlay'));
}

function onMenuClick(e) {
  const btn = e.target.closest('[data-teams],[data-cpu],[data-shape],[id="start-btn"]');
  if (!btn) return;
  audio.play('select', 0.15);
  if (btn.id === 'start-btn') {
    if (!game) {
      game = { selectedTeam: 2, selectedCpu: 0, selectedShape: 'single-island' };
    }
    startGame();
    return;
  }
  if (btn.dataset.teams !== undefined) game.selectedTeam = parseInt(btn.dataset.teams);
  if (btn.dataset.cpu !== undefined) game.selectedCpu = parseInt(btn.dataset.cpu);
  if (btn.dataset.shape !== undefined) game.selectedShape = btn.dataset.shape;
  buildMenu(document.getElementById('menu-overlay'));
}

function onWeaponBarClick(e) {
  const btn = e.target.closest('[data-weapon],[data-item]');
  if (!btn) return;
  audio.play('click', 0.1);
  if (btn.dataset.weapon !== undefined) {
    game.selectedWeapon = parseInt(btn.dataset.weapon);
    game.selectedItem = -1;
  }
  if (btn.dataset.item !== undefined) {
    game.selectedItem = parseInt(btn.dataset.item);
    game.selectedWeapon = -1;
  }
  updateWeaponBar(game);
}

function onCanvasMouseMove(e) {
  if (!game || game.phase !== 'aiming') return;
  const w = game.worms[game.currentWormIndex];
  const rect = e.target.getBoundingClientRect();
  const mx = (e.clientX - rect.left) / rect.width * W + game.camera.x;
  const my = (e.clientY - rect.top) / rect.height * H + game.camera.y;
  const dx = mx - w.x;
  const dy = my - (w.y - 10);
  game.aimAngle = Math.atan2(dy, dx);
  game.aimAngle = Math.max(-Math.PI + 0.1, Math.min(-0.05, game.aimAngle));
  game.facing = dx > 0 ? 1 : -1;
  const dist = Math.sqrt(dx*dx + dy*dy);
  game.aimPower = Math.min(16, Math.max(3, dist * 0.02));
}

function onCanvasClick(e) {
  if (!game) return;
  audio.play('click', 0.1);
  if (game.phase === 'aiming') {
    fireWeapon(game);
  } else if (game.phase === 'place') {
    game.dynamiteActive = false;
    game.dynamiteActive = true;
    game.dynamiteX = game.dynamiteX;
    game.dynamiteY = game.dynamiteY;
    // Place dynamite at current worm position
    game.phase = 'aiming';
  }
}

function onKeyDown(e) {
  if (!game) return;
  audio.play('click', 0.08);
  const w = game.worms[game.currentWormIndex];
  if (e.key === 'ArrowLeft' || e.key === 'a') {
    w.x -= 2;
    w.facing = -1;
    w.getOnTerrain(terrainGen);
  } else if (e.key === 'ArrowRight' || e.key === 'd') {
    w.x += 2;
    w.facing = 1;
    w.getOnTerrain(terrainGen);
  } else if (e.key === 'ArrowUp' || e.key === 'w') {
    if (w.y >= getTerrainHeight(terrainGen, w.x) - 16) {
      w.vy = -6;
      spawnDust(w.x, w.y + 14, 5, game.particles);
      audio.play('click', 0.1);
    }
  } else if (e.key === ' ' || e.key === 'Enter') {
    if (game.phase === 'aiming') fireWeapon(game);
  } else if (e.key === 'ArrowDown' || e.key === 's') {
    game.aimPower = Math.max(3, game.aimPower - 0.5);
  } else if (e.key === 'r') {
    game.aimAngle = Math.max(-Math.PI + 0.1, game.aimAngle - 0.05);
  } else if (e.key === 't') {
    game.aimAngle = Math.min(-0.05, game.aimAngle + 0.05);
  } else if (e.key === 'p') {
    game.aimPower = Math.max(3, game.aimPower - 0.5);
  } else if (e.key === 'o') {
    game.aimPower = Math.min(16, game.aimPower + 0.5);
  } else if (e.key === 'Escape') {
    showMenu();
  }
}

function gameLoop(timestamp) {
  const dt = Math.min(60, (timestamp - lastTime));
  lastTime = timestamp;

  if (game && game.phase !== 'gameover') {
    updateGame(game, dt);
    updateHUD(game);
    updateWeaponBar(game);

    // Turn announcement
    const turnOverlay = document.getElementById('turn-overlay');
    if (game.turnShowTimer > 0) {
      turnOverlay.classList.add('visible');
      turnOverlay.innerHTML = `<div class="turn-announce show">${game.turnWormName}</div><div class="turn-worm-name">${TEAM_NAMES[game.worms[game.currentWormIndex]?.team] || ''}</div>`;
    } else {
      turnOverlay.classList.remove('visible');
    }
  } else if (game && game.phase === 'gameover') {
    const turnOverlay = document.getElementById('turn-overlay');
    turnOverlay.classList.add('visible');
    turnOverlay.innerHTML = `<div class="turn-announce show">${game.winner >= 0 ? TEAM_NAMES[game.winner] + ' WINS!' : 'Draw!'}</div><div class="turn-worm-name" style="margin-top:20px"><div class="menu-btn" id="restart-btn" style="font-size:16px;padding:10px 30px">Play Again</div></div>`;
    const restartBtn = document.getElementById('restart-btn');
    if (restartBtn && !restartBtn._bound) {
      restartBtn._bound = true;
      restartBtn.addEventListener('click', () => {
        audio.play('select', 0.2);
        game = null;
        showMenu();
      });
    }
  }

  // Render
  const canvas = document.getElementById('game');
  const ctx = canvas.getContext('2d');
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  ctx.save();
  ctx.translate(game ? game.shake.x : 0, game ? game.shake.y : 0);

  if (game) {
    const cam = game.camera;
    drawTerrain(ctx, terrainGen, cam.x, cam.y, cam.zoom);

    // Draw projectiles
    for (const proj of game.projectiles) {
      drawProjectile(ctx, proj, cam.x, cam.y, cam.zoom);
    }

    // Draw trajectory line when aiming
    if (game.phase === 'aiming' && game.worms[game.currentWormIndex] && game.worms[game.currentWormIndex].alive) {
      const w = game.worms[game.currentWormIndex];
      const pts = predictTrajectory(w.x + Math.cos(game.aimAngle)*20*w.facing, w.y - 10, game.aimAngle, game.aimPower, WEAPONS[game.selectedWeapon], windAngle);
      drawTrajectory(ctx, pts, cam.x, cam.y, cam.zoom);
    }

    // Draw worms
    for (const worm of game.worms) {
      if (!worm.alive && !worm.dying) continue;
      drawWorm(ctx, worm, cam.x, cam.y, cam.zoom, worm === game.worms[game.currentWormIndex]);
    }

    // Draw particles
    drawParticles(ctx, game.particles, cam.x, cam.y, cam.zoom);
    drawFloatingTexts(ctx, game.floatingTexts, cam.x, cam.y, cam.zoom);

    // Dynamite indicator
    if (game.dynamiteActive) {
      ctx.fillStyle = 'rgba(255,100,50,0.8)';
      ctx.font = 'bold 20px sans-serif';
      ctx.textAlign = 'center';
      ctx.fillText('🧨 Click to place!', game.dynamiteX, game.dynamiteY - 30);
    }
  }

  ctx.restore();

  animFrame = requestAnimationFrame(gameLoop);
}

init();
</script>
</body>
</html>
<!-- agent-meta {"model":"qwen/qwen3.6-35b-a3b","provider":"lmstudio","persona":"composer","sessionId":"d4d548d4-456c-4d62-8eee-b1004d90255b","tokensIn":4083800,"tokensOut":64871,"tokensTotal":4148671,"turns":88,"toolCalls":87,"failedToolCalls":6,"timestamp":"2026-07-20T23:07:35.553Z"} -->