← Worms results

Worms

Qwen 3.6 27b MTP · 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 95,629 bytes · SHA-256 73c73350b6af
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Worm Warfare</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { background: #0a0a1a; overflow: hidden; font-family: 'Segoe UI', Tahoma, sans-serif; user-select: none; }
canvas { display: block; cursor: crosshair; }
#ui-overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none; }
#ui-overlay > * { pointer-events: auto; }

/* Menu Screen */
#menu-screen {
  position: absolute; top: 0; left: 0; width: 100%; height: 100%;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  background: linear-gradient(180deg, #0a0e27 0%, #1a1040 50%, #2d1b69 100%);
  z-index: 100;
}
#menu-screen h1 {
  font-size: 72px; color: #ffcc00; text-shadow: 0 0 30px #ff6600, 0 4px 8px rgba(0,0,0,0.5);
  margin-bottom: 10px; letter-spacing: 4px; font-weight: 900;
}
#menu-screen .subtitle { color: #aaa; font-size: 16px; margin-bottom: 40px; }
.menu-section { margin-bottom: 25px; text-align: center; }
.menu-section label { color: #ccc; font-size: 14px; display: block; margin-bottom: 8px; }
.menu-row { display: flex; gap: 10px; justify-content: center; flex-wrap: wrap; }
.menu-btn {
  padding: 12px 28px; border: 2px solid #555; background: rgba(255,255,255,0.05);
  color: #ddd; font-size: 16px; cursor: pointer; border-radius: 8px; transition: all 0.2s;
  min-width: 100px; text-align: center;
}
.menu-btn:hover { background: rgba(255,255,255,0.15); border-color: #ffcc00; color: #fff; }
.menu-btn.selected { background: rgba(255,204,0,0.2); border-color: #ffcc00; color: #ffcc00; font-weight: bold; }
#start-btn {
  margin-top: 30px; padding: 16px 60px; font-size: 22px; background: linear-gradient(180deg, #ff6600, #cc3300);
  border: none; color: white; cursor: pointer; border-radius: 12px; font-weight: bold;
  text-shadow: 0 2px 4px rgba(0,0,0,0.5); box-shadow: 0 4px 15px rgba(255,102,0,0.4);
  transition: all 0.2s; letter-spacing: 2px;
}
#start-btn:hover { transform: scale(1.05); box-shadow: 0 6px 25px rgba(255,102,0,0.6); }

/* HUD */
#hud-top {
  position: absolute; top: 0; left: 0; width: 100%; padding: 8px 16px;
  display: flex; justify-content: space-between; align-items: center;
  background: linear-gradient(180deg, rgba(0,0,0,0.7) 0%, transparent 100%);
}
.team-info { display: flex; gap: 6px; align-items: center; }
.team-badge {
  padding: 4px 12px; border-radius: 4px; font-size: 13px; font-weight: bold; color: white;
  text-shadow: 0 1px 2px rgba(0,0,0,0.5); min-width: 80px; text-align: center;
}
.team-badge.active { box-shadow: 0 0 12px currentColor; animation: pulse-glow 1s infinite alternate; }
@keyframes pulse-glow { from { opacity: 0.7; } to { opacity: 1; } }

#turn-timer-container { text-align: center; min-width: 160px; }
#turn-timer-bar { width: 140px; height: 8px; background: rgba(255,255,255,0.1); border-radius: 4px; overflow: hidden; margin-top: 4px; }
#turn-timer-fill { height: 100%; background: #4f4; transition: width 0.1s linear, background 0.3s; border-radius: 4px; }
#turn-timer-text { color: white; font-size: 20px; font-weight: bold; text-shadow: 0 0 8px rgba(255,255,255,0.3); }

#wind-indicator {
  position: absolute; top: 60px; left: 50%; transform: translateX(-50%);
  color: white; font-size: 14px; text-align: center; background: rgba(0,0,0,0.5);
  padding: 4px 16px; border-radius: 20px;
}

/* Bottom HUD */
#hud-bottom {
  position: absolute; bottom: 0; left: 0; width: 100%; padding: 8px 16px;
  display: flex; justify-content: space-between; align-items: flex-end;
  background: linear-gradient(0deg, rgba(0,0,0,0.7) 0%, transparent 100%);
}

/* Unit Info */
#unit-info {
  color: white; font-size: 14px; min-width: 200px;
}
#unit-name { font-weight: bold; font-size: 18px; margin-bottom: 4px; }
#health-bar-container { width: 180px; height: 16px; background: rgba(0,0,0,0.5); border-radius: 8px; overflow: hidden; border: 2px solid rgba(255,255,255,0.2); }
#health-bar { height: 100%; background: linear-gradient(90deg, #ff3333, #44ff44); transition: width 0.3s; border-radius: 6px; }

/* Weapon Panel */
#weapon-panel {
  display: flex; gap: 6px; align-items: center; padding: 8px;
  background: rgba(0,0,0,0.5); border-radius: 12px; margin-bottom: 4px;
}
.weapon-btn {
  width: 56px; height: 56px; border: 2px solid #444; background: rgba(255,255,255,0.05);
  color: white; cursor: pointer; border-radius: 8px; display: flex; flex-direction: column;
  align-items: center; justify-content: center; font-size: 10px; transition: all 0.15s;
}
.weapon-btn:hover { background: rgba(255,255,255,0.15); border-color: #888; }
.weapon-btn.selected { border-color: #ffcc00; background: rgba(255,204,0,0.2); box-shadow: 0 0 10px rgba(255,204,0,0.3); }
.weapon-btn .icon { font-size: 22px; margin-bottom: 2px; }

/* Action Buttons */
#action-buttons { display: flex; gap: 8px; align-items: center; }
.action-btn {
  padding: 10px 24px; border: none; color: white; cursor: pointer; border-radius: 8px;
  font-size: 14px; font-weight: bold; transition: all 0.15s; text-transform: uppercase; letter-spacing: 1px;
}
#skip-btn { background: linear-gradient(180deg, #666, #444); }
#skip-btn:hover { background: linear-gradient(180deg, #777, #555); }

/* Power Meter */
#power-meter-container {
  position: absolute; bottom: 120px; left: 50%; transform: translateX(-50%);
  width: 300px; height: 24px; background: rgba(0,0,0,0.6); border-radius: 12px;
  overflow: hidden; border: 2px solid rgba(255,255,255,0.2); display: none;
}
#power-meter-fill { height: 100%; width: 0%; background: linear-gradient(90deg, #4f4, #ff0, #f44); border-radius: 10px; transition: width 0.05s; }
#power-text { position: absolute; top: -22px; left: 50%; transform: translateX(-50%); color: white; font-size: 16px; font-weight: bold; text-shadow: 0 0 4px black; }

/* Angle display */
#angle-display {
  position: absolute; bottom: 150px; left: 50%; transform: translateX(-50%);
  color: white; font-size: 20px; font-weight: bold; text-shadow: 0 0 8px rgba(0,0,0,0.8); display: none;
}

/* Game Over */
#game-over {
  position: absolute; top: 0; left: 0; width: 100%; height: 100%;
  background: rgba(0,0,0,0.85); display: none; flex-direction: column;
  align-items: center; justify-content: center; z-index: 200;
}
#game-over h2 { font-size: 64px; color: #ffcc00; text-shadow: 0 0 30px #ff6600; margin-bottom: 20px; }
#game-over p { color: #ccc; font-size: 20px; margin-bottom: 30px; }

/* Turn announcement */
#turn-announce {
  position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
  color: white; font-size: 48px; font-weight: bold; text-shadow: 0 0 20px rgba(255,255,255,0.5), 0 4px 8px rgba(0,0,0,0.8);
  opacity: 0; transition: opacity 0.3s; pointer-events: none; z-index: 50; text-align: center;
}

/* Utility items */
#utility-panel { display: flex; gap: 4px; margin-top: 6px; }
.util-btn {
  width: 40px; height: 32px; border: 1px solid #555; background: rgba(255,255,255,0.08);
  color: #ccc; cursor: pointer; border-radius: 4px; font-size: 9px; display: flex;
  align-items: center; justify-content: center; transition: all 0.15s;
}
.util-btn:hover { background: rgba(255,255,255,0.2); }
.util-btn.selected { border-color: #ffcc00; color: #ffcc00; }

/* Minimap */
#minimap-container {
  position: absolute; top: 60px; right: 10px; width: 180px; height: 100px;
  background: rgba(0,0,0,0.5); border-radius: 8px; overflow: hidden; border: 2px solid rgba(255,255,255,0.1);
}
#minimap { width: 100%; height: 100%; }

/* Chat/Log */
#game-log {
  position: absolute; top: 60px; left: 10px; max-width: 250px;
  color: rgba(255,255,255,0.7); font-size: 12px; pointer-events: none;
}
.log-entry { padding: 2px 8px; background: rgba(0,0,0,0.3); border-radius: 4px; margin-bottom: 2px; animation: fade-log 5s forwards; }
@keyframes fade-log { from { opacity: 1; } to { opacity: 0; } }

/* Zoom controls */
#zoom-controls {
  position: absolute; bottom: 130px; right: 16px; display: flex; gap: 4px;
}
.zoom-btn {
  width: 32px; height: 32px; border: 1px solid #555; background: rgba(0,0,0,0.5);
  color: white; cursor: pointer; border-radius: 6px; font-size: 18px; display: flex;
  align-items: center; justify-content: center;
}
.zoom-btn:hover { background: rgba(255,255,255,0.2); }

/* Team colors */
.team-0 { color: #ff4444; } .team-bg-0 { background-color: #cc3333; }
.team-1 { color: #4488ff; } .team-bg-1 { background-color: #3366cc; }
.team-2 { color: #44dd44; } .team-bg-2 { background-color: #33aa33; }
.team-3 { color: #ddaa00; } .team-bg-3 { background-color: #bb8800; }

/* Instructions overlay */
#instructions {
  position: absolute; bottom: 160px; left: 50%; transform: translateX(-50%);
  color: rgba(255,255,255,0.6); font-size: 13px; text-align: center; pointer-events: none;
}
</style>
</head>
<body>

<canvas id="game-canvas"></canvas>

<div id="ui-overlay">
  <!-- Menu Screen -->
  <div id="menu-screen">
    <h1>🪱 WORM WARFARE</h1>
    <p class="subtitle">Tactical Artillery Mayhem</p>
    
    <div class="menu-section">
      <label>Terrain Type</label>
      <div class="menu-row" id="terrain-select">
        <button class="menu-btn selected" data-val="island">🏝️ Island</button>
        <button class="menu-btn" data-val="cave">🕳️ Cave</button>
        <button class="menu-btn" data-val="dual-islands">⛰️ Dual Islands</button>
      </div>
    </div>

    <div class="menu-section">
      <label>Number of Teams</label>
      <div class="menu-row" id="teams-select">
        <button class="menu-btn" data-val="2">2 Teams</button>
        <button class="menu-btn selected" data-val="3">3 Teams</button>
        <button class="menu-btn" data-val="4">4 Teams</button>
      </div>
    </div>

    <div class="menu-section">
      <label>CPU Difficulty</label>
      <div class="menu-row" id="difficulty-select">
        <button class="menu-btn" data-val="easy">Easy</button>
        <button class="menu-btn selected" data-val="medium">Medium</button>
        <button class="menu-btn" data-val="hard">Hard</button>
      </div>
    </div>

    <div class="menu-section">
      <label>CPU Teams (rest are human)</label>
      <div class="menu-row" id="cpu-select">
        <button class="menu-btn selected" data-val="1">1 CPU</button>
        <button class="menu-btn" data-val="2">2 CPU</button>
        <button class="menu-btn" data-val="3">3 CPU</button>
        <button class="menu-btn" data-val="4">All CPU</button>
      </div>
    </div>

    <button id="start-btn">⚔️ START BATTLE ⚔️</button>
  </div>

  <!-- HUD -->
  <div id="hud-top" style="display:none;">
    <div class="team-info" id="team-badges"></div>
    <div id="turn-timer-container">
      <div id="turn-timer-text">30</div>
      <div id="turn-timer-bar"><div id="turn-timer-fill"></div></div>
    </div>
    <div style="display:flex;gap:8px;">
      <button class="action-btn" id="sound-toggle" style="background:#444;padding:6px 12px;font-size:12px;">🔊</button>
      <button class="action-btn" id="menu-btn" style="background:#444;padding:6px 12px;font-size:12px;">☰ Menu</button>
    </div>
  </div>

  <div id="wind-indicator" style="display:none;">💨 Wind: <span id="wind-value">0</span></div>

  <div id="hud-bottom" style="display:none;">
    <div id="unit-info">
      <div id="unit-name">Worm 1</div>
      <div id="health-bar-container"><div id="health-bar"></div></div>
      <div id="utility-panel"></div>
    </div>

    <div style="text-align:center;">
      <div id="weapon-panel"></div>
      <div id="action-buttons">
        <button class="action-btn" id="skip-btn">Skip Turn</button>
      </div>
    </div>

    <div style="min-width:200px;text-align:right;color:white;font-size:13px;" id="turn-info"></div>
  </div>

  <div id="power-meter-container"><div id="power-text">Power</div><div id="power-meter-fill"></div></div>
  <div id="angle-display">0°</div>
  <div id="minimap-container" style="display:none;"><canvas id="minimap"></canvas></div>
  <div id="game-log"></div>
  <div id="turn-announce"></div>
  <div id="instructions"></div>

  <div id="zoom-controls" style="display:none;">
    <button class="zoom-btn" id="zoom-in">+</button>
    <button class="zoom-btn" id="zoom-out">−</button>
  </div>

  <!-- Game Over -->
  <div id="game-over">
    <h2 id="winner-text">Team Wins!</h2>
    <p id="winner-sub"></p>
    <button class="action-btn" id="restart-btn" style="background:linear-gradient(180deg,#ff6600,#cc3300);padding:14px 50px;font-size:18px;">Play Again</button>
  </div>
</div>

<script>
// ============================================================
// WORM WARFARE - Complete Game Engine
// ============================================================

const canvas = document.getElementById('game-canvas');
const ctx = canvas.getContext('2d');
const minimapCanvas = document.getElementById('minimap');
const minimapCtx = minimapCanvas.getContext('2d');

// --- Constants ---
const WORLD_W = 3000;
const WORLD_H = 1500;
const GRAVITY = 0.4;
const WATER_LEVEL = WORLD_H - 200;
const TURN_TIME = 30; // seconds per turn
const UNIT_MAX_HP = 100;
const MOVE_SPEED = 3;
const JUMP_FORCE = -9;
const FRICTION_GROUND = 0.85;
const FRICTION_AIR = 0.98;

// --- Audio Engine (Web Audio API) ---
class SoundEngine {
  constructor() {
    this.ctx = null;
    this.enabled = true;
    try { this.ctx = new (window.AudioContext || window.webkitAudioContext)(); } catch(e) {}
  }
  
  resume() { if(this.ctx && this.ctx.state === 'suspended') this.ctx.resume(); }
  
  play(type, params = {}) {
    if (!this.enabled || !this.ctx) return;
    this.resume();
    const t = this.ctx.currentTime;
    
    switch(type) {
      case 'click': this._tone(800, 0.05, 'square', 0.1); break;
      case 'select': this._tone(600, 0.08, 'sine', 0.15); break;
      case 'explosion': this._noise(params.duration || 0.4, params.vol || 0.3); break;
      case 'big_explosion': 
        this._noise(0.8, 0.5); 
        this._tone(60, 0.6, 'sine', 0.4);
        break;
      case 'hit': this._tone(200 + Math.random()*300, 0.1, 'square', 0.2); break;
      case 'jump': this._tone(400, 0.15, 'sine', 0.1, {freqEnd: 800}); break;
      case 'weapon_fire': 
        if(params.weapon === 'nuke') { this._noise(1.2, 0.6); this._tone(40, 1.0, 'sawtooth', 0.5); }
        else if(params.weapon === 'earthquake') { this._noise(0.8, 0.4); this._tone(30, 0.8, 'sine', 0.3); }
        else this._tone(150 + Math.random()*200, 0.2, 'square', 0.15);
        break;
      case 'drown': this._noise(0.3, 0.15); this._tone(300, 0.4, 'sine', 0.1, {freqEnd: 100}); break;
      case 'death': 
        this._tone(500, 0.3, 'sawtooth', 0.2, {freqEnd: 100});
        this._noise(0.3, 0.2);
        break;
      case 'timer_warning': this._tone(880, 0.05, 'square', 0.1); break;
      case 'turn_start': 
        this._tone(440, 0.1, 'sine', 0.2);
        setTimeout(() => { if(this.ctx) this._tone(660, 0.15, 'sine', 0.2); }, 100);
        break;
      case 'splash': this._noise(0.2, 0.15); break;
      case 'laser': 
        const o = this.ctx.createOscillator(); const g = this.ctx.createGain();
        o.type = 'sawtooth'; o.frequency.setValueAtTime(2000, t);
        o.frequency.exponentialRampToValueAtTime(200, t + 0.3);
        g.gain.setValueAtTime(0.15, t); g.gain.exponentialRampToValueAtTime(0.001, t + 0.3);
        o.connect(g).connect(this.ctx.destination); o.start(t); o.stop(t+0.3);
        break;
      case 'medkit': this._tone(600, 0.1, 'sine', 0.15); setTimeout(()=>this._tone(900,0.1,'sine',0.15),80); break;
      case 'parachute': this._noise(0.4, 0.08); break;
    }
  }

  _tone(freq, dur, type='sine', vol=0.1, opts={}) {
    if(!this.ctx) return;
    const t = this.ctx.currentTime;
    const o = this.ctx.createOscillator();
    const g = this.ctx.createGain();
    o.type = type;
    o.frequency.setValueAtTime(freq, t);
    if(opts.freqEnd) o.frequency.exponentialRampToValueAtTime(opts.freqEnd, t + dur);
    g.gain.setValueAtTime(vol, t);
    g.gain.exponentialRampToValueAtTime(0.001, t + dur);
    o.connect(g).connect(this.ctx.destination);
    o.start(t); o.stop(t + dur);
  }

  _noise(dur, vol) {
    if(!this.ctx) return;
    const t = this.ctx.currentTime;
    const bufSize = this.ctx.sampleRate * dur;
    const buf = this.ctx.createBuffer(1, bufSize, this.ctx.sampleRate);
    const data = buf.getChannelData(0);
    for(let i=0;i<bufSize;i++) data[i] = (Math.random()*2-1) * Math.pow(1 - i/bufSize, 2);
    const src = this.ctx.createBufferSource();
    const g = this.ctx.createGain();
    const filt = this.ctx.createBiquadFilter();
    filt.type = 'lowpass'; filt.frequency.value = 800;
    src.buffer = buf;
    g.gain.setValueAtTime(vol, t);
    g.gain.exponentialRampToValueAtTime(0.001, t + dur);
    src.connect(filt).connect(g).connect(this.ctx.destination);
    src.start(t);
  }

  // Timer tick sound that speeds up as time runs out
  playTimerTick(urgency) {
    if(!this.enabled || !this.ctx) return;
    const freq = 600 + urgency * 800;
    this._tone(freq, 0.03, 'square', 0.05);
  }
}

const sound = new SoundEngine();

// --- Particle System ---
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 = life; this.maxLife = life;
    this.color = color; this.size = size; this.type = type;
    this.gravity = 0.15; this.alpha = 1;
  }
  update() {
    this.x += this.vx; this.y += this.vy;
    this.vy += this.gravity;
    this.life--;
    this.alpha = Math.max(0, this.life / this.maxLife);
    return this.life > 0;
  }
  draw(ctx) {
    ctx.globalAlpha = this.alpha;
    if(this.type === 'circle') {
      ctx.fillStyle = this.color;
      ctx.beginPath(); ctx.arc(this.x, this.y, this.size * this.alpha, 0, Math.PI*2); ctx.fill();
    } else if(this.type === 'debris') {
      ctx.save(); ctx.translate(this.x, this.y); ctx.rotate(Math.random()*6.28);
      ctx.fillStyle = this.color;
      ctx.fillRect(-this.size/2, -this.size/2, this.size, this.size * 0.6);
      ctx.restore();
    } else if(this.type === 'smoke') {
      ctx.fillStyle = this.color;
      const s = this.size * (1 + (1-this.alpha)*2);
      ctx.beginPath(); ctx.arc(this.x, this.y, s, 0, Math.PI*2); ctx.fill();
    } else if(this.type === 'spark') {
      ctx.strokeStyle = this.color; ctx.lineWidth = 2;
      ctx.beginPath(); ctx.moveTo(this.x, this.y);
      ctx.lineTo(this.x - this.vx*3, this.y - this.vy*3); ctx.stroke();
    }
    ctx.globalAlpha = 1;
  }
}

class ParticleSystem {
  constructor() { this.particles = []; }
  
  emit(x, y, count, config) {
    for(let i=0;i<count;i++) {
      const angle = config.angle !== undefined ? config.angle + (Math.random()-0.5)*(config.spread||1) : Math.random()*Math.PI*2;
      const speed = (config.speedMin||1) + Math.random()*((config.speedMax||3)-(config.speedMin||1));
      this.particles.push(new Particle(
        x + (Math.random()-0.5)*(config.offsetX||0),
        y + (Math.random()-0.5)*(config.offsetY||0),
        Math.cos(angle)*speed, Math.sin(angle)*speed,
        config.lifeMin + Math.random()*((config.lifeMax||30)-(config.lifeMin||15)),
        config.colors ? config.colors[Math.floor(Math.random()*config.colors.length)] : (config.color||'#ff8800'),
        config.sizeMin + Math.random()*((config.sizeMax||4)-(config.sizeMin||2)),
        config.type || 'circle'
      ));
    }
  }

  explosion(x, y, radius) {
    // Fire particles
    this.emit(x, y, 30, { angle: -Math.PI/2, spread: Math.PI*1.5, speedMin: 2, speedMax: 8, lifeMin: 15, lifeMax: 40, colors: ['#ff4400','#ff8800','#ffcc00','#ffee44'], sizeMin: 3, sizeMax: 8 });
    // Smoke
    this.emit(x, y, 15, { angle: -Math.PI/2, spread: Math.PI*0.8, speedMin: 0.5, speedMax: 3, lifeMin: 30, lifeMax: 60, colors: ['#444','#555','#666'], sizeMin: 5, sizeMax: 12, type: 'smoke' });
    // Debris chunks
    this.emit(x, y, 20, { angle: -Math.PI/2, spread: Math.PI*1.2, speedMin: 3, speedMax: 10, lifeMin: 40, lifeMax: 80, colors: ['#8B6914','#A0782C','#6B5010'], sizeMin: 3, sizeMax: 8, type: 'debris' });
    // Sparks
    this.emit(x, y, 15, { angle: -Math.PI/2, spread: Math.PI*2, speedMin: 4, speedMax: 12, lifeMin: 10, lifeMax: 25, colors: ['#ffff88','#ffffff','#ffcc00'], sizeMin: 1, sizeMax: 3, type: 'spark' });
    // Ring particles
    for(let i=0;i<24;i++) {
      const a = (i/24)*Math.PI*2;
      this.particles.push(new Particle(x,y, Math.cos(a)*(radius*0.15), Math.sin(a)*(radius*0.15), 20, '#ff6600', radius*0.3, 'circle'));
    }
  }

  smallExplosion(x, y) {
    this.emit(x, y, 10, { angle: -Math.PI/2, spread: Math.PI, speedMin: 1, speedMax: 4, lifeMin: 10, lifeMax: 25, colors: ['#ff8800','#ffcc00'], sizeMin: 2, sizeMax: 5 });
    this.emit(x, y, 5, { angle: -Math.PI/2, spread: Math.PI*0.6, speedMin: 0.3, speedMax: 1.5, lifeMin: 20, lifeMax: 40, colors: ['#555','#777'], sizeMin: 3, sizeMax: 8, type: 'smoke' });
  }

  dust(x, y) {
    this.emit(x, y, 5, { angle: -Math.PI/2, spread: Math.PI*0.6, speedMin: 0.5, speedMax: 2, lifeMin: 15, lifeMax: 30, colors: ['#aa9977','#bb9955'], sizeMin: 2, sizeMax: 5, type: 'smoke' });
  }

  splash(x, y) {
    this.emit(x, y, 12, { angle: -Math.PI/2, spread: Math.PI*0.8, speedMin: 2, speedMax: 6, lifeMin: 15, lifeMax: 30, colors: ['#4488ff','#66aaff','#88ccff'], sizeMin: 2, sizeMax: 5 });
  }

  blood(x, y) {
    this.emit(x, y, 8, { angle: -Math.PI/2, spread: Math.PI*0.8, speedMin: 1, speedMax: 4, lifeMin: 10, lifeMax: 25, colors: ['#ff3333','#cc0000','#ff6666'], sizeMin: 2, sizeMax: 4 });
  }

  update() { this.particles = this.particles.filter(p => p.update()); }
  draw(ctx) { this.particles.forEach(p => p.draw(ctx)); }
}

const particles = new ParticleSystem();

// --- Floating Combat Text ---
class FloatingText {
  constructor(x, y, text, color='#fff', size=20) {
    this.x = x; this.y = y; this.text = text; this.color = color; this.size = size;
    this.life = 60; this.maxLife = 60; this.vy = -2;
  }
  update() { this.y += this.vy; this.vy *= 0.95; return --this.life > 0; }
  draw(ctx) {
    const alpha = Math.min(1, this.life / (this.maxLife * 0.3));
    ctx.globalAlpha = alpha;
    ctx.fillStyle = this.color;
    ctx.font = `bold ${this.size}px 'Segoe UI', sans-serif`;
    ctx.textAlign = 'center';
    ctx.strokeStyle = '#000'; ctx.lineWidth = 3;
    ctx.strokeText(this.text, this.x, this.y);
    ctx.fillText(this.text, this.x, this.y);
    ctx.globalAlpha = 1;
  }
}

let floatingTexts = [];

// --- Terrain System ---
class Terrain {
  constructor() {
    this.canvas = document.createElement('canvas');
    this.ctx = this.canvas.getContext('2d');
    this.terrainCanvas = document.createElement('canvas');
    this.terrainCtx = this.terrainCanvas.getContext('2d');
    this.waterCanvas = document.createElement('canvas');
    this.waterCtx = this.waterCanvas.getContext('2d');
  }

  generate(type, waterLevel) {
    const w = WORLD_W, h = WORLD_H;
    this.canvas.width = w; this.canvas.height = h;
    this.terrainCanvas.width = w; this.terrainCanvas.height = h;
    this.waterCanvas.width = w; this.waterCanvas.height = h;

    // Sky gradient background
    const skyGrad = this.ctx.createLinearGradient(0, 0, 0, h);
    skyGrad.addColorStop(0, '#1a237e');
    skyGrad.addColorStop(0.4, '#283593');
    skyGrad.addColorStop(0.7, '#5c6bc0');
    skyGrad.addColorStop(1, '#9fa8da');
    this.ctx.fillStyle = skyGrad;
    this.ctx.fillRect(0, 0, w, h);

    // Stars
    for(let i=0;i<200;i++) {
      const sx = Math.random()*w, sy = Math.random()*h*0.5;
      this.ctx.fillStyle = `rgba(255,255,255,${Math.random()*0.8+0.2})`;
      this.ctx.fillRect(sx, sy, 1+Math.random(), 1+Math.random());
    }

    // Generate terrain heightmap
    const heights = new Float32Array(w);
    
    if(type === 'island') {
      for(let x=0;x<w;x++) {
        const nx = x/w;
        let h_val = 0.5 + 0.3*Math.sin(nx*6) + 0.15*Math.sin(nx*13+2) + 0.08*Math.sin(nx*27+5);
        // Make edges lower (island shape)
        const edgeFactor = Math.pow(Math.sin(nx * Math.PI), 0.5);
        heights[x] = h_val * edgeFactor;
      }
    } else if(type === 'cave') {
      for(let x=0;x<w;x++) {
        const nx = x/w;
        let topH = 0.3 + 0.1*Math.sin(nx*8) + 0.05*Math.sin(nx*20);
        let botH = 0.7 + 0.1*Math.sin(nx*6+1) + 0.05*Math.sin(nx*15+3);
        heights[x] = (topH + botH)/2; // We'll handle cave differently
      }
    } else if(type === 'dual-islands') {
      for(let x=0;x<w;x++) {
        const nx = x/w;
        let h_val = 0.3 + 0.2*Math.sin(nx*10) + 0.1*Math.sin(nx*22);
        // Two peaks
        const peak1 = Math.exp(-Math.pow((nx-0.3)*4, 2));
        const peak2 = Math.exp(-Math.pow((nx-0.7)*4, 2));
        heights[x] = h_val + (peak1+peak2)*0.5;
      }
    }

    // Draw terrain with pixel-level detail
    this._drawTerrain(heights, type);
    
    // Build cached heightmap for fast ground lookups
    this._buildHeightMap(type, heights);
    
    // Water
    this._drawWater(waterLevel);
  }

  _buildHeightMap(type, heights) {
    const w = WORLD_W, h = WORLD_H;
    this._heightMap = new Int32Array(w).fill(WORLD_H);
    
    if(type === 'cave') {
      // For caves, the "ground" is the floor (bottom surface)
      for(let x=0;x<w;x++) {
        const nx = x/w;
        let floorY = Math.floor((0.75 + 0.08*Math.sin(nx*8+2) + 0.04*Math.sin(nx*20+1))*h);
        this._heightMap[x] = floorY - 1;
      }
    } else {
      // For islands, use the heightmap directly
      for(let x=0;x<w;x++) {
        const surfaceY = Math.ceil(heights[x] * h);
        this._heightMap[x] = surfaceY - 1;
      }
    }
  }

  _drawTerrain(heights, type) {
    const w = WORLD_W, h = WORLD_H;
    const tCtx = this.terrainCtx;
    
    // Use ImageData for fast pixel manipulation
    const imgData = tCtx.createImageData(w, h);
    const data = imgData.data;
    
    if(type === 'cave') {
      for(let x=0;x<w;x++) {
        const nx = x/w;
        let ceilY = Math.floor((0.2 + 0.08*Math.sin(nx*10) + 0.04*Math.sin(nx*25))*h);
        let floorY = Math.floor((0.75 + 0.08*Math.sin(nx*8+2) + 0.04*Math.sin(nx*20+1))*h);
        
        for(let y=0;y<ceilY;y++) {
          const depth = y/ceilY;
          const idx = (y*w+x)*4;
          data[idx]   = Math.floor(60-depth*30);
          data[idx+1] = Math.floor(50-depth*25);
          data[idx+2] = Math.floor(40-depth*20);
          data[idx+3] = 255;
        }
        
        for(let y=floorY;y<h;y++) {
          const depth = (y-floorY)/(h-floorY);
          const idx = (y*w+x)*4;
          data[idx]   = Math.floor(80-depth*40);
          data[idx+1] = Math.floor(65-depth*35);
          data[idx+2] = Math.floor(45-depth*25);
          data[idx+3] = 255;
        }
      }
    } else {
      for(let x=0;x<w;x++) {
        const surfaceY = heights[x] * h;
        
        for(let y=Math.ceil(surfaceY);y<h;y++) {
          const depth = (y-surfaceY)/(h-surfaceY);
          const idx = (y*w+x)*4;
          
          if(depth < 0.02) {
            data[idx]   = Math.floor(60+Math.random()*30);
            data[idx+1] = Math.floor(140+Math.random()*40);
            data[idx+2] = Math.floor(40+Math.random()*20);
          } else if(depth < 0.15) {
            const noise = Math.sin(x*0.5+y*0.3)*10;
            data[idx]   = Math.floor(139+noise);
            data[idx+1] = Math.floor(119+noise);
            data[idx+2] = Math.floor(80+noise);
          } else if(depth < 0.5) {
            const noise = Math.sin(x*0.3+y*0.7)*15;
            data[idx]   = Math.floor(100+noise);
            data[idx+1] = Math.floor(90+noise);
            data[idx+2] = Math.floor(80+noise);
          } else {
            const noise = Math.sin(x*0.2+y*0.4)*10;
            data[idx]   = Math.floor(70+noise);
            data[idx+1] = Math.floor(65+noise);
            data[idx+2] = Math.floor(60+noise);
          }
          data[idx+3] = 255;
        }
      }
    }

    tCtx.putImageData(imgData, 0, 0);
    this.ctx.drawImage(this.terrainCanvas, 0, 0);
  }

  _drawWater(waterLevel) {
    // Water is drawn in the main draw loop now for animation
  }

  // Get terrain pixel data for collision/destruction
  getTerrainPixel(x, y) {
    if(x<0||x>=WORLD_W||y<0||y>=WORLD_H) return 'sky';
    const px = Math.floor(x), py = Math.y=Math.floor(y);
    try {
      const pixel = this.terrainCtx.getImageData(px, py, 1, 1).data;
      if(pixel[3] > 0) return 'ground';
      return 'sky';
    } catch(e) { return 'sky'; }
  }

  // Destroy terrain in a circular area - returns debris info
  destroy(x, y, radius) {
    const px = Math.floor(x), py = Math.floor(y), pr = Math.ceil(radius);
    
    try {
      this.terrainCtx.globalCompositeOperation = 'destination-out';
      this.terrainCtx.beginPath();
      this.terrainCtx.arc(px, py, pr, 0, Math.PI*2);
      this.terrainCtx.fill();
      this.terrainCtx.globalCompositeOperation = 'source-over';
      
      // Also update main canvas
      this.ctx.drawImage(this.terrainCanvas, 0, 0);
    } catch(e) {}

    // Generate falling debris chunks from destroyed area
    const debrisCount = Math.floor(radius * 1.5);
    for(let i=0;i<debrisCount;i++) {
      const angle = Math.random()*Math.PI*2;
      const dist = Math.random()*radius;
      const dx = px + Math.cos(angle)*dist;
      const dy = py + Math.sin(angle)*dist;
      
      if(dy > 0 && dy < WORLD_H) {
        particles.particles.push(new Particle(
          dx, dy, (Math.random()-0.5)*3, -Math.random()*2-1,
          60+Math.random()*40,
          ['#8B6914','#A0782C','#6B5010','#5a4510'][Math.floor(Math.random()*4)],
          2+Math.random()*6, 'debris'
        ));
      }
    }
  }

  // Get ground Y at X position (for unit standing)
  // Cached heightmap for fast ground lookups
  _heightMap = null;

  getGroundY(x) {
    const px = Math.floor(x);
    if(px<0||px>=WORLD_W) return WORLD_H;
    
    // Use cached height map if available
    if(this._heightMap && this._heightMap[px] !== undefined) {
      return this._heightMap[px];
    }
    
    // Fallback: scan from top
    for(let y=0;y<WORLD_H;y++) {
      try {
        const pixel = this.terrainCtx.getImageData(px, y, 1, 1).data;
        if(pixel[3] > 0) return y - 1;
      } catch(e) {}
    }
    return WORLD_H; // No ground found
  }

  isGround(x, y) {
    const px = Math.floor(x), py = Math.floor(y);
    if(px<0||px>=WORLD_W||py<0||py>=WORLD_H) return false;
    
    // Use cached height map for fast check
    if(this._heightMap && this._heightMap[px] !== undefined) {
      const groundY = this._heightMap[px];
      // Check if y is at or below the surface (but also check cave ceiling)
      return py >= groundY;
    }
    
    try {
      const pixel = this.terrainCtx.getImageData(px, py, 1, 1).data;
      return pixel[3] > 0;
    } catch(e) { return false; }
  }

  // Check if position is in water
  isInWater(x, y) {
    return y >= WATER_LEVEL && !this.isGround(x, y);
  }

  render(ctx, camX, camY, zoom) {
    ctx.drawImage(this.canvas, 0, 0);
    
    // Draw terrain on top of sky
    ctx.save();
    ctx.setTransform(zoom, 0, 0, zoom, -camX*zoom + canvas.width/2, -camY*zoom + canvas.height/2);
    ctx.drawImage(this.terrainCanvas, 0, 0);
    
    // Draw water with animation
    this._drawWaterAnimated(WATER_LEVEL);
    ctx.restore();
  }

  _drawWaterAnimated(waterLevel) {
    const w = WORLD_W;
    this.ctx.save();
    
    // Animated water surface
    for(let x=0;x<w;x+=2) {
      const waveY = waterLevel + Math.sin(x*0.03+Date.now()*0.003)*4;
      this.ctx.fillStyle = `rgba(30,80,180,0.5)`;
      this.ctx.fillRect(x, waveY, 2, WORLD_H - waveY);
    }
    
    // Surface highlight
    this.ctx.strokeStyle = 'rgba(100,180,255,0.4)';
    this.ctx.lineWidth = 2;
    this.ctx.beginPath();
    for(let x=0;x<w;x+=3) {
      const waveY = waterLevel + Math.sin(x*0.03+Date.now()*0.003)*4;
      if(x===0) this.ctx.moveTo(x, waveY);
      else this.ctx.lineTo(x, waveY);
    }
    this.ctx.stroke();
    
    this.ctx.restore();
  }

  renderMinimap(ctx) {
    const mw = minimapCanvas.width, mh = minimapCanvas.height;
    ctx.fillStyle = '#1a237e';
    ctx.fillRect(0, 0, mw, mh);
    
    // Draw terrain
    const scaleX = mw / WORLD_W;
    const scaleY = mh / WORLD_H;
    
    for(let x=0;x<WORLD_W;x+=4) {
      const gy = this.getGroundY(x);
      ctx.fillStyle = '#5a7a3a';
      ctx.fillRect(x*scaleX, gy*scaleY, scaleX*4, (WORLD_H-gy)*scaleY);
    }
    
    // Water
    ctx.fillStyle = 'rgba(30,80,180,0.5)';
    ctx.fillRect(0, WATER_LEVEL*scaleY, mw, mh - WATER_LEVEL*scaleY);
  }
}

// --- Weapon Definitions ---
const WEAPONS = [
  { id:'rocket', name:'Rocket', icon:'🚀', category:'indirect', damage:35, radius:60, speed:8, color:'#ff4400', desc:'Parabolic explosive projectile' },
  { id:'grenade', name:'Grenade', icon:'💣', category:'indirect', damage:25, radius:40, speed:7, color:'#ccaa00', desc:'Bouncing explosive' },
  { id:'cannonball', name:'Cannon', icon:'⚫', category:'indirect', damage:30, radius:50, speed:12, color:'#888', desc:'Heavy fast projectile' },
  { id:'shotgun', name:'Shotgun', icon:'🔫', category:'direct', damage:20, radius:0, speed:0, color:'#ffaa00', desc:'Instant-hit spread shot' },
  { id:'laser', name:'Laser', icon:'⚡', category:'direct', damage:15, radius:0, speed:0, color:'#ff00ff', desc:'Instant beam weapon' },
  { id:'cluster', name:'Cluster', icon:'💥', category:'indirect', damage:20, radius:35, speed:6, color:'#ff8844', desc:'Splits into sub-bombs' },
  { id:'nuke', name:'NUKE', icon:'☢️', category:'aoe', damage:80, radius:200, speed:5, color:'#ff0000', desc:'Massive area destruction (1 per game)' },
  { id:'earthquake', name:'Quake', icon:'🌋', category:'aoe', damage:40, radius:300, speed:0, color:'#aa6600', desc:'Shakes entire map' },
];

const UTILITIES = [
  { id:'medkit', name:'Medkit', icon:'💊', desc:'+25 HP', maxUses:1 },
  { id:'parachute', name:'Parachute', icon:'🪂', desc:'Safe landing', maxUses:1 },
  { id:'binoculars', name:'Scout', icon:'🔭', desc:'See enemy positions', maxUses:1 },
];

// --- Projectile ---
class Projectile {
  constructor(x, y, vx, vy, weapon, owner) {
    this.x = x; this.y = y; this.vx = vx; this.vy = vy;
    this.weapon = weapon; this.owner = owner;
    this.alive = true; this.bounces = 0;
    this.trail = [];
    this.age = 0;
    
    if(weapon.id === 'cluster') {
      this.splitDone = false;
      this.splitAge = 60; // Split after ~1 second
    }
  }

  update(wind) {
    if(!this.alive) return;
    this.age++;
    
    // Store trail position
    this.trail.push({x: this.x, y: this.y});
    if(this.trail.length > 20) this.trail.shift();
    
    // Apply wind
    this.vx += wind * 0.01;
    
    // Gravity (except for direct weapons which don't use projectiles)
    if(this.weapon.category === 'indirect') {
      this.vy += GRAVITY;
    }
    
    this.x += this.vx;
    this.y += this.vy;

    // Cluster bomb split
    if(this.weapon.id === 'cluster' && !this.splitDone && this.age >= this.splitAge) {
      this.splitDone = true;
      for(let i=0;i<5;i++) {
        const a = (i/5)*Math.PI*2 + Math.random()*0.3;
        game.projectiles.push(new Projectile(
          this.x, this.y,
          Math.cos(a)*4, Math.sin(a)*4 - 2,
          {id:'grenade', name:'Sub-bomb', category:'indirect', damage:15, radius:20, speed:4},
          this.owner
        ));
      }
    }

    // Check bounds
    if(this.x < -50 || this.x > WORLD_W+50 || this.y > WORLD_H+50) {
      this.alive = false;
      return;
    }

    // Terrain collision
    if(terrain.isGround(Math.floor(this.x), Math.floor(this.y))) {
      this.explode();
      return;
    }

    // Water collision
    if(this.y >= WATER_LEVEL) {
      sound.play('splash');
      particles.splash(this.x, this.y);
      this.alive = false;
      return;
    }

    // Unit collision
    for(const unit of game.units) {
      if(!unit.alive || unit.team === this.owner) continue;
      const dx = this.x - unit.x, dy = this.y - unit.y;
      if(Math.sqrt(dx*dx+dy*dy) < 15) {
        this.explode();
        return;
      }
    }

    // Bounce for grenades
    if(this.weapon.id === 'grenade' && this.bounces < 3) {
      const groundY = terrain.getGroundY(this.x);
      if(Math.abs(this.y - groundY) < 5 && this.vy > 0) {
        this.vy *= -0.5;
        this.vx *= 0.7;
        this.bounces++;
        particles.dust(this.x, this.y);
        sound.play('hit');
      }
    }
  }

  explode() {
    this.alive = false;
    
    if(this.weapon.category === 'aoe' && this.weapon.id === 'nuke') {
      terrain.destroy(this.x, this.y, this.weapon.radius);
      particles.explosion(this.x, this.y, this.weapon.radius);
      sound.play('big_explosion');
      game.screenShake = 20;
      game.impactFreeze = 30;
      
      // Damage all units in radius
      for(const unit of game.units) {
        if(!unit.alive) continue;
        const dx = this.x - unit.x, dy = this.y - unit.y;
        const dist = Math.sqrt(dx*dx+dy*dy);
        if(dist < this.weapon.radius) {
          const dmg = Math.floor(this.weapon.damage * (1 - dist/this.weapon.radius));
          unit.takeDamage(dmg, this.owner);
        }
      }
    } else if(this.weapon.category === 'aoe' && this.weapon.id === 'earthquake') {
      sound.play('weapon_fire', {weapon:'earthquake'});
      game.screenShake = 15;
      
      // Damage all units based on terrain instability
      for(const unit of game.units) {
        if(!unit.alive || unit.team === this.owner) continue;
        const groundY = terrain.getGroundY(unit.x);
        if(Math.abs(unit.y - groundY) > 50) {
          // Units high up take more damage from shaking
          unit.takeDamage(this.weapon.damage, this.owner);
        } else {
          unit.takeDamage(Math.floor(this.weapon.damage * 0.3), this.owner);
        }
      }
      
      // Create random terrain destructions
      for(let i=0;i<8;i++) {
        const rx = Math.random()*WORLD_W;
        const ry = terrain.getGroundY(rx) - Math.random()*50;
        terrain.destroy(rx, ry, 30+Math.random()*20);
        particles.smallExplosion(rx, ry);
      }
    } else {
      // Normal explosion
      terrain.destroy(this.x, this.y, this.weapon.radius);
      
      if(this.weapon.radius > 40) {
        particles.explosion(this.x, this.y, this.weapon.radius);
        sound.play('explosion', {duration: 0.3+this.weapon.radius/200});
        game.screenShake = Math.min(15, this.weapon.radius / 8);
      } else {
        particles.smallExplosion(this.x, this.y);
        sound.play('hit');
        game.screenShake = 5;
      }

      // Damage units in radius
      for(const unit of game.units) {
        if(!unit.alive || unit.team === this.owner) continue;
        const dx = this.x - unit.x, dy = this.y - unit.y;
        const dist = Math.sqrt(dx*dx+dy*dy);
        if(dist < this.weapon.radius + 10) {
          const falloff = Math.max(0, 1 - dist/(this.weapon.radius+10));
          const dmg = Math.floor(this.weapon.damage * falloff);
          unit.takeDamage(dmg, this.owner);
        }
      }
    }
    
    game.addLog(`${WEAPONS.find(w=>w.id===this.weapon.id)?.name||'Weapon'} hit!`, '#ff8800');
  }

  draw(ctx) {
    if(!this.alive) return;
    
    // Draw trail
    if(this.trail.length > 1 && this.weapon.category === 'indirect') {
      ctx.strokeStyle = `rgba(255,200,100,0.3)`;
      ctx.lineWidth = 2;
      ctx.beginPath();
      for(let i=0;i<this.trail.length;i++) {
        const p = this.trail[i];
        if(i===0) ctx.moveTo(p.x, p.y);
        else ctx.lineTo(p.x, p.y);
      }
      ctx.stroke();
    }

    // Draw projectile
    ctx.fillStyle = this.weapon.color;
    ctx.beginPath();
    const size = this.weapon.id === 'nuke' ? 8 : (this.weapon.id === 'cannonball' ? 6 : 4);
    ctx.arc(this.x, this.y, size, 0, Math.PI*2);
    ctx.fill();
    
    // Glow effect
    ctx.fillStyle = `rgba(255,255,200,${0.3+Math.sin(this.age*0.3)*0.2})`;
    ctx.beginPath();
    ctx.arc(this.x, this.y, size+3, 0, Math.PI*2);
    ctx.fill();
  }
}

// --- Unit/Worm Class ---
class Worm {
  constructor(x, y, team, index) {
    this.x = x; this.y = y;
    this.vx = 0; this.vy = 0;
    this.team = team;
    this.index = index;
    this.hp = UNIT_MAX_HP;
    this.alive = true;
    this.onGround = false;
    this.facing = 1; // 1=right, -1=left
    this.name = `Worm ${index+1}`;
    this.hasParachute = false;
    this.animFrame = 0;
    this.deathAnim = 0;
    this.invincible = 0;
    
    // Movement budget per turn
    this.moveBudget = 200; // pixels of movement allowed
    
    // Team colors
    const teamColors = ['#ff4444','#4488ff','#44dd44','#ddaa00'];
    this.color = teamColors[team % 4];
    this.bodyColor = this._lighten(teamColors[team % 4], 30);
  }

  _lighten(hex, amount) {
    let r = parseInt(hex.slice(1,3),16), g = parseInt(hex.slice(3,5),16), b = parseInt(hex.slice(5,7),16);
    r = Math.min(255,r+amount); g = Math.min(255,g+amount); b = Math.min(255,b+amount);
    return `rgb(${r},${g},${b})`;
  }

  update(wind) {
    if(!this.alive) { this.deathAnim++; return; }
    
    if(this.invincible > 0) this.invincible--;
    this.animFrame += 0.1;

    // Apply wind to movement (slight effect on units too)
    this.vx += wind * 0.002;

    // Gravity
    this.vy += GRAVITY;
    
    // Friction
    if(this.onGround) {
      this.vx *= FRICTION_GROUND;
    } else {
      this.vx *= FRICTION_AIR;
      
      // Parachute effect
      if(this.hasParachute && this.vy > 0) {
        this.vy *= 0.92;
        if(Math.random()<0.3) particles.dust(this.x + (Math.random()-0.5)*10, this.y-10);
      }
    }

    // Move
    const newX = this.x + this.vx;
    const newY = this.y + this.vy;

    // Horizontal collision with terrain
    if(!terrain.isGround(Math.floor(newX), Math.floor(this.y)) && 
       !terrain.isGround(Math.floor(newX), Math.floor(this.y-10))) {
      this.x = newX;
    } else {
      this.vx = 0;
    }

    // Vertical collision with terrain
    if(!terrain.isGround(Math.floor(this.x), Math.floor(newY)) && 
       !terrain.isGround(Math.floor(this.x+5), Math.floor(newY)) &&
       !terrain.isGround(Math.floor(this.x-5), Math.floor(newY))) {
      this.y = newY;
      this.onGround = false;
    } else {
      if(this.vy > 0) {
        // Landing on ground
        const groundY = terrain.getGroundY(this.x);
        if(groundY < WORLD_H - 5 && Math.abs(newY - groundY) < 15) {
          this.y = groundY;
          
          // Fall damage
          if(this.vy > 12 && !this.hasParachute) {
            const fallDmg = Math.floor((this.vy - 12) * 3);
            if(fallDmg > 0) {
              this.takeDamage(fallDmg, -1); // Environmental damage
              game.addLog(`${this.name} took ${fallDmg} fall damage!`, '#ffaa00');
            }
          }
          
          if(Math.abs(this.vy) > 3) particles.dust(this.x, this.y);
        }
        this.vy = 0;
        this.onGround = true;
      } else {
        this.vy = 0;
      }
    }

    // Water physics - drowning
    if(terrain.isInWater(this.x, this.y)) {
      if(!this.hasParachute) {
        // Drowning damage
        if(Math.random() < 0.02) {
          this.takeDamage(1, -1);
          particles.splash(this.x + (Math.random()-0.5)*10, this.y);
        }
      } else {
        // Float on water with parachute
        this.vy = Math.min(this.vy, 0);
        if(this.y >= WATER_LEVEL) this.y = WATER_LEVEL - 2;
      }
    }

    // Keep in bounds
    this.x = Math.max(10, Math.min(WORLD_W-10, this.x));
    
    // If fell off the world
    if(this.y > WORLD_H + 50) {
      this.hp = 0;
      this.alive = false;
      game.addLog(`${this.name} fell to their doom!`, '#ff4444');
      sound.play('death');
    }
  }

  move(dir) {
    if(!this.alive || this.moveBudget <= 0) return;
    this.vx = dir * MOVE_SPEED;
    this.facing = dir;
    this.moveBudget -= Math.abs(this.vx);
  }

  jump() {
    if(!this.alive || !this.onGround || this.moveBudget <= 0) return;
    this.vy = JUMP_FORCE;
    this.onGround = false;
    this.moveBudget -= 15;
    sound.play('jump');
    particles.dust(this.x, this.y);
  }

  takeDamage(amount, attackerTeam) {
    if(!this.alive || this.invincible > 0) return;
    
    const oldHp = this.hp;
    this.hp = Math.max(0, this.hp - amount);
    
    // Floating damage text
    floatingTexts.push(new FloatingText(this.x, this.y-20, `-${amount}`, '#ff4444', 18));
    
    particles.blood(this.x, this.y);
    sound.play('hit');

    if(this.hp <= 0 && oldHp > 0) {
      this.alive = false;
      game.addLog(`${this.name} was eliminated!`, '#ff0000');
      sound.play('death');
      
      // Death explosion particles
      particles.explosion(this.x, this.y, 30);
    }
  }

  draw(ctx) {
    if(!this.alive && this.deathAnim > 60) return;
    
    const alpha = this.alive ? 1 : Math.max(0, 1 - this.deathAnim/60);
    ctx.globalAlpha = alpha;
    
    // Invincibility flash
    if(this.invincible > 0 && Math.floor(this.invincible/3)%2===0) {
      ctx.globalAlpha *= 0.5;
    }

    const x = this.x, y = this.y;
    
    // Parachute visual
    if(this.hasParachute && !this.onGround) {
      ctx.strokeStyle = '#fff';
      ctx.lineWidth = 2;
      ctx.beginPath();
      ctx.arc(x, y-30, 15, Math.PI, 0);
      ctx.stroke();
      // Strings
      ctx.beginPath();
      ctx.moveTo(x-15, y-30); ctx.lineTo(x-5, y-8);
      ctx.moveTo(x+15, y-30); ctx.lineTo(x+5, y-8);
      ctx.stroke();
    }

    // Worm body (segmented)
    const segments = 4;
    for(let i=segments-1;i>=0;i--) {
      const segX = x + Math.sin(this.animFrame+i*0.8)*3 - this.facing*i*5;
      const segY = y - i*6 + Math.abs(Math.sin(this.animFrame+i*0.5))*2;
      const size = 7 - i*1.2;
      
      ctx.fillStyle = i===0 ? this.color : this.bodyColor;
      ctx.beginPath();
      ctx.arc(segX, segY, size, 0, Math.PI*2);
      ctx.fill();
      
      // Outline
      ctx.strokeStyle = 'rgba(0,0,0,0.3)';
      ctx.lineWidth = 1;
      ctx.stroke();
    }

    // Eyes (on head segment)
    const eyeX = x + this.facing*4;
    const eyeY = y - 2;
    
    // Eye whites
    ctx.fillStyle = '#fff';
    ctx.beginPath(); ctx.arc(eyeX-3, eyeY-2, 3.5, 0, Math.PI*2); ctx.fill();
    ctx.beginPath(); ctx.arc(eyeX+3, eyeY-2, 3.5, 0, Math.PI*2); ctx.fill();
    
    // Pupils (look in facing direction)
    ctx.fillStyle = '#111';
    ctx.beginPath(); ctx.arc(eyeX-3+this.facing*1.5, eyeY-2, 1.8, 0, Math.PI*2); ctx.fill();
    ctx.beginPath(); ctx.arc(eyeX+3+this.facing*1.5, eyeY-2, 1.8, 0, Math.PI*2); ctx.fill();

    // Mouth (smirk)
    if(this.alive) {
      ctx.strokeStyle = '#333';
      ctx.lineWidth = 1.5;
      ctx.beginPath();
      ctx.arc(eyeX+this.facing*2, eyeY+4, 3, 0.2, Math.PI-0.2);
      ctx.stroke();
    }

    // Health bar above worm
    if(this.alive && this.hp < UNIT_MAX_HP) {
      const barW = 30;
      const hpRatio = this.hp / UNIT_MAX_HP;
      ctx.fillStyle = 'rgba(0,0,0,0.5)';
      ctx.fillRect(x-barW/2-1, y-25, barW+2, 6);
      ctx.fillStyle = hpRatio > 0.5 ? '#4f4' : (hpRatio > 0.25 ? '#ff0' : '#f44');
      ctx.fillRect(x-barW/2, y-24, barW*hpRatio, 4);
    }

    // Active unit indicator
    if(game.activeUnit === this) {
      // Pulsing arrow above
      const arrowY = y - 35 + Math.sin(Date.now()*0.005)*5;
      ctx.fillStyle = '#ffcc00';
      ctx.beginPath();
      ctx.moveTo(x, arrowY+8);
      ctx.lineTo(x-6, arrowY);
      ctx.lineTo(x+6, arrowY);
      ctx.closePath();
      ctx.fill();
    }

    ctx.globalAlpha = 1;
  }
}

// --- AI Controller ---
class AIController {
  constructor(difficulty) {
    this.difficulty = difficulty; // 'easy', 'medium', 'hard'
    this.thinkingTimer = 0;
    this.actionQueue = [];
  }

  getAccuracy() {
    switch(this.difficulty) {
      case 'easy': return 0.4;
      case 'medium': return 0.65;
      case 'hard': return 0.85;
      default: return 0.65;
    }
  }

  getReactionTime() {
    switch(this.difficulty) {
      case 'easy': return 120; // ~2 seconds
      case 'medium': return 80;
      case 'hard': return 40;
      default: return 80;
    }
  }

  startTurn(unit) {
    this.currentUnit = unit;
    this.thinkingTimer = this.getReactionTime();
    this.actionQueue = [];
    
    // AI decides what to do
    setTimeout(() => this.planAction(), 500);
  }

  planAction() {
    const unit = this.currentUnit;
    if(!unit || !unit.alive) return;

    // Risk assessment: check for environmental hazards
    const groundY = terrain.getGroundY(unit.x);
    const nearWater = terrain.isInWater(unit.x, unit.y);
    const onCliffEdge = this._isOnCliffEdge(unit);
    
    // If in danger, move to safety first
    if(nearWater && !unit.hasParachute) {
      this.actionQueue.push({type:'move', dir: this._findSafeDirection(unit)});
      return;
    }
    
    if(onCliffEdge && Math.random() < 0.7) {
      // Move away from cliff edge
      const safeDir = unit.x < groundY ? -1 : 1;
      this.actionQueue.push({type:'move', dir: safeDir});
    }

    // Find best target and weapon
    const targets = game.units.filter(u => u.alive && u.team !== unit.team);
    if(targets.length === 0) {
      this.actionQueue.push({type:'skip'});
      return;
    }

    // Prioritize low HP targets (hard AI) or random (easy)
    let target;
    if(this.difficulty === 'hard') {
      target = [...targets].sort((a,b) => a.hp - b.hp)[0];
    } else {
      target = targets[Math.floor(Math.random()*targets.length)];
    }

    // Choose weapon based on distance and accuracy
    const dist = Math.abs(target.x - unit.x);
    const heightDiff = target.y - unit.y;
    
    let bestWeapon = this._selectBestWeapon(unit, target, dist, heightDiff);
    
    // Check if nuke is available and worth using (hard AI)
    if(this.difficulty === 'hard' && !game.nukeUsed[unit.team] && targets.length <= 2) {
      const nukeW = WEAPONS.find(w => w.id === 'nuke');
      bestWeapon = nukeW;
    }

    // Use utility items if beneficial
    if(unit.hp < 50 && Math.random() < (this.difficulty==='hard'?0.8:0.4)) {
      this.actionQueue.push({type:'utility', item:'medkit'});
    }

    // Aim and fire
    const aimData = this._calculateAim(unit, target, bestWeapon);
    
    if(aimData) {
      this.actionQueue.push({type:'fire', weapon:bestWeapon, angle:aimData.angle, power:aimData.power});
    } else {
      // Fallback: random shot
      const angle = -Math.PI/4 + (Math.random()-0.5)*0.5;
      const power = 30 + Math.random()*60;
      this.actionQueue.push({type:'fire', weapon:bestWeapon, angle, power});
    }

    // Execute actions with delays
    let delay = 0;
    for(const action of this.actionQueue) {
      setTimeout(() => this.executeAction(action), delay);
      delay += 800 + Math.random()*400;
    }
    
    // Skip if no actions
    if(this.actionQueue.length === 0) {
      setTimeout(() => game.skipTurn(), 1000);
    }
  }

  _isOnCliffEdge(unit) {
    const groundY = terrain.getGroundY(unit.x);
    const leftY = terrain.getGroundY(unit.x - 30);
    const rightY = terrain.getGroundY(unit.x + 30);
    
    return (leftY > groundY + 50 || rightY > groundY + 50);
  }

  _findSafeDirection(unit) {
    // Find direction away from water and toward higher ground
    const leftY = terrain.getGroundY(unit.x - 50);
    const rightY = terrain.getGroundY(unit.x + 50);
    
    if(leftY < rightY) return -1;
    return 1;
  }

  _selectBestWeapon(unit, target, dist, heightDiff) {
    const availableWeapons = WEAPONS.filter(w => w.id !== 'nuke' || !game.nukeUsed[unit.team]);
    
    // Score each weapon
    let bestScore = -Infinity;
    let bestW = availableWeapons[0];
    
    for(const w of availableWeapons) {
      let score = 0;
      
      if(w.category === 'indirect') {
        // Good for long range and targets above/below
        score += Math.max(0, 100 - Math.abs(dist - 400)) * 0.2;
        score += (heightDiff > 0 ? 20 : 0); // Prefer indirect when target is below
      } else if(w.category === 'direct') {
        // Good for close range, line of sight
        score += Math.max(0, 100 - dist) * 0.5;
        // Check line of sight
        if(this._hasLineOfSight(unit, target)) score += 30;
      } else if(w.category === 'aoe') {
        // Good when multiple targets nearby
        const nearbyTargets = game.units.filter(u => u.alive && u.team !== unit.team && Math.abs(u.x-target.x)<200).length;
        score += nearbyTargets * 40;
      }
      
      // Accuracy factor
      score *= this.getAccuracy();
      
      if(score > bestScore) {
        bestScore = score;
        bestW = w;
      }
    }
    
    return bestW;
  }

  _hasLineOfSight(from, to) {
    const steps = Math.floor(Math.sqrt((to.x-from.x)**2 + (to.y-from.y)**2) / 10);
    for(let i=0;i<=steps;i++) {
      const t = i/steps;
      const cx = from.x + (to.x-from.x)*t;
      const cy = from.y + (to.y-from.y)*t;
      if(terrain.isGround(Math.floor(cx), Math.floor(cy))) return false;
    }
    return true;
  }

  _calculateAim(unit, target, weapon) {
    if(weapon.category === 'direct') {
      // Direct weapons: aim at target with some inaccuracy
      const dx = target.x - unit.x;
      const dy = target.y - unit.y;
      const angle = Math.atan2(dy, dx);
      
      // Add inaccuracy based on difficulty
      const accuracy = this.getAccuracy();
      const noise = (1-accuracy) * 0.3;
      return { angle: angle + (Math.random()-0.5)*noise, power: 100 };
    }

    if(weapon.category === 'aoe') {
      // AoE: aim at center of target cluster or directly at target
      const dx = target.x - unit.x;
      const dy = target.y - unit.y;
      
      if(weapon.id === 'earthquake') {
        return { angle: 0, power: 100 }; // Earthquake doesn't need aiming
      }
      
      // For nuke, aim at cluster center
      let cx=target.x, cy=target.y;
      const nearby = game.units.filter(u => u.alive && u.team !== unit.team);
      if(nearby.length > 1) {
        cx = nearby.reduce((s,u)=>s+u.x,0)/nearby.length;
        cy = nearby.reduce((s,u)=>s+u.y,0)/nearby.length;
      }
      
      const aimAngle = this._solveTrajectory(unit.x, unit.y, cx, cy, weapon.speed);
      if(aimAngle) return aimAngle;
    }

    // Indirect: solve parabolic trajectory
    const accuracy = this.getAccuracy();
    const noise = (1-accuracy) * 0.2;
    
    // Add some randomness to target position
    const tx = target.x + (Math.random()-0.5)*40*(1-accuracy);
    const ty = target.y + (Math.random()-0.5)*30*(1-accuracy);
    
    const aimAngle = this._solveTrajectory(unit.x, unit.y, tx, ty, weapon.speed);
    if(aimAngle) {
      return {
        angle: aimAngle.angle + (Math.random()-0.5)*noise,
        power: aimAngle.power * (0.9 + Math.random()*0.2*accuracy)
      };
    }
    
    // Fallback trajectory calculation
    const dx = target.x - unit.x;
    const dist = Math.abs(dx);
    const angle = dx > 0 ? -Math.PI/4 : -Math.PI*3/4;
    return { angle: angle + (Math.random()-0.5)*noise, power: Math.min(100, dist/8) };
  }

  _solveTrajectory(x1, y1, x2, y2, speed) {
    const dx = x2 - x1;
    const dy = y2 - y1;
    
    // Try different angles and find one that works
    let bestAngle = null;
    let bestError = Infinity;
    
    for(let angle=-0.1;angle<-Math.PI+0.1;angle-=0.05) {
      const vx = Math.cos(angle)*speed;
      const vy = Math.sin(angle)*speed;
      
      // Simulate trajectory
      let sx=x1, sy=y1, svx=vx, svy=vy;
      for(let t=0;t<200;t++) {
        svy += GRAVITY;
        svx += game.wind * 0.01;
        sx += svx;
        sy += svy;
        
        if(sy > WORLD_H) break; // Hit ground
        
        const err = Math.sqrt((sx-x2)**2 + (sy-y2)**2);
        if(err < bestError && t > 10) {
          bestError = err;
          bestAngle = angle;
        }
        
        if(sx >= x2-5 && sx <= x2+5 && Math.abs(sy-y2)<20) {
          return { angle: bestAngle, power: speed/15*100 };
        }
      }
    }
    
    if(bestError < 100) {
      return { angle: bestAngle, power: speed/15*100 };
    }
    return null;
  }

  executeAction(action) {
    const unit = this.currentUnit;
    if(!unit || !unit.alive) return;

    switch(action.type) {
      case 'move':
        // Move toward a safer position or closer to target
        for(let i=0;i<20;i++) {
          unit.move(action.dir);
        }
        break;
        
      case 'utility':
        if(action.item === 'medkit' && unit.hp < UNIT_MAX_HP) {
          unit.hp = Math.min(UNIT_MAX_HP, unit.hp + 25);
          floatingTexts.push(new FloatingText(unit.x, unit.y-20, '+25', '#44ff44'));
          sound.play('medkit');
          game.addLog(`${unit.name} used a Medkit!`, '#44ff44');
        } else if(action.item === 'parachute') {
          unit.hasParachute = true;
          sound.play('parachute');
          game.addLog(`${unit.name} deployed a Parachute!`, '#88ccff');
        }
        break;
        
      case 'fire':
        if(action.weapon.id === 'earthquake') {
          // Earthquake: no aiming needed
          const proj = new Projectile(unit.x, unit.y, 0, 0, action.weapon, unit.team);
          game.projectiles.push(proj);
          setTimeout(() => proj.explode(), 100);
          sound.play('weapon_fire', {weapon:'earthquake'});
        } else if(action.weapon.category === 'direct') {
          // Direct fire
          this._fireDirect(unit, action.weapon, action.angle);
        } else {
          // Indirect fire
          const power = (action.power / 100) * 15;
          const vx = Math.cos(action.angle) * power;
          const vy = Math.sin(action.angle) * power;
          const proj = new Projectile(unit.x, unit.y-15, vx, vy, action.weapon, unit.team);
          game.projectiles.push(proj);
          sound.play('weapon_fire', {weapon:action.weapon.id});
        }
        
        // After firing, skip turn after delay
        setTimeout(() => game.skipTurn(), 2000);
        break;
        
      case 'skip':
        game.skipTurn();
        break;
    }
  }

  _fireDirect(unit, weapon, angle) {
    if(weapon.id === 'shotgun') {
      // Spread shot: multiple pellets
      for(let i=0;i<7;i++) {
        const spread = (i-3)*0.08 + (Math.random()-0.5)*0.05;
        const dx = Math.cos(angle+spread)*20;
        const dy = Math.sin(angle+spread)*20;
        
        // Raycast for hit detection
        let hitX = unit.x, hitY = unit.y-10;
        const stepSize = 5;
        for(let s=0;s<60;s++) {
          hitX += dx*stepSize/20;
          hitY += dy*stepSize/20;
          
          // Check terrain
          if(terrain.isGround(Math.floor(hitX), Math.floor(hitY))) break;
          
          // Check units
          for(const target of game.units) {
            if(!target.alive || target.team === unit.team) continue;
            const tdx = hitX-target.x, tdy = hitY-target.y;
            if(Math.sqrt(tdx*tdx+tdy*tdy)<12) {
              target.takeDamage(weapon.damage/7, unit.team);
              particles.blood(target.x, target.y);
              break;
            }
          }
        }
      }
      
      // Visual effect
      ctx.save();
      const endX = unit.x + Math.cos(angle)*300;
      const endY = unit.y-10 + Math.sin(angle)*300;
      ctx.strokeStyle = '#ffaa00';
      ctx.lineWidth = 2;
      for(let i=0;i<7;i++) {
        const spread = (i-3)*0.08;
        ctx.beginPath();
        ctx.moveTo(unit.x, unit.y-10);
        ctx.lineTo(unit.x+Math.cos(angle+spread)*200, unit.y-10+Math.sin(angle+spread)*200);
        ctx.stroke();
      }
      ctx.restore();
      
      sound.play('weapon_fire');
      particles.smallExplosion(endX, endY);
      
    } else if(weapon.id === 'laser') {
      // Laser beam: instant line damage
      const maxDist = 500;
      let hitX = unit.x + Math.cos(angle)*maxDist;
      let hitY = unit.y-10 + Math.sin(angle)*maxDist;
      
      // Find closest intersection with terrain or units
      let minDist = maxDist;
      for(const target of game.units) {
        if(!target.alive || target.team === unit.team) continue;
        const dist = this._lineToCircleDistance(unit.x, unit.y-10, angle, target.x, target.y, 15);
        if(dist > 0 && dist < minDist) {
          minDist = dist;
          hitX = unit.x + Math.cos(angle)*dist;
          hitY = unit.y-10 + Math.sin(angle)*dist;
          target.takeDamage(weapon.damage, unit.team);
          particles.blood(target.x, target.y);
        }
      }
      
      // Draw laser beam
      game.laserBeam = {x1:unit.x, y1:unit.y-10, x2:hitX, y2:hitY, life:15};
      
      sound.play('laser');
    }
  }

  _lineToCircleDistance(lx, ly, angle, cx, cy, r) {
    const dx = Math.cos(angle), dy = Math.sin(angle);
    const fx = cx-lx, fy = cy-ly;
    const t = fx*dx + fy*dy;
    if(t < 0) return -1;
    const distX = lx+dx*t-cx, distY = ly+dy*t-cy;
    const perpDist = Math.sqrt(distX*distX+distY*distY);
    if(perpDist <= r) {
      return t - Math.sqrt(r*r-perpDist*perpDist);
    }
    return -1;
  }
}

// --- Main Game Class ---
class Game {
  constructor() {
    this.state = 'menu'; // menu, playing, gameover
    this.units = [];
    this.projectiles = [];
    this.activeTeamIndex = 0;
    this.activeUnitIndex = 0;
    this.turnTimer = TURN_TIME;
    this.turnTimerMax = TURN_TIME;
    this.wind = 0;
    this.cameraX = WORLD_W/2;
    this.cameraY = WORLD_H/2;
    this.targetCameraX = WORLD_W/2;
    this.targetCameraY = WORLD_H/2;
    this.zoom = 1;
    this.targetZoom = 1;
    this.screenShake = 0;
    this.impactFreeze = 0;
    this.nukeUsed = {}; // team -> bool
    this.laserBeam = null;
    this.battleCount = 0;
    
    // Input state
    this.keys = {};
    this.mouseX = 0;
    this.mouseY = 0;
    this.mouseDown = false;
    this.aimAngle = -Math.PI/4;
    this.aimPower = 50;
    this.isAiming = false;
    this.selectedWeapon = 0;
    this.selectedUtility = null;
    
    // Menu settings
    this.menuTerrain = 'island';
    this.menuTeams = 3;
    this.menuDifficulty = 'medium';
    this.menuCPUCount = 1;
    
    // AI controllers per team
    this.aiControllers = {};
    
    // Log entries
    this.logEntries = [];
    
    // Trajectory preview points
    this.trajectoryPoints = [];
    
    // Turn announcement
    this.announceTimer = 0;
    this.announceText = '';
    
    // Tick timer for sound
    this.lastTickSecond = -1;
  }

  get activeUnit() {
    if(this.activeTeamIndex >= this.units.length) return null;
    const teamUnits = this.units.filter(u => u.team === this.activeTeamIndex && u.alive);
    if(teamUnits.length === 0) return null;
    return teamUnits[this.activeUnitIndex % teamUnits.length];
  }

  get isCPU() {
    const unit = this.activeUnit;
    if(!unit) return false;
    return !!this.aiControllers[unit.team];
  }

  init(terrainType, numTeams, difficulty, cpuCount) {
    this.menuTerrain = terrainType;
    this.menuTeams = numTeams;
    this.menuDifficulty = difficulty;
    this.menuCPUCount = cpuCount;
    
    // Generate terrain
    terrain.generate(terrainType, WATER_LEVEL);
    
    // Create units
    this.units = [];
    const teamColors = ['#ff4444','#4488ff','#44dd44','#ddaa00'];
    const teamNames = ['Red','Blue','Green','Gold'];
    
    for(let t=0;t<numTeams;t++) {
      // Place units on terrain
      const spacing = WORLD_W / (numTeams * 4);
      const offsetX = spacing * 2 + Math.random()*spacing;
      
      for(let i=0;i<4;i++) {
        const x = offsetX + i * spacing * 1.5 + (Math.random()-0.5)*30;
        const y = terrain.getGroundY(x) - 5;
        const worm = new Worm(x, y, t, i);
        worm.name = `${teamNames[t]} ${i+1}`;
        this.units.push(worm);
      }
    }
    
    // Setup AI controllers
    this.aiControllers = {};
    for(let c=0;c<cpuCount && c<numTeams;c++) {
      this.aiControllers[c] = new AIController(difficulty);
    }
    
    // Reset game state
    this.activeTeamIndex = 0;
    this.activeUnitIndex = 0;
    this.turnTimer = TURN_TIME;
    this.wind = (Math.random()-0.5)*10;
    this.nukeUsed = {};
    this.battleCount++;
    
    // Set initial camera
    const firstUnit = this.units.find(u => u.alive);
    if(firstUnit) {
      this.targetCameraX = firstUnit.x;
      this.targetCameraY = firstUnit.y - 100;
    }
    
    this.state = 'playing';
    this.startTurn();
  }

  startTurn() {
    // Find next alive unit
    let found = false;
    const maxAttempts = this.units.length * 2;
    let attempts = 0;
    
    while(attempts < maxAttempts) {
      const teamUnits = this.units.filter(u => u.team === this.activeTeamIndex && u.alive);
      
      if(teamUnits.length === 0) {
        // Team eliminated!
        this.addLog(`Team ${this.activeTeamIndex+1} has been eliminated!`, '#ff4444');
        this.activeTeamIndex = (this.activeTeamIndex + 1) % this.menuTeams;
        attempts++;
        continue;
      }
      
      if(this.activeUnitIndex >= teamUnits.length) {
        // All units on this team have taken turns, move to next team
        this.activeTeamIndex = (this.activeTeamIndex + 1) % this.menuTeams;
        this.activeUnitIndex = 0;
        attempts++;
        continue;
      }
      
      found = true;
      break;
    }
    
    if(!found) {
      // Check if only one team remains
      const aliveTeams = new Set(this.units.filter(u=>u.alive).map(u=>u.team));
      if(aliveTeams.size <= 1) {
        this.endGame();
        return;
      }
    }
    
    const unit = this.activeUnit;
    if(!unit) return;
    
    // Reset turn state
    this.turnTimer = TURN_TIME;
    this.lastTickSecond = -1;
    unit.moveBudget = 200;
    this.selectedWeapon = 0;
    this.selectedUtility = null;
    this.isAiming = false;
    this.aimPower = 50;
    
    // Announce turn
    const teamNames = ['Red','Blue','Green','Gold'];
    this.showAnnouncement(`${teamNames[unit.team]} Team's Turn\n${unit.name}`, unit.color);
    sound.play('turn_start');
    
    // Update camera target
    this.targetCameraX = unit.x;
    this.targetCameraY = Math.min(unit.y - 100, WATER_LEVEL - 200);
    
    // Dynamic zoom based on number of alive units
    const aliveCount = this.units.filter(u=>u.alive).length;
    if(aliveCount <= 4) this.targetZoom = 1.3;
    else if(aliveCount <= 8) this.targetZoom = 1.1;
    else this.targetZoom = 0.9;
    
    // CPU turn?
    if(this.isCPU) {
      const ai = this.aiControllers[unit.team];
      if(ai) ai.startTurn(unit);
    }
    
    this.updateHUD();
  }

  skipTurn() {
    if(this.state !== 'playing') return;
    sound.play('click');
    
    // Move to next unit
    const teamUnits = this.units.filter(u => u.team === this.activeTeamIndex && u.alive);
    this.activeUnitIndex++;
    
    if(this.activeUnitIndex >= teamUnits.length) {
      this.activeTeamIndex = (this.activeTeamIndex + 1) % this.menuTeams;
      this.activeUnitIndex = 0;
    }
    
    this.startTurn();
  }

  fireWeapon() {
    const unit = this.activeUnit;
    if(!unit || !unit.alive || this.isCPU) return;
    
    const weapon = WEAPONS[this.selectedWeapon];
    sound.play('resume');
    
    // Check nuke usage limit
    if(weapon.id === 'nuke' && this.nukeUsed[unit.team]) {
      this.addLog('Nuke already used by your team!', '#ff4444');
      return;
    }
    
    if(weapon.category === 'direct') {
      // Direct fire - aim at mouse position
      const worldMouseX = (this.mouseX - canvas.width/2) / this.zoom + this.cameraX;
      const worldMouseY = (this.mouseY - canvas.height/2) / this.zoom + this.cameraY;
      
      const dx = worldMouseX - unit.x;
      const dy = worldMouseY - (unit.y-10);
      const angle = Math.atan2(dy, dx);
      
      // Simulate direct weapon fire
      if(weapon.id === 'shotgun') {
        for(let i=0;i<7;i++) {
          const spread = (i-3)*0.08 + (Math.random()-0.5)*0.05;
          this._raycastShot(unit, angle+spread, weapon);
        }
        sound.play('weapon_fire');
      } else if(weapon.id === 'laser') {
        this._fireLaser(unit, angle, weapon);
        sound.play('laser');
      }
      
    } else if(weapon.category === 'aoe' && weapon.id === 'earthquake') {
      // Earthquake - instant effect
      const proj = new Projectile(unit.x, unit.y, 0, 0, weapon, unit.team);
      this.projectiles.push(proj);
      setTimeout(() => proj.explode(), 200);
      sound.play('weapon_fire', {weapon:'earthquake'});
      
    } else if(weapon.category === 'aoe' && weapon.id === 'nuke') {
      // Nuke - fire projectile
      const power = (this.aimPower / 100) * 15;
      const vx = Math.cos(this.aimAngle) * power;
      const vy = Math.sin(this.aimAngle) * power;
      const proj = new Projectile(unit.x, unit.y-15, vx, vy, weapon, unit.team);
      this.projectiles.push(proj);
      this.nukeUsed[unit.team] = true;
      sound.play('weapon_fire', {weapon:'nuke'});
      
    } else {
      // Indirect projectile
      const power = (this.aimPower / 100) * 15;
      const vx = Math.cos(this.aimAngle) * power;
      const vy = Math.sin(this.aimAngle) * power;
      const proj = new Projectile(unit.x, unit.y-15, vx, vy, weapon, unit.team);
      this.projectiles.push(proj);
      sound.play('weapon_fire', {weapon:weapon.id});
    }
    
    // Stop aiming after firing
    this.isAiming = false;
  }

  _raycastShot(unit, angle, weapon) {
    const dx = Math.cos(angle)*5;
    const dy = Math.sin(angle)*5;
    let hx = unit.x, hy = unit.y-10;
    
    for(let s=0;s<80;s++) {
      hx += dx; hy += dy;
      
      if(terrain.isGround(Math.floor(hx), Math.floor(hy))) {
        particles.dust(hx, hy);
        break;
      }
      
      for(const target of this.units) {
        if(!target.alive || target.team === unit.team) continue;
        const tdx = hx-target.x, tdy = hy-target.y;
        if(Math.sqrt(tdx*tdx+tdy*tdy)<12) {
          target.takeDamage(weapon.damage/7, unit.team);
          particles.blood(target.x, target.y);
          break;
        }
      }
    }
  }

  _fireLaser(unit, angle, weapon) {
    const maxDist = 500;
    let hitX = unit.x + Math.cos(angle)*maxDist;
    let hitY = unit.y-10 + Math.sin(angle)*maxDist;
    
    // Find closest intersection
    let minDist = maxDist;
    for(const target of this.units) {
      if(!target.alive || target.team === unit.team) continue;
      
      // Simple distance check along beam
      const steps = 50;
      for(let s=1;s<=steps;s++) {
        const t = s/steps;
        const bx = unit.x + Math.cos(angle)*maxDist*t;
        const by = unit.y-10 + Math.sin(angle)*maxDist*t;
        
        const dx = bx-target.x, dy = by-target.y;
        if(Math.sqrt(dx*dx+dy*dy)<15) {
          const dist = s/steps * maxDist;
          if(dist < minDist) {
            minDist = dist;
            hitX = bx; hitY = by;
            target.takeDamage(weapon.damage, unit.team);
            particles.blood(target.x, target.y);
          }
        }
      }
    }
    
    // Check terrain intersection
    for(let s=1;s<=steps;s++) {
      const t = s/50;
      const bx = unit.x + Math.cos(angle)*maxDist*t;
      const by = unit.y-10 + Math.sin(angle)*maxDist*t;
      if(terrain.isGround(Math.floor(bx), Math.floor(by))) {
        const dist = s/50*maxDist;
        if(dist < minDist) {
          minDist = dist;
          hitX = bx; hitY = by;
        }
        break;
      }
    }
    
    this.laserBeam = {x1:unit.x, y1:unit.y-10, x2:hitX, y2:hitY, life:20};
  }

  useUtility(item) {
    const unit = this.activeUnit;
    if(!unit || !unit.alive || this.isCPU) return;
    
    switch(item.id) {
      case 'medkit':
        if(unit.hp < UNIT_MAX_HP) {
          unit.hp = Math.min(UNIT_MAX_HP, unit.hp + 25);
          floatingTexts.push(new FloatingText(unit.x, unit.y-20, '+25 HP', '#44ff44'));
          sound.play('medkit');
          this.addLog(`${unit.name} used a Medkit!`, '#44ff44');
        }
        break;
      case 'parachute':
        unit.hasParachute = true;
        sound.play('parachute');
        this.addLog(`${unit.name} deployed a Parachute!`, '#88ccff');
        break;
      case 'binoculars':
        // Reveal enemy positions briefly (show on minimap)
        this.showEnemyPositions = true;
        setTimeout(() => { this.showEnemyPositions = false; }, 5000);
        sound.play('select');
        this.addLog(`${unit.name} used Binoculars!`, '#88ccff');
        break;
    }
    
    this.selectedUtility = null;
  }

  showAnnouncement(text, color) {
    const el = document.getElementById('turn-announce');
    el.textContent = text;
    el.style.color = color || 'white';
    el.style.opacity = 1;
    this.announceTimer = 120;
  }

  addLog(text, color='#fff') {
    const el = document.getElementById('game-log');
    const entry = document.createElement('div');
    entry.className = 'log-entry';
    entry.style.color = color || '#ccc';
    entry.textContent = text;
    el.appendChild(entry);
    
    // Remove old entries
    while(el.children.length > 8) {
      el.removeChild(el.firstChild);
    }
    
    setTimeout(() => { if(entry.parentNode) entry.parentNode.removeChild(entry); }, 5000);
  }

  endGame() {
    this.state = 'gameover';
    const aliveTeams = new Set(this.units.filter(u=>u.alive).map(u=>u.team));
    const winnerTeam = [...aliveTeams][0];
    
    if(winnerTeam !== undefined) {
      const teamNames = ['Red','Blue','Green','Gold'];
      document.getElementById('winner-text').textContent = `${teamNames[winnerTeam]} Team Wins!`;
      document.getElementById('winner-sub').textContent = `Survivors: ${this.units.filter(u=>u.alive && u.team===winnerTeam).length}`;
    } else {
      document.getElementById('winner-text').textContent = 'Draw!';
      document.getElementById('winner-sub').textContent = 'All teams eliminated';
    }
    
    document.getElementById('game-over').style.display = 'flex';
  }

  updateHUD() {
    const unit = this.activeUnit;
    if(!unit) return;
    
    // Team badges
    const badgesEl = document.getElementById('team-badges');
    badgesEl.innerHTML = '';
    const teamNames = ['Red','Blue','Green','Gold'];
    
    for(let t=0;t<this.menuTeams;t++) {
      const aliveCount = this.units.filter(u=>u.alive && u.team===t).length;
      const badge = document.createElement('div');
      badge.className = `team-badge team-bg-${t}` + (t === this.activeTeamIndex ? ' active' : '');
      badge.textContent = `${teamNames[t]} (${aliveCount})`;
      badgesEl.appendChild(badge);
    }
    
    // Unit info
    document.getElementById('unit-name').textContent = unit.name;
    document.getElementById('unit-name').style.color = unit.color;
    const hpRatio = unit.hp / UNIT_MAX_HP;
    const hpBar = document.getElementById('health-bar');
    hpBar.style.width = `${hpRatio*100}%`;
    hpBar.style.background = `linear-gradient(90deg, ${hpRatio>0.5?'#44ff44':'#ff4444'}, ${hpRatio>0.25?'#aaff00':'#cc0000'})`;
    
    // Weapon panel
    const wpEl = document.getElementById('weapon-panel');
    wpEl.innerHTML = '';
    WEAPONS.forEach((w, i) => {
      const btn = document.createElement('button');
      btn.className = 'weapon-btn' + (i === this.selectedWeapon ? ' selected' : '');
      btn.innerHTML = `<span class="icon">${w.icon}</span><span>${w.name}</span>`;
      btn.title = w.desc;
      
      // Check if nuke already used
      if(w.id === 'nuke' && unit && this.nukeUsed[unit.team]) {
        btn.style.opacity = '0.3';
        btn.title += ' (Already Used)';
      }
      
      btn.onclick = () => {
        sound.play('select');
        this.selectedWeapon = i;
        this.updateHUD();
      };
      wpEl.appendChild(btn);
    });
    
    // Utility panel
    const utilEl = document.getElementById('utility-panel');
    utilEl.innerHTML = '';
    UTILITIES.forEach(u => {
      const btn = document.createElement('button');
      btn.className = 'util-btn' + (this.selectedUtility === u ? ' selected' : '');
      btn.textContent = u.icon;
      btn.title = `${u.name}: ${u.desc}`;
      btn.onclick = () => {
        sound.play('select');
        this.useUtility(u);
        this.updateHUD();
      };
      utilEl.appendChild(btn);
    });
    
    // Turn info
    document.getElementById('turn-info').innerHTML = `Turn: ${this.battleCount}<br>Wind: ${this.wind > 0 ? '→' : '←'} ${Math.abs(this.wind).toFixed(1)}`;
    
    // Wind indicator
    document.getElementById('wind-value').textContent = `${this.wind > 0 ? '→' : '←'} ${Math.abs(this.wind).toFixed(1)}`;
    
    // Instructions
    const instrEl = document.getElementById('instructions');
    if(!this.isCPU) {
      instrEl.textContent = 'Arrow Keys: Move | Space: Jump | Click & Hold: Aim | Release: Fire | Scroll: Zoom';
    } else {
      instrEl.textContent = `${unit.name} (CPU) is thinking...`;
    }
  }

  update() {
    if(this.state !== 'playing') return;
    
    // Impact freeze
    if(this.impactFreeze > 0) {
      this.impactFreeze--;
      return;
    }
    
    const unit = this.activeUnit;
    
    // Turn timer
    if(!this.isCPU && unit) {
      this.turnTimer -= 1/60;
      
      // Timer tick sounds
      const currentSecond = Math.floor(this.turnTimer);
      if(currentSecond !== this.lastTickSecond && currentSecond <= 5 && currentSecond > 0) {
        sound.play('timer_tick', {urgency: (5-currentSecond)/5});
        this.lastTickSecond = currentSecond;
      }
      
      if(this.turnTimer <= 0) {
        this.addLog(`${unit.name} ran out of time!`, '#ff8800');
        this.skipTurn();
        return;
      }
    }
    
    // Update timer UI
    const timerFill = document.getElementById('turn-timer-fill');
    const timerText = document.getElementById('turn-timer-text');
    if(timerFill && timerText) {
      const ratio = Math.max(0, this.turnTimer / TURN_TIME);
      timerFill.style.width = `${ratio*100}%`;
      timerText.textContent = Math.ceil(this.turnTimer);
      
      // Color changes with urgency
      if(ratio > 0.5) timerFill.style.background = '#4f4';
      else if(ratio > 0.25) timerFill.style.background = '#ff0';
      else {
        timerFill.style.background = `rgb(${Math.floor(255*(1-ratio*2))},${Math.floor(255*ratio*2)},0)`;
        // Pulsing effect when low
        if(Math.sin(Date.now()*0.01) > 0.5) {
          timerText.style.transform = 'scale(1.2)';
        } else {
          timerText.style.transform = 'scale(1)';
        }
      }
    }
    
    // Announce timer
    if(this.announceTimer > 0) {
      this.announceTimer--;
      const el = document.getElementById('turn-announce');
      if(this.announceTimer < 30) {
        el.style.opacity = Math.max(0, this.announceTimer/30);
      }
    }
    
    // Player input (only for human turns)
    if(!this.isCPU && unit && unit.alive) {
      if(this.keys['ArrowLeft'] || this.keys['a']) unit.move(-1);
      if(this.keys['ArrowRight'] || this.keys['d']) unit.move(1);
      if(this.keys[' '] || this.keys['ArrowUp'] || this.keys['w']) unit.jump();
      
      // Aiming with mouse
      if(this.mouseDown) {
        const worldMouseX = (this.mouseX - canvas.width/2) / this.zoom + this.cameraX;
        const worldMouseY = (this.mouseY - canvas.height/2) / this.zoom + this.cameraY;
        
        const dx = worldMouseX - unit.x;
        const dy = worldMouseY - (unit.y-10);
        this.aimAngle = Math.atan2(dy, dx);
        
        // Power based on distance held or mouse distance
        const dist = Math.sqrt(dx*dx+dy*dy);
        this.aimPower = Math.min(100, Math.max(10, dist / 5));
      }
    }
    
    // Update units
    for(const u of this.units) {
      u.update(this.wind);
    }
    
    // Update projectiles
    for(const p of this.projectiles) {
      p.update(this.wind);
    }
    this.projectiles = this.projectiles.filter(p => p.alive);
    
    // Update particles
    particles.update();
    
    // Update floating texts
    floatingTexts = floatingTexts.filter(ft => ft.update());
    
    // Screen shake decay
    if(this.screenShake > 0) this.screenShake *= 0.9;
    if(this.screenShake < 0.5) this.screenShake = 0;
    
    // Camera smoothing
    const camUnit = this.activeUnit;
    if(camUnit && camUnit.alive) {
      this.targetCameraX += (camUnit.x - this.targetCameraX) * 0.02;
      this.targetCameraY += (Math.min(camUnit.y - 100, WATER_LEVEL - 250) - this.targetCameraY) * 0.02;
    }
    
    this.cameraX += (this.targetCameraX - this.cameraX) * 0.08;
    this.cameraY += (this.targetCameraY - this.cameraY) * 0.08;
    this.zoom += (this.targetZoom - this.zoom) * 0.05;
    
    // Calculate trajectory preview for aiming
    if(this.isAiming && unit && !this.isCPU) {
      const weapon = WEAPONS[this.selectedWeapon];
      if(weapon.category === 'indirect' || (weapon.category === 'aoe' && weapon.id !== 'earthquake')) {
        this.trajectoryPoints = [];
        const power = (this.aimPower / 100) * 15;
        let px = unit.x, py = unit.y-15;
        let pvx = Math.cos(this.aimAngle)*power;
        let pvy = Math.sin(this.aimAngle)*power;
        
        for(let t=0;t<120;t++) {
          pvy += GRAVITY;
          pvx += this.wind * 0.01;
          px += pvx;
          py += pvy;
          
          if(py > WORLD_H || px < 0 || px > WORLD_W) break;
          if(terrain.isGround(Math.floor(px), Math.floor(py))) {
            this.trajectoryPoints.push({x:px, y:py, hit:true});
            break;
          }
          
          if(t % 3 === 0) {
            this.trajectoryPoints.push({x:px, y:py, hit:false});
          }
        }
      }
    } else {
      this.trajectoryPoints = [];
    }
    
    // Laser beam decay
    if(this.laserBeam && --this.laserBeam.life <= 0) {
      this.laserBeam = null;
    }
    
    // Check for game end
    const aliveTeams = new Set(this.units.filter(u=>u.alive).map(u=>u.team));
    if(aliveTeams.size === 1 && this.state === 'playing') {
      setTimeout(() => this.endGame(), 1000);
    }
    
    // Update HUD periodically
    if(Math.random() < 0.05) this.updateHUD();
  }

  draw() {
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    
    // Screen shake offset
    const shakeX = (Math.random()-0.5)*this.screenShake*2;
    const shakeY = (Math.random()-0.5)*this.screenShake*2;
    
    ctx.save();
    ctx.translate(shakeX, shakeY);
    
    // Camera transform
    ctx.setTransform(this.zoom, 0, 0, this.zoom, 
      -this.cameraX*this.zoom + canvas.width/2, 
      -this.cameraY*this.zoom + canvas.height/2);
    
    // Draw terrain (sky + ground)
    terrain.ctx.save();
    terrain.ctx.restore();
    
    // Sky background
    const skyGrad = ctx.createLinearGradient(0, 0, 0, WORLD_H);
    skyGrad.addColorStop(0, '#1a237e');
    skyGrad.addColorStop(0.4, '#283593');
    skyGrad.addColorStop(0.7, '#5c6bc0');
    skyGrad.addColorStop(1, '#9fa8da');
    ctx.fillStyle = skyGrad;
    ctx.fillRect(-100, -100, WORLD_W+200, WORLD_H+200);
    
    // Stars
    for(let i=0;i<150;i++) {
      const sx = (i*137.5)%WORLD_W;
      const sy = (i*97.3)%(WORLD_H*0.4);
      ctx.fillStyle = `rgba(255,255,255,${0.3+Math.sin(Date.now()*0.001+i)*0.3})`;
      ctx.fillRect(sx, sy, 1.5, 1.5);
    }
    
    // Draw terrain
    ctx.drawImage(terrain.canvas, 0, 0);
    ctx.drawImage(terrain.terrainCanvas, 0, 0);
    
    // Water
    this._drawWater(ctx);
    
    // Trajectory preview
    if(this.trajectoryPoints.length > 1) {
      ctx.setLineDash([5, 5]);
      ctx.strokeStyle = 'rgba(255,255,255,0.4)';
      ctx.lineWidth = 2;
      ctx.beginPath();
      for(let i=0;i<this.trajectoryPoints.length;i++) {
        const p = this.trajectoryPoints[i];
        if(i===0) ctx.moveTo(p.x, p.y);
        else ctx.lineTo(p.x, p.y);
      }
      ctx.stroke();
      ctx.setLineDash([]);
      
      // Explosion preview at end point
      const lastPt = this.trajectoryPoints[this.trajectoryPoints.length-1];
      if(lastPt && lastPt.hit) {
        const weapon = WEAPONS[this.selectedWeapon];
        ctx.strokeStyle = 'rgba(255,100,0,0.3)';
        ctx.lineWidth = 2;
        ctx.beginPath();
        ctx.arc(lastPt.x, lastPt.y, weapon.radius, 0, Math.PI*2);
        ctx.stroke();
      }
    }
    
    // Laser beam
    if(this.laserBeam) {
      const alpha = this.laserBeam.life / 20;
      ctx.strokeStyle = `rgba(255,0,255,${alpha})`;
      ctx.lineWidth = 4*alpha;
      ctx.shadowColor = '#ff00ff';
      ctx.shadowBlur = 15;
      ctx.beginPath();
      ctx.moveTo(this.laserBeam.x1, this.laserBeam.y1);
      ctx.lineTo(this.laserBeam.x2, this.laserBeam.y2);
      ctx.stroke();
      
      // Inner beam
      ctx.strokeStyle = `rgba(255,200,255,${alpha})`;
      ctx.lineWidth = 2*alpha;
      ctx.beginPath();
      ctx.moveTo(this.laserBeam.x1, this.laserBeam.y1);
      ctx.lineTo(this.laserBeam.x2, this.laserBeam.y2);
      ctx.stroke();
      ctx.shadowBlur = 0;
    }
    
    // Draw units (sorted by Y for depth)
    const sortedUnits = [...this.units].sort((a,b) => a.y - b.y);
    for(const u of sortedUnits) {
      u.draw(ctx);
    }
    
    // Draw projectiles
    for(const p of this.projectiles) {
      p.draw(ctx);
    }
    
    // Draw particles
    particles.draw(ctx);
    
    // Draw floating texts
    for(const ft of floatingTexts) {
      ft.draw(ctx);
    }
    
    ctx.restore();
  }

  _drawWater(ctx) {
    const time = Date.now() * 0.002;
    
    // Water body
    ctx.save();
    ctx.globalAlpha = 0.6;
    
    for(let x=0;x<WORLD_W;x+=3) {
      const waveY = WATER_LEVEL + Math.sin(x*0.03+time)*4 + Math.sin(x*0.07+time*1.5)*2;
      
      // Water gradient
      const grad = ctx.createLinearGradient(0, waveY, 0, WORLD_H);
      grad.addColorStop(0, 'rgba(30,80,180,0.6)');
      grad.addColorStop(0.5, 'rgba(20,50,140,0.7)');
      grad.addColorStop(1, 'rgba(10,20,80,0.8)');
      
      ctx.fillStyle = grad;
      ctx.fillRect(x, waveY, 3, WORLD_H - waveY);
    }
    
    // Surface highlight
    ctx.globalAlpha = 0.4;
    ctx.strokeStyle = '#66aaff';
    ctx.lineWidth = 2;
    ctx.beginPath();
    for(let x=0;x<WORLD_W;x+=3) {
      const waveY = WATER_LEVEL + Math.sin(x*0.03+time)*4 + Math.sin(x*0.07+time*1.5)*2;
      if(x===0) ctx.moveTo(x, waveY);
      else ctx.lineTo(x, waveY);
    }
    ctx.stroke();
    
    // Sparkles on water surface
    for(let i=0;i<30;i++) {
      const sx = (i*97+time*50)%WORLD_W;
      const sy = WATER_LEVEL + Math.sin(sx*0.03+time)*4 + 2;
      if(Math.sin(time+i*2)>0.7) {
        ctx.fillStyle = 'rgba(150,200,255,0.6)';
        ctx.fillRect(sx, sy, 2, 2);
      }
    }
    
    ctx.restore();
  }

  drawUI() {
    // Power meter (when aiming)
    const powerEl = document.getElementById('power-meter-container');
    const angleEl = document.getElementById('angle-display');
    
    if(this.isAiming && !this.isCPU) {
      powerEl.style.display = 'block';
      angleEl.style.display = 'block';
      
      document.getElementById('power-meter-fill').style.width = `${this.aimPower}%`;
      const degrees = Math.round((this.aimAngle * 180 / Math.PI) % 360);
      angleEl.textContent = `${degrees}° | Power: ${Math.round(this.aimPower)}%`;
    } else {
      powerEl.style.display = 'none';
      angleEl.style.display = 'none';
    }
    
    // Minimap
    if(document.getElementById('minimap-container').style.display !== 'none') {
      minimapCanvas.width = 180;
      minimapCanvas.height = 100;
      terrain.renderMinimap(minimapCtx);
      
      // Draw units on minimap
      const scaleX = 180 / WORLD_W;
      const scaleY = 100 / WORLD_H;
      
      for(const u of this.units) {
        if(!u.alive) continue;
        minimapCtx.fillStyle = u.color;
        minimapCtx.beginPath();
        minimapCtx.arc(u.x*scaleX, u.y*scaleY, 3, 0, Math.PI*2);
        minimapCtx.fill();
        
        // Highlight active unit
        if(this.activeUnit === u) {
          minimapCtx.strokeStyle = '#ffcc00';
          minimapCtx.lineWidth = 1;
          minimapCtx.beginPath();
          minimapCtx.arc(u.x*scaleX, u.y*scaleY, 5, 0, Math.PI*2);
          minimapCtx.stroke();
        }
      }
      
      // Camera viewport on minimap
      const viewW = (canvas.width/this.zoom) * scaleX;
      const viewH = (canvas.height/this.zoom) * scaleY;
      minimapCtx.strokeStyle = 'rgba(255,255,255,0.3)';
      minimapCtx.lineWidth = 1;
      minimapCtx.strokeRect(
        (this.cameraX - canvas.width/(2*this.zoom))*scaleX,
        (this.cameraY - canvas.height/(2*this.zoom))*scaleY,
        viewW, viewH
      );
    }
  }
}

// --- Initialize ---
const terrain = new Terrain();
const game = new Game();

// Resize handler
function resize() {
  canvas.width = window.innerWidth;
  canvas.height = window.innerHeight;
}
window.addEventListener('resize', resize);
resize();

// Input handlers
document.addEventListener('keydown', e => {
  game.keys[e.key] = true;
  
  if(e.key === 'Escape') {
    if(game.state === 'playing' && !game.isCPU) {
      game.skipTurn();
    }
  }
  
  // Weapon selection with number keys
  const num = parseInt(e.key);
  if(num >= 1 && num <= WEAPONS.length) {
    game.selectedWeapon = num - 1;
    sound.play('select');
    game.updateHUD();
  }
});

document.addEventListener('keyup', e => {
  game.keys[e.key] = false;
});

canvas.addEventListener('mousedown', e => {
  if(game.state !== 'playing' || game.isCPU) return;
  game.mouseDown = true;
  game.isAiming = true;
  sound.play('click');
});

canvas.addEventListener('mousemove', e => {
  game.mouseX = e.clientX;
  game.mouseY = e.clientY;
});

canvas.addEventListener('mouseup', e => {
  if(game.state !== 'playing' || game.isCPU) return;
  if(game.mouseDown && game.isAiming) {
    game.fireWeapon();
  }
  game.mouseDown = false;
  game.isAiming = false;
});

canvas.addEventListener('wheel', e => {
  e.preventDefault();
  const zoomDelta = e.deltaY > 0 ? -0.1 : 0.1;
  game.targetZoom = Math.max(0.4, Math.min(2.5, game.targetZoom + zoomDelta));
});

// Touch support for mobile
canvas.addEventListener('touchstart', e => {
  if(game.state !== 'playing' || game.isCPU) return;
  const touch = e.touches[0];
  game.mouseX = touch.clientX;
  game.mouseY = touch.clientY;
  game.mouseDown = true;
  game.isAiming = true;
});

canvas.addEventListener('touchmove', e => {
  e.preventDefault();
  const touch = e.touches[0];
  game.mouseX = touch.clientX;
  game.mouseY = touch.clientY;
}, {passive: false});

canvas.addEventListener('touchend', e => {
  if(game.state !== 'playing' || game.isCPU) return;
  if(game.mouseDown && game.isAiming) {
    game.fireWeapon();
  }
  game.mouseDown = false;
  game.isAiming = false;
});

// Menu handlers
document.querySelectorAll('#terrain-select .menu-btn').forEach(btn => {
  btn.onclick = () => {
    document.querySelectorAll('#terrain-select .menu-btn').forEach(b=>b.classList.remove('selected'));
    btn.classList.add('selected');
    sound.play('click');
  };
});

document.querySelectorAll('#teams-select .menu-btn').forEach(btn => {
  btn.onclick = () => {
    document.querySelectorAll('#teams-select .menu-btn').forEach(b=>b.classList.remove('selected'));
    btn.classList.add('selected');
    sound.play('click');
  };
});

document.querySelectorAll('#difficulty-select .menu-btn').forEach(btn => {
  btn.onclick = () => {
    document.querySelectorAll('#difficulty-select .menu-btn').forEach(b=>b.classList.remove('selected'));
    btn.classList.add('selected');
    sound.play('click');
  };
});

document.querySelectorAll('#cpu-select .menu-btn').forEach(btn => {
  btn.onclick = () => {
    document.querySelectorAll('#cpu-select .menu-btn').forEach(b=>b.classList.remove('selected'));
    btn.classList.add('selected');
    sound.play('click');
  };
});

// Start button
document.getElementById('start-btn').onclick = () => {
  sound.resume();
  sound.play('select');
  
  const terrainType = document.querySelector('#terrain-select .menu-btn.selected')?.dataset.val || 'island';
  const numTeams = parseInt(document.querySelector('#teams-select .menu-btn.selected')?.dataset.val || '3');
  const difficulty = document.querySelector('#difficulty-select .menu-btn.selected')?.dataset.val || 'medium';
  const cpuCount = parseInt(document.querySelector('#cpu-select .menu-btn.selected')?.dataset.val || '1');
  
  game.init(terrainType, numTeams, difficulty, cpuCount);
  
  // Show HUD elements
  document.getElementById('menu-screen').style.display = 'none';
  document.getElementById('hud-top').style.display = 'flex';
  document.getElementById('hud-bottom').style.display = 'flex';
  document.getElementById('wind-indicator').style.display = 'block';
  document.getElementById('minimap-container').style.display = 'block';
  document.getElementById('zoom-controls').style.display = 'flex';
};

// Skip button
document.getElementById('skip-btn').onclick = () => {
  sound.play('click');
  game.skipTurn();
};

// Sound toggle
document.getElementById('sound-toggle').onclick = () => {
  sound.enabled = !sound.enabled;
  document.getElementById('sound-toggle').textContent = sound.enabled ? '🔊' : '🔇';
};

// Menu button (back to menu)
document.getElementById('menu-btn').onclick = () => {
  game.state = 'menu';
  document.getElementById('menu-screen').style.display = 'flex';
  document.getElementById('hud-top').style.display = 'none';
  document.getElementById('hud-bottom').style.display = 'none';
  document.getElementById('wind-indicator').style.display = 'none';
  document.getElementById('minimap-container').style.display = 'none';
  document.getElementById('zoom-controls').style.display = 'none';
  document.getElementById('game-over').style.display = 'none';
};

// Restart button
document.getElementById('restart-btn').onclick = () => {
  document.getElementById('game-over').style.display = 'none';
  game.state = 'menu';
  document.getElementById('menu-screen').style.display = 'flex';
  document.getElementById('hud-top').style.display = 'none';
  document.getElementById('hud-bottom').style.display = 'none';
};

// Zoom buttons
document.getElementById('zoom-in').onclick = () => {
  game.targetZoom = Math.min(2.5, game.targetZoom + 0.2);
  sound.play('click');
};
document.getElementById('zoom-out').onclick = () => {
  game.targetZoom = Math.max(0.4, game.targetZoom - 0.2);
  sound.play('click');
};

// --- Main Game Loop ---
function gameLoop() {
  game.update();
  
  if(game.state === 'playing') {
    game.draw();
    game.drawUI();
    
    // Redraw water animation on terrain canvas periodically
    if(Math.random() < 0.1) {
      terrain._drawWaterAnimated(WATER_LEVEL);
    }
  } else if(game.state === 'menu') {
    // Animated background for menu
    ctx.fillStyle = '#0a0e27';
    ctx.fillRect(0, 0, canvas.width, canvas.height);
    
    // Floating particles in menu
    const time = Date.now() * 0.001;
    for(let i=0;i<50;i++) {
      const x = (Math.sin(time+i*0.7)*0.5+0.5)*canvas.width;
      const y = (Math.cos(time*0.7+i*1.1)*0.5+0.5)*canvas.height;
      ctx.fillStyle = `rgba(255,204,0,${0.1+Math.sin(time+i)*0.1})`;
      ctx.beginPath();
      ctx.arc(x, y, 2+Math.sin(time*2+i)*1, 0, Math.PI*2);
      ctx.fill();
    }
  }
  
  requestAnimationFrame(gameLoop);
}

// Start the game loop
gameLoop();

</script>
</body>
</html>
<!-- agent-meta {"model":"qwen3.6-27b-mtp","provider":"lmstudio","persona":"full","sessionId":"810f9e77-7c27-4bf5-9452-4a76c6612ff5","tokensIn":2688366,"tokensOut":40177,"tokensTotal":2728543,"turns":36,"toolCalls":35,"failedToolCalls":0,"timestamp":"2026-07-30T03:24:39.266Z"} -->