← Worms results

Worms

Gemini 3.6 Flash · basic

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 76,058 bytes · SHA-256 8ad451ef509e
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Worm Chaos - Artillery Mayhem</title>
    <link href="https://fonts.googleapis.com/css2?family=Fredoka:wght@400;600;700&display=swap" rel="stylesheet">
    <style>
        * {
            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: #0b0e14;
            font-family: 'Fredoka', cursive, sans-serif;
            color: #fff;
        }

        #game-container {
            position: relative;
            width: 100vw;
            height: 100vh;
            display: flex;
            justify-content: center;
            align-items: center;
            background: #111827;
        }

        canvas {
            display: block;
            width: 100%;
            height: 100%;
            background: #000;
        }

        /* UI OVERLAYS */
        .ui-layer {
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            pointer-events: none;
            display: flex;
            flex-direction: column;
            justify-content: space-between;
            padding: 20px;
            z-index: 10;
        }

        /* TOP HUD */
        .top-bar {
            display: flex;
            justify-content: space-between;
            align-items: flex-start;
            width: 100%;
        }

        .teams-summary {
            display: flex;
            gap: 15px;
            background: rgba(15, 23, 42, 0.75);
            padding: 12px 20px;
            border-radius: 16px;
            backdrop-filter: blur(8px);
            border: 2px solid rgba(255, 255, 255, 0.1);
            box-shadow: 0 10px 25px rgba(0,0,0,0.5);
        }

        .team-card {
            display: flex;
            flex-direction: column;
            gap: 4px;
            min-width: 110px;
        }

        .team-card .team-name {
            font-size: 14px;
            font-weight: 700;
            text-shadow: 0 2px 4px rgba(0,0,0,0.8);
            display: flex;
            align-items: center;
            gap: 6px;
        }

        .team-card .hp-bar-bg {
            width: 100%;
            height: 12px;
            background: rgba(0, 0, 0, 0.6);
            border-radius: 6px;
            overflow: hidden;
            border: 1px solid rgba(255, 255, 255, 0.15);
        }

        .team-card .hp-bar-fill {
            height: 100%;
            width: 100%;
            transition: width 0.3s ease-out, background-color 0.3s;
            border-radius: 6px;
        }

        /* TIMER & WIND GAUGE */
        .center-hud {
            display: flex;
            flex-direction: column;
            align-items: center;
            gap: 8px;
        }

        .timer-box {
            background: radial-gradient(circle, #1e293b 0%, #0f172a 100%);
            border: 3px solid #38bdf8;
            border-radius: 50%;
            width: 76px;
            height: 76px;
            display: flex;
            justify-content: center;
            align-items: center;
            font-size: 32px;
            font-weight: 700;
            color: #fff;
            box-shadow: 0 0 20px rgba(56, 189, 248, 0.4), inset 0 0 10px rgba(0,0,0,0.8);
            text-shadow: 0 2px 4px rgba(0,0,0,0.8);
            transition: transform 0.2s cubic-bezier(0.175, 0.885, 0.32, 1.275);
        }

        .timer-box.urgent {
            border-color: #ef4444;
            color: #ef4444;
            box-shadow: 0 0 25px rgba(239, 68, 68, 0.7);
            animation: pulse-urgent 0.5s infinite alternate;
        }

        @keyframes pulse-urgent {
            0% { transform: scale(1); }
            100% { transform: scale(1.15); }
        }

        .wind-gauge {
            background: rgba(15, 23, 42, 0.85);
            border: 2px solid rgba(255,255,255,0.15);
            border-radius: 20px;
            padding: 6px 16px;
            display: flex;
            align-items: center;
            gap: 10px;
            backdrop-filter: blur(8px);
            box-shadow: 0 4px 12px rgba(0,0,0,0.3);
        }

        .wind-arrow {
            font-size: 18px;
            font-weight: bold;
            transition: transform 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
        }

        .wind-text {
            font-size: 13px;
            font-weight: 600;
            letter-spacing: 0.5px;
            color: #94a3b8;
        }

        /* BOTTOM HUD */
        .bottom-bar {
            display: flex;
            justify-content: space-between;
            align-items: flex-end;
            width: 100%;
        }

        .weapon-btn {
            pointer-events: auto;
            background: linear-gradient(135deg, #3b82f6 0%, #1d4ed8 100%);
            border: 3px solid #60a5fa;
            border-radius: 16px;
            padding: 12px 24px;
            color: white;
            font-size: 18px;
            font-weight: 700;
            cursor: pointer;
            box-shadow: 0 8px 20px rgba(0,0,0,0.4), 0 0 15px rgba(59, 130, 246, 0.4);
            display: flex;
            align-items: center;
            gap: 10px;
            transition: all 0.15s ease;
        }

        .weapon-btn:hover {
            transform: translateY(-3px) scale(1.03);
            box-shadow: 0 12px 25px rgba(0,0,0,0.5), 0 0 20px rgba(59, 130, 246, 0.6);
            background: linear-gradient(135deg, #60a5fa 0%, #2563eb 100%);
        }

        .weapon-btn:active {
            transform: translateY(1px) scale(0.98);
        }

        .status-msg {
            font-size: 22px;
            font-weight: 700;
            text-shadow: 0 3px 6px rgba(0,0,0,0.9), 0 0 10px rgba(0,0,0,0.5);
            background: rgba(15, 23, 42, 0.7);
            padding: 8px 20px;
            border-radius: 30px;
            border: 1px solid rgba(255,255,255,0.1);
            backdrop-filter: blur(6px);
            color: #f1f5f9;
        }

        /* WEAPON SELECTION MODAL */
        .weapon-menu {
            position: absolute;
            bottom: 90px;
            left: 20px;
            background: rgba(15, 23, 42, 0.95);
            border: 2px solid #38bdf8;
            border-radius: 20px;
            padding: 16px;
            display: grid;
            grid-template-columns: repeat(4, 1fr);
            gap: 10px;
            backdrop-filter: blur(12px);
            box-shadow: 0 15px 35px rgba(0,0,0,0.6);
            pointer-events: auto;
            transform: translateY(20px) scale(0.95);
            opacity: 0;
            visibility: hidden;
            transition: all 0.2s cubic-bezier(0.175, 0.885, 0.32, 1.275);
            z-index: 20;
        }

        .weapon-menu.open {
            opacity: 1;
            visibility: visible;
            transform: translateY(0) scale(1);
        }

        .weapon-item {
            background: rgba(30, 41, 59, 0.8);
            border: 2px solid rgba(255, 255, 255, 0.1);
            border-radius: 12px;
            padding: 10px;
            display: flex;
            flex-direction: column;
            align-items: center;
            gap: 6px;
            cursor: pointer;
            transition: all 0.15s ease;
            width: 85px;
        }

        .weapon-item:hover {
            background: rgba(51, 65, 85, 1);
            border-color: #38bdf8;
            transform: translateY(-2px);
        }

        .weapon-item.active {
            border-color: #f59e0b;
            background: rgba(245, 158, 11, 0.2);
        }

        .weapon-icon {
            font-size: 26px;
        }

        .weapon-name {
            font-size: 11px;
            font-weight: 600;
            text-align: center;
            color: #e2e8f0;
        }

        .weapon-cat {
            font-size: 8px;
            text-transform: uppercase;
            padding: 2px 4px;
            border-radius: 4px;
            font-weight: 700;
        }

        .cat-indirect { background: #3b82f6; color: #fff; }
        .cat-direct { background: #ef4444; color: #fff; }
        .cat-aoe { background: #8b5cf6; color: #fff; }
        .cat-utility { background: #10b981; color: #fff; }

        /* START MENU OVERLAY */
        .modal-overlay {
            position: absolute;
            inset: 0;
            background: rgba(11, 15, 20, 0.85);
            backdrop-filter: blur(10px);
            display: flex;
            justify-content: center;
            align-items: center;
            z-index: 100;
            pointer-events: auto;
        }

        .menu-card {
            background: linear-gradient(145deg, #1e293b, #0f172a);
            border: 3px solid #38bdf8;
            border-radius: 24px;
            padding: 35px 45px;
            width: 520px;
            max-width: 90vw;
            box-shadow: 0 20px 50px rgba(0,0,0,0.7), 0 0 30px rgba(56, 189, 248, 0.3);
            display: flex;
            flex-direction: column;
            gap: 20px;
            text-align: center;
        }

        .menu-card h1 {
            font-size: 42px;
            font-weight: 700;
            background: linear-gradient(to right, #38bdf8, #f43f5e, #f59e0b);
            -webkit-background-clip: text;
            -webkit-text-fill-color: transparent;
            text-shadow: 0 4px 10px rgba(0,0,0,0.5);
            letter-spacing: 1px;
        }

        .menu-option {
            display: flex;
            flex-direction: column;
            gap: 8px;
            text-align: left;
        }

        .menu-option label {
            font-size: 14px;
            font-weight: 600;
            color: #94a3b8;
            text-transform: uppercase;
            letter-spacing: 0.5px;
        }

        .btn-group {
            display: flex;
            gap: 10px;
        }

        .opt-btn {
            flex: 1;
            padding: 10px 14px;
            background: rgba(30, 41, 59, 0.8);
            border: 2px solid rgba(255,255,255,0.1);
            border-radius: 12px;
            color: #cbd5e1;
            font-family: inherit;
            font-size: 14px;
            font-weight: 600;
            cursor: pointer;
            transition: all 0.15s ease;
        }

        .opt-btn:hover {
            border-color: #38bdf8;
            color: #fff;
        }

        .opt-btn.selected {
            background: #38bdf8;
            color: #0f172a;
            border-color: #38bdf8;
            font-weight: 700;
        }

        .start-btn {
            margin-top: 10px;
            padding: 16px;
            background: linear-gradient(135deg, #10b981 0%, #059669 100%);
            border: 2px solid #34d399;
            border-radius: 16px;
            color: white;
            font-size: 22px;
            font-weight: 700;
            cursor: pointer;
            box-shadow: 0 8px 20px rgba(16, 185, 129, 0.4);
            transition: all 0.2s ease;
        }

        .start-btn:hover {
            transform: translateY(-2px);
            box-shadow: 0 12px 25px rgba(16, 185, 129, 0.6);
            background: linear-gradient(135deg, #34d399 0%, #10b981 100%);
        }

        /* CONTROLS GUIDE */
        .controls-hint {
            font-size: 12px;
            color: #64748b;
            line-height: 1.5;
            background: rgba(0,0,0,0.2);
            padding: 10px;
            border-radius: 8px;
        }
    </style>
</head>
<body>
    <div id="game-container">
        <canvas id="gameCanvas"></canvas>

        <!-- UI LAYER -->
        <div class="ui-layer">
            <div class="top-bar">
                <div class="teams-summary" id="teamsSummary">
                    <!-- Dynamic Team Cards -->
                </div>

                <div class="center-hud">
                    <div class="timer-box" id="turnTimer">45</div>
                    <div class="wind-gauge">
                        <span class="wind-text">WIND</span>
                        <span class="wind-arrow" id="windArrow">➔</span>
                        <span id="windValue" style="font-size: 14px; font-weight: 700;">0</span>
                    </div>
                </div>
            </div>

            <div class="bottom-bar">
                <button class="weapon-btn" id="weaponBtn">
                    <span id="currentWpnIcon">🚀</span>
                    <span id="currentWpnName">Bazooka</span>
                </button>

                <div class="status-msg" id="statusMsg">Team Red's Turn</div>
            </div>

            <!-- WEAPON SELECTOR MENU -->
            <div class="weapon-menu" id="weaponMenu">
                <!-- Dynamic Weapons -->
            </div>
        </div>

        <!-- START / GAME OVER MENU -->
        <div class="modal-overlay" id="menuModal">
            <div class="menu-card">
                <h1 id="menuTitle">WORM CHAOS</h1>
                
                <div class="menu-option">
                    <label>Teams & Players</label>
                    <div class="btn-group" id="teamModeGroup">
                        <button class="opt-btn selected" data-val="2">2 Teams (1v1)</button>
                        <button class="opt-btn" data-val="3">3 Teams</button>
                        <button class="opt-btn" data-val="4">4 Teams</button>
                    </div>
                </div>

                <div class="menu-option">
                    <label>Opponents</label>
                    <div class="btn-group" id="aiModeGroup">
                        <button class="opt-btn selected" data-val="pvp">Human Hot-Seat</button>
                        <button class="opt-btn" data-val="cpu">VS CPU AI</button>
                    </div>
                </div>

                <div class="menu-option">
                    <label>Map Terrain Shape</label>
                    <div class="btn-group" id="mapShapeGroup">
                        <button class="opt-btn selected" data-val="single">Single Island</button>
                        <button class="opt-btn" data-val="double">Two Islands</button>
                        <button class="opt-btn" data-val="cave">Cavern</button>
                    </div>
                </div>

                <div class="controls-hint">
                    <strong>Controls:</strong> [A/D] Walk | [W] Aim Up / Jump | [S] Aim Down | [Space] Charge & Fire | [BackSpace] Backflip | [1-9 / Q] Quick Weapons
                </div>

                <button class="start-btn" id="startGameBtn">LET'S BATTLE!</button>
            </div>
        </div>
    </div>

    <script>
        /* --- AUDIO SYNTHESIZER ENGINE --- */
        class SoundEngine {
            constructor() {
                this.ctx = null;
                this.muted = false;
            }

            init() {
                if (!this.ctx) {
                    const AudioCtx = window.AudioContext || window.webkitAudioContext;
                    this.ctx = new AudioCtx();
                }
                if (this.ctx.state === 'suspended') {
                    this.ctx.resume();
                }
            }

            playJump() {
                if (!this.ctx) return;
                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.exponentialRampToValueAtTime(0.01, this.ctx.currentTime + 0.15);
                osc.connect(gain);
                gain.connect(this.ctx.destination);
                osc.start();
                osc.stop(this.ctx.currentTime + 0.15);
            }

            playExplosion(size = 1) {
                if (!this.ctx) return;
                const dur = 0.4 * size;
                const bufferSize = this.ctx.sampleRate * dur;
                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(800 * size, this.ctx.currentTime);
                filter.frequency.exponentialRampToValueAtTime(30, this.ctx.currentTime + dur);

                const gain = this.ctx.createGain();
                gain.gain.setValueAtTime(0.8, this.ctx.currentTime);
                gain.gain.exponentialRampToValueAtTime(0.01, this.ctx.currentTime + dur);

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

            playShoot() {
                if (!this.ctx) return;
                const osc = this.ctx.createOscillator();
                const gain = this.ctx.createGain();
                osc.type = 'triangle';
                osc.frequency.setValueAtTime(600, this.ctx.currentTime);
                osc.frequency.exponentialRampToValueAtTime(80, this.ctx.currentTime + 0.2);
                gain.gain.setValueAtTime(0.4, this.ctx.currentTime);
                gain.gain.exponentialRampToValueAtTime(0.01, this.ctx.currentTime + 0.2);
                osc.connect(gain);
                gain.connect(this.ctx.destination);
                osc.start();
                osc.stop(this.ctx.currentTime + 0.2);
            }

            playTimerTick(isUrgent) {
                if (!this.ctx) return;
                const osc = this.ctx.createOscillator();
                const gain = this.ctx.createGain();
                osc.type = 'sine';
                const freq = isUrgent ? 880 : 440;
                osc.frequency.setValueAtTime(freq, this.ctx.currentTime);
                gain.gain.setValueAtTime(0.15, this.ctx.currentTime);
                gain.gain.exponentialRampToValueAtTime(0.001, this.ctx.currentTime + 0.08);
                osc.connect(gain);
                gain.connect(this.ctx.destination);
                osc.start();
                osc.stop(this.ctx.currentTime + 0.08);
            }

            playSplash() {
                if (!this.ctx) return;
                const dur = 0.3;
                const bufferSize = this.ctx.sampleRate * dur;
                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(1200, 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 + dur);
                noise.connect(filter);
                filter.connect(gain);
                gain.connect(this.ctx.destination);
                noise.start();
            }

            playSqueak(pitch = 1) {
                if (!this.ctx) return;
                const osc = this.ctx.createOscillator();
                const gain = this.ctx.createGain();
                osc.type = 'sine';
                osc.frequency.setValueAtTime(600 * pitch, this.ctx.currentTime);
                osc.frequency.linearRampToValueAtTime(1200 * pitch, this.ctx.currentTime + 0.08);
                osc.frequency.linearRampToValueAtTime(800 * pitch, this.ctx.currentTime + 0.15);
                gain.gain.setValueAtTime(0.2, this.ctx.currentTime);
                gain.gain.exponentialRampToValueAtTime(0.01, this.ctx.currentTime + 0.15);
                osc.connect(gain);
                gain.connect(this.ctx.destination);
                osc.start();
                osc.stop(this.ctx.currentTime + 0.15);
            }

            playChoir() {
                if (!this.ctx) return;
                const freqs = [261.63, 329.63, 392.00, 523.25]; // C major chord
                freqs.forEach(f => {
                    const osc = this.ctx.createOscillator();
                    const gain = this.ctx.createGain();
                    osc.type = 'sine';
                    osc.frequency.setValueAtTime(f, this.ctx.currentTime);
                    gain.gain.setValueAtTime(0.08, this.ctx.currentTime);
                    gain.gain.exponentialRampToValueAtTime(0.001, this.ctx.currentTime + 1.2);
                    osc.connect(gain);
                    gain.connect(this.ctx.destination);
                    osc.start();
                    osc.stop(this.ctx.currentTime + 1.2);
                });
            }

            playClick() {
                if (!this.ctx) return;
                const osc = this.ctx.createOscillator();
                const gain = this.ctx.createGain();
                osc.type = 'sine';
                osc.frequency.setValueAtTime(800, this.ctx.currentTime);
                gain.gain.setValueAtTime(0.1, this.ctx.currentTime);
                gain.gain.exponentialRampToValueAtTime(0.001, this.ctx.currentTime + 0.04);
                osc.connect(gain);
                gain.connect(this.ctx.destination);
                osc.start();
                osc.stop(this.ctx.currentTime + 0.04);
            }
        }

        const SFX = new SoundEngine();

        /* --- WEAPONS DEFINITIONS --- */
        const WEAPONS = [
            { id: 'bazooka', name: 'Bazooka', icon: '🚀', category: 'indirect', type: 'projectile', gravity: 0.25, windFactor: 1.0, bounce: 0, radius: 32, damage: 45, fuse: 0, desc: 'Classic wind-affected missile' },
            { id: 'grenade', name: 'Grenade', icon: '💣', category: 'indirect', type: 'projectile', gravity: 0.28, windFactor: 0.2, bounce: 0.6, radius: 36, damage: 50, fuse: 3, desc: 'Bouncy 3-second fuse explosive' },
            { id: 'holyhand', name: 'Holy Grenade', icon: '✝️', category: 'aoe', type: 'projectile', gravity: 0.22, windFactor: 0.1, bounce: 0.3, radius: 75, damage: 90, fuse: 3.5, choir: true, desc: 'Devastating high-AoE divine bomb' },
            { id: 'cluster', name: 'Cluster Bomb', icon: '💥', category: 'indirect', type: 'projectile', gravity: 0.28, windFactor: 0.3, bounce: 0.4, radius: 28, damage: 25, fuse: 2.5, clusters: 5, desc: 'Splits into multiple bomblets' },
            { id: 'shotgun', name: 'Shotgun', icon: '🔫', category: 'direct', type: 'instant', radius: 18, damage: 28, shots: 2, knockback: 8, desc: 'Direct hit, 2 shots per turn' },
            { id: 'sniper', name: 'Sniper Rifle', icon: '🎯', category: 'direct', type: 'instant', radius: 12, damage: 60, shots: 1, knockback: 14, desc: 'High accuracy direct laser beam' },
            { id: 'airstrike', name: 'Air Strike', icon: '✈️', category: 'aoe', type: 'target', radius: 30, damage: 35, bombs: 5, desc: 'Call in missiles from above' },
            { id: 'dynamite', name: 'Dynamite', icon: '🧨', category: 'aoe', type: 'drop', radius: 65, damage: 75, fuse: 4, desc: 'Heavy drop explosive with HUGE crater' },
            { id: 'jetpack', name: 'Jetpack', icon: '🎒', category: 'utility', type: 'utility', fuel: 100, desc: 'Fly freely through the air' },
            { id: 'teleport', name: 'Teleport', icon: '🌀', category: 'utility', type: 'utility', desc: 'Instantly move anywhere on map' },
            { id: 'girder', name: 'Girder Bridge', icon: '0️⃣', category: 'utility', type: 'utility', desc: 'Place a custom bridge platform' }
        ];

        const TEAMS_CONFIG = [
            { id: 0, name: 'Red Rogues', color: '#ef4444', darkColor: '#991b1b' },
            { id: 1, name: 'Blue Bazookas', color: '#3b82f6', darkColor: '#1e40af' },
            { id: 2, name: 'Green Grenadiers', color: '#10b981', darkColor: '#065f46' },
            { id: 3, name: 'Yellow Yahoos', color: '#f59e0b', darkColor: '#92400e' }
        ];

        /* --- GAME ENGINE & CONSTANTS --- */
        const WORLD_WIDTH = 2200;
        const WORLD_HEIGHT = 1200;
        const WATER_LEVEL = 1060;

        class Game {
            constructor() {
                this.canvas = document.getElementById('gameCanvas');
                this.ctx = this.canvas.getContext('2d');
                
                // Offscreen canvas for destructible terrain
                this.terrainCanvas = document.createElement('canvas');
                this.terrainCanvas.width = WORLD_WIDTH;
                this.terrainCanvas.height = WORLD_HEIGHT;
                this.terrainCtx = this.terrainCanvas.getContext('2d');

                // Game setup parameters
                this.numTeams = 2;
                this.isVsCPU = false;
                this.mapShape = 'single'; // single, double, cave

                // State
                this.teams = [];
                this.worms = [];
                this.activeWormIdx = 0;
                this.activeTeamIdx = 0;
                this.projectiles = [];
                this.particles = [];
                this.debris = [];
                this.floatingTexts = [];

                this.turnTimer = 45;
                this.timerInterval = null;
                this.wind = 0; // -100 to 100
                this.gameState = 'MENU'; // MENU, AIMING, FIRING, RETREAT, GAME_OVER
                this.retreatTimer = 0;

                // Camera
                this.camera = { x: WORLD_WIDTH / 2, y: WORLD_HEIGHT / 2, zoom: 1, targetX: WORLD_WIDTH / 2, targetY: WORLD_HEIGHT / 2, targetZoom: 1 };
                this.shakeAmount = 0;
                this.freezeFrame = 0;

                // Input
                this.keys = {};
                this.aimAngle = -Math.PI / 4;
                this.aimPower = 0;
                this.isCharging = false;
                this.selectedWeapon = WEAPONS[0];

                this.mouseWorld = { x: 0, y: 0 };

                this.bindEvents();
                this.resizeCanvas();
            }

            resizeCanvas() {
                this.canvas.width = window.innerWidth;
                this.canvas.height = window.innerHeight;
            }

            bindEvents() {
                window.addEventListener('resize', () => this.resizeCanvas());

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

                    if (this.gameState === 'AIMING' && this.getCurrentWorm() && !this.getCurrentWorm().isCPU) {
                        // Quick weapon selection keys 1-9
                        if (e.key >= '1' && e.key <= '9') {
                            const idx = parseInt(e.key) - 1;
                            if (idx < WEAPONS.length) this.selectWeapon(WEAPONS[idx]);
                        }
                        if (e.code === 'KeyQ') {
                            this.toggleWeaponMenu();
                        }
                        if (e.code === 'Backspace') {
                            // Backflip jump
                            this.getCurrentWorm().jump(true);
                        }
                    }
                });

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

                    if (e.code === 'Space' && this.isCharging && this.gameState === 'AIMING') {
                        this.fireWeapon();
                    }
                });

                this.canvas.addEventListener('mousemove', (e) => {
                    const rect = this.canvas.getBoundingClientRect();
                    const screenX = e.clientX - rect.left;
                    const screenY = e.clientY - rect.top;
                    
                    // Convert screen to world coordinates
                    this.mouseWorld.x = (screenX - this.canvas.width / 2) / this.camera.zoom + this.camera.x;
                    this.mouseWorld.y = (screenY - this.canvas.height / 2) / this.camera.zoom + this.camera.y;
                });

                this.canvas.addEventListener('click', (e) => {
                    if (this.gameState === 'AIMING' && this.selectedWeapon.type === 'target') {
                        this.useTargetWeapon(this.mouseWorld.x, this.mouseWorld.y);
                    } else if (this.gameState === 'AIMING' && this.selectedWeapon.id === 'teleport') {
                        this.teleportActiveWorm(this.mouseWorld.x, this.mouseWorld.y);
                    } else if (this.gameState === 'AIMING' && this.selectedWeapon.id === 'girder') {
                        this.placeGirder(this.mouseWorld.x, this.mouseWorld.y);
                    }
                });

                // UI Event Handlers
                document.getElementById('weaponBtn').onclick = () => this.toggleWeaponMenu();
                
                // Menu selection buttons
                this.setupMenuButtons('teamModeGroup', (val) => this.numTeams = parseInt(val));
                this.setupMenuButtons('aiModeGroup', (val) => this.isVsCPU = (val === 'cpu'));
                this.setupMenuButtons('mapShapeGroup', (val) => this.mapShape = val);

                document.getElementById('startGameBtn').onclick = () => {
                    SFX.init();
                    SFX.playClick();
                    document.getElementById('menuModal').style.display = 'none';
                    this.startNewGame();
                };
            }

            setupMenuButtons(groupId, callback) {
                const group = document.getElementById(groupId);
                const btns = group.querySelectorAll('.opt-btn');
                btns.forEach(btn => {
                    btn.onclick = () => {
                        SFX.init();
                        SFX.playClick();
                        btns.forEach(b => b.classList.remove('selected'));
                        btn.classList.add('selected');
                        callback(btn.dataset.val);
                    };
                });
            }

            toggleWeaponMenu() {
                SFX.playClick();
                const menu = document.getElementById('weaponMenu');
                menu.classList.toggle('open');
            }

            selectWeapon(wpn) {
                this.selectedWeapon = wpn;
                document.getElementById('currentWpnIcon').textContent = wpn.icon;
                document.getElementById('currentWpnName').textContent = wpn.name;
                document.getElementById('weaponMenu').classList.remove('open');
                this.renderWeaponMenu();
                SFX.playClick();
            }

            renderWeaponMenu() {
                const menu = document.getElementById('weaponMenu');
                menu.innerHTML = '';
                WEAPONS.forEach(w => {
                    const item = document.createElement('div');
                    item.className = `weapon-item ${w.id === this.selectedWeapon.id ? 'active' : ''}`;
                    item.innerHTML = `
                        <div class="weapon-icon">${w.icon}</div>
                        <div class="weapon-name">${w.name}</div>
                        <div class="weapon-cat cat-${w.category}">${w.category}</div>
                    `;
                    item.onclick = () => this.selectWeapon(w);
                    menu.appendChild(item);
                });
            }

            /* --- MAP GENERATION --- */
            generateTerrain() {
                const ctx = this.terrainCtx;
                ctx.clearRect(0, 0, WORLD_WIDTH, WORLD_HEIGHT);

                // Base terrain path
                ctx.beginPath();
                ctx.fillStyle = '#1e293b'; // Base rock background fill

                if (this.mapShape === 'single') {
                    ctx.moveTo(100, WORLD_HEIGHT);
                    for (let x = 100; x <= WORLD_WIDTH - 100; x += 20) {
                        const mid = WORLD_WIDTH / 2;
                        const distFromCenter = Math.abs(x - mid) / mid;
                        const heightFactor = Math.cos(distFromCenter * Math.PI * 0.8);
                        const n = Math.sin(x * 0.005) * 80 + Math.sin(x * 0.02) * 30 + Math.cos(x * 0.05) * 15;
                        const y = 600 - heightFactor * 350 + n;
                        ctx.lineTo(x, y);
                    }
                    ctx.lineTo(WORLD_WIDTH - 100, WORLD_HEIGHT);
                } else if (this.mapShape === 'double') {
                    ctx.moveTo(100, WORLD_HEIGHT);
                    for (let x = 100; x <= WORLD_WIDTH - 100; x += 20) {
                        const island1 = Math.exp(-Math.pow((x - 600) / 300, 2)) * 380;
                        const island2 = Math.exp(-Math.pow((x - 1600) / 300, 2)) * 380;
                        const n = Math.sin(x * 0.015) * 30 + Math.cos(x * 0.04) * 15;
                        const y = 750 - (island1 + island2) + n;
                        ctx.lineTo(x, y);
                    }
                    ctx.lineTo(WORLD_WIDTH - 100, WORLD_HEIGHT);
                } else if (this.mapShape === 'cave') {
                    // Floor
                    ctx.moveTo(0, WORLD_HEIGHT);
                    for (let x = 0; x <= WORLD_WIDTH; x += 20) {
                        const n = Math.sin(x * 0.008) * 120 + Math.cos(x * 0.02) * 40;
                        ctx.lineTo(x, 700 + n);
                    }
                    ctx.lineTo(WORLD_WIDTH, WORLD_HEIGHT);
                    ctx.closePath();
                    ctx.fill();

                    // Ceiling
                    ctx.beginPath();
                    ctx.moveTo(0, 0);
                    for (let x = 0; x <= WORLD_WIDTH; x += 20) {
                        const n = Math.cos(x * 0.008) * 100 + Math.sin(x * 0.03) * 35;
                        ctx.lineTo(x, 350 + n);
                    }
                    ctx.lineTo(WORLD_WIDTH, 0);
                }

                ctx.closePath();
                
                // Beautiful gradient for terrain body
                const grad = ctx.createLinearGradient(0, 200, 0, WORLD_HEIGHT);
                grad.addColorStop(0, '#475569');
                grad.addColorStop(0.4, '#334155');
                grad.addColorStop(1, '#0f172a');
                ctx.fillStyle = grad;
                ctx.fill();

                // Top Grass/Surface Layer
                this.addSurfaceGrass();
            }

            addSurfaceGrass() {
                const ctx = this.terrainCtx;
                const imgData = ctx.getImageData(0, 0, WORLD_WIDTH, WORLD_HEIGHT);
                const pixels = imgData.data;

                // Create a secondary canvas layer for grass texturing
                const tempCanvas = document.createElement('canvas');
                tempCanvas.width = WORLD_WIDTH;
                tempCanvas.height = WORLD_HEIGHT;
                const tempCtx = tempCanvas.getContext('2d');

                tempCtx.strokeStyle = '#22c55e'; // Bright green grass line
                tempCtx.lineWidth = 14;
                tempCtx.lineCap = 'round';

                ctx.save();
                ctx.globalCompositeOperation = 'source-over';
                
                // Find top boundary pixels to draw grass
                for (let x = 0; x < WORLD_WIDTH; x += 6) {
                    for (let y = 10; y < WORLD_HEIGHT - 50; y += 4) {
                        const idx = (y * WORLD_WIDTH + x) * 4;
                        if (pixels[idx + 3] > 200) { // Solid pixel
                            ctx.fillStyle = '#22c55e';
                            ctx.beginPath();
                            ctx.arc(x, y, 6, 0, Math.PI * 2);
                            ctx.fill();

                            // Dirt accent under grass
                            ctx.fillStyle = '#854d0e';
                            ctx.beginPath();
                            ctx.arc(x, y + 6, 5, 0, Math.PI * 2);
                            ctx.fill();
                            break;
                        }
                    }
                }
                ctx.restore();
            }

            /* --- GAME LIFECYCLE --- */
            startNewGame() {
                this.generateTerrain();
                this.renderWeaponMenu();

                // Setup Teams
                this.teams = [];
                this.worms = [];

                for (let i = 0; i < this.numTeams; i++) {
                    const cfg = TEAMS_CONFIG[i];
                    const isCPU = (this.isVsCPU && i > 0);
                    const team = { id: i, name: cfg.name, color: cfg.color, darkColor: cfg.darkColor, isCPU: isCPU, worms: [] };
                    
                    for (let w = 0; w < 4; w++) {
                        const name = `${cfg.name.split(' ')[0]} ${w + 1}`;
                        const worm = new Worm(this, w, team, name);
                        this.spawnWormSafely(worm);
                        team.worms.push(worm);
                        this.worms.push(worm);
                    }
                    this.teams.push(team);
                }

                this.activeTeamIdx = 0;
                this.activeWormIdx = 0;
                this.projectiles = [];
                this.particles = [];
                this.debris = [];
                this.floatingTexts = [];

                this.updateTeamsHUD();
                this.startTurn();
                this.gameState = 'AIMING';

                requestAnimationFrame((t) => this.loop(t));
            }

            spawnWormSafely(worm) {
                let attempts = 0;
                while (attempts < 500) {
                    const x = 150 + Math.random() * (WORLD_WIDTH - 300);
                    for (let y = 50; y < WATER_LEVEL - 50; y += 10) {
                        if (this.isSolidPixel(x, y)) {
                            worm.x = x;
                            worm.y = y - 20;
                            worm.vx = 0;
                            worm.vy = 0;
                            return;
                        }
                    }
                    attempts++;
                }
                // Fallback position
                worm.x = 400 + Math.random() * 800;
                worm.y = 400;
            }

            startTurn() {
                // Find next alive worm
                let attempts = 0;
                const totalWorms = this.worms.length;

                do {
                    this.activeWormIdx = (this.activeWormIdx + 1) % totalWorms;
                    attempts++;
                } while (this.worms[this.activeWormIdx].isDead && attempts < totalWorms * 2);

                const worm = this.getCurrentWorm();
                if (!worm || worm.isDead) {
                    this.checkWinCondition();
                    return;
                }

                this.activeTeamIdx = worm.team.id;
                this.turnTimer = 45;
                this.wind = Math.floor((Math.random() * 200) - 100);
                this.updateWindUI();

                this.gameState = 'AIMING';
                this.isCharging = false;
                this.aimPower = 0;

                // Reset Jetpack fuel
                worm.jetpackFuel = 100;
                worm.isJetpacking = false;

                // Focus Camera
                this.camera.targetX = worm.x;
                this.camera.targetY = worm.y;
                this.camera.targetZoom = 1.2;

                const msg = document.getElementById('statusMsg');
                msg.textContent = `${worm.name}'s Turn!`;
                msg.style.color = worm.team.color;

                clearInterval(this.timerInterval);
                this.timerInterval = setInterval(() => {
                    if (this.gameState === 'AIMING') {
                        this.turnTimer--;
                        const timerEl = document.getElementById('turnTimer');
                        timerEl.textContent = this.turnTimer;

                        if (this.turnTimer <= 10) {
                            timerEl.classList.add('urgent');
                            SFX.playTimerTick(true);
                        } else {
                            timerEl.classList.remove('urgent');
                            SFX.playTimerTick(false);
                        }

                        if (this.turnTimer <= 0) {
                            this.endTurn(500);
                        }
                    }
                }, 1000);

                if (worm.isCPU) {
                    this.handleCPUTurn(worm);
                }
            }

            endTurn(delay = 2000) {
                clearInterval(this.timerInterval);
                this.gameState = 'RETREAT';
                this.retreatTimer = delay;

                setTimeout(() => {
                    this.checkWinCondition();
                    if (this.gameState !== 'GAME_OVER') {
                        this.startTurn();
                    }
                }, delay);
            }

            checkWinCondition() {
                const aliveTeams = this.teams.filter(t => t.worms.some(w => !w.isDead));
                if (aliveTeams.length <= 1) {
                    this.gameState = 'GAME_OVER';
                    const winner = aliveTeams.length === 1 ? aliveTeams[0].name : 'Nobody';
                    
                    const modal = document.getElementById('menuModal');
                    const title = document.getElementById('menuTitle');
                    title.textContent = aliveTeams.length === 1 ? `🎉 ${winner} WINS!` : `DRAW! GAME OVER`;
                    document.getElementById('startGameBtn').textContent = 'PLAY AGAIN';
                    modal.style.display = 'flex';
                }
            }

            getCurrentWorm() {
                return this.worms[this.activeWormIdx];
            }

            updateWindUI() {
                const arrow = document.getElementById('windArrow');
                const val = document.getElementById('windValue');
                val.textContent = Math.abs(this.wind);
                arrow.style.transform = `rotate(${this.wind >= 0 ? 0 : 180}deg)`;
            }

            updateTeamsHUD() {
                const summary = document.getElementById('teamsSummary');
                summary.innerHTML = '';

                this.teams.forEach(team => {
                    const totalHp = team.worms.reduce((acc, w) => acc + (w.isDead ? 0 : w.hp), 0);
                    const maxHp = team.worms.length * 100;
                    const pct = Math.max(0, (totalHp / maxHp) * 100);

                    const card = document.createElement('div');
                    card.className = 'team-card';
                    card.innerHTML = `
                        <div class="team-name" style="color:${team.color}">
                            <span>${team.name}</span>
                        </div>
                        <div class="hp-bar-bg">
                            <div class="hp-bar-fill" style="width:${pct}%; background-color:${team.color}"></div>
                        </div>
                    `;
                    summary.appendChild(card);
                });
            }

            /* --- COLLISION & TERRAIN LOGIC --- */
            isSolidPixel(x, y) {
                if (x < 0 || x >= WORLD_WIDTH || y < 0 || y >= WORLD_HEIGHT) return false;
                const pixel = this.terrainCtx.getImageData(Math.floor(x), Math.floor(y), 1, 1).data;
                return pixel[3] > 128; // Alpha threshold
            }

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

                // Spawn Falling Debris Chunks
                const numDebris = Math.floor(radius * 0.4);
                for (let i = 0; i < numDebris; i++) {
                    const ang = Math.random() * Math.PI * 2;
                    const dist = Math.random() * radius * 0.8;
                    const px = cx + Math.cos(ang) * dist;
                    const py = cy + Math.sin(ang) * dist;
                    this.debris.push(new DebrisChunk(this, px, py));
                }

                // Explosion Smoke & Spark Particles
                for (let i = 0; i < radius * 1.5; i++) {
                    this.particles.push(new Particle(cx, cy, 'explosion'));
                }

                // Damage Worms & Apply Blast Wave Knockback
                this.worms.forEach(w => {
                    if (w.isDead) return;
                    const dx = w.x - cx;
                    const dy = w.y - cy;
                    const dist = Math.sqrt(dx * dx + dy * dy);
                    const damageRadius = radius * 1.4;

                    if (dist < damageRadius) {
                        const dmg = Math.floor((1 - dist / damageRadius) * this.selectedWeapon.damage);
                        if (dmg > 0) {
                            w.takeDamage(dmg);
                            // Blast impulse
                            const force = (1 - dist / damageRadius) * 16;
                            const angle = Math.atan2(dy, dx);
                            w.vx += Math.cos(angle) * force;
                            w.vy += Math.sin(angle) * force - 3; // UP shockwave
                        }
                    }
                });

                // Screen shake & Impact Freeze Frame
                this.shakeAmount = Math.min(35, radius * 0.6);
                if (radius > 50) this.freezeFrame = 4; // Pause frame logic briefly for visceral juice

                SFX.playExplosion(radius / 40);
            }

            /* --- WEAPON ACTION & FIRING --- */
            fireWeapon() {
                if (this.gameState !== 'AIMING') return;
                const worm = this.getCurrentWorm();
                if (!worm) return;

                const wpn = this.selectedWeapon;
                const speed = this.aimPower * 0.24;
                const vx = Math.cos(this.aimAngle) * speed + (worm.facingRight ? 2 : -2);
                const vy = Math.sin(this.aimAngle) * speed;

                if (wpn.type === 'projectile') {
                    this.projectiles.push(new Projectile(this, worm.x, worm.y - 10, vx, vy, wpn));
                    SFX.playShoot();
                } else if (wpn.type === 'instant') {
                    this.fireInstantWeapon(worm, wpn);
                } else if (wpn.type === 'drop') {
                    this.projectiles.push(new Projectile(this, worm.x, worm.y, 0, 1, wpn));
                }

                this.gameState = 'FIRING';
                this.isCharging = false;
                this.aimPower = 0;

                // Follow projectile or worm
                if (this.projectiles.length > 0) {
                    const p = this.projectiles[this.projectiles.length - 1];
                    this.camera.targetX = p.x;
                    this.camera.targetY = p.y;
                }
            }

            fireInstantWeapon(worm, wpn) {
                const dirX = Math.cos(this.aimAngle);
                const dirY = Math.sin(this.aimAngle);
                let rayX = worm.x;
                let rayY = worm.y - 10;

                SFX.playShoot();

                // Raycast instant hit
                for (let step = 0; step < 1200; step += 8) {
                    rayX += dirX * 8;
                    rayY += dirY * 8;

                    // Particle beam tracer
                    this.particles.push(new Particle(rayX, rayY, 'tracer'));

                    // Check hit worm
                    for (let target of this.worms) {
                        if (target !== worm && !target.isDead && Math.hypot(target.x - rayX, target.y - rayY) < 20) {
                            target.takeDamage(wpn.damage);
                            target.vx += dirX * wpn.knockback;
                            target.vy += dirY * wpn.knockback - 2;
                            this.carveExplosion(rayX, rayY, wpn.radius);
                            this.endTurn(1500);
                            return;
                        }
                    }

                    // Check hit terrain
                    if (this.isSolidPixel(rayX, rayY)) {
                        this.carveExplosion(rayX, rayY, wpn.radius);
                        this.endTurn(1500);
                        return;
                    }
                }
                this.endTurn(1000);
            }

            useTargetWeapon(tx, ty) {
                const wpn = this.selectedWeapon;
                if (wpn.id === 'airstrike') {
                    this.gameState = 'FIRING';
                    SFX.playSqueak(1.2);
                    
                    // Drop multiple bombs in sequence
                    for (let i = 0; i < wpn.bombs; i++) {
                        setTimeout(() => {
                            const dropX = tx + (i - 2) * 35;
                            const dropY = 50;
                            const p = new Projectile(this, dropX, dropY, 0, 4, WEAPONS[0]);
                            this.projectiles.push(p);
                        }, i * 250);
                    }
                    setTimeout(() => this.endTurn(2500), wpn.bombs * 250 + 1000);
                }
            }

            teleportActiveWorm(tx, ty) {
                const worm = this.getCurrentWorm();
                if (!worm || this.isSolidPixel(tx, ty)) return;

                // Teleport effect
                for (let i = 0; i < 25; i++) this.particles.push(new Particle(worm.x, worm.y, 'teleport'));
                worm.x = tx;
                worm.y = ty;
                worm.vx = 0;
                worm.vy = 0;
                for (let i = 0; i < 25; i++) this.particles.push(new Particle(worm.x, worm.y, 'teleport'));
                
                SFX.playSqueak(1.4);
                this.endTurn(1000);
            }

            placeGirder(gx, gy) {
                const ctx = this.terrainCtx;
                ctx.save();
                ctx.fillStyle = '#64748b'; // Slate bridge girder
                ctx.translate(gx, gy);
                ctx.rotate(this.aimAngle);
                ctx.fillRect(-60, -8, 120, 16);
                ctx.restore();

                SFX.playClick();
                this.endTurn(1000);
            }

            /* --- CPU AI CONTROLLER --- */
            handleCPUTurn(worm) {
                setTimeout(() => {
                    if (this.gameState !== 'AIMING' || worm.isDead) return;

                    // AI Risk & Target Evaluation
                    let bestTarget = null;
                    let minDistance = 99999;

                    this.worms.forEach(enemy => {
                        if (enemy.team.id !== worm.team.id && !enemy.isDead) {
                            const d = Math.hypot(enemy.x - worm.x, enemy.y - worm.y);
                            if (d < minDistance) {
                                minDistance = d;
                                bestTarget = enemy;
                            }
                        }
                    });

                    if (!bestTarget) return;

                    // Choose weapon based on range
                    if (minDistance < 200) {
                        this.selectWeapon(WEAPONS.find(w => w.id === 'shotgun') || WEAPONS[0]);
                    } else if (minDistance > 700 && Math.random() > 0.4) {
                        this.selectWeapon(WEAPONS.find(w => w.id === 'airstrike') || WEAPONS[0]);
                    } else {
                        this.selectWeapon(WEAPONS.find(w => w.id === 'bazooka') || WEAPONS[0]);
                    }

                    if (this.selectedWeapon.type === 'target') {
                        this.useTargetWeapon(bestTarget.x, bestTarget.y);
                        return;
                    }

                    // Ballistic Trajectory Calculation Simulation
                    const dx = bestTarget.x - worm.x;
                    const dy = bestTarget.y - worm.y;
                    let computedAngle = Math.atan2(dy, dx);
                    if (dx < 0) worm.facingRight = false;
                    else worm.facingRight = true;

                    // High arc trajectory tweak
                    computedAngle -= 0.35;

                    this.aimAngle = computedAngle;
                    this.aimPower = Math.min(100, Math.max(30, minDistance * 0.12));

                    // Aiming delay simulation
                    setTimeout(() => {
                        if (this.gameState === 'AIMING') {
                            this.fireWeapon();
                        }
                    }, 1200);

                }, 1000);
            }

            /* --- UPDATE & RENDER LOOP --- */
            loop(timestamp) {
                if (this.freezeFrame > 0) {
                    this.freezeFrame--;
                    requestAnimationFrame((t) => this.loop(t));
                    return;
                }

                this.update();
                this.render();

                requestAnimationFrame((t) => this.loop(t));
            }

            update() {
                const activeWorm = this.getCurrentWorm();

                // Player Key Inputs
                if (this.gameState === 'AIMING' && activeWorm && !activeWorm.isCPU) {
                    // Aim controls
                    if (this.keys['KeyW']) this.aimAngle -= 0.03;
                    if (this.keys['KeyS']) this.aimAngle += 0.03;

                    // Walk controls
                    if (this.keys['KeyA']) {
                        activeWorm.walk(-1);
                    } else if (this.keys['KeyD']) {
                        activeWorm.walk(1);
                    }

                    // Jump controls
                    if (this.keys['KeyW'] && activeWorm.isGrounded) {
                        activeWorm.jump(false);
                    }

                    // Jetpack control
                    if (this.selectedWeapon.id === 'jetpack' && this.keys['Space']) {
                        activeWorm.flyWithJetpack();
                    } else if (this.keys['Space'] && this.selectedWeapon.type === 'projectile') {
                        // Power Charge
                        this.isCharging = true;
                        this.aimPower = Math.min(100, this.aimPower + 1.8);
                    }
                }

                // Update Worms
                this.worms.forEach(w => w.update());

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

                // Update Particles
                for (let i = this.particles.length - 1; i >= 0; i--) {
                    const pt = this.particles[i];
                    pt.update();
                    if (pt.life <= 0) this.particles.splice(i, 1);
                }

                // Update Debris Chunks
                for (let i = this.debris.length - 1; i >= 0; i--) {
                    const d = this.debris[i];
                    d.update();
                    if (d.destroyed) this.debris.splice(i, 1);
                }

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

                // Smooth Dynamic Camera
                if (this.shakeAmount > 0) this.shakeAmount *= 0.88;

                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.05;
            }

            render() {
                const ctx = this.ctx;
                ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);

                ctx.save();

                // Apply Camera Transformation & Screen Shake Juice
                const shakeX = (Math.random() - 0.5) * this.shakeAmount;
                const shakeY = (Math.random() - 0.5) * this.shakeAmount;

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

                // Render Parallax Dynamic Sky Gradient
                const skyGrad = ctx.createLinearGradient(0, 0, 0, WORLD_HEIGHT);
                skyGrad.addColorStop(0, '#020617');
                skyGrad.addColorStop(0.5, '#0f172a');
                skyGrad.addColorStop(1, '#1e1b4b');
                ctx.fillStyle = skyGrad;
                ctx.fillRect(0, 0, WORLD_WIDTH, WORLD_HEIGHT);

                // Draw Destructible Terrain
                ctx.drawImage(this.terrainCanvas, 0, 0);

                // Draw Debris
                this.debris.forEach(d => d.draw(ctx));

                // Draw Worms
                this.worms.forEach(w => w.draw(ctx));

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

                // Draw Particles
                this.particles.forEach(pt => pt.draw(ctx));

                // Draw Predictive Aiming Line & Power Bar
                this.renderAimingTrajectory(ctx);

                // Draw Water Layer
                this.renderWater(ctx);

                // Draw Floating Damage Texts
                this.floatingTexts.forEach(ft => ft.draw(ctx));

                ctx.restore();
            }

            renderAimingTrajectory(ctx) {
                const worm = this.getCurrentWorm();
                if (!worm || this.gameState !== 'AIMING' || worm.isCPU) return;

                const wpn = this.selectedWeapon;
                if (wpn.type !== 'projectile') return;

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

                let simX = worm.x;
                let simY = worm.y - 10;
                const simPower = (this.isCharging ? this.aimPower : 50) * 0.24;
                let simVx = Math.cos(this.aimAngle) * simPower + (worm.facingRight ? 2 : -2);
                let simVy = Math.sin(this.aimAngle) * simPower;

                ctx.moveTo(simX, simY);

                for (let t = 0; t < 35; t++) {
                    simVx += (this.wind * 0.002) * (wpn.windFactor || 1);
                    simVy += wpn.gravity || 0.25;
                    simX += simVx;
                    simY += simVy;

                    ctx.lineTo(simX, simY);
                    if (this.isSolidPixel(simX, simY)) break;
                }
                ctx.stroke();
                ctx.restore();

                // Draw Power Meter Bar when Charging
                if (this.isCharging) {
                    ctx.save();
                    const barW = 60;
                    const barH = 8;
                    const bx = worm.x - barW / 2;
                    const by = worm.y - 45;

                    ctx.fillStyle = 'rgba(0,0,0,0.6)';
                    ctx.fillRect(bx, by, barW, barH);

                    const fillW = (this.aimPower / 100) * barW;
                    const grad = ctx.createLinearGradient(bx, 0, bx + barW, 0);
                    grad.addColorStop(0, '#f59e0b');
                    grad.addColorStop(1, '#ef4444');

                    ctx.fillStyle = grad;
                    ctx.fillRect(bx, by, fillW, barH);
                    ctx.strokeStyle = '#fff';
                    ctx.strokeRect(bx, by, barW, barH);
                    ctx.restore();
                }
            }

            renderWater(ctx) {
                ctx.save();
                const time = Date.now() * 0.003;
                
                ctx.fillStyle = 'rgba(14, 165, 233, 0.65)';
                ctx.beginPath();
                ctx.moveTo(0, WORLD_HEIGHT);
                ctx.lineTo(0, WATER_LEVEL);

                for (let x = 0; x <= WORLD_WIDTH; x += 30) {
                    const y = WATER_LEVEL + Math.sin(x * 0.02 + time) * 8 + Math.cos(x * 0.01 - time) * 4;
                    ctx.lineTo(x, y);
                }

                ctx.lineTo(WORLD_WIDTH, WORLD_HEIGHT);
                ctx.closePath();
                ctx.fill();

                // Bright crest highlight
                ctx.strokeStyle = 'rgba(186, 230, 253, 0.8)';
                ctx.lineWidth = 3;
                ctx.stroke();
                ctx.restore();
            }
        }

        /* --- WORM ENTITY CLASS --- */
        class Worm {
            constructor(game, id, team, name) {
                this.game = game;
                this.id = id;
                this.team = team;
                this.name = name;

                this.x = 0;
                this.y = 0;
                this.vx = 0;
                this.vy = 0;
                this.hp = 100;
                this.isDead = false;

                this.facingRight = true;
                this.isGrounded = false;
                this.jetpackFuel = 100;

                this.radius = 12;
            }

            walk(dir) {
                this.facingRight = (dir > 0);
                const stepX = this.x + dir * 2;
                
                // Climb slopes or walk
                if (!this.game.isSolidPixel(stepX, this.y)) {
                    this.x = stepX;
                } else if (!this.game.isSolidPixel(stepX, this.y - 6)) {
                    this.x = stepX;
                    this.y -= 4;
                }
            }

            jump(isBackflip = false) {
                if (!this.isGrounded) return;
                SFX.playJump();
                this.isGrounded = false;

                if (isBackflip) {
                    this.vy = -8.5;
                    this.vx = (this.facingRight ? -3.5 : 3.5);
                } else {
                    this.vy = -6.5;
                    this.vx = (this.facingRight ? 3.5 : -3.5);
                }
            }

            flyWithJetpack() {
                if (this.jetpackFuel <= 0) return;
                this.vy -= 0.6;
                this.jetpackFuel -= 0.8;
                this.game.particles.push(new Particle(this.x, this.y + 10, 'jetpack'));
            }

            takeDamage(amount) {
                if (this.isDead) return;
                this.hp = Math.max(0, this.hp - amount);
                this.game.floatingTexts.push(new FloatingText(this.x, this.y - 20, `-${amount}`, '#ef4444'));
                SFX.playSqueak(0.8);

                this.game.updateTeamsHUD();

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

            die() {
                this.isDead = true;
                this.hp = 0;
                this.game.floatingTexts.push(new FloatingText(this.x, this.y - 30, 'RIP', '#94a3b8'));
            }

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

                // Physics & Gravity
                this.vy += 0.28; // Gravity
                this.vx *= 0.92; // Friction

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

                // Terrain Collision Check
                if (this.game.isSolidPixel(nextX, nextY + this.radius)) {
                    // Fall Damage Check
                    if (this.vy > 11) {
                        const fallDmg = Math.floor((this.vy - 11) * 6);
                        this.takeDamage(fallDmg);
                    }

                    this.vy = 0;
                    this.isGrounded = true;
                    // Align to surface
                    while (this.game.isSolidPixel(this.x, this.y + this.radius - 1)) {
                        this.y--;
                    }
                } else {
                    this.isGrounded = false;
                    this.y = nextY;
                }

                if (!this.game.isSolidPixel(nextX + (this.vx > 0 ? this.radius : -this.radius), this.y)) {
                    this.x = nextX;
                } else {
                    this.vx = 0;
                }

                // Drowning Water Physics Check
                if (this.y > WATER_LEVEL) {
                    this.game.particles.push(new Particle(this.x, WATER_LEVEL, 'splash'));
                    SFX.playSplash();
                    this.game.floatingTexts.push(new FloatingText(this.x, WATER_LEVEL - 20, 'DROWNED!', '#38bdf8'));
                    this.die();
                }
            }

            draw(ctx) {
                if (this.isDead) {
                    // Draw Tombstone
                    ctx.save();
                    ctx.fillStyle = '#64748b';
                    ctx.fillRect(this.x - 8, this.y - 16, 16, 18);
                    ctx.fillStyle = '#cbd5e1';
                    ctx.font = '10px Fredoka';
                    ctx.fillText('RIP', this.x - 7, this.y - 4);
                    ctx.restore();
                    return;
                }

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

                // Worm Body
                ctx.fillStyle = '#f43f5e'; // Pink cute body
                ctx.beginPath();
                ctx.arc(0, -6, this.radius, 0, Math.PI * 2);
                ctx.fill();
                ctx.strokeStyle = '#9f1239';
                ctx.lineWidth = 2;
                ctx.stroke();

                // Team Headband / Helmet
                ctx.fillStyle = this.team.color;
                ctx.fillRect(-this.radius, -18, this.radius * 2, 6);

                // Eye
                const eyeOffset = this.facingRight ? 4 : -4;
                ctx.fillStyle = '#fff';
                ctx.beginPath();
                ctx.arc(eyeOffset, -8, 4, 0, Math.PI * 2);
                ctx.fill();
                ctx.fillStyle = '#000';
                ctx.beginPath();
                ctx.arc(eyeOffset + (this.facingRight ? 1 : -1), -8, 2, 0, Math.PI * 2);
                ctx.fill();

                // Active Worm Pointer & Name Tag
                if (this === this.game.getCurrentWorm()) {
                    // Reticle Arrow
                    ctx.fillStyle = '#f59e0b';
                    ctx.beginPath();
                    ctx.moveTo(0, -32);
                    ctx.lineTo(-6, -42);
                    ctx.lineTo(6, -42);
                    ctx.closePath();
                    ctx.fill();
                }

                // Health Bar
                ctx.fillStyle = 'rgba(0,0,0,0.5)';
                ctx.fillRect(-16, -26, 32, 5);
                ctx.fillStyle = this.hp > 30 ? '#22c55e' : '#ef4444';
                ctx.fillRect(-16, -26, (this.hp / 100) * 32, 5);

                ctx.restore();
            }
        }

        /* --- PROJECTILE ENTITY CLASS --- */
        class Projectile {
            constructor(game, x, y, vx, vy, wpn) {
                this.game = game;
                this.x = x;
                this.y = y;
                this.vx = vx;
                this.vy = vy;
                this.wpn = wpn;
                this.fuse = wpn.fuse || 0;
                this.bounces = 0;
                this.destroyed = false;
                this.spawnTime = Date.now();
            }

            update() {
                if (this.destroyed) return;

                // Physics
                this.vx += (this.game.wind * 0.002) * (this.wpn.windFactor || 1);
                this.vy += this.wpn.gravity || 0.25;

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

                // Smoke Trail Particles
                if (Math.random() > 0.3) {
                    this.game.particles.push(new Particle(this.x, this.y, 'smoke'));
                }

                // Fuse Countdown for Grenades
                if (this.fuse > 0) {
                    this.fuse -= 0.016;
                    if (this.fuse <= 0) {
                        this.explode();
                        return;
                    }
                }

                // Collision with Terrain
                if (this.game.isSolidPixel(this.x, this.y)) {
                    if (this.wpn.bounce > 0 && this.bounces < 3) {
                        this.vx *= -this.wpn.bounce;
                        this.vy *= -this.wpn.bounce;
                        this.bounces++;
                        SFX.playClick();
                    } else {
                        this.explode();
                    }
                }

                // Water Splash
                if (this.y > WATER_LEVEL) {
                    SFX.playSplash();
                    this.game.particles.push(new Particle(this.x, WATER_LEVEL, 'splash'));
                    this.destroyed = true;
                    this.game.endTurn(1200);
                }
            }

            explode() {
                if (this.destroyed) return;
                this.destroyed = true;

                if (this.wpn.choir) SFX.playChoir();

                // Cluster Bomb Splitting
                if (this.wpn.clusters) {
                    for (let i = 0; i < this.wpn.clusters; i++) {
                        const subVx = (Math.random() - 0.5) * 8;
                        const subVy = -Math.random() * 6 - 2;
                        this.game.projectiles.push(new Projectile(this.game, this.x, this.y - 10, subVx, subVy, WEAPONS[0]));
                    }
                }

                this.game.carveExplosion(this.x, this.y, this.wpn.radius);
                this.game.endTurn(1500);
            }

            draw(ctx) {
                ctx.save();
                ctx.translate(this.x, this.y);
                ctx.rotate(Math.atan2(this.vy, this.vx));

                ctx.font = '18px sans-serif';
                ctx.textAlign = 'center';
                ctx.textBaseline = 'middle';
                ctx.fillText(this.wpn.icon, 0, 0);

                ctx.restore();
            }
        }

        /* --- DEBRIS CHUNK ENTITY CLASS --- */
        class DebrisChunk {
            constructor(game, x, y) {
                this.game = game;
                this.x = x;
                this.y = y;
                this.vx = (Math.random() - 0.5) * 7;
                this.vy = -Math.random() * 6 - 1;
                this.size = Math.random() * 5 + 3;
                this.rotation = Math.random() * Math.PI * 2;
                this.vRot = (Math.random() - 0.5) * 0.2;
                this.destroyed = false;
            }

            update() {
                this.vy += 0.3; // Gravity
                this.x += this.vx;
                this.y += this.vy;
                this.rotation += this.vRot;

                if (this.game.isSolidPixel(this.x, this.y) || this.y > WATER_LEVEL) {
                    this.destroyed = true;
                }
            }

            draw(ctx) {
                ctx.save();
                ctx.translate(this.x, this.y);
                ctx.rotate(this.rotation);
                ctx.fillStyle = '#475569';
                ctx.fillRect(-this.size / 2, -this.size / 2, this.size, this.size);
                ctx.restore();
            }
        }

        /* --- PARTICLE SYSTEM --- */
        class Particle {
            constructor(x, y, type) {
                this.x = x;
                this.y = y;
                this.type = type;
                this.life = 1.0;
                this.decay = Math.random() * 0.03 + 0.015;

                if (type === 'explosion') {
                    const ang = Math.random() * Math.PI * 2;
                    const spd = Math.random() * 8 + 2;
                    this.vx = Math.cos(ang) * spd;
                    this.vy = Math.sin(ang) * spd;
                    this.size = Math.random() * 12 + 6;
                    this.color = ['#f97316', '#ef4444', '#eab308', '#475569'][Math.floor(Math.random() * 4)];
                } else if (type === 'smoke') {
                    this.vx = (Math.random() - 0.5) * 1;
                    this.vy = -Math.random() * 1.5;
                    this.size = Math.random() * 8 + 4;
                    this.color = 'rgba(148, 163, 184, 0.4)';
                } else if (type === 'splash') {
                    this.vx = (Math.random() - 0.5) * 4;
                    this.vy = -Math.random() * 5 - 2;
                    this.size = Math.random() * 4 + 2;
                    this.color = '#38bdf8';
                } else if (type === 'tracer') {
                    this.vx = 0;
                    this.vy = 0;
                    this.size = 3;
                    this.color = '#fef08a';
                    this.decay = 0.08;
                } else if (type === 'teleport' || type === 'jetpack') {
                    this.vx = (Math.random() - 0.5) * 3;
                    this.vy = Math.random() * 3;
                    this.size = 4;
                    this.color = type === 'jetpack' ? '#f97316' : '#a855f7';
                }
            }

            update() {
                this.x += this.vx;
                this.y += this.vy;
                this.life -= this.decay;
            }

            draw(ctx) {
                ctx.save();
                ctx.globalAlpha = Math.max(0, this.life);
                ctx.fillStyle = this.color;
                ctx.beginPath();
                ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
                ctx.fill();
                ctx.restore();
            }
        }

        /* --- FLOATING COMBAT TEXT --- */
        class FloatingText {
            constructor(x, y, text, color) {
                this.x = x;
                this.y = y;
                this.text = text;
                this.color = color;
                this.life = 1.0;
                this.vy = -1.2;
            }

            update() {
                this.y += this.vy;
                this.life -= 0.02;
            }

            draw(ctx) {
                ctx.save();
                ctx.globalAlpha = Math.max(0, this.life);
                ctx.font = 'bold 18px Fredoka';
                ctx.fillStyle = this.color;
                ctx.textAlign = 'center';
                ctx.shadowColor = '#000';
                ctx.shadowBlur = 4;
                ctx.fillText(this.text, this.x, this.y);
                ctx.restore();
            }
        }

        // Initialize Game Instance on Page Load
        window.onload = () => {
            window.game = new Game();
        };
    </script>
</body>
</html>
<!-- agent-meta {"model":"google/gemini-3.6-flash","provider":"openrouter","persona":"basic","sessionId":"a027258f-236b-4c7c-bbe4-a4ea76b26aa7","tokensIn":544,"tokensOut":23625,"tokensTotal":24169,"turns":1,"toolCalls":0,"failedToolCalls":0,"timestamp":"2026-07-22T14:10:22.762Z"} -->