← Worms results

Worms

Gemini 3.7 Flash · 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 84,177 bytes · SHA-256 567de7f87335
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>WORMOIDS: Tactical Artillery Warfare</title>
<style>
  :root {
    --primary: #f59e0b;
    --primary-glow: rgba(245, 158, 11, 0.4);
    --accent: #ef4444;
    --accent-blue: #06b6d4;
    --bg-dark: #0f172a;
    --panel-bg: rgba(15, 23, 42, 0.85);
    --panel-border: rgba(255, 255, 255, 0.12);
  }
  * { box-sizing: border-box; margin: 0; padding: 0; user-select: none; -webkit-user-select: none; font-family: 'Segoe UI', Roboto, -apple-system, system-ui, sans-serif; }
  body, html { width: 100%; height: 100%; overflow: hidden; background: #070b14; color: #fff; }
  #canvas-container { position: absolute; top: 0; left: 0; width: 100%; height: 100%; overflow: hidden; }
  canvas#gameCanvas { display: block; width: 100%; height: 100%; }

  /* UI Layer */
  #ui-layer { position: absolute; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none; }
  .interactive { pointer-events: auto; }

  /* Top HUD */
  #top-hud {
    position: absolute; top: 12px; left: 50%; transform: translateX(-50%);
    display: flex; align-items: center; gap: 16px;
    background: var(--panel-bg); backdrop-filter: blur(10px);
    padding: 8px 24px; border-radius: 40px;
    border: 1px solid var(--panel-border);
    box-shadow: 0 10px 30px rgba(0,0,0,0.5);
    z-index: 20;
  }
  .hud-turn-badge {
    display: flex; align-items: center; gap: 8px; font-weight: 800; font-size: 14px; letter-spacing: 1px;
    text-transform: uppercase; padding: 4px 12px; border-radius: 20px;
  }
  .hud-timer {
    font-family: 'Courier New', Courier, monospace; font-size: 28px; font-weight: 900;
    color: #fff; min-width: 44px; text-align: center; text-shadow: 0 0 10px rgba(255,255,255,0.4);
    transition: transform 0.1s, color 0.2s;
  }
  .hud-timer.urgent {
    color: #ef4444; text-shadow: 0 0 15px rgba(239, 68, 68, 0.8);
    animation: timerPulse 0.5s infinite alternate;
  }
  @keyframes timerPulse { from { transform: scale(1); } to { transform: scale(1.25); } }

  .hud-wind {
    display: flex; align-items: center; gap: 6px; font-size: 13px; font-weight: 700;
    background: rgba(0,0,0,0.3); padding: 4px 12px; border-radius: 20px;
  }
  .wind-meter {
    width: 80px; height: 10px; background: rgba(255,255,255,0.1); border-radius: 5px; position: relative; overflow: hidden;
  }
  .wind-bar {
    position: absolute; top: 0; height: 100%; background: linear-gradient(90deg, #38bdf8, #818cf8);
    transition: width 0.3s, left 0.3s;
  }

  /* Teams Health Bar */
  #teams-hud {
    position: absolute; top: 12px; left: 16px; display: flex; flex-direction: column; gap: 6px;
    background: var(--panel-bg); backdrop-filter: blur(10px); padding: 8px 14px; border-radius: 12px;
    border: 1px solid var(--panel-border); font-size: 12px; font-weight: 700;
  }
  .team-status-row { display: flex; align-items: center; gap: 10px; min-width: 140px; }
  .team-dot { width: 10px; height: 10px; border-radius: 50%; }
  .team-hp-bar { flex: 1; height: 6px; background: rgba(255,255,255,0.15); border-radius: 3px; overflow: hidden; }
  .team-hp-fill { height: 100%; transition: width 0.3s ease; }

  /* Top Right Buttons */
  #top-right-tools {
    position: absolute; top: 12px; right: 16px; display: flex; gap: 8px;
  }
  .btn-icon {
    width: 38px; height: 38px; border-radius: 50%; background: var(--panel-bg);
    border: 1px solid var(--panel-border); color: #fff; display: flex; align-items: center; justify-content: center;
    cursor: pointer; transition: all 0.2s; backdrop-filter: blur(10px);
  }
  .btn-icon:hover { transform: scale(1.08); border-color: var(--primary); color: var(--primary); }

  /* Bottom Controls HUD */
  #bottom-hud {
    position: absolute; bottom: 16px; left: 50%; transform: translateX(-50%);
    display: flex; align-items: center; gap: 14px;
    background: var(--panel-bg); backdrop-filter: blur(12px);
    padding: 10px 20px; border-radius: 24px;
    border: 1px solid var(--panel-border);
    box-shadow: 0 15px 35px rgba(0,0,0,0.6);
  }
  .weapon-btn {
    display: flex; align-items: center; gap: 10px; padding: 6px 14px;
    background: rgba(255,255,255,0.08); border: 1px solid rgba(255,255,255,0.15);
    border-radius: 14px; cursor: pointer; transition: all 0.2s; font-weight: 700; font-size: 14px;
  }
  .weapon-btn:hover { background: rgba(255,255,255,0.18); border-color: var(--primary); transform: translateY(-2px); }
  .weapon-btn .weapon-icon { font-size: 20px; }

  .power-meter-box {
    display: flex; flex-direction: column; gap: 3px; min-width: 140px;
  }
  .power-meter-bar {
    width: 100%; height: 12px; background: rgba(255,255,255,0.15); border-radius: 6px;
    overflow: hidden; position: relative;
  }
  .power-meter-fill {
    height: 100%; width: 0%; background: linear-gradient(90deg, #22c55e, #eab308, #ef4444);
    box-shadow: 0 0 10px rgba(239, 68, 68, 0.5);
  }
  .stats-info {
    display: flex; justify-content: space-between; font-size: 11px; font-weight: 800; color: #94a3b8;
  }

  /* Weapon Selector Menu Modal / Overlay */
  #weapon-menu {
    position: absolute; bottom: 85px; left: 50%; transform: translateX(-50%);
    background: rgba(15, 23, 42, 0.95); backdrop-filter: blur(20px);
    border: 2px solid rgba(255,255,255,0.18); border-radius: 20px;
    padding: 16px; display: none; grid-template-columns: repeat(4, 1fr); gap: 10px;
    box-shadow: 0 25px 60px rgba(0,0,0,0.8); z-index: 50; width: 440px;
  }
  #weapon-menu.show { display: grid; animation: popUp 0.2s cubic-bezier(0.175, 0.885, 0.32, 1.275); }
  @keyframes popUp { from { opacity: 0; transform: translate(-50%, 15px) scale(0.95); } to { opacity: 1; transform: translate(-50%, 0) scale(1); } }

  .weapon-card {
    display: flex; flex-direction: column; align-items: center; justify-content: center;
    padding: 12px 6px; background: rgba(255,255,255,0.05); border-radius: 12px;
    border: 1px solid rgba(255,255,255,0.08); cursor: pointer; transition: all 0.15s;
    text-align: center; gap: 4px; position: relative;
  }
  .weapon-card:hover {
    background: rgba(245, 158, 11, 0.2); border-color: var(--primary);
    transform: translateY(-3px); box-shadow: 0 6px 16px rgba(245, 158, 11, 0.3);
  }
  .weapon-card.active {
    background: var(--primary); color: #000; border-color: #fff;
  }
  .weapon-card .w-icon { font-size: 24px; }
  .weapon-card .w-name { font-size: 11px; font-weight: 800; line-height: 1.1; }
  .weapon-card .w-cat { font-size: 9px; opacity: 0.7; text-transform: uppercase; font-weight: 700; }
  .weapon-card .w-ammo {
    position: absolute; top: 4px; right: 6px; font-size: 10px; font-weight: 900;
    background: rgba(0,0,0,0.5); padding: 1px 4px; border-radius: 4px; color: #fff;
  }

  /* Modals (Menu, Game Over, Help, Settings) */
  .modal-overlay {
    position: absolute; top: 0; left: 0; width: 100%; height: 100%;
    background: rgba(7, 11, 20, 0.85); backdrop-filter: blur(10px);
    display: flex; align-items: center; justify-content: center; z-index: 100;
  }
  .modal-overlay.hidden { display: none; }
  .modal-card {
    background: #1e293b; border: 1px solid rgba(255,255,255,0.15);
    border-radius: 24px; padding: 28px; width: 90%; max-width: 520px;
    box-shadow: 0 30px 60px rgba(0,0,0,0.7); text-align: center;
    animation: modalIn 0.3s cubic-bezier(0.16, 1, 0.3, 1);
  }
  @keyframes modalIn { from { opacity: 0; transform: scale(0.9); } to { opacity: 1; transform: scale(1); } }
  .modal-title { font-size: 28px; font-weight: 900; margin-bottom: 8px; letter-spacing: 1px; color: var(--primary); text-transform: uppercase; }
  .modal-subtitle { font-size: 14px; color: #94a3b8; margin-bottom: 22px; }

  .settings-grid { display: flex; flex-direction: column; gap: 14px; margin-bottom: 24px; text-align: left; }
  .setting-row { display: flex; justify-content: space-between; align-items: center; }
  .setting-label { font-size: 14px; font-weight: 700; }

  select, input[type="range"], button.btn-menu {
    background: #0f172a; border: 1px solid rgba(255,255,255,0.2); color: #fff;
    padding: 8px 12px; border-radius: 10px; font-size: 13px; font-weight: 700; outline: none;
    cursor: pointer;
  }
  button.btn-menu:hover { background: #334155; border-color: var(--primary); }
  .btn-primary-action {
    background: linear-gradient(135deg, #f59e0b, #d97706); color: #000;
    font-size: 16px; font-weight: 900; padding: 14px 28px; border: none; border-radius: 16px;
    cursor: pointer; box-shadow: 0 10px 25px var(--primary-glow); transition: all 0.2s;
    width: 100%; text-transform: uppercase; letter-spacing: 1px;
  }
  .btn-primary-action:hover { transform: translateY(-2px); box-shadow: 0 14px 30px rgba(245,158,11,0.6); }

  /* Mobile Onscreen Controls */
  #mobile-controls {
    position: absolute; bottom: 80px; width: 100%; padding: 0 20px;
    display: flex; justify-content: space-between; pointer-events: none;
    display: none; /* Auto revealed on touch devices */
  }
  .d-pad, .action-pad { display: flex; gap: 10px; pointer-events: auto; }
  .touch-btn {
    width: 50px; height: 50px; border-radius: 50%; background: rgba(15, 23, 42, 0.75);
    border: 2px solid rgba(255,255,255,0.2); color: #fff; font-size: 20px; font-weight: 900;
    display: flex; align-items: center; justify-content: center; backdrop-filter: blur(5px);
    box-shadow: 0 6px 15px rgba(0,0,0,0.4);
  }
  .touch-btn:active { background: var(--primary); color: #000; transform: scale(0.92); }

  /* Instructions modal quick list */
  .key-badge {
    background: rgba(255,255,255,0.15); padding: 2px 6px; border-radius: 4px;
    font-family: monospace; font-size: 11px; font-weight: bold;
  }
  .guide-row { display: flex; justify-content: space-between; margin-bottom: 6px; font-size: 13px; color: #cbd5e1; }

  /* Turn banner banner overlay */
  #turn-announce {
    position: absolute; top: 35%; left: 50%; transform: translate(-50%, -50%) scale(0.5);
    background: linear-gradient(135deg, rgba(15,23,42,0.95), rgba(30,41,59,0.95));
    border: 3px solid var(--primary); padding: 18px 45px; border-radius: 28px;
    text-align: center; pointer-events: none; opacity: 0;
    transition: all 0.35s cubic-bezier(0.175, 0.885, 0.32, 1.275);
    box-shadow: 0 20px 50px rgba(0,0,0,0.8); z-index: 60;
  }
  #turn-announce.show { opacity: 1; transform: translate(-50%, -50%) scale(1); }
  #turn-announce h2 { font-size: 32px; font-weight: 900; text-transform: uppercase; margin-bottom: 4px; }
  #turn-announce p { font-size: 14px; font-weight: 700; color: #94a3b8; }
</style>
</head>
<body>

<div id="canvas-container">
  <canvas id="gameCanvas"></canvas>
</div>

<div id="ui-layer">
  <!-- Top HUD -->
  <div id="top-hud" class="interactive">
    <div id="turn-badge" class="hud-turn-badge" style="background:#ef4444; color:#fff;">
      <span id="turn-team-name">Red Team</span>
      <span id="turn-unit-name" style="font-weight:400; font-size:12px; opacity:0.9;">(Worm 1)</span>
    </div>
    <div id="turn-timer" class="hud-timer">45</div>
    <div class="hud-wind">
      <span>💨 WIND</span>
      <div class="wind-meter">
        <div id="wind-fill" class="wind-bar" style="left:50%; width:20%;"></div>
      </div>
      <span id="wind-text" style="font-family:monospace; min-width:32px;">0 E</span>
    </div>
  </div>

  <!-- Teams Status HUD -->
  <div id="teams-hud" class="interactive"></div>

  <!-- Top Right Tools -->
  <div id="top-right-tools" class="interactive">
    <button id="btn-help" class="btn-icon" title="Controls & Help">❓</button>
    <button id="btn-audio" class="btn-icon" title="Toggle Sound">🔊</button>
    <button id="btn-pause" class="btn-icon" title="Menu / Restart">⚙️</button>
  </div>

  <!-- Mobile Controls (if touch detected) -->
  <div id="mobile-controls">
    <div class="d-pad">
      <div class="touch-btn" id="m-left">◀</div>
      <div class="touch-btn" id="m-right">▶</div>
      <div class="touch-btn" id="m-jump">▲</div>
    </div>
    <div class="action-pad">
      <div class="touch-btn" id="m-aim-up">∧</div>
      <div class="touch-btn" id="m-aim-down">∨</div>
      <div class="touch-btn" id="m-fire" style="background:#ef4444;">💥</div>
    </div>
  </div>

  <!-- Bottom Weapon & Power HUD -->
  <div id="bottom-hud" class="interactive">
    <div id="btn-weapon-select" class="weapon-btn">
      <span class="weapon-icon" id="cur-w-icon">🚀</span>
      <div>
        <div id="cur-w-name" style="font-size:13px; line-height:1.1;">Bazooka</div>
        <div id="cur-w-ammo" style="font-size:10px; color:#94a3b8;">Unlimited</div>
      </div>
      <span style="font-size:11px; opacity:0.6; margin-left:4px;">▼</span>
    </div>

    <div class="power-meter-box">
      <div class="stats-info">
        <span>POWER / CHARGE</span>
        <span id="cur-angle-text">45°</span>
      </div>
      <div class="power-meter-bar">
        <div id="power-fill" class="power-fill power-meter-fill"></div>
      </div>
    </div>
  </div>

  <!-- Weapon Selector Modal Drawer -->
  <div id="weapon-menu" class="interactive">
    <!-- Populated dynamically -->
  </div>

  <!-- Turn Announcement Banner -->
  <div id="turn-announce">
    <h2 id="banner-team-name">Red Team's Turn</h2>
    <p id="banner-unit-name">Wormy is ready for action!</p>
  </div>

  <!-- Main Menu / Match Config Modal -->
  <div id="modal-menu" class="modal-overlay interactive">
    <div class="modal-card">
      <div class="modal-title">WORMOIDS</div>
      <div class="modal-subtitle">Tactical 2D Artillery Mayhem</div>
      <div class="settings-grid">
        <div class="setting-row">
          <span class="setting-label">Terrain Map</span>
          <select id="cfg-map">
            <option value="islands">Two Islands</option>
            <option value="single">Single Island Peak</option>
            <option value="cave">Subterranean Cave</option>
          </select>
        </div>
        <div class="setting-row">
          <span class="setting-label">Teams Count</span>
          <select id="cfg-teams">
            <option value="2">2 Teams (Red vs Blue)</option>
            <option value="3">3 Teams (Red, Blue, Green)</option>
            <option value="4" selected>4 Teams (Red, Blue, Green, Yellow)</option>
          </select>
        </div>
        <div class="setting-row">
          <span class="setting-label">Units per Team</span>
          <select id="cfg-units">
            <option value="2">2 Units each</option>
            <option value="3">3 Units each</option>
            <option value="4" selected>4 Units each (16 total)</option>
          </select>
        </div>
        <div class="setting-row">
          <span class="setting-label">Match Mode</span>
          <select id="cfg-ai">
            <option value="all-human">Hot-Seat (All Humans)</option>
            <option value="vs-cpu" selected>Solo (You vs CPU Teams)</option>
            <option value="all-cpu">Spectator (All CPUs)</option>
          </select>
        </div>
        <div class="setting-row">
          <span class="setting-label">Turn Duration</span>
          <select id="cfg-turntime">
            <option value="30">30 Seconds</option>
            <option value="45" selected>45 Seconds</option>
            <option value="60">60 Seconds</option>
          </select>
        </div>
      </div>
      <button id="btn-start-game" class="btn-primary-action">START BATTLE</button>
    </div>
  </div>

  <!-- Help & Guide Modal -->
  <div id="modal-help" class="modal-overlay hidden interactive">
    <div class="modal-card">
      <div class="modal-title">Field Manual</div>
      <div class="modal-subtitle">Controls & Tactical Information</div>
      <div style="text-align:left; margin-bottom:20px;">
        <div class="guide-row"><span>Move Left / Right:</span> <span><span class="key-badge">A</span> / <span class="key-badge">D</span> or <span class="key-badge">←</span> <span class="key-badge">→</span></span></div>
        <div class="guide-row"><span>Jump / Leap:</span> <span><span class="key-badge">W</span> or <span class="key-badge">↑</span> / <span class="key-badge">Backspace</span></span></div>
        <div class="guide-row"><span>Aim Angle:</span> <span><span class="key-badge">Mouse</span> or <span class="key-badge">Q</span> / <span class="key-badge">E</span></span></div>
        <div class="guide-row"><span>Charge & Fire Weapon:</span> <span><span class="key-badge">Hold Space</span> or <span class="key-badge">Left Click</span></span></div>
        <div class="guide-row"><span>Choose Weapon:</span> <span><span class="key-badge">Tab</span> or Click Bottom HUD</span></div>
        <div class="guide-row"><span>Pan Camera:</span> <span><span class="key-badge">Right Click Drag</span> / <span class="key-badge">Middle Drag</span></span></div>
        <div class="guide-row"><span>Zoom In / Out:</span> <span><span class="key-badge">Mouse Wheel</span></span></div>
      </div>
      <div style="font-size:12px; color:#94a3b8; text-align:left; margin-bottom:18px; line-height:1.4;">
        ⚠️ <strong>Tips:</strong> Water kills instantly! Wind curves Bazooka rockets and affects jump momentum. Earthquakes and Dynamite trigger falling dirt blocks. Airstrikes must be clicked directly on target.
      </div>
      <button id="btn-close-help" class="btn-primary-action">Understood, Soldier!</button>
    </div>
  </div>

  <!-- Game Over Modal -->
  <div id="modal-gameover" class="modal-overlay hidden interactive">
    <div class="modal-card">
      <div class="modal-title" id="winner-title">VICTORY!</div>
      <div class="modal-subtitle" id="winner-subtitle">Red Team has conquered the island!</div>
      <div id="game-stats-summary" style="font-size:13px; color:#cbd5e1; margin-bottom:20px; line-height:1.5;"></div>
      <button id="btn-restart-game" class="btn-primary-action">Play Again</button>
    </div>
  </div>
</div>

<script>
/**
 * WORMOIDS - High Polish Artillery Game Engine
 * Includes:
 * - Pixel & Chunk Destructible Dual-Layer Terrain Canvas
 * - Parabolic & Direct Projectile Physics with Global Wind
 * - Hydrodynamic Rising/Waving Water with Drowning & Splashes
 * - Falling Soil / Debris physics chunks
 * - Advanced CPU Risk-Assessment AI (Avoid cliffs/water, trajectory simulation)
 * - Rich Synthesized Sound Effects (Web Audio API)
 * - Dynamic Camera follow, Zooming, Impact Screen Shake & Freeze-frames
 * - 8+ Diverse weapons & tactical utilities
 */

// --- SOUND ENGINE (Web Audio API) ---
class SoundManager {
  constructor() {
    this.ctx = null;
    this.enabled = true;
  }
  init() {
    if (!this.ctx) {
      const AudioCtx = window.AudioContext || window.webkitAudioContext;
      if (AudioCtx) this.ctx = new AudioCtx();
    }
    if (this.ctx && this.ctx.state === 'suspended') {
      this.ctx.resume();
    }
  }
  playTone(freq, type, duration, gainStart=0.2, gainEnd=0.001) {
    if (!this.enabled || !this.ctx) return;
    try {
      const osc = this.ctx.createOscillator();
      const gain = this.ctx.createGain();
      osc.type = type;
      osc.frequency.setValueAtTime(freq, this.ctx.currentTime);
      gain.gain.setValueAtTime(gainStart, this.ctx.currentTime);
      gain.gain.exponentialRampToValueAtTime(gainEnd, this.ctx.currentTime + duration);
      osc.connect(gain);
      gain.connect(this.ctx.destination);
      osc.start();
      osc.stop(this.ctx.currentTime + duration);
    } catch(e){}
  }
  playNoise(duration, filterFreq=1000, gainStart=0.3) {
    if (!this.enabled || !this.ctx) return;
    try {
      const bufferSize = this.ctx.sampleRate * duration;
      const buffer = this.ctx.createBuffer(1, bufferSize, this.ctx.sampleRate);
      const output = buffer.getChannelData(0);
      for (let i = 0; i < bufferSize; i++) {
        output[i] = Math.random() * 2 - 1;
      }
      const whiteNoise = this.ctx.createBufferSource();
      whiteNoise.buffer = buffer;
      const filter = this.ctx.createBiquadFilter();
      filter.type = 'lowpass';
      filter.frequency.setValueAtTime(filterFreq, this.ctx.currentTime);
      filter.frequency.exponentialRampToValueAtTime(100, this.ctx.currentTime + duration);
      const gain = this.ctx.createGain();
      gain.gain.setValueAtTime(gainStart, this.ctx.currentTime);
      gain.gain.exponentialRampToValueAtTime(0.01, this.ctx.currentTime + duration);
      whiteNoise.connect(filter);
      filter.connect(gain);
      gain.connect(this.ctx.destination);
      whiteNoise.start();
    } catch(e){}
  }
  explosion(heavy = false) {
    this.init();
    this.playNoise(heavy ? 0.9 : 0.5, heavy ? 800 : 1200, heavy ? 0.6 : 0.35);
    this.playTone(heavy ? 65 : 120, 'sawtooth', heavy ? 0.6 : 0.35, 0.4, 0.01);
  }
  launch() {
    this.init();
    if (!this.enabled || !this.ctx) return;
    try {
      const osc = this.ctx.createOscillator();
      const gain = this.ctx.createGain();
      osc.type = 'sine';
      osc.frequency.setValueAtTime(250, this.ctx.currentTime);
      osc.frequency.exponentialRampToValueAtTime(800, this.ctx.currentTime + 0.25);
      gain.gain.setValueAtTime(0.25, this.ctx.currentTime);
      gain.gain.linearRampToValueAtTime(0.01, this.ctx.currentTime + 0.25);
      osc.connect(gain);
      gain.connect(this.ctx.destination);
      osc.start();
      osc.stop(this.ctx.currentTime + 0.25);
    }catch(e){}
  }
  gunshot() {
    this.init();
    this.playNoise(0.2, 3500, 0.4);
    this.playTone(300, 'sawtooth', 0.15, 0.3, 0.01);
  }
  jump() {
    this.init();
    this.playTone(320, 'sine', 0.18, 0.15, 0.01);
  }
  wormHit() {
    this.init();
    this.playTone(180, 'triangle', 0.2, 0.3, 0.01);
  }
  splash() {
    this.init();
    this.playNoise(0.4, 900, 0.3);
    this.playTone(280, 'sine', 0.25, 0.2, 0.01);
  }
  tick(urgent = false) {
    this.init();
    this.playTone(urgent ? 880 : 440, 'square', 0.04, urgent ? 0.15 : 0.05, 0.001);
  }
  teleport() {
    this.init();
    if (!this.enabled || !this.ctx) return;
    try {
      const osc = this.ctx.createOscillator();
      const gain = this.ctx.createGain();
      osc.type = 'sawtooth';
      osc.frequency.setValueAtTime(300, this.ctx.currentTime);
      osc.frequency.exponentialRampToValueAtTime(1400, this.ctx.currentTime + 0.3);
      gain.gain.setValueAtTime(0.2, this.ctx.currentTime);
      gain.gain.exponentialRampToValueAtTime(0.01, this.ctx.currentTime + 0.3);
      osc.connect(gain);
      gain.connect(this.ctx.destination);
      osc.start();
      osc.stop(this.ctx.currentTime + 0.3);
    }catch(e){}
  }
  click() {
    this.init();
    this.playTone(900, 'sine', 0.03, 0.08, 0.001);
  }
}
const sounds = new SoundManager();

// --- WEAPONS DATABASE ---
const WEAPONS = [
  {
    id: 'bazooka',
    name: 'Bazooka',
    icon: '🚀',
    category: 'Indirect',
    type: 'projectile',
    damage: 50,
    radius: 48,
    affectedByWind: true,
    bounces: 0,
    fuse: 0,
    ammo: -1,
    desc: 'Classic rocket. Trajectory bends with atmospheric wind.'
  },
  {
    id: 'grenade',
    name: 'Grenade',
    icon: '💣',
    category: 'Indirect',
    type: 'grenade',
    damage: 60,
    radius: 54,
    affectedByWind: false,
    bounces: 3,
    fuse: 3.0,
    ammo: -1,
    desc: 'Bounces on terrain before detonating after 3 seconds.'
  },
  {
    id: 'cluster',
    name: 'Cluster Bomb',
    icon: '💥',
    category: 'Indirect',
    type: 'cluster',
    damage: 35,
    radius: 38,
    clusterCount: 5,
    clusterDamage: 25,
    clusterRadius: 28,
    affectedByWind: false,
    bounces: 2,
    fuse: 2.2,
    ammo: 3,
    desc: 'Explodes into 5 bouncing sub-munitions with huge spread.'
  },
  {
    id: 'sniper',
    name: 'Sniper Rifle',
    icon: '🎯',
    category: 'Direct',
    type: 'instant_ray',
    damage: 45,
    radius: 18,
    ammo: -1,
    desc: 'High velocity pinpoint shot with heavy kinetic knockback.'
  },
  {
    id: 'shotgun',
    name: 'Shotgun',
    icon: '💥',
    category: 'Direct',
    type: 'shotgun',
    damage: 25,
    pellets: 4,
    radius: 14,
    ammo: -1,
    desc: 'Fires a tight spread of 4 buckshot shells.'
  },
  {
    id: 'airstrike',
    name: 'Air Strike',
    icon: '✈️',
    category: 'Map-altering',
    type: 'airstrike',
    damage: 40,
    bombs: 5,
    radius: 36,
    ammo: 2,
    desc: 'Radio in a bomber for 5 napalm explosives directly on coordinate.'
  },
  {
    id: 'dynamite',
    name: 'Dynamite',
    icon: '🧨',
    category: 'Map-altering',
    type: 'plantable',
    damage: 85,
    radius: 75,
    fuse: 5.0,
    ammo: 2,
    desc: 'High-yield explosive. Drops at your feet. Massive crater.'
  },
  {
    id: 'holygrenade',
    name: 'Holy Hand Bomb',
    icon: '✨',
    category: 'Map-altering',
    type: 'holy',
    damage: 100,
    radius: 95,
    fuse: 3.5,
    ammo: 1,
    desc: 'Legendary divine ordinance. Clears huge chunks of earth!'
  },
  {
    id: 'teleport',
    name: 'Teleporter',
    icon: '🌀',
    category: 'Utility',
    type: 'teleport',
    ammo: 2,
    desc: 'Instantly beam active worm to any open point on map.'
  },
  {
    id: 'girder',
    name: 'Girder Bridge',
    icon: '🏗️',
    category: 'Utility',
    type: 'girder',
    ammo: 3,
    desc: 'Place a solid steel platform for bridge or protective shield.'
  }
];

const TEAM_PALETTES = [
  { name: 'Red Commando', color: '#ef4444', dark: '#991b1b', light: '#fca5a5' },
  { name: 'Blue Marines', color: '#3b82f6', dark: '#1e40af', light: '#93c5fd' },
  { name: 'Green Guerrilla', color: '#22c55e', dark: '#166534', light: '#86efac' },
  { name: 'Yellow Blitz', color: '#eab308', dark: '#854d0e', light: '#fde047' }
];

const WORM_NAMES = [
  ['Sparky', 'Blaze', 'Pyro', 'Ash'],
  ['Splash', 'Frost', 'Torpedo', 'Riptide'],
  ['Sprout', 'Viper', 'Fern', 'Moss'],
  ['Zappy', 'Flash', 'Volt', 'Thunder']
];

// --- MAIN ENGINE STATE ---
class WormoidsGame {
  constructor() {
    this.canvas = document.getElementById('gameCanvas');
    this.ctx = this.canvas.getContext('2d');
    
    // World Dimensions
    this.worldWidth = 2200;
    this.worldHeight = 1200;
    this.waterLevel = 1080;
    this.waterPhase = 0;

    // Terrain canvas for fast pixel masks & pixel-accurate collision
    this.terrainCanvas = document.createElement('canvas');
    this.terrainCanvas.width = this.worldWidth;
    this.terrainCanvas.height = this.worldHeight;
    this.terrainCtx = this.terrainCanvas.getContext('2d', { willReadFrequently: true });

    // Terrain Decor Canvas (grass, dirt veins, rocks)
    this.terrainDecorCanvas = document.createElement('canvas');
    this.terrainDecorCanvas.width = this.worldWidth;
    this.terrainDecorCanvas.height = this.worldHeight;
    this.terrainDecorCtx = this.terrainDecorCanvas.getContext('2d');

    // Camera
    this.camera = {
      x: 1100,
      y: 600,
      targetX: 1100,
      targetY: 600,
      zoom: 0.9,
      targetZoom: 0.9,
      shake: 0,
      freezeFrames: 0
    };

    // Environment & Physics constants
    this.gravity = 0.32;
    this.wind = 0; // -1 to +1
    this.windTarget = 0;

    // Game Entities
    this.teams = [];
    this.currentTeamIndex = 0;
    this.currentUnitIndex = 0;
    this.projectiles = [];
    this.particles = [];
    this.debrisChunks = [];
    this.floatingTexts = [];

    // Turn Control
    this.turnDuration = 45;
    this.turnTimer = 45;
    this.turnState = 'aiming'; // 'aiming', 'firing', 'resolving', 'turn_end', 'game_over'
    this.lastTickTime = 0;
    this.turnStartTime = 0;
    this.activeWorm = null;

    // Settings
    this.gameConfig = {
      mapShape: 'islands',
      teamCount: 4,
      unitsPerTeam: 4,
      mode: 'vs-cpu', // 'all-human', 'vs-cpu', 'all-cpu'
      turnTime: 45
    };

    // User Input
    this.keys = {};
    this.mouse = {
      worldX: 0,
      worldY: 0,
      screenX: 0,
      screenY: 0,
      isDown: false,
      rightDown: false,
      dragStart: { x: 0, y: 0 }
    };
    this.isCharging = false;
    this.power = 0; // 0 to 1
    this.powerDirection = 1;

    // Selected Weapon
    this.selectedWeaponIndex = 0;

    // Utility placing mode (Girder / AirStrike / Teleport)
    this.placementAngle = 0;

    // Animation & Loop
    this.lastTime = performance.now();
    this.initWindow();
    this.initEvents();
    this.initUI();
  }

  initWindow() {
    const resize = () => {
      this.canvas.width = window.innerWidth;
      this.canvas.height = window.innerHeight;
    };
    window.addEventListener('resize', resize);
    resize();
  }

  initEvents() {
    window.addEventListener('keydown', (e) => {
      this.keys[e.key.toLowerCase()] = true;
      this.keys[e.code] = true;

      if (e.key === 'Tab') {
        e.preventDefault();
        this.toggleWeaponMenu();
      }
      if (e.key === ' ' && !e.repeat && this.isTurnActive() && !this.isActiveWormCPU()) {
        const w = this.getCurrentWeapon();
        if (w.type !== 'airstrike' && w.type !== 'teleport' && w.type !== 'girder') {
          this.startCharging();
        }
      }
      if ((e.key === 'w' || e.key === 'ArrowUp' || e.key === 'Backspace') && this.isTurnActive() && !this.isActiveWormCPU()) {
        if (this.activeWorm && this.activeWorm.grounded) {
          this.activeWorm.jump();
        }
      }
    });

    window.addEventListener('keyup', (e) => {
      this.keys[e.key.toLowerCase()] = false;
      this.keys[e.code] = false;

      if (e.key === ' ' && this.isCharging && !this.isActiveWormCPU()) {
        this.releaseFire();
      }
    });

    this.canvas.addEventListener('mousemove', (e) => {
      this.mouse.screenX = e.clientX;
      this.mouse.screenY = e.clientY;
      const worldPos = this.screenToWorld(e.clientX, e.clientY);
      this.mouse.worldX = worldPos.x;
      this.mouse.worldY = worldPos.y;

      if (this.mouse.rightDown) {
        const dx = (e.clientX - this.mouse.dragStart.x) / this.camera.zoom;
        const dy = (e.clientY - this.mouse.dragStart.y) / this.camera.zoom;
        this.camera.targetX -= dx;
        this.camera.targetY -= dy;
        this.mouse.dragStart = { x: e.clientX, y: e.clientY };
      }

      // Update worm aim angle if mouse is moved during aiming
      if (this.activeWorm && !this.isActiveWormCPU() && this.turnState === 'aiming') {
        const dx = this.mouse.worldX - this.activeWorm.x;
        const dy = this.mouse.worldY - (this.activeWorm.y - 12);
        this.activeWorm.aimAngle = Math.atan2(dy, dx);
        this.activeWorm.facing = dx >= 0 ? 1 : -1;
      }
    });

    this.canvas.addEventListener('mousedown', (e) => {
      if (e.button === 0) { // Left click
        this.mouse.isDown = true;
        if (this.isTurnActive() && !this.isActiveWormCPU()) {
          const w = this.getCurrentWeapon();
          if (w.type === 'airstrike' || w.type === 'teleport' || w.type === 'girder') {
            this.executeTargetAction(this.mouse.worldX, this.mouse.worldY);
          } else {
            this.startCharging();
          }
        }
      } else if (e.button === 2 || e.button === 1) { // Right or middle click drag
        e.preventDefault();
        this.mouse.rightDown = true;
        this.mouse.dragStart = { x: e.clientX, y: e.clientY };
      }
    });

    this.canvas.addEventListener('mouseup', (e) => {
      if (e.button === 0) {
        this.mouse.isDown = false;
        if (this.isCharging && !this.isActiveWormCPU()) {
          this.releaseFire();
        }
      } else if (e.button === 2 || e.button === 1) {
        this.mouse.rightDown = false;
      }
    });

    this.canvas.addEventListener('contextmenu', (e) => e.preventDefault());

    this.canvas.addEventListener('wheel', (e) => {
      e.preventDefault();
      const zoomFactor = e.deltaY < 0 ? 1.1 : 0.9;
      this.camera.targetZoom = Math.max(0.5, Math.min(1.8, this.camera.targetZoom * zoomFactor));
    }, { passive: false });

    // Touch Support for Mobile
    this.setupTouchControls();
  }

  setupTouchControls() {
    const isTouch = ('ontouchstart' in window) || (navigator.maxTouchPoints > 0);
    if (isTouch) {
      const mob = document.getElementById('mobile-controls');
      if (mob) mob.style.display = 'flex';
      
      const hookBtn = (id, onDown, onUp) => {
        const el = document.getElementById(id);
        if (!el) return;
        el.addEventListener('touchstart', (e) => { e.preventDefault(); onDown(); });
        el.addEventListener('touchend', (e) => { e.preventDefault(); if (onUp) onUp(); });
      };

      hookBtn('m-left', () => { this.keys['arrowleft'] = true; }, () => { this.keys['arrowleft'] = false; });
      hookBtn('m-right', () => { this.keys['arrowright'] = true; }, () => { this.keys['arrowright'] = false; });
      hookBtn('m-jump', () => { if (this.activeWorm) this.activeWorm.jump(); });
      hookBtn('m-aim-up', () => { if (this.activeWorm) this.activeWorm.aimAngle -= 0.1; });
      hookBtn('m-aim-down', () => { if (this.activeWorm) this.activeWorm.aimAngle += 0.1; });
      hookBtn('m-fire', () => { this.startCharging(); }, () => { this.releaseFire(); });
    }
  }

  initUI() {
    // Menu Buttons
    document.getElementById('btn-start-game').onclick = () => {
      sounds.click();
      this.gameConfig.mapShape = document.getElementById('cfg-map').value;
      this.gameConfig.teamCount = parseInt(document.getElementById('cfg-teams').value);
      this.gameConfig.unitsPerTeam = parseInt(document.getElementById('cfg-units').value);
      this.gameConfig.mode = document.getElementById('cfg-ai').value;
      this.gameConfig.turnTime = parseInt(document.getElementById('cfg-turntime').value);
      this.turnDuration = this.gameConfig.turnTime;
      
      document.getElementById('modal-menu').classList.add('hidden');
      this.startNewMatch();
    };

    document.getElementById('btn-pause').onclick = () => {
      sounds.click();
      document.getElementById('modal-menu').classList.remove('hidden');
    };

    document.getElementById('btn-help').onclick = () => {
      sounds.click();
      document.getElementById('modal-help').classList.remove('hidden');
    };

    document.getElementById('btn-close-help').onclick = () => {
      sounds.click();
      document.getElementById('modal-help').classList.add('hidden');
    };

    document.getElementById('btn-audio').onclick = () => {
      sounds.enabled = !sounds.enabled;
      document.getElementById('btn-audio').innerText = sounds.enabled ? '🔊' : '🔇';
      sounds.click();
    };

    document.getElementById('btn-restart-game').onclick = () => {
      sounds.click();
      document.getElementById('modal-gameover').classList.add('hidden');
      this.startNewMatch();
    };

    const weaponBtn = document.getElementById('btn-weapon-select');
    weaponBtn.onclick = () => {
      sounds.click();
      this.toggleWeaponMenu();
    };

    this.populateWeaponMenu();
  }

  populateWeaponMenu() {
    const container = document.getElementById('weapon-menu');
    container.innerHTML = '';
    WEAPONS.forEach((w, idx) => {
      const card = document.createElement('div');
      card.className = `weapon-card ${idx === this.selectedWeaponIndex ? 'active' : ''}`;
      card.innerHTML = `
        <div class="w-ammo">${w.ammo === -1 ? '∞' : w.ammo}</div>
        <div class="w-icon">${w.icon}</div>
        <div class="w-name">${w.name}</div>
        <div class="w-cat">${w.category}</div>
      `;
      card.onclick = () => {
        sounds.click();
        this.selectWeapon(idx);
        this.toggleWeaponMenu(false);
      };
      container.appendChild(card);
    });
  }

  toggleWeaponMenu(forceState) {
    const menu = document.getElementById('weapon-menu');
    if (forceState !== undefined) {
      if (forceState) menu.classList.add('show');
      else menu.classList.remove('show');
    } else {
      menu.classList.toggle('show');
    }
  }

  selectWeapon(index) {
    this.selectedWeaponIndex = index;
    const w = WEAPONS[index];
    document.getElementById('cur-w-icon').innerText = w.icon;
    document.getElementById('cur-w-name').innerText = w.name;
    document.getElementById('cur-w-ammo').innerText = w.ammo === -1 ? 'Unlimited' : `${w.ammo} Left`;

    // update active classes in menu
    const cards = document.querySelectorAll('.weapon-card');
    cards.forEach((c, i) => {
      c.classList.toggle('active', i === index);
    });
  }

  getCurrentWeapon() {
    return WEAPONS[this.selectedWeaponIndex];
  }

  isTurnActive() {
    return this.turnState === 'aiming' && this.activeWorm && this.activeWorm.alive;
  }

  isActiveWormCPU() {
    if (!this.activeWorm) return false;
    const team = this.teams[this.currentTeamIndex];
    return team.isCPU;
  }

  startCharging() {
    if (this.isCharging || this.turnState !== 'aiming') return;
    const w = this.getCurrentWeapon();
    if (w.ammo === 0) {
      this.spawnFloatingText(this.activeWorm.x, this.activeWorm.y - 30, 'Out of Ammo!', '#f87171');
      return;
    }
    this.isCharging = true;
    this.power = 0.05;
    this.powerDirection = 1;
  }

  releaseFire() {
    if (!this.isCharging) return;
    this.isCharging = false;
    const finalPower = this.power;
    this.power = 0;
    this.fireWeapon(finalPower);
  }

  // --- MAP PROCEDURAL GENERATION ---
  generateTerrain(mapShape) {
    const ctx = this.terrainCtx;
    const dCtx = this.terrainDecorCtx;
    const W = this.worldWidth;
    const H = this.worldHeight;

    ctx.clearRect(0, 0, W, H);
    dCtx.clearRect(0, 0, W, H);

    ctx.fillStyle = '#65a30d'; // Base soil collision fill

    // 1D Perlin-like composite noise
    const noise = (x, freq, seed) => {
      return Math.sin(x * freq + seed) * 0.5 + Math.sin(x * freq * 2.3 + seed * 1.5) * 0.25 + Math.cos(x * freq * 0.7 + seed * 3.1) * 0.25;
    };

    const seed = Math.random() * 100;

    if (mapShape === 'islands') {
      // Two distinct land masses separated by water in the middle
      ctx.beginPath();
      ctx.moveTo(0, H);
      for (let x = 0; x <= W; x += 8) {
        let island1 = Math.exp(-Math.pow((x - 550) / 320, 2)) * 480;
        let island2 = Math.exp(-Math.pow((x - 1650) / 320, 2)) * 480;
        let surfaceY = H - 240 - (island1 + island2) + noise(x, 0.008, seed) * 70;
        ctx.lineTo(x, surfaceY);
      }
      ctx.lineTo(W, H);
      ctx.closePath();
      ctx.fill();

    } else if (mapShape === 'single') {
      // One massive central mountain island
      ctx.beginPath();
      ctx.moveTo(0, H);
      for (let x = 0; x <= W; x += 8) {
        let centerDist = Math.abs(x - W / 2);
        let peak = Math.max(0, 600 - Math.pow(centerDist / 38, 1.8));
        let surfaceY = H - 200 - peak + noise(x, 0.007, seed) * 90;
        ctx.lineTo(x, surfaceY);
      }
      ctx.lineTo(W, H);
      ctx.closePath();
      ctx.fill();

    } else if (mapShape === 'cave') {
      // Top ceiling and bottom floor with cavernous pockets
      ctx.beginPath();
      ctx.moveTo(0, H);
      // Floor
      for (let x = 0; x <= W; x += 8) {
        let surfaceY = H - 280 + noise(x, 0.006, seed) * 120;
        ctx.lineTo(x, surfaceY);
      }
      ctx.lineTo(W, H);
      ctx.closePath();
      ctx.fill();

      // Ceiling
      ctx.beginPath();
      ctx.moveTo(0, 0);
      for (let x = 0; x <= W; x += 8) {
        let ceilingY = 280 + noise(x, 0.007, seed + 50) * 140;
        ctx.lineTo(x, ceilingY);
      }
      ctx.lineTo(W, 0);
      ctx.closePath();
      ctx.fill();

      // Add central floating stalactites/pillars
      for (let i = 0; i < 3; i++) {
        let px = 500 + i * 600 + (Math.random() * 100 - 50);
        let py = 550 + noise(px, 0.01, seed) * 80;
        ctx.beginPath();
        ctx.arc(px, py, 110 + Math.random() * 40, 0, Math.PI * 2);
        ctx.fill();
      }
    }

    // Carve random cave bubbles & tunnels in the terrain for tactical depth
    ctx.globalCompositeOperation = 'destination-out';
    for (let i = 0; i < 7; i++) {
      let cx = 200 + Math.random() * (W - 400);
      let cy = 500 + Math.random() * 400;
      let r = 50 + Math.random() * 60;
      ctx.beginPath();
      ctx.arc(cx, cy, r, 0, Math.PI * 2);
      ctx.fill();
    }
    ctx.globalCompositeOperation = 'source-over';

    // Build gorgeous visual Decor Layer
    this.renderTerrainDecorations();
  }

  renderTerrainDecorations() {
    const dCtx = this.terrainDecorCtx;
    const W = this.worldWidth;
    const H = this.worldHeight;

    // Rich underground dirt & rock
    const grad = dCtx.createLinearGradient(0, 0, 0, H);
    grad.addColorStop(0, '#854d0e');
    grad.addColorStop(0.3, '#713f12');
    grad.addColorStop(0.7, '#451a03');
    grad.addColorStop(1, '#1c1917');

    dCtx.fillStyle = grad;
    dCtx.fillRect(0, 0, W, H);

    // Overlay strata rock stripes
    dCtx.fillStyle = 'rgba(0,0,0,0.18)';
    for (let y = 0; y < H; y += 35) {
      dCtx.fillRect(0, y + (Math.sin(y * 0.05) * 12), W, 12);
    }

    // Ore and rock specks
    dCtx.fillStyle = 'rgba(255,255,255,0.08)';
    for (let i = 0; i < 800; i++) {
      let rx = Math.random() * W;
      let ry = Math.random() * H;
      dCtx.beginPath();
      dCtx.arc(rx, ry, Math.random() * 3 + 1, 0, Math.PI * 2);
      dCtx.fill();
    }

    // Add lush green surface grass fringe on top of solid pixels
    const terrainImg = this.terrainCtx.getImageData(0, 0, W, H);
    const data = terrainImg.data;
    dCtx.fillStyle = '#22c55e'; // vibrant grass green
    
    // Quick scan surface edges to paint grass
    for (let x = 0; x < W; x += 2) {
      for (let y = 2; y < H - 2; y += 2) {
        const idx = (y * W + x) * 4;
        if (data[idx + 3] > 120) { // Solid pixel
          const aboveIdx = ((y - 4) * W + x) * 4;
          if (aboveIdx >= 0 && data[aboveIdx + 3] < 50) { // Air above it
            // Grass crest
            dCtx.fillRect(x - 2, y - 2, 4, 8);
          }
        }
      }
    }

    // Mask the decoration using the collision terrain canvas
    dCtx.globalCompositeOperation = 'destination-in';
    dCtx.drawImage(this.terrainCanvas, 0, 0);
    dCtx.globalCompositeOperation = 'source-over';
  }

  // Pixel-based terrain destruction crater
  destroyTerrain(cx, cy, radius) {
    if (radius <= 0) return;
    
    // Punch hole in collision canvas
    this.terrainCtx.globalCompositeOperation = 'destination-out';
    this.terrainCtx.beginPath();
    this.terrainCtx.arc(cx, cy, radius, 0, Math.PI * 2);
    this.terrainCtx.fill();
    this.terrainCtx.globalCompositeOperation = 'source-over';

    // Punch matching hole in visual decor canvas
    this.terrainDecorCtx.globalCompositeOperation = 'destination-out';
    this.terrainDecorCtx.beginPath();
    this.terrainDecorCtx.arc(cx, cy, radius, 0, Math.PI * 2);
    this.terrainDecorCtx.fill();
    this.terrainDecorCtx.globalCompositeOperation = 'source-over';

    // Spawn physics falling debris chunks from the blast
    const chunkCount = Math.floor(radius / 4);
    for (let i = 0; i < chunkCount; i++) {
      const ang = Math.random() * Math.PI * 2;
      const dist = Math.random() * radius * 0.8;
      const speed = 3 + Math.random() * 7;
      this.debrisChunks.push({
        x: cx + Math.cos(ang) * dist,
        y: cy + Math.sin(ang) * dist,
        vx: Math.cos(ang) * speed + (this.wind * 1.5),
        vy: Math.sin(ang) * speed - (2 + Math.random() * 4),
        radius: 2 + Math.random() * 4.5,
        rotation: Math.random() * Math.PI * 2,
        vRot: (Math.random() - 0.5) * 0.2,
        color: Math.random() > 0.4 ? '#854d0e' : '#65a30d',
        life: 180
      });
    }
  }

  addGirder(x1, y1, x2, y2, thickness = 14) {
    const ctx = this.terrainCtx;
    const dCtx = this.terrainDecorCtx;

    [ctx, dCtx].forEach(c => {
      c.strokeStyle = '#64748b';
      c.lineWidth = thickness;
      c.lineCap = 'round';
      c.beginPath();
      c.moveTo(x1, y1);
      c.lineTo(x2, y2);
      c.stroke();
    });

    // Girder steel rivet visuals
    dCtx.strokeStyle = '#94a3b8';
    dCtx.lineWidth = 2;
    dCtx.beginPath();
    dCtx.moveTo(x1, y1);
    dCtx.lineTo(x2, y2);
    dCtx.stroke();
  }

  isSolid(x, y) {
    if (x < 0 || x >= this.worldWidth || y >= this.worldHeight) return true;
    if (y < 0) return false;
    const pixel = this.terrainCtx.getImageData(Math.floor(x), Math.floor(y), 1, 1).data;
    return pixel[3] > 120; // alpha check
  }

  // --- MATCH INITIALIZATION ---
  startNewMatch() {
    this.projectiles = [];
    this.particles = [];
    this.debrisChunks = [];
    this.floatingTexts = [];
    this.teams = [];
    this.currentTeamIndex = 0;
    this.currentUnitIndex = 0;
    this.turnState = 'aiming';
    this.turnTimer = this.turnDuration;
    this.randomizeWind();

    // 1. Generate procedural map
    this.generateTerrain(this.gameConfig.mapShape);

    // 2. Instantiate Teams and Worms
    const numTeams = this.gameConfig.teamCount;
    const unitsCount = this.gameConfig.unitsPerTeam;
    const mode = this.gameConfig.mode;

    for (let t = 0; t < numTeams; t++) {
      const p = TEAM_PALETTES[t];
      let isCPU = false;
      if (mode === 'all-cpu') isCPU = true;
      else if (mode === 'vs-cpu' && t > 0) isCPU = true;

      const team = {
        id: t,
        name: p.name,
        color: p.color,
        darkColor: p.dark,
        lightColor: p.light,
        isCPU: isCPU,
        units: []
      };

      for (let u = 0; u < unitsCount; u++) {
        const wormName = WORM_NAMES[t][u % WORM_NAMES[t].length] || `Worm ${u+1}`;
        const worm = new Worm(this, team, wormName);
        this.spawnWormSafely(worm);
        team.units.push(worm);
      }
      this.teams.push(team);
    }

    this.selectWeapon(0);
    this.setupTurn();
    this.updateHUD();
  }

  spawnWormSafely(worm) {
    let safe = false;
    let attempts = 0;
    while (!safe && attempts < 150) {
      attempts++;
      const testX = 120 + Math.random() * (this.worldWidth - 240);
      let foundGround = false;
      let groundY = 0;

      for (let y = 100; y < this.waterLevel - 30; y += 4) {
        if (this.isSolid(testX, y)) {
          groundY = y;
          foundGround = true;
          break;
        }
      }

      if (foundGround && groundY < this.waterLevel - 50) {
        worm.x = testX;
        worm.y = groundY - 14;
        worm.vx = 0;
        worm.vy = 0;
        worm.grounded = true;
        safe = true;
      }
    }
  }

  randomizeWind() {
    this.windTarget = (Math.random() * 2 - 1) * 0.8;
    this.wind = this.windTarget;
    this.updateWindUI();
  }

  updateWindUI() {
    const windBar = document.getElementById('wind-fill');
    const windText = document.getElementById('wind-text');
    if (!windBar || !windText) return;
    
    const pct = Math.abs(this.wind) * 50;
    if (this.wind >= 0) {
      windBar.style.left = '50%';
      windBar.style.width = `${pct}%`;
      windText.innerText = `${Math.round(this.wind * 100)} E`;
      windText.style.color = '#38bdf8';
    } else {
      windBar.style.left = `${50 - pct}%`;
      windBar.style.width = `${pct}%`;
      windText.innerText = `${Math.round(Math.abs(this.wind) * 100)} W`;
      windText.style.color = '#f472b6';
    }
  }

  // --- TURN SYSTEM ---
  setupTurn() {
    // Pick next active living worm
    let attempts = 0;
    const totalTeams = this.teams.length;
    
    // Check if match already won
    const aliveTeams = this.teams.filter(t => t.units.some(u => u.alive));
    if (aliveTeams.length <= 1) {
      this.handleGameOver(aliveTeams[0]);
      return;
    }

    let nextWorm = null;
    while (!nextWorm && attempts < 30) {
      const team = this.teams[this.currentTeamIndex];
      const livingWorms = team.units.filter(u => u.alive);
      if (livingWorms.length > 0) {
        const uIdx = this.currentUnitIndex % livingWorms.length;
        nextWorm = livingWorms[uIdx];
      } else {
        this.currentTeamIndex = (this.currentTeamIndex + 1) % totalTeams;
      }
      attempts++;
    }

    if (!nextWorm) return;

    this.activeWorm = nextWorm;
    this.turnState = 'aiming';
    this.turnTimer = this.turnDuration;
    this.turnStartTime = performance.now();
    this.isCharging = false;
    this.power = 0;
    this.randomizeWind();

    // Focus camera
    this.camera.targetX = this.activeWorm.x;
    this.camera.targetY = this.activeWorm.y - 40;

    // Show banner notification
    this.showTurnBanner(this.teams[this.currentTeamIndex].name, `${this.activeWorm.name} (${this.teams[this.currentTeamIndex].isCPU ? 'CPU' : 'Player'})`);

    this.updateHUD();

    // If CPU, trigger AI thinking routine
    if (this.isActiveWormCPU()) {
      setTimeout(() => {
        if (this.turnState === 'aiming') {
          this.executeCPUTurn();
        }
      }, 900);
    }
  }

  showTurnBanner(teamName, wormName) {
    const banner = document.getElementById('turn-announce');
    const teamEl = document.getElementById('banner-team-name');
    const unitEl = document.getElementById('banner-unit-name');
    const curTeam = this.teams[this.currentTeamIndex];
    
    if (banner && teamEl && unitEl) {
      teamEl.innerText = teamName;
      teamEl.style.color = curTeam.color;
      unitEl.innerText = `${wormName}'s Turn!`;
      banner.classList.add('show');
      setTimeout(() => {
        banner.classList.remove('show');
      }, 1400);
    }
  }

  endTurn() {
    this.turnState = 'turn_end';
    this.isCharging = false;
    this.power = 0;

    setTimeout(() => {
      // Step to next team and unit
      this.currentUnitIndex++;
      this.currentTeamIndex = (this.currentTeamIndex + 1) % this.teams.length;
      this.setupTurn();
    }, 1200);
  }

  handleGameOver(winnerTeam) {
    this.turnState = 'game_over';
    const modal = document.getElementById('modal-gameover');
    const title = document.getElementById('winner-title');
    const subtitle = document.getElementById('winner-subtitle');
    const stats = document.getElementById('game-stats-summary');

    if (winnerTeam) {
      title.innerText = `🏆 ${winnerTeam.name} WINS!`;
      title.style.color = winnerTeam.color;
      subtitle.innerText = `Total battlefield victory in Wormoids!`;
    } else {
      title.innerText = `DRAW GAME!`;
      subtitle.innerText = `All worms succumbed to the tides of battle.`;
    }

    stats.innerHTML = `
      Match Summary:<br>
      Total Teams: ${this.teams.length}<br>
      Casualties: ${this.teams.reduce((acc, t) => acc + t.units.filter(u => !u.alive).length, 0)} worms fallen in combat.
    `;

    modal.classList.remove('hidden');
  }

  // --- WEAPON FIRING & ACTION RESOLUTION ---
  fireWeapon(chargePower) {
    if (!this.activeWorm || !this.activeWorm.alive) return;
    const w = this.getCurrentWeapon();
    if (w.ammo > 0) w.ammo--;

    this.turnState = 'firing';
    const angle = this.activeWorm.aimAngle;
    const startX = this.activeWorm.x + Math.cos(angle) * 18;
    const startY = this.activeWorm.y - 10 + Math.sin(angle) * 18;
    const speed = 4 + chargePower * 24;

    if (w.type === 'projectile' || w.type === 'grenade' || w.type === 'cluster' || w.type === 'holy') {
      sounds.launch();
      this.projectiles.push(new Projectile(this, {
        x: startX,
        y: startY,
        vx: Math.cos(angle) * speed,
        vy: Math.sin(angle) * speed,
        weapon: w,
        owner: this.activeWorm
      }));
    } else if (w.type === 'instant_ray') {
      sounds.gunshot();
      this.fireSniperRay(startX, startY, angle, w.damage);
      this.onAttackCompleted();
    } else if (w.type === 'shotgun') {
      sounds.gunshot();
      for (let i = 0; i < (w.pellets || 4); i++) {
        const spread = (Math.random() - 0.5) * 0.18;
        const pSpeed = speed * (0.85 + Math.random() * 0.3);
        this.projectiles.push(new Projectile(this, {
          x: startX,
          y: startY,
          vx: Math.cos(angle + spread) * pSpeed,
          vy: Math.sin(angle + spread) * pSpeed,
          weapon: w,
          owner: this.activeWorm
        }));
      }
    } else if (w.type === 'plantable') {
      // Plant dynamite at worm feet
      sounds.launch();
      this.projectiles.push(new Projectile(this, {
        x: this.activeWorm.x,
        y: this.activeWorm.y - 4,
        vx: 0,
        vy: 0,
        weapon: w,
        owner: this.activeWorm
      }));
      // Worm gets 3 seconds to flee!
      this.spawnFloatingText(this.activeWorm.x, this.activeWorm.y - 35, 'RUN FOR IT!!', '#fbbf24');
    }

    this.updateHUD();
  }

  fireSniperRay(startX, startY, angle, damage) {
    const dirX = Math.cos(angle);
    const dirY = Math.sin(angle);
    let hitX = startX;
    let hitY = startY;
    let maxDist = 1800;

    for (let d = 0; d < maxDist; d += 4) {
      hitX = startX + dirX * d;
      hitY = startY + dirY * d;

      // Check hit worm
      let hitWorm = null;
      for (const t of this.teams) {
        for (const u of t.units) {
          if (u.alive && Math.hypot(u.x - hitX, u.y - 8 - hitY) < 14) {
            hitWorm = u;
            break;
          }
        }
        if (hitWorm) break;
      }

      if (hitWorm) {
        hitWorm.takeDamage(damage, dirX * 10, dirY * 10 - 2);
        this.createExplosion(hitX, hitY, 22, damage * 0.4, false);
        break;
      }

      // Check hit terrain
      if (this.isSolid(hitX, hitY)) {
        this.createExplosion(hitX, hitY, 26, damage * 0.4, false);
        break;
      }
    }

    // Tracer line visual
    this.particles.push({
      type: 'laser',
      x1: startX,
      y1: startY,
      x2: hitX,
      y2: hitY,
      life: 25,
      maxLife: 25,
      color: '#f43f5e'
    });
  }

  executeTargetAction(targetX, targetY) {
    const w = this.getCurrentWeapon();
    if (w.ammo > 0) w.ammo--;

    if (w.type === 'airstrike') {
      sounds.launch();
      this.turnState = 'firing';
      const numBombs = w.bombs || 5;
      const spacing = 45;
      const startX = targetX - (numBombs * spacing) / 2;

      for (let i = 0; i < numBombs; i++) {
        setTimeout(() => {
          this.projectiles.push(new Projectile(this, {
            x: startX + i * spacing + (Math.random() * 15 - 7),
            y: -50,
            vx: this.wind * 2 + (Math.random() - 0.5),
            vy: 8 + Math.random() * 3,
            weapon: { ...w, type: 'projectile' },
            owner: this.activeWorm
          }));
        }, i * 180);
      }
    } else if (w.type === 'teleport') {
      if (!this.isSolid(targetX, targetY) && targetY < this.waterLevel - 20) {
        sounds.teleport();
        this.createTeleportEffect(this.activeWorm.x, this.activeWorm.y);
        this.activeWorm.x = targetX;
        this.activeWorm.y = targetY;
        this.activeWorm.vx = 0;
        this.activeWorm.vy = 0;
        this.createTeleportEffect(targetX, targetY);
        this.onAttackCompleted();
      } else {
        this.spawnFloatingText(targetX, targetY, 'Blocked!', '#ef4444');
      }
    } else if (w.type === 'girder') {
      const len = 90;
      const rad = this.placementAngle;
      const x1 = targetX - Math.cos(rad) * len;
      const y1 = targetY - Math.sin(rad) * len;
      const x2 = targetX + Math.cos(rad) * len;
      const y2 = targetY + Math.sin(rad) * len;
      
      this.addGirder(x1, y1, x2, y2, 14);
      sounds.jump();
      this.createExplosion(targetX, targetY, 15, 0, false);
      this.onAttackCompleted();
    }
    this.updateHUD();
  }

  onAttackCompleted() {
    this.turnState = 'resolving';
    // Let physics settle then advance turn
    setTimeout(() => {
      this.endTurn();
    }, 1500);
  }

  createExplosion(x, y, radius, damage, heavy = false) {
    sounds.explosion(heavy);
    this.destroyTerrain(x, y, radius);

    // Screen Shake & Impact Freeze Frame
    this.camera.shake = Math.min(25, this.camera.shake + (heavy ? 18 : 8));
    if (heavy) {
      this.camera.freezeFrames = 4; // micro-freeze for dramatic weight
    }

    // Damage & Knockback living worms
    for (const t of this.teams) {
      for (const u of t.units) {
        if (!u.alive) continue;
        const dist = Math.hypot(u.x - x, u.y - 8 - y);
        if (dist < radius * 1.5) {
          const factor = Math.max(0.1, 1 - (dist / (radius * 1.5)));
          const hitDmg = Math.round(damage * factor);
          const force = (radius / (dist + 8)) * 12;
          const kx = ((u.x - x) / (dist + 0.1)) * force;
          const ky = ((u.y - 8 - y) / (dist + 0.1)) * force - 3;
          u.takeDamage(hitDmg, kx, ky);
        }
      }
    }

    // Fire & Smoke Particles
    const pCount = Math.floor(radius * 1.2);
    for (let i = 0; i < pCount; i++) {
      const pAng = Math.random() * Math.PI * 2;
      const pSpeed = 1 + Math.random() * (radius / 5);
      this.particles.push({
        type: 'smoke',
        x: x,
        y: y,
        vx: Math.cos(pAng) * pSpeed + (this.wind * 0.8),
        vy: Math.sin(pAng) * pSpeed - (1 + Math.random() * 2),
        size: 8 + Math.random() * (radius / 2.5),
        color: Math.random() > 0.4 ? 'rgba(249, 115, 22, 0.8)' : 'rgba(75, 85, 99, 0.6)',
        life: 30 + Math.random() * 25,
        maxLife: 55
      });
    }

    // Shockwave ring
    this.particles.push({
      type: 'ring',
      x: x,
      y: y,
      radius: 5,
      maxRadius: radius * 1.6,
      life: 18,
      maxLife: 18,
      color: 'rgba(255, 255, 255, 0.9)'
    });
  }

  createWaterSplash(x, count = 25) {
    sounds.splash();
    for (let i = 0; i < count; i++) {
      const ang = -Math.PI / 2 + (Math.random() - 0.5) * 1.2;
      const spd = 3 + Math.random() * 8;
      this.particles.push({
        type: 'water',
        x: x + (Math.random() - 0.5) * 16,
        y: this.waterLevel,
        vx: Math.cos(ang) * spd,
        vy: Math.sin(ang) * spd,
        size: 3 + Math.random() * 4,
        color: 'rgba(56, 189, 248, 0.85)',
        life: 40,
        maxLife: 40
      });
    }
  }

  createTeleportEffect(x, y) {
    for (let i = 0; i < 30; i++) {
      const a = Math.random() * Math.PI * 2;
      const s = 1 + Math.random() * 5;
      this.particles.push({
        type: 'spark',
        x: x,
        y: y - 8,
        vx: Math.cos(a) * s,
        vy: Math.sin(a) * s,
        size: 2 + Math.random() * 3,
        color: '#06b6d4',
        life: 25,
        maxLife: 25
      });
    }
  }

  spawnFloatingText(x, y, text, color = '#fff') {
    this.floatingTexts.push({
      x: x,
      y: y,
      text: text,
      color: color,
      vy: -1.2,
      life: 60,
      maxLife: 60
    });
  }

  // --- CPU AI RISK-ASSESSMENT LOGIC ---
  executeCPUTurn() {
    if (!this.activeWorm || !this.activeWorm.alive || this.turnState !== 'aiming') return;
    const cpuWorm = this.activeWorm;
    const myTeam = this.teams[this.currentTeamIndex];

    // Find living enemies
    let bestTarget = null;
    let highestScore = -9999;

    for (const t of this.teams) {
      if (t.id === myTeam.id) continue;
      for (const enemy of t.units) {
        if (!enemy.alive) continue;
        const dist = Math.hypot(enemy.x - cpuWorm.x, enemy.y - cpuWorm.y);
        // Risk assessment score: Closer enemies with lower health = higher priority
        let score = 1000 - dist + (100 - enemy.hp) * 3;
        
        // Hazard penalty: Check if target is near water edge
        if (enemy.y > this.waterLevel - 80) score += 300; // high value drownable target!

        if (score > highestScore) {
          highestScore = score;
          bestTarget = enemy;
        }
      }
    }

    if (!bestTarget) return;

    // AI Weapon Selection Logic
    const distToTarget = Math.hypot(bestTarget.x - cpuWorm.x, bestTarget.y - cpuWorm.y);
    let chosenWeaponIdx = 0; // default bazooka

    if (distToTarget < 120 && Math.random() > 0.5) {
      chosenWeaponIdx = 4; // shotgun close range
    } else if (distToTarget > 600 && Math.random() > 0.4) {
      chosenWeaponIdx = 5; // Airstrike long range if available
      if (WEAPONS[chosenWeaponIdx].ammo === 0) chosenWeaponIdx = 0;
    } else if (distToTarget > 350 && Math.random() > 0.6) {
      chosenWeaponIdx = 3; // Sniper rifle
    } else if (distToTarget < 280 && Math.random() > 0.5) {
      chosenWeaponIdx = 1; // Grenade
    }

    this.selectWeapon(chosenWeaponIdx);
    const weapon = this.getCurrentWeapon();

    // If Airstrike, drop directly on best target
    if (weapon.type === 'airstrike') {
      setTimeout(() => {
        this.executeTargetAction(bestTarget.x + (Math.random() * 20 - 10), bestTarget.y);
      }, 1000);
      return;
    }

    // Ballistic Trajectory Simulation to calculate optimal angle & power
    let bestAngle = 0;
    let bestPower = 0.5;
    let minSimulationError = 99999;

    const testFacing = bestTarget.x >= cpuWorm.x ? 1 : -1;
    cpuWorm.facing = testFacing;

    // Simulate 30 candidate trajectory arcs
    for (let testDeg = (testFacing === 1 ? -80 : -100); (testFacing === 1 ? testDeg <= 10 : testDeg >= -170); testDeg += (testFacing === 1 ? 5 : -5)) {
      const rad = (testDeg * Math.PI) / 180;
      for (let testP = 0.3; testP <= 1.0; testP += 0.15) {
        const speed = 4 + testP * 24;
        let simX = cpuWorm.x + Math.cos(rad) * 18;
        let simY = cpuWorm.y - 10 + Math.sin(rad) * 18;
        let simVx = Math.cos(rad) * speed;
        let simVy = Math.sin(rad) * speed;

        let closestDist = 99999;

        for (let step = 0; step < 70; step++) {
          if (weapon.affectedByWind) simVx += this.wind * 0.12;
          simVy += this.gravity;
          simX += simVx;
          simY += simVy;

          const d = Math.hypot(simX - bestTarget.x, simY - bestTarget.y);
          if (d < closestDist) closestDist = d;

          // Check if hits self or water early (Hazard avoidance)
          if (step > 4 && Math.hypot(simX - cpuWorm.x, simY - cpuWorm.y) < 30) {
            closestDist += 5000; // huge penalty for self-harm!
            break;
          }
          if (this.isSolid(simX, simY) || simY > this.waterLevel) break;
        }

        if (closestDist < minSimulationError) {
          minSimulationError = closestDist;
          bestAngle = rad;
          bestPower = testP;
        }
      }
    }

    // Add subtle human-like CPU inaccuracy
    bestAngle += (Math.random() - 0.5) * 0.06;
    bestPower = Math.max(0.1, Math.min(1.0, bestPower + (Math.random() - 0.5) * 0.08));

    cpuWorm.aimAngle = bestAngle;

    // Simulate charge & fire with visual pacing
    setTimeout(() => {
      if (this.turnState === 'aiming') {
        this.fireWeapon(bestPower);
      }
    }, 1200);
  }

  // --- COORDINATE TRANSFORMS ---
  screenToWorld(sx, sy) {
    const cx = this.canvas.width / 2;
    const cy = this.canvas.height / 2;
    return {
      x: (sx - cx) / this.camera.zoom + this.camera.x,
      y: (sy - cy) / this.camera.zoom + this.camera.y
    };
  }

  worldToScreen(wx, wy) {
    const cx = this.canvas.width / 2;
    const cy = this.canvas.height / 2;
    return {
      x: (wx - this.camera.x) * this.camera.zoom + cx,
      y: (wy - this.camera.y) * this.camera.zoom + cy
    };
  }

  // --- MAIN LOOP UPDATE & RENDER ---
  update(dt) {
    // Impact freeze frame handling
    if (this.camera.freezeFrames > 0) {
      this.camera.freezeFrames--;
      return;
    }

    this.waterPhase += dt * 2.5;

    // Handle Turn Timer
    if (this.turnState === 'aiming') {
      this.turnTimer -= dt;
      const sec = Math.ceil(this.turnTimer);
      const timerEl = document.getElementById('turn-timer');
      
      if (timerEl) {
        timerEl.innerText = Math.max(0, sec);
        if (sec <= 5) {
          timerEl.classList.add('urgent');
          if (Math.floor(this.turnTimer * 2) !== Math.floor((this.turnTimer + dt) * 2)) {
            sounds.tick(true);
          }
        } else {
          timerEl.classList.remove('urgent');
        }
      }

      if (this.turnTimer <= 0) {
        sounds.explosion(false);
        this.spawnFloatingText(this.activeWorm.x, this.activeWorm.y - 30, 'Time Expired!', '#ef4444');
        this.endTurn();
      }
    }

    // Power Charge Animation
    if (this.isCharging) {
      this.power += dt * 0.9 * this.powerDirection;
      if (this.power >= 1.0) {
        this.power = 1.0;
        this.powerDirection = -1;
      } else if (this.power <= 0.05) {
        this.power = 0.05;
        this.powerDirection = 1;
      }
      const pFill = document.getElementById('power-fill');
      if (pFill) pFill.style.width = `${Math.round(this.power * 100)}%`;
    } else {
      const pFill = document.getElementById('power-fill');
      if (pFill && this.power === 0) pFill.style.width = '0%';
    }

    // Update Worms
    for (const t of this.teams) {
      for (const u of t.units) {
        u.update(dt);
      }
    }

    // Active Human Worm Controls
    if (this.isTurnActive() && !this.isActiveWormCPU()) {
      const worm = this.activeWorm;
      if (this.keys['a'] || this.keys['arrowleft']) worm.move(-1);
      if (this.keys['d'] || this.keys['arrowright']) worm.move(1);
      if (this.keys['q']) worm.aimAngle -= 1.8 * dt;
      if (this.keys['e']) worm.aimAngle += 1.8 * dt;
    }

    // Update Projectiles
    for (let i = this.projectiles.length - 1; i >= 0; i--) {
      const p = this.projectiles[i];
      p.update(dt);
      if (p.dead) {
        this.projectiles.splice(i, 1);
        // If all projectiles cleared and turn is firing, resolve turn
        if (this.projectiles.length === 0 && this.turnState === 'firing') {
          this.onAttackCompleted();
        }
      }
    }

    // Update Falling Terrain Debris Chunks
    for (let i = this.debrisChunks.length - 1; i >= 0; i--) {
      const d = this.debrisChunks[i];
      d.vy += this.gravity;
      d.vx += this.wind * 0.04;
      d.x += d.vx;
      d.y += d.vy;
      d.rotation += d.vRot;
      d.life--;

      // Collision with solid terrain
      if (this.isSolid(d.x, d.y)) {
        d.vx *= 0.4;
        d.vy = -d.vy * 0.3;
        d.vRot *= 0.5;
      }
      // Drown in water
      if (d.y > this.waterLevel) {
        d.vy *= 0.8;
        d.vx *= 0.8;
      }
      if (d.life <= 0 || d.y > this.worldHeight + 50) {
        this.debrisChunks.splice(i, 1);
      }
    }

    // Update Particles
    for (let i = this.particles.length - 1; i >= 0; i--) {
      const pt = this.particles[i];
      pt.life--;
      if (pt.type === 'smoke' || pt.type === 'spark') {
        pt.x += pt.vx;
        pt.y += pt.vy;
        pt.vx *= 0.96;
        pt.vy *= 0.96;
      } else if (pt.type === 'water') {
        pt.vy += this.gravity;
        pt.x += pt.vx;
        pt.y += pt.vy;
      } else if (pt.type === 'ring') {
        pt.radius += (pt.maxRadius - pt.radius) * 0.2;
      }
      if (pt.life <= 0) this.particles.splice(i, 1);
    }

    // Update Floating Text
    for (let i = this.floatingTexts.length - 1; i >= 0; i--) {
      const ft = this.floatingTexts[i];
      ft.y += ft.vy;
      ft.life--;
      if (ft.life <= 0) this.floatingTexts.splice(i, 1);
    }

    // Camera Tracking & Smooth Interpolation
    let targetX = this.camera.x;
    let targetY = this.camera.y;

    if (this.projectiles.length > 0) {
      const leadP = this.projectiles[0];
      targetX = leadP.x;
      targetY = leadP.y;
    } else if (this.activeWorm && this.activeWorm.alive) {
      targetX = this.activeWorm.x;
      targetY = this.activeWorm.y - 40;
    }

    this.camera.targetX = Math.max(300, Math.min(this.worldWidth - 300, targetX));
    this.camera.targetY = Math.max(200, Math.min(this.worldHeight - 150, targetY));

    this.camera.x += (this.camera.targetX - this.camera.x) * 0.08;
    this.camera.y += (this.camera.targetY - this.camera.y) * 0.08;
    this.camera.zoom += (this.camera.targetZoom - this.camera.zoom) * 0.08;

    if (this.camera.shake > 0) {
      this.camera.shake *= 0.9;
      if (this.camera.shake < 0.2) this.camera.shake = 0;
    }
  }

  draw() {
    const ctx = this.ctx;
    const W = this.canvas.width;
    const H = this.canvas.height;

    ctx.clearRect(0, 0, W, H);

    // Apply Camera Transform & Screen Shake
    ctx.save();
    ctx.translate(W / 2, H / 2);
    ctx.scale(this.camera.zoom, this.camera.zoom);

    let shakeX = 0;
    let shakeY = 0;
    if (this.camera.shake > 0) {
      shakeX = (Math.random() - 0.5) * this.camera.shake * 2;
      shakeY = (Math.random() - 0.5) * this.camera.shake * 2;
    }
    ctx.translate(-this.camera.x + shakeX, -this.camera.y + shakeY);

    // 1. Draw Atmospheric Sky & Parallax Clouds
    this.drawSky(ctx);

    // 2. Draw Terrain (Decor layer clipped by destructible collision mask)
    ctx.drawImage(this.terrainDecorCanvas, 0, 0);

    // Draw Falling Debris Chunks
    for (const d of this.debrisChunks) {
      ctx.save();
      ctx.translate(d.x, d.y);
      ctx.rotate(d.rotation);
      ctx.fillStyle = d.color;
      ctx.fillRect(-d.radius, -d.radius, d.radius * 2, d.radius * 2);
      ctx.restore();
    }

    // 3. Draw Worms
    for (const t of this.teams) {
      for (const u of t.units) {
        u.draw(ctx);
      }
    }

    // 4. Draw Predictive Aiming Trajectory (for active human worm)
    if (this.isTurnActive() && !this.isActiveWormCPU()) {
      this.drawAimingArc(ctx);
    }

    // 5. Draw Projectiles
    for (const p of this.projectiles) {
      p.draw(ctx);
    }

    // 6. Draw Hydrodynamic Water
    this.drawWater(ctx);

    // 7. Draw Particle Effects & Lasers
    for (const pt of this.particles) {
      if (pt.type === 'laser') {
        ctx.strokeStyle = pt.color;
        ctx.lineWidth = (pt.life / pt.maxLife) * 5;
        ctx.beginPath();
        ctx.moveTo(pt.x1, pt.y1);
        ctx.lineTo(pt.x2, pt.y2);
        ctx.stroke();
      } else if (pt.type === 'smoke' || pt.type === 'spark' || pt.type === 'water') {
        ctx.fillStyle = pt.color;
        ctx.globalAlpha = Math.max(0, pt.life / pt.maxLife);
        ctx.beginPath();
        ctx.arc(pt.x, pt.y, pt.size, 0, Math.PI * 2);
        ctx.fill();
        ctx.globalAlpha = 1.0;
      } else if (pt.type === 'ring') {
        ctx.strokeStyle = pt.color;
        ctx.lineWidth = 3;
        ctx.globalAlpha = Math.max(0, pt.life / pt.maxLife);
        ctx.beginPath();
        ctx.arc(pt.x, pt.y, pt.radius, 0, Math.PI * 2);
        ctx.stroke();
        ctx.globalAlpha = 1.0;
      }
    }

    // 8. Draw Floating Combat Text
    for (const ft of this.floatingTexts) {
      ctx.save();
      ctx.font = 'bold 16px "Segoe UI", sans-serif';
      ctx.fillStyle = ft.color;
      ctx.textAlign = 'center';
      ctx.shadowColor = 'rgba(0,0,0,0.8)';
      ctx.shadowBlur = 6;
      ctx.globalAlpha = Math.max(0, ft.life / ft.maxLife);
      ctx.fillText(ft.text, ft.x, ft.y);
      ctx.restore();
    }

    ctx.restore();
  }

  drawSky(ctx) {
    const W = this.worldWidth;
    const H = this.worldHeight;

    // Deep atmospheric sky gradient
    const skyGrad = ctx.createLinearGradient(0, 0, 0, H);
    skyGrad.addColorStop(0, '#0c4a6e');
    skyGrad.addColorStop(0.5, '#0369a1');
    skyGrad.addColorStop(0.8, '#0284c7');
    skyGrad.addColorStop(1, '#38bdf8');
    ctx.fillStyle = skyGrad;
    ctx.fillRect(0, 0, W, H);

    // Parallax background mountain silhouettes
    ctx.fillStyle = 'rgba(12, 74, 110, 0.45)';
    ctx.beginPath();
    ctx.moveTo(0, H);
    for (let x = 0; x <= W; x += 60) {
      let my = 580 + Math.sin(x * 0.003) * 140 + Math.cos(x * 0.007) * 80;
      ctx.lineTo(x, my);
    }
    ctx.lineTo(W, H);
    ctx.closePath();
    ctx.fill();
  }

  drawWater(ctx) {
    const W = this.worldWidth;
    const H = this.worldHeight;
    const baseWater = this.waterLevel;

    // Multi-layer sine-wave water
    ctx.fillStyle = 'rgba(14, 116, 144, 0.75)';
    ctx.beginPath();
    ctx.moveTo(0, H);
    for (let x = 0; x <= W; x += 16) {
      const wave = Math.sin(x * 0.02 + this.waterPhase) * 6 + Math.cos(x * 0.01 - this.waterPhase * 0.8) * 4;
      ctx.lineTo(x, baseWater + wave);
    }
    ctx.lineTo(W, H);
    ctx.closePath();
    ctx.fill();

    // Frothy water crest line
    ctx.strokeStyle = 'rgba(255, 255, 255, 0.4)';
    ctx.lineWidth = 3;
    ctx.stroke();
  }

  drawAimingArc(ctx) {
    const worm = this.activeWorm;
    const w = this.getCurrentWeapon();
    if (w.type === 'airstrike' || w.type === 'teleport' || w.type === 'girder') {
      // Draw targeting crosshair
      ctx.strokeStyle = '#ef4444';
      ctx.lineWidth = 2;
      ctx.beginPath();
      ctx.arc(this.mouse.worldX, this.mouse.worldY, 18, 0, Math.PI * 2);
      ctx.moveTo(this.mouse.worldX - 25, this.mouse.worldY);
      ctx.lineTo(this.mouse.worldX + 25, this.mouse.worldY);
      ctx.moveTo(this.mouse.worldX, this.mouse.worldY - 25);
      ctx.lineTo(this.mouse.worldX, this.mouse.worldY + 25);
      ctx.stroke();
      return;
    }

    const angle = worm.aimAngle;
    const pPower = this.isCharging ? this.power : 0.65;
    const speed = 4 + pPower * 24;
    let simX = worm.x + Math.cos(angle) * 18;
    let simY = worm.y - 10 + Math.sin(angle) * 18;
    let simVx = Math.cos(angle) * speed;
    let simVy = Math.sin(angle) * speed;

    ctx.save();
    ctx.strokeStyle = 'rgba(255, 255, 255, 0.5)';
    ctx.setLineDash([4, 6]);
    ctx.lineWidth = 2;
    ctx.beginPath();
    ctx.moveTo(simX, simY);

    for (let i = 0; i < 28; i++) {
      if (w.affectedByWind) simVx += this.wind * 0.12;
      simVy += this.gravity;
      simX += simVx;
      simY += simVy;
      ctx.lineTo(simX, simY);
      if (this.isSolid(simX, simY) || simY > this.waterLevel) break;
    }
    ctx.stroke();

    // Aim crosshair dot at end
    ctx.fillStyle = '#ef4444';
    ctx.beginPath();
    ctx.arc(simX, simY, 4, 0, Math.PI * 2);
    ctx.fill();
    ctx.restore();
  }

  updateHUD() {
    // Update active team badge
    const curTeam = this.teams[this.currentTeamIndex];
    if (curTeam && this.activeWorm) {
      const badge = document.getElementById('turn-badge');
      const teamName = document.getElementById('turn-team-name');
      const unitName = document.getElementById('turn-unit-name');
      if (badge && teamName && unitName) {
        badge.style.background = curTeam.color;
        teamName.innerText = curTeam.name;
        unitName.innerText = `(${this.activeWorm.name})`;
      }
    }

    // Update angle display
    if (this.activeWorm) {
      const angleDeg = Math.round((this.activeWorm.aimAngle * 180) / Math.PI);
      const angleEl = document.getElementById('cur-angle-text');
      if (angleEl) angleEl.innerText = `${angleDeg}°`;
    }

    // Update Teams Health Bars
    const teamsHud = document.getElementById('teams-hud');
    if (teamsHud) {
      teamsHud.innerHTML = '';
      this.teams.forEach((t) => {
        const totalHp = t.units.reduce((acc, u) => acc + (u.alive ? u.hp : 0), 0);
        const maxHp = t.units.length * 100;
        const pct = Math.max(0, (totalHp / maxHp) * 100);

        const row = document.createElement('div');
        row.className = 'team-status-row';
        row.innerHTML = `
          <div class="team-dot" style="background:${t.color};"></div>
          <span style="min-width:70px; color:${t.color};">${t.name.split(' ')[0]}</span>
          <div class="team-hp-bar">
            <div class="team-hp-fill" style="width:${pct}%; background:${t.color};"></div>
          </div>
          <span style="font-family:monospace; min-width:30px; text-align:right;">${totalHp}</span>
        `;
        teamsHud.appendChild(row);
      });
    }
  }

  run() {
    const loop = (timestamp) => {
      const dt = Math.min(0.1, (timestamp - this.lastTime) / 1000);
      this.lastTime = timestamp;

      this.update(dt);
      this.draw();

      requestAnimationFrame(loop);
    };
    requestAnimationFrame(loop);
  }
}

// --- WORM UNIT ENTITY ---
class Worm {
  constructor(game, team, name) {
    this.game = game;
    this.team = team;
    this.name = name;
    this.x = 0;
    this.y = 0;
    this.vx = 0;
    this.vy = 0;
    this.radius = 12;
    this.hp = 100;
    this.alive = true;
    this.grounded = false;
    this.aimAngle = -Math.PI / 4;
    this.facing = 1; // 1: right, -1: left
    this.animPhase = Math.random() * 10;
  }

  move(dir) {
    this.facing = dir;
    this.aimAngle = dir === 1 ? Math.abs(this.aimAngle) : -Math.PI - Math.abs(this.aimAngle);
    if (!this.grounded) return;

    const stepX = this.x + dir * 2.2;
    // Walk over slight inclines (step climbing)
    for (let dy = -6; dy <= 6; dy++) {
      if (!this.game.isSolid(stepX, this.y + dy) && this.game.isSolid(stepX, this.y + dy + 1)) {
        this.x = stepX;
        this.y += dy;
        sounds.jump();
        return;
      }
    }
  }

  jump() {
    if (!this.grounded) return;
    sounds.jump();
    this.vy = -7.2;
    this.vx = this.facing * 3.5 + this.game.wind * 1.5;
    this.grounded = false;
  }

  takeDamage(amount, kx = 0, ky = 0) {
    if (!this.alive) return;
    this.hp = Math.max(0, this.hp - amount);
    this.vx += kx;
    this.vy += ky;
    this.grounded = false;
    sounds.wormHit();

    this.game.spawnFloatingText(this.x, this.y - 25, `-${amount}`, '#ef4444');

    if (this.hp <= 0) {
      this.die();
    }
    this.game.updateHUD();
  }

  die() {
    this.alive = false;
    this.game.spawnFloatingText(this.x, this.y - 30, '💀 RIP', this.team.color);
    // Spurt tombstone or grave explosion
    this.game.createExplosion(this.x, this.y, 25, 20, false);
  }

  update(dt) {
    if (!this.alive) return;

    this.animPhase += dt * 5;

    // Apply gravity
    this.vy += this.game.gravity;
    this.x += this.vx;
    this.y += this.vy;

    this.vx *= 0.94; // friction

    // Check drowning in water
    if (this.y > this.game.waterLevel) {
      this.game.createWaterSplash(this.x);
      this.takeDamage(100);
      return;
    }

    // Terrain Collision
    if (this.game.isSolid(this.x, this.y + 1)) {
      if (this.vy > 10) {
        // Fall damage for visceral physics
        const fallDmg = Math.round((this.vy - 10) * 4);
        if (fallDmg > 0) this.takeDamage(fallDmg);
      }
      this.vy = 0;
      this.grounded = true;
      // Push out of terrain if embedded
      while (this.game.isSolid(this.x, this.y) && this.y > 0) {
        this.y -= 1;
      }
    } else {
      this.grounded = false;
    }
  }

  draw(ctx) {
    if (!this.alive) return;

    ctx.save();
    ctx.translate(this.x, this.y);

    const isActive = this.game.activeWorm === this;

    // Active Arrow Indicator
    if (isActive) {
      const bob = Math.sin(this.animPhase * 2) * 5;
      ctx.fillStyle = '#f59e0b';
      ctx.beginPath();
      ctx.moveTo(0, -32 + bob);
      ctx.lineTo(-7, -42 + bob);
      ctx.lineTo(7, -42 + bob);
      ctx.closePath();
      ctx.fill();
    }

    // Name & HP Bar
    ctx.font = 'bold 10px "Segoe UI", sans-serif';
    ctx.textAlign = 'center';
    ctx.fillStyle = '#fff';
    ctx.shadowColor = 'rgba(0,0,0,0.8)';
    ctx.shadowBlur = 4;
    ctx.fillText(this.name, 0, -22);

    // HP Mini Bar
    ctx.fillStyle = 'rgba(0,0,0,0.6)';
    ctx.fillRect(-16, -18, 32, 4);
    ctx.fillStyle = this.hp > 40 ? '#22c55e' : (this.hp > 20 ? '#eab308' : '#ef4444');
    ctx.fillRect(-16, -18, (this.hp / 100) * 32, 4);
    ctx.shadowBlur = 0;

    // Draw Quirky Worm Body
    ctx.scale(this.facing, 1);

    // Cute animated worm segments
    ctx.fillStyle = this.team.color;
    ctx.strokeStyle = this.team.darkColor;
    ctx.lineWidth = 2;

    // Tail segment
    ctx.beginPath();
    ctx.arc(-8, -4 + Math.sin(this.animPhase) * 1.5, 6, 0, Math.PI * 2);
    ctx.fill();
    ctx.stroke();

    // Body segment
    ctx.beginPath();
    ctx.arc(-3, -7, 7, 0, Math.PI * 2);
    ctx.fill();
    ctx.stroke();

    // Head segment
    ctx.beginPath();
    ctx.arc(4, -10, 8, 0, Math.PI * 2);
    ctx.fill();
    ctx.stroke();

    // Big expressive eyes
    ctx.fillStyle = '#fff';
    ctx.beginPath();
    ctx.arc(7, -12, 3.5, 0, Math.PI * 2);
    ctx.fill();

    // Pupils
    ctx.fillStyle = '#000';
    ctx.beginPath();
    ctx.arc(8.5, -12, 1.8, 0, Math.PI * 2);
    ctx.fill();

    // Draw active weapon in hand
    if (isActive) {
      const curW = this.game.getCurrentWeapon();
      ctx.font = '14px sans-serif';
      ctx.fillText(curW.icon, 8, -6);
    }

    ctx.restore();
  }
}

// --- PROJECTILE ENTITY ---
class Projectile {
  constructor(game, opts) {
    this.game = game;
    this.x = opts.x;
    this.y = opts.y;
    this.vx = opts.vx;
    this.vy = opts.vy;
    this.weapon = opts.weapon;
    this.owner = opts.owner;
    this.bounces = opts.weapon.bounces || 0;
    this.fuse = opts.weapon.fuse || 0;
    this.age = 0;
    this.dead = false;
    this.radius = 4;
  }

  update(dt) {
    if (this.dead) return;

    this.age += dt;

    // Physics
    if (this.weapon.affectedByWind) {
      this.vx += this.game.wind * 0.14;
    }
    this.vy += this.game.gravity;

    this.x += this.vx;
    this.y += this.vy;

    // Smoke trail particles
    if (Math.random() > 0.3) {
      this.game.particles.push({
        type: 'smoke',
        x: this.x,
        y: this.y,
        vx: (Math.random() - 0.5) * 0.5,
        vy: (Math.random() - 0.5) * 0.5,
        size: 3 + Math.random() * 3,
        color: 'rgba(255, 200, 100, 0.6)',
        life: 20,
        maxLife: 20
      });
    }

    // Water entry
    if (this.y > this.game.waterLevel) {
      this.game.createWaterSplash(this.x);
      this.dead = true;
      return;
    }

    // Fuse detonation
    if (this.fuse > 0 && this.age >= this.fuse) {
      this.detonate();
      return;
    }

    // Direct Hit on Worms
    for (const t of this.game.teams) {
      for (const u of t.units) {
        if (u.alive && Math.hypot(u.x - this.x, u.y - 8 - this.y) < 14) {
          this.detonate();
          return;
        }
      }
    }

    // Terrain Collision
    if (this.game.isSolid(this.x, this.y)) {
      if (this.bounces > 0) {
        this.bounces--;
        sounds.wormHit();
        this.vx *= -0.55;
        this.vy *= -0.55;
        this.x += this.vx;
        this.y += this.vy;
      } else {
        this.detonate();
      }
    }
  }

  detonate() {
    this.dead = true;
    const w = this.weapon;

    if (w.type === 'cluster') {
      // Explode into bouncing cluster sub-bombs
      this.game.createExplosion(this.x, this.y, w.radius, w.damage, false);
      for (let i = 0; i < (w.clusterCount || 5); i++) {
        const ang = -Math.PI / 2 + (Math.random() - 0.5) * 1.8;
        const spd = 4 + Math.random() * 6;
        this.game.projectiles.push(new Projectile(this.game, {
          x: this.x,
          y: this.y - 5,
          vx: Math.cos(ang) * spd,
          vy: Math.sin(ang) * spd,
          weapon: {
            damage: w.clusterDamage,
            radius: w.clusterRadius,
            bounces: 1,
            fuse: 1.5,
            affectedByWind: false
          },
          owner: this.owner
        }));
      }
    } else {
      const isHeavy = w.radius >= 65;
      this.game.createExplosion(this.x, this.y, w.radius, w.damage, isHeavy);
    }
  }

  draw(ctx) {
    if (this.dead) return;
    ctx.save();
    ctx.translate(this.x, this.y);
    const angle = Math.atan2(this.vy, this.vx);
    ctx.rotate(angle);

    ctx.font = '16px sans-serif';
    ctx.textAlign = 'center';
    ctx.textBaseline = 'middle';
    ctx.fillText(this.weapon.icon || '🚀', 0, 0);

    // Fuse countdown timer text for grenades
    if (this.fuse > 0) {
      const rem = Math.max(0, Math.ceil(this.fuse - this.age));
      ctx.rotate(-angle); // unrotate text
      ctx.font = 'bold 12px monospace';
      ctx.fillStyle = '#ef4444';
      ctx.fillText(`${rem}`, 0, -14);
    }

    ctx.restore();
  }
}

// Instantiate and kick off game engine
window.addEventListener('DOMContentLoaded', () => {
  window.game = new WormoidsGame();
  window.game.run();
});
</script>
</body>
</html>
<!-- agent-meta {"model":"google/gemini-3.7-flash","provider":"openrouter","persona":"full","sessionId":"db77d1ba-119d-45b7-951d-f903f0c6b930","tokensIn":309977,"tokensOut":34224,"tokensTotal":344201,"cost":0.15129180000000003,"turns":10,"toolCalls":9,"failedToolCalls":0,"timestamp":"2026-08-15T22:15:16.313Z"} -->