← Worms results

Worms

Gemini 3.6 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 62,721 bytes · SHA-256 60f158205b30
<!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>WORMAGE: Tactical Artillery Mayhem</title>
<style>
  :root {
    --bg-dark: #0f172a;
    --panel-bg: rgba(15, 23, 42, 0.85);
    --panel-border: rgba(255, 255, 255, 0.15);
    --accent-gold: #fbbf24;
    --accent-red: #ef4444;
    --accent-blue: #3b82f6;
    --accent-green: #10b981;
    --accent-purple: #8b5cf6;
    --font-main: 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
  }

  * {
    box-sizing: border-box;
    user-select: none;
    -webkit-user-select: none;
    margin: 0;
    padding: 0;
  }

  body, html {
    width: 100%;
    height: 100%;
    overflow: hidden;
    background-color: #000;
    font-family: var(--font-main);
    color: #fff;
  }

  #game-container {
    position: relative;
    width: 100vw;
    height: 100vh;
    display: flex;
    justify-content: center;
    align-items: center;
    background: radial-gradient(circle at center, #1e293b 0%, #090d16 100%);
  }

  canvas#gameCanvas {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: contain;
    background: transparent;
  }

  /* HUD Overlay */
  #hud {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    pointer-events: none;
    display: flex;
    flex-direction: column;
    justify-content: space-between;
    padding: 16px;
    z-index: 10;
  }

  .hud-top {
    display: flex;
    justify-content: space-between;
    align-items: flex-start;
    width: 100%;
  }

  .team-bars {
    display: flex;
    flex-direction: column;
    gap: 8px;
    background: var(--panel-bg);
    padding: 12px 16px;
    border-radius: 12px;
    border: 1px solid var(--panel-border);
    backdrop-filter: blur(8px);
    box-shadow: 0 8px 32px rgba(0,0,0,0.4);
    min-width: 220px;
  }

  .team-status-row {
    display: flex;
    align-items: center;
    gap: 10px;
    font-size: 13px;
    font-weight: 700;
  }

  .team-color-badge {
    width: 12px;
    height: 12px;
    border-radius: 50%;
    box-shadow: 0 0 8px currentColor;
  }

  .team-health-bar-bg {
    flex: 1;
    height: 10px;
    background: rgba(255,255,255,0.1);
    border-radius: 5px;
    overflow: hidden;
    position: relative;
  }

  .team-health-bar-fill {
    height: 100%;
    width: 100%;
    transition: width 0.3s ease;
    border-radius: 5px;
  }

  .timer-box {
    background: var(--panel-bg);
    border: 2px solid var(--panel-border);
    border-radius: 16px;
    padding: 10px 24px;
    text-align: center;
    backdrop-filter: blur(8px);
    box-shadow: 0 8px 32px rgba(0,0,0,0.5);
    transition: transform 0.2s ease, border-color 0.2s ease;
  }

  .timer-box.urgent {
    border-color: var(--accent-red);
    animation: timerPulse 0.5s infinite alternate;
  }

  @keyframes timerPulse {
    from { transform: scale(1); box-shadow: 0 0 15px rgba(239, 68, 68, 0.4); }
    to { transform: scale(1.08); box-shadow: 0 0 30px rgba(239, 68, 68, 0.8); }
  }

  .timer-val {
    font-size: 36px;
    font-weight: 900;
    line-height: 1;
    font-family: monospace;
    text-shadow: 0 0 10px rgba(255,255,255,0.3);
  }

  .timer-label {
    font-size: 10px;
    text-transform: uppercase;
    letter-spacing: 1.5px;
    opacity: 0.7;
    margin-top: 2px;
  }

  .wind-box {
    background: var(--panel-bg);
    border: 1px solid var(--panel-border);
    border-radius: 12px;
    padding: 10px 16px;
    backdrop-filter: blur(8px);
    display: flex;
    flex-direction: column;
    align-items: center;
    min-width: 180px;
  }

  .wind-title {
    font-size: 11px;
    letter-spacing: 1px;
    text-transform: uppercase;
    opacity: 0.8;
    margin-bottom: 6px;
  }

  .wind-gauge {
    position: relative;
    width: 140px;
    height: 12px;
    background: rgba(0,0,0,0.4);
    border-radius: 6px;
    overflow: hidden;
    border: 1px solid rgba(255,255,255,0.1);
  }

  .wind-center-mark {
    position: absolute;
    left: 50%;
    top: 0;
    bottom: 0;
    width: 2px;
    background: rgba(255,255,255,0.5);
    z-index: 2;
  }

  .wind-bar {
    position: absolute;
    top: 0;
    bottom: 0;
    background: linear-gradient(90deg, #38bdf8, #818cf8);
    transition: all 0.3s ease;
  }

  .hud-bottom {
    display: flex;
    justify-content: space-between;
    align-items: flex-end;
    width: 100%;
  }

  .weapon-indicator {
    background: var(--panel-bg);
    border: 1px solid var(--panel-border);
    border-radius: 16px;
    padding: 12px 20px;
    display: flex;
    align-items: center;
    gap: 16px;
    pointer-events: auto;
    cursor: pointer;
    backdrop-filter: blur(8px);
    transition: transform 0.2s ease, background 0.2s ease;
    box-shadow: 0 8px 32px rgba(0,0,0,0.4);
  }

  .weapon-indicator:hover {
    transform: translateY(-2px);
    background: rgba(30, 41, 59, 0.95);
  }

  .weapon-icon-box {
    width: 48px;
    height: 48px;
    border-radius: 10px;
    background: rgba(255,255,255,0.08);
    display: flex;
    justify-content: center;
    align-items: center;
    font-size: 26px;
    border: 1px solid rgba(255,255,255,0.15);
  }

  .weapon-details {
    display: flex;
    flex-direction: column;
  }

  .weapon-name {
    font-size: 16px;
    font-weight: 800;
    color: var(--accent-gold);
  }

  .weapon-ammo {
    font-size: 12px;
    opacity: 0.7;
  }

  .controls-hint {
    background: var(--panel-bg);
    border: 1px solid var(--panel-border);
    border-radius: 12px;
    padding: 10px 16px;
    font-size: 11px;
    line-height: 1.5;
    opacity: 0.85;
    backdrop-filter: blur(8px);
  }

  /* Weapon Selector Menu Modal */
  #weapon-menu {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%) scale(0.95);
    background: rgba(15, 23, 42, 0.96);
    border: 2px solid var(--panel-border);
    border-radius: 20px;
    padding: 24px;
    z-index: 100;
    width: 90%;
    max-width: 680px;
    backdrop-filter: blur(16px);
    box-shadow: 0 20px 50px rgba(0,0,0,0.8);
    display: none;
    opacity: 0;
    transition: opacity 0.2s ease, transform 0.2s ease;
    pointer-events: auto;
  }

  #weapon-menu.active {
    display: block;
    opacity: 1;
    transform: translate(-50%, -50%) scale(1);
  }

  .menu-header {
    font-size: 20px;
    font-weight: 800;
    margin-bottom: 16px;
    text-transform: uppercase;
    letter-spacing: 1px;
    color: var(--accent-gold);
    display: flex;
    justify-content: space-between;
    align-items: center;
  }

  .weapon-category-title {
    font-size: 12px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 1px;
    color: #94a3b8;
    margin: 12px 0 8px 0;
  }

  .weapon-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
    gap: 10px;
  }

  .weapon-btn {
    background: rgba(255,255,255,0.05);
    border: 1px solid rgba(255,255,255,0.1);
    border-radius: 12px;
    padding: 10px;
    display: flex;
    align-items: center;
    gap: 10px;
    cursor: pointer;
    transition: all 0.2s ease;
    text-align: left;
    color: #fff;
  }

  .weapon-btn:hover {
    background: rgba(255,255,255,0.15);
    border-color: var(--accent-gold);
    transform: translateY(-2px);
  }

  .weapon-btn.selected {
    border-color: var(--accent-gold);
    background: rgba(251, 191, 36, 0.15);
    box-shadow: 0 0 12px rgba(251, 191, 36, 0.3);
  }

  .weapon-btn-icon {
    font-size: 22px;
  }

  .weapon-btn-info {
    display: flex;
    flex-direction: column;
  }

  .weapon-btn-title {
    font-size: 13px;
    font-weight: 700;
  }

  .weapon-btn-count {
    font-size: 10px;
    opacity: 0.6;
  }

  /* Banner overlay for Turn announcements / Notifications */
  #announcer-banner {
    position: absolute;
    top: 25%;
    left: 50%;
    transform: translate(-50%, -50%) scale(0.8);
    font-size: 42px;
    font-weight: 900;
    text-transform: uppercase;
    letter-spacing: 2px;
    text-shadow: 0 0 20px rgba(0,0,0,0.8), 0 4px 10px rgba(0,0,0,0.9);
    pointer-events: none;
    opacity: 0;
    transition: all 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
    z-index: 50;
    white-space: nowrap;
    padding: 12px 36px;
    border-radius: 20px;
    background: rgba(15,23,42,0.85);
    border: 2px solid var(--accent-gold);
    backdrop-filter: blur(12px);
  }

  #announcer-banner.show {
    opacity: 1;
    transform: translate(-50%, -50%) scale(1);
  }

  /* Fullscreens / Modals (Setup & Game Over) */
  .modal-overlay {
    position: absolute;
    inset: 0;
    background: rgba(10, 15, 26, 0.92);
    backdrop-filter: blur(16px);
    z-index: 200;
    display: flex;
    justify-content: center;
    align-items: center;
    padding: 20px;
  }

  .modal-card {
    background: #1e293b;
    border: 2px solid var(--panel-border);
    border-radius: 24px;
    padding: 36px;
    width: 100%;
    max-width: 580px;
    box-shadow: 0 25px 60px rgba(0,0,0,0.8);
    display: flex;
    flex-direction: column;
    gap: 20px;
  }

  .modal-title {
    font-size: 32px;
    font-weight: 900;
    text-align: center;
    color: var(--accent-gold);
    letter-spacing: 1px;
    text-shadow: 0 0 15px rgba(251, 191, 36, 0.3);
  }

  .modal-subtitle {
    font-size: 14px;
    color: #94a3b8;
    text-align: center;
    margin-top: -12px;
  }

  .form-group {
    display: flex;
    flex-direction: column;
    gap: 8px;
  }

  .form-label {
    font-size: 12px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 1px;
    color: #cbd5e1;
  }

  .option-selector {
    display: flex;
    gap: 10px;
  }

  .opt-btn {
    flex: 1;
    padding: 12px;
    background: rgba(255,255,255,0.05);
    border: 1px solid rgba(255,255,255,0.1);
    border-radius: 12px;
    color: #fff;
    font-weight: 700;
    cursor: pointer;
    transition: all 0.2s ease;
    text-align: center;
  }

  .opt-btn:hover {
    background: rgba(255,255,255,0.15);
  }

  .opt-btn.selected {
    background: var(--accent-blue);
    border-color: #60a5fa;
    box-shadow: 0 0 12px rgba(59, 130, 246, 0.4);
  }

  .teams-setup-list {
    display: flex;
    flex-direction: column;
    gap: 10px;
  }

  .team-setup-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    background: rgba(0,0,0,0.2);
    padding: 10px 14px;
    border-radius: 12px;
    border: 1px solid rgba(255,255,255,0.05);
  }

  .team-setup-name {
    display: flex;
    align-items: center;
    gap: 10px;
    font-weight: 700;
  }

  .btn-primary {
    background: linear-gradient(135deg, #f59e0b, #d97706);
    border: none;
    border-radius: 14px;
    padding: 16px;
    font-size: 18px;
    font-weight: 900;
    color: #fff;
    cursor: pointer;
    box-shadow: 0 8px 20px rgba(217, 119, 6, 0.4);
    transition: all 0.2s ease;
    text-transform: uppercase;
    letter-spacing: 1px;
    margin-top: 10px;
  }

  .btn-primary:hover {
    transform: translateY(-2px);
    box-shadow: 0 12px 28px rgba(217, 119, 6, 0.6);
    background: linear-gradient(135deg, #fbbf24, #f59e0b);
  }

  /* Audio enable banner overlay for web audio autoplay rules */
  #audio-prompt {
    position: absolute;
    bottom: 20px;
    left: 50%;
    transform: translateX(-50%);
    background: rgba(0,0,0,0.8);
    border: 1px solid var(--accent-gold);
    padding: 8px 16px;
    border-radius: 20px;
    font-size: 12px;
    pointer-events: none;
    z-index: 300;
  }
</style>
</head>
<body>

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

  <!-- HUD -->
  <div id="hud">
    <div class="hud-top">
      <div class="team-bars" id="team-bars-container">
        <!-- Team bars injected dynamically -->
      </div>

      <div class="timer-box" id="timer-box">
        <div class="timer-val" id="timer-text">45</div>
        <div class="timer-label">Turn Time</div>
      </div>

      <div class="wind-box">
        <div class="wind-title">Wind Vector</div>
        <div class="wind-gauge">
          <div class="wind-center-mark"></div>
          <div class="wind-bar" id="wind-bar"></div>
        </div>
      </div>
    </div>

    <div class="hud-bottom">
      <div class="weapon-indicator" id="weapon-indicator-btn" onclick="toggleWeaponMenu()">
        <div class="weapon-icon-box" id="hud-weapon-icon">🚀</div>
        <div class="weapon-details">
          <div class="weapon-name" id="hud-weapon-name">Bazooka</div>
          <div class="weapon-ammo" id="hud-weapon-ammo">Ammo: Infinite</div>
        </div>
      </div>

      <div class="controls-hint">
        [A/D or ◄/►] Move &nbsp;|&nbsp; [W/S or ▲/▼] Aim &nbsp;|&nbsp; [SPACE] Fire (Hold Power) <br>
        [E or UP] Jump &nbsp;|&nbsp; [Q] Backflip &nbsp;|&nbsp; [TAB] Weapons &nbsp;|&nbsp; [R] Rope/Retract
      </div>
    </div>
  </div>

  <!-- Weapon Selection Grid -->
  <div id="weapon-menu">
    <div class="menu-header">
      <span>Arsenal Selection</span>
      <span style="font-size: 14px; color: #94a3b8; cursor: pointer;" onclick="toggleWeaponMenu()">✕ Close</span>
    </div>
    <div id="weapon-grid-container">
      <!-- Weapon categories & buttons injected dynamically -->
    </div>
  </div>

  <!-- Announcement Banner -->
  <div id="announcer-banner">TEAM RED'S TURN</div>

  <!-- Lobby / Game Setup Modal -->
  <div class="modal-overlay" id="setup-modal">
    <div class="modal-card">
      <div class="modal-title">💣 WORMAGE 💣</div>
      <div class="modal-subtitle">Tactical Artillery & Terrain Destruction</div>

      <div class="form-group">
        <div class="form-label">Map Terrain Shape</div>
        <div class="option-selector" id="terrain-selector">
          <div class="opt-btn selected" data-val="island">Single Island</div>
          <div class="opt-btn" data-val="two_islands">Two Islands</div>
          <div class="opt-btn" data-val="cave">Deep Cavern</div>
        </div>
      </div>

      <div class="form-group">
        <div class="form-label">Teams Configuration</div>
        <div class="teams-setup-list" id="teams-setup-list">
          <!-- Dynamically populated -->
        </div>
      </div>

      <button class="btn-primary" onclick="startGameFromLobby()">Commence Warfare!</button>
    </div>
  </div>

  <!-- Game Over Modal -->
  <div class="modal-overlay" id="gameover-modal" style="display: none;">
    <div class="modal-card">
      <div class="modal-title" id="winner-title">VICTORY!</div>
      <div class="modal-subtitle" id="winner-subtitle">Team Alpha dominated the battlefield</div>

      <div class="form-group" id="match-stats" style="line-height: 1.8; font-size: 14px; color: #cbd5e1;">
        <!-- Stats summary -->
      </div>

      <button class="btn-primary" onclick="returnToLobby()">Back to Menu</button>
    </div>
  </div>
</div>

<script>
/**
 * WORMAGE - Complete Artillery Engine
 * Includes: WebAudio Synth, Procedural Terrain, Pixel Destruction, Physics with Gravity/Wind/Water,
 * Custom Debris Chunks, Predictive Trajectory Lines, Risk-Assessment AI, Dynamic Camera with Zoom,
 * Particle Systems, and Full Weapon Arsenal.
 */

// Global Audio Engine (Web Audio API Synthesizer)
class SoundEngine {
  constructor() {
    this.ctx = null;
    this.enabled = false;
  }

  init() {
    if (this.ctx) return;
    const AudioCtx = window.AudioContext || window.webkitAudioContext;
    if (AudioCtx) {
      this.ctx = new AudioCtx();
      this.enabled = true;
    }
  }

  playTone(freq, type, duration, startVol = 0.3, endVol = 0) {
    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(startVol, this.ctx.currentTime);
      gain.gain.exponentialRampToValueAtTime(Math.max(0.0001, endVol), this.ctx.currentTime + duration);
      osc.connect(gain);
      gain.connect(this.ctx.destination);
      osc.start();
      osc.stop(this.ctx.currentTime + duration);
    } catch (e) {}
  }

  playExplosion(size = 1.0) {
    if (!this.enabled || !this.ctx) return;
    try {
      const bufferSize = this.ctx.sampleRate * (0.3 * size);
      const buffer = this.ctx.createBuffer(1, bufferSize, this.ctx.sampleRate);
      const data = buffer.getChannelData(0);
      for (let i = 0; i < bufferSize; i++) {
        data[i] = Math.random() * 2 - 1;
      }
      const noise = this.ctx.createBufferSource();
      noise.buffer = buffer;

      const filter = this.ctx.createBiquadFilter();
      filter.type = 'lowpass';
      filter.frequency.setValueAtTime(400, this.ctx.currentTime);
      filter.frequency.exponentialRampToValueAtTime(30, this.ctx.currentTime + (0.3 * size));

      const gain = this.ctx.createGain();
      gain.gain.setValueAtTime(0.6 * size, this.ctx.currentTime);
      gain.gain.exponentialRampToValueAtTime(0.01, this.ctx.currentTime + (0.3 * size));

      noise.connect(filter);
      filter.connect(gain);
      gain.connect(this.ctx.destination);
      noise.start();

      // Low boom oscillator
      this.playTone(80, 'sine', 0.4 * size, 0.5 * size, 0.01);
    } catch (e) {}
  }

  playJump() {
    if (!this.enabled || !this.ctx) return;
    try {
      const osc = this.ctx.createOscillator();
      const gain = this.ctx.createGain();
      osc.type = 'sine';
      osc.frequency.setValueAtTime(150, this.ctx.currentTime);
      osc.frequency.exponentialRampToValueAtTime(400, this.ctx.currentTime + 0.15);
      gain.gain.setValueAtTime(0.3, this.ctx.currentTime);
      gain.gain.linearRampToValueAtTime(0, this.ctx.currentTime + 0.15);
      osc.connect(gain);
      gain.connect(this.ctx.destination);
      osc.start();
      osc.stop(this.ctx.currentTime + 0.15);
    } catch(e){}
  }

  playLaunch() {
    if (!this.enabled || !this.ctx) return;
    try {
      const osc = this.ctx.createOscillator();
      const gain = this.ctx.createGain();
      osc.type = 'triangle';
      osc.frequency.setValueAtTime(300, this.ctx.currentTime);
      osc.frequency.exponentialRampToValueAtTime(80, this.ctx.currentTime + 0.2);
      gain.gain.setValueAtTime(0.4, this.ctx.currentTime);
      gain.gain.linearRampToValueAtTime(0, this.ctx.currentTime + 0.2);
      osc.connect(gain);
      gain.connect(this.ctx.destination);
      osc.start();
      osc.stop(this.ctx.currentTime + 0.2);
    } catch(e){}
  }

  playSplash() {
    if (!this.enabled || !this.ctx) return;
    try {
      const bufferSize = this.ctx.sampleRate * 0.25;
      const buffer = this.ctx.createBuffer(1, bufferSize, this.ctx.sampleRate);
      const data = buffer.getChannelData(0);
      for (let i = 0; i < bufferSize; i++) data[i] = Math.random() * 2 - 1;
      const noise = this.ctx.createBufferSource();
      noise.buffer = buffer;
      const filter = this.ctx.createBiquadFilter();
      filter.type = 'bandpass';
      filter.frequency.setValueAtTime(1000, this.ctx.currentTime);
      filter.Q.setValueAtTime(3, this.ctx.currentTime);
      const gain = this.ctx.createGain();
      gain.gain.setValueAtTime(0.4, this.ctx.currentTime);
      gain.gain.exponentialRampToValueAtTime(0.01, this.ctx.currentTime + 0.25);
      noise.connect(filter);
      filter.connect(gain);
      gain.connect(this.ctx.destination);
      noise.start();
    } catch(e){}
  }

  playClick() {
    this.playTone(800, 'sine', 0.04, 0.1, 0.01);
  }

  playTimerTick(urgent = false) {
    this.playTone(urgent ? 900 : 600, 'square', 0.05, urgent ? 0.25 : 0.1, 0.01);
  }

  playHoly() {
    if (!this.enabled || !this.ctx) return;
    [440, 554.37, 659.25, 880].forEach(f => {
      this.playTone(f, 'sine', 1.2, 0.25, 0.01);
    });
  }
}

const sound = new SoundEngine();
document.addEventListener('pointerdown', () => sound.init(), { once: true });

// Game Constants & Configuration
const WORLD_WIDTH = 1600;
const WORLD_HEIGHT = 900;
const GRAVITY = 380; // px/s^2
const WATER_LEVEL = 820;

const TEAMS_DEF = [
  { id: 0, name: 'Red Rockets', color: '#ef4444', accent: '#fca5a5' },
  { id: 1, name: 'Blue Bombers', color: '#3b82f6', accent: '#93c5fd' },
  { id: 2, name: 'Green Grenadiers', color: '#10b981', accent: '#6ee7b7' },
  { id: 3, name: 'Yellow Yahoos', color: '#f59e0b', accent: '#fde047' }
];

const WEAPONS = [
  // Indirect
  { id: 'bazooka', name: 'Bazooka', icon: '🚀', category: 'Indirect', ammo: -1, damage: 45, radius: 42, speed: 650, windAffect: 0.8, fuse: 0 },
  { id: 'grenade', name: 'Grenade', icon: '💣', category: 'Indirect', ammo: -1, damage: 50, radius: 45, speed: 550, windAffect: 0.2, fuse: 3.5, bounce: true },
  { id: 'cluster', name: 'Cluster Bomb', icon: '💥', category: 'Indirect', ammo: 3, damage: 30, radius: 35, speed: 520, windAffect: 0.3, fuse: 3, clusters: 5 },
  { id: 'mortar', name: 'Mortar', icon: '🔻', category: 'Indirect', ammo: 2, damage: 55, radius: 50, speed: 700, windAffect: 0.9, fuse: 0 },
  { id: 'holy', name: 'Holy Grenade', icon: '✨', category: 'Indirect', ammo: 1, damage: 90, radius: 85, speed: 480, windAffect: 0.1, fuse: 4.0, holy: true },

  // Direct
  { id: 'shotgun', name: 'Shotgun', icon: '🔫', category: 'Direct', ammo: -1, damage: 25, radius: 18, speed: 1200, windAffect: 0, direct: true, shots: 2 },
  { id: 'sniper', name: 'Sniper Rifle', icon: '🎯', category: 'Direct', ammo: 2, damage: 60, radius: 12, speed: 2000, windAffect: 0, direct: true },
  { id: 'firepunch', name: 'Fire Punch', icon: '🥊', category: 'Direct', ammo: -1, damage: 40, radius: 25, melee: true },

  // High-AoE / Map-altering
  { id: 'airstrike', name: 'Air Strike', icon: '✈️', category: 'AoE / Special', ammo: 1, damage: 40, radius: 40, strike: true },
  { id: 'dynamite', name: 'Dynamite', icon: '🧨', category: 'AoE / Special', ammo: 2, damage: 75, radius: 70, drop: true, fuse: 5 },
  { id: 'drill', name: 'Drill Bomb', icon: '🌀', category: 'AoE / Special', ammo: 2, damage: 50, radius: 45, speed: 550, drill: true },

  // Utilities
  { id: 'rope', name: 'Ninja Rope', icon: '🪢', category: 'Utility', ammo: -1, utility: 'rope' },
  { id: 'jetpack', name: 'Jetpack', icon: '🎒', category: 'Utility', ammo: 2, utility: 'jetpack' },
  { id: 'teleport', name: 'Teleporter', icon: '🌀', category: 'Utility', ammo: 1, utility: 'teleport' },
  { id: 'girder', name: 'Girder Bridge', icon: '🧱', category: 'Utility', ammo: 2, utility: 'girder' },
  { id: 'blowtorch', name: 'Blowtorch', icon: '🔥', category: 'Utility', ammo: 3, utility: 'torch' }
];

// Canvas & Terrain Setup
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
canvas.width = WORLD_WIDTH;
canvas.height = WORLD_HEIGHT;

const terrainCanvas = document.createElement('canvas');
terrainCanvas.width = WORLD_WIDTH;
terrainCanvas.height = WORLD_HEIGHT;
const terrainCtx = terrainCanvas.getContext('2d');

let terrainImageData = null;

// Game State
let gameState = 'LOBBY'; // LOBBY, PLAYING, GAMEOVER
let selectedTerrainType = 'island';
let teams = [];
let activeTeamIdx = 0;
let activeWormIdx = 0;
let turnTimer = 45;
let turnTimerInterval = null;
let currentWind = 0; // -100 to 100
let projectiles = [];
let particles = [];
let debrisChunks = [];
let combatTexts = [];
let camera = { x: WORLD_WIDTH / 2, y: WORLD_HEIGHT / 2, zoom: 1, targetX: WORLD_WIDTH / 2, targetY: WORLD_HEIGHT / 2, shake: 0 };
let impactFreezeFrames = 0;
let selectedWeapon = WEAPONS[0];
let isCharging = false;
let chargePower = 0;
let aimAngle = -Math.PI / 4; // -45 deg
let matchStats = { totalDamage: {}, totalKills: {} };

// Controls Input Tracker
const keys = {};

window.addEventListener('keydown', e => {
  keys[e.code] = true;
  if (e.code === 'KeyTab') {
    e.preventDefault();
    toggleWeaponMenu();
  }
});
window.addEventListener('keyup', e => { keys[e.code] = false; });

// Mouse interaction for aiming / targeting
let mousePos = { x: 0, y: 0, worldX: 0, worldY: 0 };
canvas.addEventListener('mousemove', e => {
  const rect = canvas.getBoundingClientRect();
  const scaleX = canvas.width / rect.width;
  const scaleY = canvas.height / rect.height;
  mousePos.x = (e.clientX - rect.left) * scaleX;
  mousePos.y = (e.clientY - rect.top) * scaleY;

  // Convert to world space considering camera
  mousePos.worldX = (mousePos.x - canvas.width/2) / camera.zoom + camera.x;
  mousePos.worldY = (mousePos.y - canvas.height/2) / camera.zoom + camera.y;

  const currentWorm = getActiveWorm();
  if (currentWorm && gameState === 'PLAYING' && !currentWorm.isCPU) {
    const dx = mousePos.worldX - currentWorm.x;
    const dy = mousePos.worldY - (currentWorm.y - 12);
    aimAngle = Math.atan2(dy, dx);
    currentWorm.facing = dx >= 0 ? 1 : -1;
  }
});

canvas.addEventListener('mousedown', e => {
  sound.init();
  const currentWorm = getActiveWorm();
  if (!currentWorm || gameState !== 'PLAYING' || currentWorm.isCPU || turnTimer <= 0) return;

  if (selectedWeapon.utility === 'teleport') {
    // Perform Teleport
    currentWorm.x = mousePos.worldX;
    currentWorm.y = mousePos.worldY;
    currentWorm.vx = 0;
    currentWorm.vy = 0;
    spawnParticles(currentWorm.x, currentWorm.y, '#8b5cf6', 20);
    sound.playTone(600, 'sine', 0.2, 0.4, 0);
    consumeWeaponAmmo();
    endTurnDelayed(1.5);
  } else if (selectedWeapon.utility === 'girder') {
    // Build Girder
    placeGirder(mousePos.worldX, mousePos.worldY);
    consumeWeaponAmmo();
  } else if (selectedWeapon.strike) {
    // Air strike target
    triggerAirStrike(mousePos.worldX);
    consumeWeaponAmmo();
    endTurnDelayed(3.0);
  } else if (!selectedWeapon.utility && !selectedWeapon.melee) {
    isCharging = true;
    chargePower = 0;
  }
});

canvas.addEventListener('mouseup', e => {
  if (isCharging) {
    fireWeapon(chargePower);
    isCharging = false;
    chargePower = 0;
  }
});

// UI Setup Events
document.querySelectorAll('#terrain-selector .opt-btn').forEach(btn => {
  btn.addEventListener('click', () => {
    document.querySelectorAll('#terrain-selector .opt-btn').forEach(b => b.classList.remove('selected'));
    btn.classList.add('selected');
    selectedTerrainType = btn.getAttribute('data-val');
    sound.playClick();
  });
});

function initLobbyUI() {
  const container = document.getElementById('teams-setup-list');
  container.innerHTML = '';
  TEAMS_DEF.forEach((t, idx) => {
    const row = document.createElement('div');
    row.className = 'team-setup-row';
    row.innerHTML = `
      <div class="team-setup-name">
        <span class="team-color-badge" style="background: ${t.color}"></span>
        <span>${t.name}</span>
      </div>
      <div class="option-selector">
        <button class="opt-btn ${idx < 2 ? 'selected' : ''}" onclick="toggleTeamActive(${idx}, 'human', this)">Human</button>
        <button class="opt-btn" onclick="toggleTeamActive(${idx}, 'cpu', this)">CPU</button>
        <button class="opt-btn ${idx >= 2 ? 'selected' : ''}" onclick="toggleTeamActive(${idx}, 'off', this)">Off</button>
      </div>
    `;
    container.appendChild(row);
  });
}

const teamConfig = [
  { type: 'human' },
  { type: 'human' },
  { type: 'off' },
  { type: 'off' }
];

function toggleTeamActive(idx, type, btn) {
  sound.playClick();
  teamConfig[idx].type = type;
  const parent = btn.parentElement;
  parent.querySelectorAll('.opt-btn').forEach(b => b.classList.remove('selected'));
  btn.classList.add('selected');
}

initLobbyUI();

// Terrain Procedural Generation System
function generateTerrain(shape) {
  terrainCtx.clearRect(0, 0, WORLD_WIDTH, WORLD_HEIGHT);

  terrainCtx.fillStyle = '#1e3a8a';
  terrainCtx.beginPath();

  if (shape === 'island') {
    // Single organic island in middle
    terrainCtx.moveTo(150, WORLD_HEIGHT);
    for (let x = 150; x <= WORLD_WIDTH - 150; x += 10) {
      let norm = (x - 150) / (WORLD_WIDTH - 300);
      let heightVal = Math.sin(norm * Math.PI) * 420;
      heightVal += Math.sin(x * 0.02) * 45 + Math.cos(x * 0.05) * 20;
      let y = WORLD_HEIGHT - 120 - heightVal;
      terrainCtx.lineTo(x, y);
    }
    terrainCtx.lineTo(WORLD_WIDTH - 150, WORLD_HEIGHT);
  } else if (shape === 'two_islands') {
    // Two islands split by water channel
    terrainCtx.moveTo(100, WORLD_HEIGHT);
    for (let x = 100; x <= 650; x += 10) {
      let norm = (x - 100) / 550;
      let y = WORLD_HEIGHT - 120 - Math.sin(norm * Math.PI) * 380 + Math.sin(x * 0.03) * 30;
      terrainCtx.lineTo(x, y);
    }
    terrainCtx.lineTo(650, WORLD_HEIGHT);
    terrainCtx.moveTo(950, WORLD_HEIGHT);
    for (let x = 950; x <= WORLD_WIDTH - 100; x += 10) {
      let norm = (x - 950) / 550;
      let y = WORLD_HEIGHT - 120 - Math.sin(norm * Math.PI) * 380 + Math.cos(x * 0.03) * 30;
      terrainCtx.lineTo(x, y);
    }
    terrainCtx.lineTo(WORLD_WIDTH - 100, WORLD_HEIGHT);
  } else if (shape === 'cave') {
    // Deep Cavern with ceiling and ground
    // Floor
    terrainCtx.moveTo(50, WORLD_HEIGHT);
    for (let x = 50; x <= WORLD_WIDTH - 50; x += 10) {
      let y = WORLD_HEIGHT - 150 - Math.sin(x * 0.008) * 120 + Math.cos(x * 0.025) * 40;
      terrainCtx.lineTo(x, y);
    }
    terrainCtx.lineTo(WORLD_WIDTH - 50, WORLD_HEIGHT);

    // Ceiling
    terrainCtx.moveTo(50, 0);
    for (let x = 50; x <= WORLD_WIDTH - 50; x += 10) {
      let y = 160 + Math.sin(x * 0.01) * 100 + Math.cos(x * 0.03) * 35;
      terrainCtx.lineTo(x, y);
    }
    terrainCtx.lineTo(WORLD_WIDTH - 50, 0);
  }

  terrainCtx.closePath();

  // Create gradient styling for terrain
  const grad = terrainCtx.createLinearGradient(0, 0, 0, WORLD_HEIGHT);
  grad.addColorStop(0, '#22c55e');
  grad.addColorStop(0.15, '#15803d');
  grad.addColorStop(0.3, '#78350f');
  grad.addColorStop(1, '#451a03');
  terrainCtx.fillStyle = grad;
  terrainCtx.fill();

  // Add dirt texture details / stones
  terrainCtx.fillStyle = 'rgba(0,0,0,0.15)';
  for (let i = 0; i < 300; i++) {
    let rx = Math.random() * WORLD_WIDTH;
    let ry = Math.random() * WORLD_HEIGHT;
    terrainCtx.beginPath();
    terrainCtx.arc(rx, ry, Math.random() * 8 + 2, 0, Math.PI * 2);
    terrainCtx.fill();
  }

  // Cache pixel data
  terrainImageData = terrainCtx.getImageData(0, 0, WORLD_WIDTH, WORLD_HEIGHT);
}

function isTerrainSolid(x, y) {
  x = Math.floor(x);
  y = Math.floor(y);
  if (x < 0 || x >= WORLD_WIDTH || y < 0 || y >= WORLD_HEIGHT) return false;
  const idx = (y * WORLD_WIDTH + x) * 4;
  return terrainImageData.data[idx + 3] > 128;
}

function carveTerrain(cx, cy, radius) {
  terrainCtx.save();
  terrainCtx.globalCompositeOperation = 'destination-out';
  terrainCtx.beginPath();
  terrainCtx.arc(cx, cy, radius, 0, Math.PI * 2);
  terrainCtx.fill();
  terrainCtx.restore();

  // Update cached imagedata
  terrainImageData = terrainCtx.getImageData(0, 0, WORLD_WIDTH, WORLD_HEIGHT);

  // Spawn visual debris particles & falling debris chunks!
  const chunkCount = Math.floor(radius / 4);
  for (let i = 0; i < chunkCount; i++) {
    const angle = Math.random() * Math.PI * 2;
    const dist = Math.random() * radius * 0.8;
    debrisChunks.push({
      x: cx + Math.cos(angle) * dist,
      y: cy + Math.sin(angle) * dist,
      vx: (Math.random() - 0.5) * 300,
      vy: (Math.random() - 0.8) * 350,
      radius: Math.random() * 4 + 3,
      color: '#78350f'
    });
  }
}

function placeGirder(gx, gy) {
  terrainCtx.save();
  terrainCtx.fillStyle = '#94a3b8';
  terrainCtx.strokeStyle = '#475569';
  terrainCtx.lineWidth = 3;
  terrainCtx.beginPath();
  terrainCtx.rect(gx - 60, gy - 10, 120, 20);
  terrainCtx.fill();
  terrainCtx.stroke();
  terrainCtx.restore();

  terrainImageData = terrainCtx.getImageData(0, 0, WORLD_WIDTH, WORLD_HEIGHT);
  spawnParticles(gx, gy, '#cbd5e1', 15);
  sound.playClick();
}

// Unit / Worm Class
class Worm {
  constructor(teamId, id, name, x, y) {
    this.teamId = teamId;
    this.id = id;
    this.name = name;
    this.x = x;
    this.y = y;
    this.vx = 0;
    this.vy = 0;
    this.radius = 12;
    this.hp = 100;
    this.maxHp = 100;
    this.facing = 1; // 1 = right, -1 = left
    this.isGrounded = false;
    this.isDead = false;
    this.isDrowning = false;
    this.parachute = false;
    this.jetpackFuel = 0;
    this.rope = null; // { anchorX, anchorY, length }
    this.isCPU = false;
  }

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

    // Water Drowning Check
    if (this.y + this.radius >= WATER_LEVEL) {
      if (!this.isDrowning) {
        this.isDrowning = true;
        sound.playSplash();
        spawnParticles(this.x, WATER_LEVEL, '#38bdf8', 25);
        addCombatText(this.x, this.y - 20, 'DROWNING!', '#38bdf8');
      }
      this.hp -= dt * 40;
      this.vy = 40; // drag slow sink
      if (this.hp <= 0) {
        this.die();
      }
      return;
    }

    // Jetpack handling
    if (this.jetpackFuel > 0 && keys['KeyW']) {
      this.vy -= 800 * dt;
      this.jetpackFuel -= dt * 25;
      spawnParticles(this.x, this.y + 10, '#f97316', 2);
    }

    // Apply Physics
    this.vy += GRAVITY * dt;

    // Apply Wind if in air or using parachute
    if (!this.isGrounded) {
      this.vx += currentWind * 2.5 * dt;
      if (this.parachute) {
        this.vy = Math.min(this.vy, 60);
      }
    }

    // Ninja Rope swing physics
    if (this.rope) {
      const dx = this.x - this.rope.anchorX;
      const dy = this.y - this.rope.anchorY;
      const dist = Math.hypot(dx, dy);
      if (dist > this.rope.length) {
        const angle = Math.atan2(dy, dx);
        this.x = this.rope.anchorX + Math.cos(angle) * this.rope.length;
        this.y = this.rope.anchorY + Math.sin(angle) * this.rope.length;
        // Dampen radial velocity
        const dot = this.vx * Math.cos(angle) + this.vy * Math.sin(angle);
        this.vx -= dot * Math.cos(angle);
        this.vy -= dot * Math.sin(angle);
      }
    }

    // Horizontal Movement
    let nextX = this.x + this.vx * dt;
    let nextY = this.y + this.vy * dt;

    // Collision Detection against Terrain
    if (isTerrainSolid(nextX, nextY + this.radius)) {
      // Check fall damage
      if (this.vy > 400 && !this.parachute) {
        const fallDmg = Math.floor((this.vy - 380) * 0.15);
        if (fallDmg > 5) {
          this.takeDamage(fallDmg);
          sound.playExplosion(0.3);
        }
      }

      // Step up slope check
      let stepUp = 0;
      while (stepUp < 12 && isTerrainSolid(nextX, nextY + this.radius - stepUp)) {
        stepUp++;
      }

      if (stepUp < 12) {
        nextY -= stepUp;
        this.vy = 0;
        this.isGrounded = true;
        this.parachute = false;
      } else {
        this.vx = -this.vx * 0.3; // bounce off wall
        nextX = this.x;
      }
    } else {
      this.isGrounded = false;
    }

    this.x = nextX;
    this.y = nextY;

    // Friction
    if (this.isGrounded) {
      this.vx *= 0.8;
    } else {
      this.vx *= 0.98;
    }
  }

  jump(powerY = -220, powerX = 0) {
    if (this.isGrounded) {
      this.vy = powerY;
      this.vx = powerX;
      this.isGrounded = false;
      sound.playJump();
    }
  }

  takeDamage(amount) {
    if (this.isDead) return;
    this.hp -= amount;
    addCombatText(this.x, this.y - 25, `-${Math.floor(amount)}`, '#ef4444');
    camera.shake = Math.max(camera.shake, amount * 0.2);

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

  die() {
    this.hp = 0;
    this.isDead = true;
    spawnParticles(this.x, this.y, '#ef4444', 30);
    addCombatText(this.x, this.y - 30, 'RIP', '#94a3b8');
  }

  draw(ctx) {
    if (this.isDead) {
      // Tombstone graphic
      ctx.fillStyle = '#64748b';
      ctx.fillRect(this.x - 8, this.y - 14, 16, 18);
      ctx.fillStyle = '#cbd5e1';
      ctx.font = 'bold 10px sans-serif';
      ctx.textAlign = 'center';
      ctx.fillText('RIP', this.x, this.y - 4);
      return;
    }

    const team = TEAMS_DEF[this.teamId];

    // Rope Line
    if (this.rope) {
      ctx.strokeStyle = '#d97706';
      ctx.lineWidth = 2;
      ctx.beginPath();
      ctx.moveTo(this.x, this.y - 8);
      ctx.lineTo(this.rope.anchorX, this.rope.anchorY);
      ctx.stroke();
    }

    // Parachute Graphic
    if (this.parachute) {
      ctx.fillStyle = team.color;
      ctx.beginPath();
      ctx.arc(this.x, this.y - 28, 22, Math.PI, 0);
      ctx.fill();
      ctx.strokeStyle = '#fff';
      ctx.lineWidth = 1;
      ctx.beginPath();
      ctx.moveTo(this.x - 20, this.y - 28);
      ctx.lineTo(this.x, this.y - 8);
      ctx.moveTo(this.x + 20, this.y - 28);
      ctx.lineTo(this.x, this.y - 8);
      ctx.stroke();
    }

    // Worm Body (cute pill/capsule)
    ctx.save();
    ctx.translate(this.x, this.y - 10);

    // Body base
    ctx.fillStyle = '#f472b6'; // Cute pink worm skin
    ctx.beginPath();
    ctx.ellipse(0, 0, 10, 12, 0, 0, Math.PI * 2);
    ctx.fill();

    // Head Bandana / Helmet with team color
    ctx.fillStyle = team.color;
    ctx.fillRect(-10, -10, 20, 6);

    // Eyes
    ctx.fillStyle = '#fff';
    ctx.beginPath();
    ctx.arc(this.facing * 4, -3, 3.5, 0, Math.PI * 2);
    ctx.fill();
    ctx.fillStyle = '#000';
    ctx.beginPath();
    ctx.arc(this.facing * 5 + Math.cos(aimAngle)*1.5, -3 + Math.sin(aimAngle)*1.5, 1.5, 0, Math.PI * 2);
    ctx.fill();

    // Active Worm Indicator Arrow
    if (this === getActiveWorm() && gameState === 'PLAYING') {
      ctx.fillStyle = '#fbbf24';
      ctx.beginPath();
      ctx.moveTo(0, -26);
      ctx.lineTo(-6, -36);
      ctx.lineTo(6, -36);
      ctx.closePath();
      ctx.fill();
    }

    ctx.restore();

    // Aim Indicator Line & Charge Bar
    if (this === getActiveWorm() && gameState === 'PLAYING') {
      ctx.strokeStyle = 'rgba(251, 191, 36, 0.8)';
      ctx.lineWidth = 2;
      ctx.setLineDash([4, 4]);
      ctx.beginPath();
      ctx.moveTo(this.x, this.y - 10);
      ctx.lineTo(this.x + Math.cos(aimAngle) * 40, this.y - 10 + Math.sin(aimAngle) * 40);
      ctx.stroke();
      ctx.setLineDash([]);

      // Draw Predictive Arc Trajectory Line
      drawTrajectoryPreview(this.x, this.y - 10, aimAngle, isCharging ? chargePower : 500);

      // Power Bar if charging
      if (isCharging) {
        ctx.fillStyle = 'rgba(0,0,0,0.5)';
        ctx.fillRect(this.x - 20, this.y + 12, 40, 6);
        ctx.fillStyle = '#ef4444';
        ctx.fillRect(this.x - 20, this.y + 12, (chargePower / 800) * 40, 6);
      }
    }

    // Name & Health Bar
    ctx.fillStyle = '#fff';
    ctx.font = 'bold 10px sans-serif';
    ctx.textAlign = 'center';
    ctx.fillText(this.name, this.x, this.y - 24);

    // HP Bar
    const hpPct = Math.max(0, this.hp / this.maxHp);
    ctx.fillStyle = 'rgba(0,0,0,0.6)';
    ctx.fillRect(this.x - 15, this.y - 20, 30, 4);
    ctx.fillStyle = hpPct > 0.5 ? '#10b981' : hpPct > 0.25 ? '#f59e0b' : '#ef4444';
    ctx.fillRect(this.x - 15, this.y - 20, 30 * hpPct, 4);
  }
}

// Draw Predictive Trajectory Preview Dot Line
function drawTrajectoryPreview(startX, startY, angle, speed) {
  if (selectedWeapon.utility || selectedWeapon.strike || selectedWeapon.melee) return;

  ctx.fillStyle = 'rgba(255, 255, 255, 0.5)';
  let px = startX;
  let py = startY;
  let vx = Math.cos(angle) * speed;
  let vy = Math.sin(angle) * speed;
  const dt = 0.04;
  const windFactor = (selectedWeapon.windAffect || 0) * currentWind * 2.5;

  for (let i = 0; i < 25; i++) {
    vx += windFactor * dt;
    vy += GRAVITY * dt;
    px += vx * dt;
    py += vy * dt;

    if (isTerrainSolid(px, py) || py >= WATER_LEVEL) break;

    ctx.beginPath();
    ctx.arc(px, py, 2, 0, Math.PI * 2);
    ctx.fill();
  }
}

// Projectile System
class Projectile {
  constructor(type, x, y, vx, vy, config = {}) {
    this.type = type; // weapon id
    this.x = x;
    this.y = y;
    this.vx = vx;
    this.vy = vy;
    this.radius = config.radius || 4;
    this.damage = config.damage || 40;
    this.blastRadius = config.blastRadius || 45;
    this.fuse = config.fuse || 0;
    this.timer = 0;
    this.windAffect = config.windAffect || 0;
    this.bounce = config.bounce || false;
    this.clusters = config.clusters || 0;
    this.holy = config.holy || false;
    this.drill = config.drill || false;
    this.isDead = false;
  }

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

    this.timer += dt;

    // Wind affect
    this.vx += currentWind * this.windAffect * 2.5 * dt;

    if (!this.drill) {
      this.vy += GRAVITY * dt;
    }

    let nextX = this.x + this.vx * dt;
    let nextY = this.y + this.vy * dt;

    // Trail particles
    if (Math.random() < 0.6) {
      particles.push({
        x: this.x,
        y: this.y,
        vx: (Math.random() - 0.5) * 20,
        vy: (Math.random() - 0.5) * 20,
        radius: Math.random() * 3 + 1,
        color: 'rgba(200,200,200,0.6)',
        life: 0.3,
        maxLife: 0.3
      });
    }

    // Fuse detonation check
    if (this.fuse > 0 && this.timer >= this.fuse) {
      if (this.holy) sound.playHoly();
      this.explode();
      return;
    }

    // Terrain or Water Collision
    if (nextY >= WATER_LEVEL) {
      sound.playSplash();
      spawnParticles(this.x, WATER_LEVEL, '#38bdf8', 15);
      this.isDead = true;
      return;
    }

    if (isTerrainSolid(nextX, nextY)) {
      if (this.drill) {
        // Drill burrows through terrain
        carveTerrain(nextX, nextY, 14);
        if (this.timer >= 2.0) this.explode();
      } else if (this.bounce) {
        this.vx *= -0.6;
        this.vy *= -0.6;
        sound.playTone(300, 'sine', 0.05, 0.2, 0);
      } else {
        this.explode();
      }
      return;
    }

    // Direct Hit on Worm check
    for (let team of teams) {
      for (let worm of team.worms) {
        if (!worm.isDead && Math.hypot(nextX - worm.x, nextY - worm.y) < worm.radius + this.radius) {
          this.explode();
          return;
        }
      }
    }

    this.x = nextX;
    this.y = nextY;
  }

  explode() {
    this.isDead = true;
    carveTerrain(this.x, this.y, this.blastRadius);
    sound.playExplosion(this.blastRadius / 45);
    impactFreezeFrames = 3; // Punchy frame-freeze

    // Particles & Screen Shake
    spawnParticles(this.x, this.y, '#f97316', 35);
    camera.shake = Math.max(camera.shake, this.blastRadius * 0.4);

    // Cluster submunitions spawn
    if (this.clusters > 0) {
      for (let i = 0; i < this.clusters; i++) {
        let angle = -Math.PI / 2 + (Math.random() - 0.5) * 1.5;
        let spd = Math.random() * 250 + 150;
        projectiles.push(new Projectile('cluster_sub', this.x, this.y, Math.cos(angle)*spd, Math.sin(angle)*spd, {
          damage: 20, blastRadius: 22, fuse: 1.5
        }));
      }
    }

    // Apply Damage & Impulse to Worms in radius
    teams.forEach(team => {
      team.worms.forEach(worm => {
        if (worm.isDead) return;
        let dist = Math.hypot(this.x - worm.x, this.y - worm.y);
        if (dist < this.blastRadius + 20) {
          let force = (1 - dist / (this.blastRadius + 20));
          let dmg = Math.floor(this.damage * force);
          let angle = Math.atan2(worm.y - this.y, worm.x - this.x);

          worm.vx += Math.cos(angle) * force * 450;
          worm.vy += Math.sin(angle) * force * 450 - 50;
          worm.takeDamage(dmg);
        }
      });
    });
  }

  draw(ctx) {
    ctx.fillStyle = '#f59e0b';
    ctx.beginPath();
    ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
    ctx.fill();
  }
}

// Particle System
function spawnParticles(x, y, color, count = 15) {
  for (let i = 0; i < count; i++) {
    const angle = Math.random() * Math.PI * 2;
    const speed = Math.random() * 200 + 50;
    particles.push({
      x: x,
      y: y,
      vx: Math.cos(angle) * speed,
      vy: Math.sin(angle) * speed,
      radius: Math.random() * 4 + 2,
      color: color,
      life: Math.random() * 0.5 + 0.3,
      maxLife: 0.8
    });
  }
}

function updateParticles(dt) {
  for (let i = particles.length - 1; i >= 0; i--) {
    let p = particles[i];
    p.life -= dt;
    p.x += p.vx * dt;
    p.y += p.vy * dt;
    p.vy += GRAVITY * 0.3 * dt;
    if (p.life <= 0) particles.splice(i, 1);
  }
}

function drawParticles(ctx) {
  particles.forEach(p => {
    ctx.fillStyle = p.color;
    ctx.globalAlpha = Math.max(0, p.life / p.maxLife);
    ctx.beginPath();
    ctx.arc(p.x, p.y, p.radius, 0, Math.PI * 2);
    ctx.fill();

  });
  ctx.globalAlpha = 1.0;
}

// Floating Combat Text
function addCombatText(x, y, text, color = '#fff') {
  combatTexts.push({ x, y, text, color, life: 1.2, vy: -40 });
}

function updateCombatTexts(dt) {
  for (let i = combatTexts.length - 1; i >= 0; i--) {
    let ct = combatTexts[i];
    ct.life -= dt;
    ct.y += ct.vy * dt;
    if (ct.life <= 0) combatTexts.splice(i, 1);
  }
}

function drawCombatTexts(ctx) {
  combatTexts.forEach(ct => {
    ctx.fillStyle = ct.color;
    ctx.font = '900 16px sans-serif';
    ctx.textAlign = 'center';
    ctx.globalAlpha = Math.max(0, ct.life);
    ctx.fillText(ct.text, ct.x, ct.y);
  });
  ctx.globalAlpha = 1.0;
}

// Physical Debris Chunks System
function updateDebrisChunks(dt) {
  for (let i = debrisChunks.length - 1; i >= 0; i--) {
    let d = debrisChunks[i];
    d.vy += GRAVITY * dt;
    d.x += d.vx * dt;
    d.y += d.vy * dt;

    if (d.y >= WATER_LEVEL) {
      debrisChunks.splice(i, 1);
      continue;
    }

    if (isTerrainSolid(d.x, d.y)) {
      debrisChunks.splice(i, 1); // Merge into ground
    }
  }
}

function drawDebrisChunks(ctx) {
  debrisChunks.forEach(d => {
    ctx.fillStyle = d.color;
    ctx.beginPath();
    ctx.arc(d.x, d.y, d.radius, 0, Math.PI * 2);
    ctx.fill();
  });
}

// Weapon Actions Execution
function fireWeapon(power) {
  const currentWorm = getActiveWorm();
  if (!currentWorm) return;

  const vx = Math.cos(aimAngle) * power;
  const vy = Math.sin(aimAngle) * power;
  const spawnX = currentWorm.x + Math.cos(aimAngle) * 20;
  const spawnY = currentWorm.y - 10 + Math.sin(aimAngle) * 20;

  sound.playLaunch();

  if (selectedWeapon.id === 'bazooka') {
    projectiles.push(new Projectile('bazooka', spawnX, spawnY, vx, vy, {
      damage: selectedWeapon.damage, blastRadius: selectedWeapon.radius, windAffect: selectedWeapon.windAffect
    }));
  } else if (selectedWeapon.id === 'grenade') {
    projectiles.push(new Projectile('grenade', spawnX, spawnY, vx, vy, {
      damage: selectedWeapon.damage, blastRadius: selectedWeapon.radius, fuse: selectedWeapon.fuse, bounce: true
    }));
  } else if (selectedWeapon.id === 'cluster') {
    projectiles.push(new Projectile('cluster', spawnX, spawnY, vx, vy, {
      damage: selectedWeapon.damage, blastRadius: selectedWeapon.radius, fuse: selectedWeapon.fuse, bounce: true, clusters: 5
    }));
  } else if (selectedWeapon.id === 'holy') {
    projectiles.push(new Projectile('holy', spawnX, spawnY, vx, vy, {
      damage: selectedWeapon.damage, blastRadius: selectedWeapon.radius, fuse: selectedWeapon.fuse, bounce: true, holy: true
    }));
  } else if (selectedWeapon.id === 'shotgun' || selectedWeapon.id === 'sniper') {
    // Direct Raycast Shot
    sound.playExplosion(0.5);
    let rx = spawnX;
    let ry = spawnY;
    let rvx = Math.cos(aimAngle) * selectedWeapon.speed;
    let rvy = Math.sin(aimAngle) * selectedWeapon.speed;
    const dt = 0.02;

    for (let step = 0; step < 100; step++) {
      rx += rvx * dt;
      ry += rvy * dt;
      particles.push({ x: rx, y: ry, vx: 0, vy: 0, radius: 2, color: '#fef08a', life: 0.2, maxLife: 0.2 });

      if (isTerrainSolid(rx, ry)) {
        carveTerrain(rx, ry, selectedWeapon.radius);
        break;
      }

      // Check worm hit
      let hit = false;
      teams.forEach(team => {
        team.worms.forEach(worm => {
          if (!worm.isDead && Math.hypot(rx - worm.x, ry - worm.y) < worm.radius + 6) {
            worm.takeDamage(selectedWeapon.damage);
            worm.vx += Math.cos(aimAngle) * 300;
            worm.vy += Math.sin(aimAngle) * 300 - 50;
            hit = true;
          }
        });
      });
      if (hit) break;
    }
  } else if (selectedWeapon.id === 'dynamite') {
    projectiles.push(new Projectile('dynamite', spawnX, spawnY, vx * 0.2, vy * 0.2, {
      damage: selectedWeapon.damage, blastRadius: selectedWeapon.radius, fuse: selectedWeapon.fuse, bounce: true
    }));
  } else if (selectedWeapon.id === 'drill') {
    projectiles.push(new Projectile('drill', spawnX, spawnY, vx, vy, {
      damage: selectedWeapon.damage, blastRadius: selectedWeapon.radius, drill: true, fuse: 3
    }));
  }

  consumeWeaponAmmo();
  endTurnDelayed(3.0);
}

function triggerAirStrike(targetX) {
  sound.playLaunch();
  addCombatText(targetX, 100, 'INCOMING AIR STRIKE!', '#ef4444');
  for (let i = 0; i < 5; i++) {
    setTimeout(() => {
      let offsetX = (i - 2) * 35;
      projectiles.push(new Projectile('airstrike', targetX + offsetX, 50, 0, 450, {
        damage: 40, blastRadius: 40
      }));
    }, i * 200);
  }
}

function consumeWeaponAmmo() {
  if (selectedWeapon.ammo > 0) {
    selectedWeapon.ammo--;
  }
  updateHUD();
}

// Turn Manager & Game Loop Control
function getActiveWorm() {
  if (!teams[activeTeamIdx]) return null;
  return teams[activeTeamIdx].worms[activeWormIdx];
}

function startNextTurn() {
  // Advance team & worm
  activeTeamIdx = (activeTeamIdx + 1) % teams.length;
  let attempts = 0;
  while (teams[activeTeamIdx].worms.every(w => w.isDead) && attempts < teams.length) {
    activeTeamIdx = (activeTeamIdx + 1) % teams.length;
    attempts++;
  }

  // Check Game Over
  const aliveTeams = teams.filter(t => t.worms.some(w => !w.isDead));
  if (aliveTeams.length <= 1) {
    endGame(aliveTeams[0]);
    return;
  }

  // Find next alive worm in team
  const team = teams[activeTeamIdx];
  do {
    activeWormIdx = (activeWormIdx + 1) % team.worms.length;
  } while (team.worms[activeWormIdx].isDead);

  const activeWorm = getActiveWorm();

  // Reset Turn Variables
  turnTimer = 45;
  currentWind = (Math.random() - 0.5) * 200; // New wind value
  document.getElementById('wind-bar').style.width = `${Math.abs(currentWind) / 2}%`;
  document.getElementById('wind-bar').style.left = currentWind >= 0 ? '50%' : `${50 - Math.abs(currentWind) / 2}%`;

  // Focus Camera
  camera.targetX = activeWorm.x;
  camera.targetY = activeWorm.y;

  // Banner Announcement
  showBanner(`${team.name}'s Turn`);

  // CPU Turn Execution
  if (activeWorm.isCPU) {
    executeCPUTurn(activeWorm);
  }

  updateHUD();
}

function endTurnDelayed(delaySec) {
  setTimeout(() => {
    // Wait until all projectiles settled
    const checkInterval = setInterval(() => {
      if (projectiles.length === 0 && debrisChunks.length === 0) {
        clearInterval(checkInterval);
        startNextTurn();
      }
    }, 200);
  }, delaySec * 1000);
}

function showBanner(text) {
  const banner = document.getElementById('announcer-banner');
  banner.innerText = text;
  banner.classList.add('show');
  setTimeout(() => banner.classList.remove('show'), 2000);
}

// Risk-Assessment CPU AI Controller
function executeCPUTurn(cpuWorm) {
  setTimeout(() => {
    if (gameState !== 'PLAYING' || cpuWorm.isDead) return;

    // 1. Find nearest enemy worm
    let targetWorm = null;
    let minDist = Infinity;

    teams.forEach((t, idx) => {
      if (idx !== activeTeamIdx) {
        t.worms.forEach(w => {
          if (!w.isDead) {
            let d = Math.hypot(w.x - cpuWorm.x, w.y - cpuWorm.y);
            if (d < minDist) {
              minDist = d;
              targetWorm = w;
            }
          }
        });
      }
    });

    if (!targetWorm) return;

    // 2. Risk Assessment: Avoid falling in water or off cliffs
    if (cpuWorm.x < 150 || cpuWorm.x > WORLD_WIDTH - 150) {
      // Walk away from water edge
      cpuWorm.vx = cpuWorm.x < 150 ? 80 : -80;
    }

    // 3. Aim Calculation with Wind & Gravity Iterative Simulation
    const dx = targetWorm.x - cpuWorm.x;
    const dy = targetWorm.y - cpuWorm.y;

    let testAngle = Math.atan2(dy, dx);
    let testPower = Math.min(750, Math.max(350, minDist * 0.9));

    aimAngle = testAngle;
    cpuWorm.facing = dx >= 0 ? 1 : -1;

    // 4. Select Weapon
    if (minDist < 120) {
      selectedWeapon = WEAPONS.find(w => w.id === 'shotgun') || WEAPONS[0];
    } else if (Math.random() < 0.3) {
      selectedWeapon = WEAPONS.find(w => w.id === 'airstrike') || WEAPONS[0];
    } else {
      selectedWeapon = WEAPONS.find(w => w.id === 'bazooka') || WEAPONS[0];
    }
    updateHUD();

    // 5. Fire after realistic delay
    setTimeout(() => {
      if (selectedWeapon.strike) {
        triggerAirStrike(targetWorm.x);
        consumeWeaponAmmo();
        endTurnDelayed(3.0);
      } else {
        fireWeapon(testPower);
      }
    }, 1500);

  }, 1000);
}

// HUD & Menu Management
function updateHUD() {
  // Update Team Health Bars
  const teamBarsContainer = document.getElementById('team-bars-container');
  teamBarsContainer.innerHTML = '';

  teams.forEach(t => {
    const totalHP = t.worms.reduce((sum, w) => sum + Math.max(0, w.hp), 0);
    const maxHP = t.worms.length * 100;
    const pct = (totalHP / maxHP) * 100;

    const row = document.createElement('div');
    row.className = 'team-status-row';
    row.innerHTML = `
      <span class="team-color-badge" style="background: ${t.color}; color: ${t.color}"></span>
      <span style="width: 100px;">${t.name}</span>
      <div class="team-health-bar-bg">
        <div class="team-health-bar-fill" style="width: ${pct}%; background: ${t.color}"></div>
      </div>
      <span style="font-size: 11px; font-family: monospace;">${Math.floor(totalHP)}</span>
    `;
    teamBarsContainer.appendChild(row);
  });

  // Weapon Indicator
  document.getElementById('hud-weapon-icon').innerText = selectedWeapon.icon;
  document.getElementById('hud-weapon-name').innerText = selectedWeapon.name;
  document.getElementById('hud-weapon-ammo').innerText = selectedWeapon.ammo < 0 ? 'Ammo: Infinite' : `Ammo: ${selectedWeapon.ammo}`;
}

function buildWeaponGrid() {
  const container = document.getElementById('weapon-grid-container');
  container.innerHTML = '';

  const categories = ['Indirect', 'Direct', 'AoE / Special', 'Utility'];

  categories.forEach(cat => {
    const catTitle = document.createElement('div');
    catTitle.className = 'weapon-category-title';
    catTitle.innerText = cat;
    container.appendChild(catTitle);

    const grid = document.createElement('div');
    grid.className = 'weapon-grid';

    WEAPONS.filter(w => w.category === cat).forEach(w => {
      const btn = document.createElement('div');
      btn.className = `weapon-btn ${w.id === selectedWeapon.id ? 'selected' : ''}`;
      btn.onclick = () => {
        selectedWeapon = w;
        sound.playClick();
        toggleWeaponMenu();
        updateHUD();
      };

      btn.innerHTML = `
        <div class="weapon-btn-icon">${w.icon}</div>
        <div class="weapon-btn-info">
          <div class="weapon-btn-title">${w.name}</div>
          <div class="weapon-btn-count">${w.ammo < 0 ? 'Infinite' : `x${w.ammo}`}</div>
        </div>
      `;
      grid.appendChild(btn);
    });

    container.appendChild(grid);
  });
}

function toggleWeaponMenu() {
  const menu = document.getElementById('weapon-menu');
  if (menu.classList.contains('active')) {
    menu.classList.remove('active');
  } else {
    buildWeaponGrid();
    menu.classList.add('active');
  }
}

// Game Setup & Start
function startGameFromLobby() {
  sound.init();
  sound.playClick();

  // Create Teams & Worms
  teams = [];
  teamConfig.forEach((cfg, idx) => {
    if (cfg.type !== 'off') {
      let tDef = TEAMS_DEF[idx];
      let worms = [];
      for (let w = 1; w <= 4; w++) {
        let worm = new Worm(idx, w, `${tDef.name.split(' ')[0]} ${w}`, 0, 0);
        worm.isCPU = cfg.type === 'cpu';
        worms.push(worm);
      }
      teams.push({ ...tDef, worms, isCPU: cfg.type === 'cpu' });
    }
  });

  if (teams.length < 2) {
    alert('Please enable at least 2 teams to play!');
    return;
  }

  // Generate Terrain
  generateTerrain(selectedTerrainType);

  // Position Worms evenly on terrain surface
  const spacing = (WORLD_WIDTH - 300) / (teams.length * 4);
  let currentX = 150;

  teams.forEach(team => {
    team.worms.forEach(worm => {
      worm.x = currentX;
      // Raycast top down to find ground
      for (let y = 50; y < WORLD_HEIGHT - 100; y += 4) {
        if (isTerrainSolid(currentX, y)) {
          worm.y = y - 12;
          break;
        }
      }
      currentX += spacing;
    });
  });

  document.getElementById('setup-modal').style.display = 'none';
  document.getElementById('gameover-modal').style.display = 'none';

  gameState = 'PLAYING';
  activeTeamIdx = -1;
  activeWormIdx = 0;

  startNextTurn();

  // Turn Timer Loop
  if (turnTimerInterval) clearInterval(turnTimerInterval);
  turnTimerInterval = setInterval(() => {
    if (gameState === 'PLAYING' && turnTimer > 0) {
      turnTimer--;
      document.getElementById('timer-text').innerText = turnTimer;

      const timerBox = document.getElementById('timer-box');
      if (turnTimer <= 10) {
        timerBox.classList.add('urgent');
        sound.playTimerTick(true);
      } else {
        timerBox.classList.remove('urgent');
        sound.playTimerTick(false);
      }

      if (turnTimer === 0) {
        addCombatText(getActiveWorm().x, getActiveWorm().y - 30, 'TIME OUT!', '#ef4444');
        endTurnDelayed(1.0);
      }
    }
  }, 1000);
}

function endGame(winningTeam) {
  gameState = 'GAMEOVER';
  if (turnTimerInterval) clearInterval(turnTimerInterval);

  const title = document.getElementById('winner-title');
  const subtitle = document.getElementById('winner-subtitle');
  const statsBox = document.getElementById('match-stats');

  if (winningTeam) {
    title.innerText = `🏆 ${winningTeam.name.toUpperCase()} WINS!`;
    title.style.color = winningTeam.color;
    subtitle.innerText = 'Total Tactical Domination Achieved!';
  } else {
    title.innerText = 'DRAW MATCH!';
    subtitle.innerText = 'Everyone was obliterated!';
  }

  statsBox.innerHTML = `
    <div><strong>Terrain Type:</strong> ${selectedTerrainType.toUpperCase()}</div>
    <div><strong>Active Teams:</strong> ${teams.length}</div>
    <div><strong>Total Casualties:</strong> ${teams.length * 4 - (winningTeam ? winningTeam.worms.filter(w=>!w.isDead).length : 0)}</div>
  `;

  document.getElementById('gameover-modal').style.display = 'flex';
}

function returnToLobby() {
  document.getElementById('gameover-modal').style.display = 'none';
  document.getElementById('setup-modal').style.display = 'flex';
  gameState = 'LOBBY';
}

// Main Render & Physics Update Loop
let lastTime = performance.now();

function gameLoop(now) {
  const dt = Math.min((now - lastTime) / 1000, 0.05); // cap delta time
  lastTime = now;

  // Handle Impact Freeze frame delay
  if (impactFreezeFrames > 0) {
    impactFreezeFrames--;
    requestAnimationFrame(gameLoop);
    return;
  }

  if (gameState === 'PLAYING') {
    const activeWorm = getActiveWorm();

    // Human Controls Input Processing
    if (activeWorm && !activeWorm.isCPU && turnTimer > 0) {
      if (keys['KeyA'] || keys['ArrowLeft']) {
        activeWorm.vx = -100;
        activeWorm.facing = -1;
      }
      if (keys['KeyD'] || keys['ArrowRight']) {
        activeWorm.vx = 100;
        activeWorm.facing = 1;
      }
      if (keys['KeyW'] || keys['ArrowUp']) {
        aimAngle = Math.max(-Math.PI + 0.1, aimAngle - dt * 1.8);
      }
      if (keys['KeyS'] || keys['ArrowDown']) {
        aimAngle = Math.min(-0.1, aimAngle + dt * 1.8);
      }
      if (keys['KeyE']) {
        activeWorm.jump(-240, activeWorm.facing * 80);
        keys['KeyE'] = false;
      }
      if (keys['KeyQ']) {
        activeWorm.jump(-280, -activeWorm.facing * 100); // Backflip
        keys['KeyQ'] = false;
      }
      if (keys['Space']) {
        if (!selectedWeapon.utility && !selectedWeapon.strike) {
          isCharging = true;
          chargePower = Math.min(800, chargePower + dt * 500);
        }
      }
    }

    // Update Worms Physics
    teams.forEach(team => {
      team.worms.forEach(worm => worm.update(dt));
    });

    // Update Projectiles
    for (let i = projectiles.length - 1; i >= 0; i--) {
      projectiles[i].update(dt);
      if (projectiles[i].isDead) projectiles.splice(i, 1);
    }

    // Update Systems
    updateParticles(dt);
    updateCombatTexts(dt);
    updateDebrisChunks(dt);

    // Smooth Camera Tracking (Follow active projectile or active worm)
    let targetObj = projectiles.length > 0 ? projectiles[0] : activeWorm;
    if (targetObj) {
      camera.targetX = targetObj.x;
      camera.targetY = targetObj.y;
    }

    camera.x += (camera.targetX - camera.x) * dt * 4;
    camera.y += (camera.targetY - camera.y) * dt * 4;

    // Dampen screen shake
    if (camera.shake > 0) {
      camera.shake = Math.max(0, camera.shake - dt * 25);
    }
  }

  // RENDER CANVAS
  ctx.save();
  ctx.clearRect(0, 0, WORLD_WIDTH, WORLD_HEIGHT);

  // Apply Camera Transform & Screen Shake
  const shakeX = (Math.random() - 0.5) * camera.shake;
  const shakeY = (Math.random() - 0.5) * camera.shake;

  ctx.translate(canvas.width / 2 + shakeX, canvas.height / 2 + shakeY);
  ctx.scale(camera.zoom, camera.zoom);
  ctx.translate(-camera.x, -camera.y);

  // Sky Gradient Background
  const skyGrad = ctx.createLinearGradient(0, 0, 0, WORLD_HEIGHT);
  skyGrad.addColorStop(0, '#0f172a');
  skyGrad.addColorStop(0.6, '#1e293b');
  skyGrad.addColorStop(1, '#0284c7');
  ctx.fillStyle = skyGrad;
  ctx.fillRect(camera.x - WORLD_WIDTH, camera.y - WORLD_HEIGHT, WORLD_WIDTH * 2, WORLD_HEIGHT * 2);

  // Draw Terrain Bitmap Mask
  ctx.drawImage(terrainCanvas, 0, 0);

  // Draw Water Layer
  ctx.fillStyle = 'rgba(14, 165, 233, 0.65)';
  ctx.beginPath();
  ctx.moveTo(0, WATER_LEVEL);
  for (let x = 0; x <= WORLD_WIDTH; x += 30) {
    let waveY = WATER_LEVEL + Math.sin(now * 0.003 + x * 0.01) * 6;
    ctx.lineTo(x, waveY);
  }
  ctx.lineTo(WORLD_WIDTH, WORLD_HEIGHT);
  ctx.lineTo(0, WORLD_HEIGHT);
  ctx.fill();

  // Draw Debris Chunks
  drawDebrisChunks(ctx);

  // Draw Worms
  teams.forEach(team => {
    team.worms.forEach(worm => worm.draw(ctx));
  });

  // Draw Projectiles
  projectiles.forEach(p => p.draw(ctx));

  // Draw Particles & Combat Text
  drawParticles(ctx);
  drawCombatTexts(ctx);

  ctx.restore();

  requestAnimationFrame(gameLoop);
}

// Start Game Loop Engine
requestAnimationFrame(gameLoop);

</script>
</body>
</html>
<!-- agent-meta {"model":"google/gemini-3.6-flash","provider":"openrouter","persona":"full","sessionId":"eeedc013-05c6-4fdf-8efb-6fd60cec0767","tokensIn":149534,"tokensOut":26845,"tokensTotal":176379,"turns":6,"toolCalls":5,"failedToolCalls":0,"timestamp":"2026-07-22T14:05:22.220Z"} -->