← Worms results

Worms

inkling · 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 42,340 bytes · SHA-256 47b014db58ec
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Worms: Artillery Arena — Pixel Warfare</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=Bangers&family=Orbitron:wght@400;700;900&family=VT323&display=swap');

:root{
  --bg-deep:#0b0c15;
  --bg-card:rgba(255,255,255,0.05);
  --accent:#ffcc00;
  --accent-2:#00e5ff;
  --accent-3:#ff4444;
  --text:#f0f0f0;
  --text-dim:#aaa;
  --font-display:'Bangers',system-ui;
  --font-mono:'VT323',monospace;
  --font-tech:'Orbitron',sans-serif;
}

*{margin:0;padding:0;box-sizing:border-box;user-select:none;}
html,body{width:100%;height:100%;overflow:hidden;background:var(--bg-deep);font-family:var(--font-display);color:var(--text);}

/* Background ambient gradient */
body::before{
  content:'';position:fixed;inset:0;pointer-events:none;
  background:radial-gradient(ellipse at 50% 40%, rgba(255,204,0,0.08) 0%, transparent 70%),
              radial-gradient(ellipse at 30% 80%, rgba(0,229,255,0.06) 0%, transparent 60%);
  z-index:0;
}

#gameCanvas{display:block;position:fixed;inset:0;width:100vw;height:100vh;z-index:1;}

/* HUD Layer */
.hud{position:fixed;z-index:10;pointer-events:none;}

/* Top Bar */
.top-bar{
  top:0;left:0;right:0;height:72px;
  display:flex;align-items:center;justify-content:space-between;
  padding:0 24px;
  background:linear-gradient(180deg,rgba(11,12,21,0.85) 0%,rgba(11,12,21,0.4) 100%);
  border-bottom:2px solid rgba(255,204,0,0.3);
  backdrop-filter:blur(12px) saturate(1.3);
  box-shadow:0 4px 30px rgba(255,204,0,0.15);
}

.team-strip{display:flex;gap:16px;align-items:center;}
.team-card{
  background:linear-gradient(135deg,rgba(255,255,255,0.08),rgba(255,255,255,0.02));
  border:1px solid rgba(255,255,255,0.12);
  border-radius:14px;padding:6px 14px 6px 8px;
  display:flex;align-items:center;gap:8px;
  transition:transform .2s, box-shadow .2s;
  box-shadow:0 2px 12px rgba(0,0,0,0.3);
}
.team-card.active{
  border-color:var(--accent);box-shadow:0 0 20px rgba(255,204,0,0.35);
  transform:translateY(-2px);
}
.team-icon{width:28px;height:28px;border-radius:50%;border:2px solid rgba(255,255,255,0.8);box-shadow:0 0 8px currentColor;flex-shrink:0;}
.team-info{line-height:1.2;}
.team-name{font-family:var(--font-tech);font-size:13px;letter-spacing:1px;text-transform:uppercase;}
.team-health{font-family:var(--font-mono);font-size:15px;color:var(--accent);}
.team-units{font-family:var(--font-mono);font-size:11px;color:var(--text-dim);}

.turn-display{
  text-align:center;pointer-events:auto;
}
.turn-label{font-family:var(--font-tech);font-size:11px;letter-spacing:3px;color:var(--accent-2);text-transform:uppercase;}
.turn-value{
  font-size:34px;font-weight:900;color:var(--accent);text-shadow:0 0 25px rgba(255,204,0,0.6);
  transition:text-shadow .3s;
}
.turn-value.pulse{text-shadow:0 0 40px rgba(255,204,0,0.9), 0 0 60px rgba(255,44,68,0.6);animation:timerPulse .8s infinite alternate;}

@keyframes timerPulse{
  from{transform:scale(1);text-shadow:0 0 25px rgba(255,204,0,0.6);}
  to{transform:scale(1.08);text-shadow:0 0 45px rgba(255,204,0,1), 0 0 60px rgba(255,44,68,0.7);}
}

/* Wind indicator */
.wind-display{
  display:flex;align-items:center;gap:8px;
  background:rgba(255,255,255,0.05);border:1px solid rgba(255,255,255,0.1);
  border-radius:20px;padding:6px 14px;
}
.wind-arrow{font-size:22px;color:var(--accent-2);transition:transform .3s;}
.wind-label{font-family:var(--font-tech);font-size:10px;color:var(--text-dim);letter-spacing:2px;}
.wind-val{font-family:var(--font-mono);font-size:15px;color:var(--text);}

/* Bottom Bar */
.bottom-bar{
  bottom:0;left:0;right:0;height:96px;
  display:flex;align-items:center;justify-content:space-between;
  padding:0 24px;
  background:linear-gradient(0deg,rgba(11,12,21,0.9) 0%,rgba(11,12,21,0.5) 100%);
  border-top:2px solid rgba(0,229,255,0.25);
  backdrop-filter:blur(12px);
}

.weapon-panel{display:flex;gap:8px;align-items:center;}
.weapon-btn{
  pointer-events:auto;position:relative;
  background:linear-gradient(135deg,rgba(255,255,255,0.08),rgba(255,255,255,0.03));
  border:1px solid rgba(255,255,255,0.15);
  border-radius:12px;padding:10px 14px;
  color:var(--text);cursor:pointer;transition:all .2s;
  font-family:var(--font-tech);font-size:12px;letter-spacing:1px;
  min-width:80px;text-align:center;overflow:hidden;
  box-shadow:0 4px 15px rgba(0,0,0,0.3);
}
.weapon-btn:hover:not(.disabled){transform:translateY(-3px);box-shadow:0 8px 25px rgba(255,204,0,0.3);border-color:rgba(255,204,0,0.5);}
.weapon-btn.active{background:linear-gradient(135deg,rgba(255,204,0,0.2),rgba(255,204,0,0.05));border-color:var(--accent);box-shadow:0 0 20px rgba(255,204,0,0.3);}
.weapon-btn.disabled{opacity:0.35;cursor:default;}
.weapon-btn .w-name{display:block;font-weight:700;font-size:11px;}
.weapon-btn .w-qty{display:block;font-family:var(--font-mono);font-size:18px;color:var(--accent);margin-top:2px;}

.unit-stats{display:flex;gap:16px;align-items:center;pointer-events:auto;}
.stat-box{background:rgba(255,255,255,0.05);border:1px solid rgba(255,255,255,0.1);border-radius:10px;padding:6px 12px;text-align:center;min-width:70px;}
.stat-label{font-family:var(--font-tech);font-size:9px;color:var(--text-dim);letter-spacing:2px;text-transform:uppercase;}
.stat-val{font-family:var(--font-mono);font-size:20px;color:var(--text);}

/* Floating combat text */
.floating-text{
  position:fixed;pointer-events:none;z-index:50;
  font-family:var(--font-display);font-size:28px;font-weight:900;
  text-shadow:0 2px 4px rgba(0,0,0,0.8),0 0 20px currentColor;
  animation:floatingUp 1.2s ease-out forwards;
}
@keyframes floatingUp{
  0%{opacity:1;transform:translateY(0) scale(1);}
  60%{opacity:1;transform:translateY(-30px) scale(1.15);}
  100%{opacity:0;transform:translateY(-60px) scale(0.9);}
}

/* Menu / Intro overlay */
.overlay{
  position:fixed;inset:0;z-index:100;display:flex;align-items:center;justify-content:center;
  background:rgba(11,12,21,0.92);backdrop-filter:blur(20px);
  flex-direction:column;gap:24px;
  transition:opacity .5s;
}
.overlay.hidden{opacity:0;pointer-events:none;}

.game-title{
  font-family:var(--font-display);font-size:72px;color:var(--accent);
  text-shadow:0 4px 30px rgba(255,204,0,0.5),0 0 80px rgba(255,204,0,0.3);
  letter-spacing:6px;line-height:1.1;text-align:center;
}
.game-sub{
  font-family:var(--font-tech);font-size:18px;color:var(--accent-2);letter-spacing:8px;text-transform:uppercase;
  text-shadow:0 0 20px rgba(0,229,255,0.4);
}

.setup-panel{
  background:rgba(255,255,255,0.04);border:2px solid rgba(255,255,255,0.1);
  border-radius:24px;padding:32px 48px;max-width:700px;width:90%;
  backdrop-filter:blur(16px);box-shadow:0 20px 60px rgba(0,0,0,0.5);
}
.setup-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:16px;}
.setup-row{display:flex;align-items:center;justify-content:space-between;padding:12px 0;border-bottom:1px solid rgba(255,255,255,0.08);}
.setup-row h3{font-family:var(--font-tech);font-size:13px;letter-spacing:2px;color:var(--text-dim);}
.setup-row .value{font-family:var(--font-mono);font-size:18px;color:var(--accent);}

.setup-btns{display:flex;gap:12px;margin-top:20px;justify-content:center;}
.setup-btn{
  pointer-events:auto;background:linear-gradient(135deg,var(--accent),#ffaa00);
  color:#1a1200;border:none;border-radius:10px;padding:14px 36px;font-family:var(--font-display);font-size:22px;cursor:pointer;
  box-shadow:0 6px 25px rgba(255,204,0,0.35);transition:all .2s;letter-spacing:2px;
}
.setup-btn:hover{transform:translateY(-2px);box-shadow:0 10px 35px rgba(255,204,0,0.5);}
.setup-btn.secondary{background:linear-gradient(135deg,rgba(255,255,255,0.1),rgba(255,255,255,0.05));color:var(--text);font-size:16px;padding:10px 24px;box-shadow:0 4px 15px rgba(0,0,0,0.3);}

.terrain-preview{
  display:flex;gap:8px;justify-content:center;margin-top:8px;
}
.terrain-card{
  background:rgba(255,255,255,0.05);border:2px solid rgba(255,255,255,0.1);border-radius:10px;padding:8px 16px;cursor:pointer;transition:all .2s;font-family:var(--font-tech);font-size:12px;color:var(--text-dim);}
.terrain-card.active{border-color:var(--accent);color:var(--accent);box-shadow:0 0 15px rgba(255,204,0,0.15);}

/* Aim preview line */
.aim-guide{pointer-events:none;position:fixed;z-index:5;}
</style>
</head>
<body>

<canvas id="gameCanvas"></canvas>

<!-- HUD -->
<div class="hud top-bar">
  <div class="team-strip" id="teamsHUD"></div>
  <div class="turn-display">
    <div class="turn-label">TURN — <span id="turnTeamName">WARRIORS</span></div>
    <div class="turn-value" id="turnTimer">30</div>
  </div>
  <div class="wind-display">
    <div class="wind-arrow" id="windArrow">→</div>
    <div>
      <div class="wind-label">WIND</div>
      <div class="wind-val" id="windVal">0.3</div>
    </div>
  </div>
</div>

<div class="hud bottom-bar">
  <div class="weapon-panel" id="weaponPanel"></div>
  <div class="unit-stats">
    <div class="stat-box">
      <div class="stat-label">HEALTH</div>
      <div class="stat-val" id="unitHealth">100</div>
    </div>
    <div class="stat-box">
      <div class="stat-label">POS</div>
      <div class="stat-val" id="unitPos">0,0</div>
    </div>
  </div>
</div>

<!-- Intro / Menu -->
<div class="overlay" id="mainOverlay">
  <h1 class="game-title">WORM ARTILLERY</h1>
  <h2 class="game-sub">PIXEL WARFARE</h2>

  <div class="setup-panel">
    <div class="setup-grid">
      <div class="setup-row"><h3>TERRAIN SHAPE</h3>
        <div class="terrain-preview">
          <button class="terrain-card active" onclick="setTerrain('cave')">CAVE</button>
          <button class="terrain-card" onclick="setTerrain('island')">ISLAND</button>
          <button class="terrain-card" onclick="setTerrain('twin')">TWIN ISLANDS</button>
        </div>
      </div>
      <div class="setup-row"><h3>TEAMS</h3><div class="value" id="teamCount">2</div></div>
      <div class="setup-row"><h3>WIND VARIABLE</h3><div class="value">ON (Dynamic)</div></div>
      <div class="setup-row"><h3>WATER PHYSICS</h3><div class="value">ON (Drown / Float)</div></div>
      <div class="setup-row"><h3>AI OPPONENTS</h3><div class="value">ENABLED</div></div>
    </div>
    <div class="setup-btns">
      <button class="setup-btn" onclick="startGame()">LAUNCH BATTLE</button>
      <button class="setup-btn secondary" onclick="cycleTeams()">TEAMS: 2 → 4</button>
    </div>
  </div>
</div>

<script>
// ============================================
// GAME ENGINE — Pixel Artillery Arena
// ============================================

const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');

let W, H, CX, CY;
function resize(){ W=canvas.width=window.innerWidth; H=canvas.height=window.innerHeight; CX=W/2; CY=H/2; }
window.addEventListener('resize', resize); resize();

// ============================================
// STATE
// ============================================

let gameState = 'menu'; // 'menu','setup','playing','gameover'
let terrainType = 'cave'; // 'cave','island','twin'
let teamCount = 2;
let activeTeam = 0;
let activeUnitIndex = 0;
let turnTimer = 30; // seconds
let turnStartTime = 0;
let wind = 0; // -1 to 1
let windTarget = 0;
let terrain = []; // 2D array of terrain pixels [y][x] 0=air, 1=solid, 2=debris, 3=water
let units = [];
let particles = [];
let projectiles = [];
let floatingTexts = [];
let screenShake = 0;
let impactFreeze = 0;
let mouseX = CX, mouseY = CY;
let aimAngle = Math.PI/4;
let aimPower = 20;
let cameraX = 0, cameraY = 0;
let cameraScale = 1;
let targetScale = 1;

// Weapons
const WEAPONS = [
  { name:'BAZOOKA', qty:3, type:'indirect', color:'#ff4444', radius:60, damage:35, icon:'💥' },
  { name:'GRENADE', qty:5, type:'indirect', color:'#ffaa00', radius:35, damage:25, icon:'💣' },
  { name:'LASER BEAM', qty:2, type:'direct', color:'#00e5ff', radius:0, damage:45, icon:'⚡' },
  { name:'AIR STRIKE', qty:1, type:'highaoe', color:'#ffcc00', radius:120, damage:60, icon:'☄️' },
  { name:'DRILL', qty:4, type:'indirect', color:'#aa44aa', radius:30, damage:20, icon:'🌀' },
  { name:'TELEPORT', qty:2, type:'utility', color:'#44ff88', radius:0, damage:0, icon:'🌀' },
  { name:'SHIELD', qty:2, type:'utility', color:'#aaaaff', radius:0, damage:0, icon:'🛡️' },
];

// Colors
const TEAM_COLORS = ['#ffcc00','#ff4444','#00e5ff','#ff66aa'];

// ============================================
// SOUND ENGINE (Web Audio API)
// ============================================

const AudioCtx = new (window.AudioContext || window.webkitAudioContext)();

function playTone(freq, type='triangle', duration=0.15, gain=0.05, detune=0){
  try{
    const o=AudioCtx.createOscillator();
    const g=AudioCtx.createGain();
    o.type=type; o.frequency.setValueAtTime(freq, AudioCtx.currentTime);
    o.detune.setValueAtTime(detune, AudioCtx.currentTime);
    g.gain.setValueAtTime(gain, AudioCtx.currentTime);
    g.gain.exponentialRampToValueAtTime(0.001, AudioCtx.currentTime+duration);
    o.connect(g); g.connect(AudioCtx.destination);
    o.start(); o.stop(AudioCtx.currentTime+duration);
  }catch(e){}
}

function playSound(name){
  if(gameState==='menu') return; // suppress in menu for now, but we'll use it
  if(name==='jump') playTone(300, 'sine', 0.12, 0.03);
  if(name==='land') playTone(120, 'sine', 0.2, 0.03);
  if(name==='shoot') playTone(500, 'triangle', 0.15, 0.02);
  if(name==='explosion'){
    playTone(150, 'sine', 0.3, 0.06);
    setTimeout(()=>playTone(60, 'sine', 0.4, 0.05), 30);
    setTimeout(()=>playTone(30, 'sine', 0.5, 0.05), 80);
  }
  if(name==='damage') playTone(220, 'square', 0.25, 0.04);
  if(name==='drown'){
    playTone(60, 'sine', 0.3, 0.05);
    playTone(30, 'sine', 0.5, 0.05);
  }
  if(name==='click'){ playTone(800, 'triangle', 0.05, 0.02); }
  if(name==='turnStart'){
    playTone(400, 'triangle', 0.1, 0.03);
    setTimeout(()=>playTone(600, 'triangle', 0.1, 0.03), 100);
  }
  if(name==='timerTick'){ playTone(200+turnTimer*5, 'triangle', 0.05, 0.015); }
  if(name==='victory'){
    playTone(523, 'triangle', 0.3, 0.03);
    setTimeout(()=>playTone(659, 'triangle', 0.3, 0.03), 150);
    setTimeout(()=>playTone(784, 'triangle', 0.5, 0.03), 300);
  }
}

// ============================================
// PARALLAX STARS BACKGROUND (behind terrain)
// ============================================

let stars = [];
for(let i=0;i<200;i++){
  stars.push({x:Math.random()*W, y:Math.random()*H, r:0.5+Math.random()*1.5, speed:0.2+Math.random()*0.8});
}

// ============================================
// TERRAIN GENERATION
// ============================================

function generateTerrain(type){
  const cols = 200; // lower resolution for pixel feel but faster
  const rows = 120;
  terrain = [];
  for(let y=0;y<rows;y++) terrain[y]=new Uint8Array(cols);

  // Generate based on shape
  if(type==='cave'){
    // Cave shape: high walls, low ceiling, floor is flat with some bumps
    for(let x=0;x<cols;x++){
      for(let y=0;y<rows;y++){
        // Ceiling curve
        const ceilingY = Math.floor(20 + Math.sin(x/20)*10 + Math.cos(x/10)*5);
        // Floor with bumps
        const floorY = Math.floor(90 + Math.sin(x/15)*8);
        // Walls on sides
        if(x<10 || x>cols-10) terrain[y][x]=1;
        if(y<ceilingY || y>floorY) terrain[y][x]=1;
        // Random debris inside
        if(y>floorY-10 && y<floorY && Math.random()>0.7) terrain[y][x]=1;
      }
    }
    // Water at bottom in some spots
    for(let x=20;x<cols-20;x++){
      if(Math.random()>0.85) for(let y=95;y<110;y++) if(y<rows) terrain[y][x]=3; // water
    }
  } else if(type==='island'){
    for(let x=0;x<cols;x++){
      for(let y=0;y<rows;y++){
        // Island mound
        const mound = Math.sin(x/cols*Math.PI)*30 + Math.sin(x/cols*Math.PI*3)*10;
        const islandTop = 60 - mound;
        if(y>islandTop && y>30) terrain[y][x]=1;
      }
    }
    // Water below island
    for(let x=0;x<cols;x++){
      for(let y=70;y<110;y++){
        if(y<rows && Math.random()>0.3) terrain[y][x]=3;
      }
    }
  } else if(type==='twin'){
    // Two islands
    for(let x=0;x<cols;x++){
      for(let y=0;y<rows;y++){
        const left = Math.sin((x-20)/30*Math.PI)*25 + Math.sin((x-20)/15)*8;
        const right = Math.sin((x-120)/30*Math.PI)*25 + Math.sin((x-120)/15)*8;
        if(x<70 && y>55-left) terrain[y][x]=1;
        if(x>100 && y>55-right) terrain[y][x]=1;
      }
    }
    // Water everywhere between and below
    for(let x=0;x<cols;x++){
      for(let y=65;y<110;y++){
        if(y<rows && (x<90 || x>80)) terrain[y][x]=3;
      }
    }
  }
  // Convert to world coordinates mapping
  // We'll treat cols = width in pixels at a certain scale
  // For simplicity: world width = 160 units, height = 80 units
}

function getTerrainAt(x, y){
  // x,y are world coordinates
  const cols = 200; const rows = 120;
  const cx = Math.floor((x / 160) * cols);
  const cy = Math.floor((y / 80) * rows);
  if(cx<0||cx>=cols||cy<0||cy>=rows) return 0;
  return terrain[cy][cx];
}

function setTerrain(x,y,val){
  const cols=200, rows=120;
  const cx=Math.floor((x/160)*cols); const cy=Math.floor((y/80)*rows);
  if(cx>=0&&cx<cols&&cy>=0&&cy<rows) terrain[cy][cx]=val;
}

// ============================================
// UNIT SETUP
// ============================================

function spawnUnits(){
  units=[];
  const cols=200; const rows=120;
  // Spawn points depending on terrain
  let spawns=[];
  if(terrainType==='cave'){
    spawns=[
      {x:20,y:75},{x:60,y:75},{x:100,y:75},{x:140,y:75}
    ];
  } else if(terrainType==='island'){
    spawns=[
      {x:35,y:45},{x:80,y:45},{x:125,y:45},{x:160,y:45}
    ];
  } else if(terrainType==='twin'){
    spawns=[
      {x:25,y:45},{x:50,y:45},{x:120,y:45},{x:150,y:45}
    ];
  }

  for(let t=0;t<teamCount;t++){
    for(let u=0;u<4;u++){
      const spawn = spawns[(t*4+u)%spawns.length];
      units.push({
        team:t,
        x: spawn.x + Math.random()*10 - 5,
        y: spawn.y,
        vx:0, vy:0,
        w:10, h:10,
        hp:100,
        maxHp:100,
        alive:true,
        shielded: false,
        name:'W'+(t+1)+(u+1),
        faceDir:1,
        jumpCooldown:0,
        moveTimer:0,
        onGround:false,
        inWater:false,
      });
    }
  }
  activeTeam=0; activeUnitIndex=0; turnTimer=30;
}

// ============================================
// PROJECTILES & PARTICLES
// ============================================

function spawnProjectile(type, x, y, vx, vy, team){
  const w = WEAPONS[type];
  projectiles.push({
    x, y, vx, vy,
    team, weapon: type,
    radius: w.radius,
    damage: w.damage,
    life: 120,
    active: true,
    trail: []
  });
}

function spawnExplosion(x,y,radius,damage,team){
  // Particles
  const count = Math.floor(radius*3);
  for(let i=0;i<count;i++){
    const angle = Math.random()*Math.PI*2;
    const speed = 1+Math.random()*4;
    particles.push({
      x, y,
      vx: Math.cos(angle)*speed, vy: Math.sin(angle)*speed,
      life: 20+Math.random()*40,
      color: Math.random()>0.5 ? '#ffaa00' : '#ff4444',
      size: 1+Math.random()*3,
      type:'fire'
    });
  }
  // Debris chunks
  for(let i=0;i<15;i++){
    const angle = Math.random()*Math.PI*2;
    particles.push({
      x, y,
      vx: Math.cos(angle)*(speed*0.5), vy: Math.sin(angle)*(speed*0.5)-2,
      life: 60+Math.random()*80,
      color: '#8a6d3b', size: 3+Math.random()*5,
      type:'debris', angle: Math.random()*Math.PI*2, rotSpeed: (Math.random()-0.5)*0.2
    });
  }
  // Sound
  playSound('explosion');
  screenShake = Math.min(radius/4, 15);
  if(radius>80) impactFreeze = 10;
  // Destroy terrain
  for(let dx=-radius;dx<=radius;dx+=3){
    for(let dy=-radius;dy<=radius;dy+=3){
      const d=Math.sqrt(dx*dx+dy*dy);
      if(d<=radius && Math.random()>0.2){
        const wx=x+dx, wy=y+dy;
        // Convert to terrain grid
        const cols=200, rows=120;
        const cx=Math.floor((wx/160)*cols);
        const cy=Math.floor((wy/80)*rows);
        if(cx>=0&&cx<cols&&cy>=0&&cy<rows){
          if(terrain[cy][cx]===1){
            terrain[cy][cx]=0; // clear solid
            // Add falling debris particle
            particles.push({
              x:wx, y:wy,
              vx:(Math.random()-0.5)*2, vy:-1-Math.random()*3,
              life:40+Math.random()*60,
              color:'#8a6d3b', size:2+Math.random()*4,
              type:'debris', angle:Math.random()*Math.PI*2, rotSpeed:(Math.random()-0.5)*0.1
            });
          } else if(terrain[cy][cx]===3){
            terrain[cy][cx]=0; // clear water too? Actually just keep for now
          }
        }
      }
    }
  }
  // Damage units
  for(let u of units){
    if(!u.alive) continue;
    const dx=u.x-x, dy=u.y-y;
    const d=Math.sqrt(dx*dx+dy*dy);
    if(d<radius+3){
      const dmg = Math.floor(w.damage*(1-d/radius));
      u.hp-=dmg; u.vx+=dx/d*2; u.vy-=dy/d*2;
      spawnFloatingText(u.x, u.y-15, '-'+dmg, '#ff4444');
      playSound('damage');
    }
  }
}

function spawnFloatingText(x,y,text,color='#ffcc00'){
  floatingTexts.push({x,y,text,color,life:60,vy:-1});
}

// ============================================
// CAMERA
// ============================================

function updateCamera(){
  const activeUnit = units.find(u=>u.team===activeTeam && u.alive);
  if(activeUnit){
    const targetX = activeUnit.x;
    const targetY = activeUnit.y - 15;
    cameraX += (targetX - cameraX) * 0.08;
    cameraY += (targetY - cameraY) * 0.08;
  }
}

function getScreenX(x){ return x*cameraScale; }
function getScreenY(y){ return y*cameraScale; }

// ============================================
// DRAWING
// ============================================

function draw(){
  // Clear with deep color
  ctx.fillStyle = '#0b0c15';
  ctx.fillRect(0,0,W,H);

  // Stars (parallax)
  ctx.save();
  ctx.translate(-cameraX*0.1 + W/2, -cameraY*0.1 + H/2);
  for(let s of stars){
    ctx.fillStyle = `rgba(255,255,255,${0.3+s.r/5})`;
    ctx.fillRect(s.x, s.y, s.r, s.r);
  }
  ctx.restore();

  // Apply camera transform
  ctx.save();
  // Center camera on target
  const activeU = units.find(u=>u.team===activeTeam && u.alive);
  let cx=0, cy=0, cs=1;
  if(activeU){ cx=activeU.x; cy=activeU.y; cs=1; }
  // Dynamic zoom based on action
  if(projectiles.length>0 || screenShake>0){ cs=0.85; }
  else if(activeU && activeU.hp<30){ cs=0.9; }
  targetScale = cs;
  cameraScale += (targetScale - cameraScale) * 0.1;

  ctx.translate(W/2 - cx*cameraScale, H/2 - cy*cameraScale*0.7); // slightly offset up for HUD

  // Apply screen shake
  if(screenShake>0){
    const sx = (Math.random()-0.5)*screenShake*2;
    const sy = (Math.random()-0.5)*screenShake*2;
    ctx.translate(sx,sy);
    screenShake *= 0.9;
  }

  // Terrain
  const cols=200, rows=120;
  const pw=160/cols, ph=80/rows; // world per cell
  for(let y=0;y<rows;y++){
    for(let x=0;x<cols;x++){
      const val = terrain[y][x];
      if(val===0) continue;
      const wx = x*pw + pw/2; // world x center
      const wy = y*ph + ph/2; // world y center
      if(val===1){
        const grad = ctx.createLinearGradient(wx-pw/2, wy-ph/2, wx+pw/2, wy+ph/2);
        grad.addColorStop(0,'#5a4a3a');
        grad.addColorStop(1,'#3a2a1a');
        ctx.fillStyle = grad;
        ctx.fillRect(wx-pw/2, wy-ph/2, pw, ph);
        // Border
        ctx.strokeStyle = '#2a1a0a';
        ctx.strokeRect(wx-pw/2, wy-ph/2, pw, ph);
      } else if(val===3){
        // Water
        const grad = ctx.createLinearGradient(wx-pw/2, wy-ph/2, wx+pw/2, wy+ph/2);
        grad.addColorStop(0,'#0066aa');
        grad.addColorStop(0.5,'#0099ff');
        grad.addColorStop(1,'#004488');
        ctx.fillStyle = grad;
        ctx.fillRect(wx-pw/2, wy-ph/2, pw, ph);
        // Ripples (animated using time - we'll approximate)
      }
    }
  }

  // Draw units
  for(let u of units){
    if(!u.alive) continue;
    // Body
    ctx.save();
    ctx.translate(u.x, u.y);
    if(u.faceDir<0) ctx.scale(-1,1);
    // Head/body
    ctx.fillStyle = TEAM_COLORS[u.team];
    ctx.shadowColor = TEAM_COLORS[u.team];
    ctx.shadowBlur = 15;
    ctx.beginPath();
    ctx.ellipse(0, -2, 5, 7, 0, 0, Math.PI*2);
    ctx.fill();
    // Eyes
    ctx.fillStyle = '#fff';
    ctx.beginPath(); ctx.arc(1, -4, 1.2, 0, Math.PI*2); ctx.fill();
    // Mouth
    ctx.strokeStyle = '#fff'; ctx.lineWidth=0.5;
    ctx.beginPath(); ctx.arc(0, -1, 2.5, 0.2, Math.PI-0.3); ctx.stroke();
    // Shield if active
    if(u.shielded){
      ctx.strokeStyle = 'rgba(170,170,255,0.6)';
      ctx.lineWidth=2;
      ctx.beginPath(); ctx.arc(0,0,10,0,Math.PI*2); ctx.stroke();
    }
    ctx.restore();

    // Health bar
    const hw = 14, hh = 3;
    ctx.fillStyle = '#333';
    ctx.fillRect(u.x-7, u.y-10, hw, hh);
    const healthRatio = u.hp/u.maxHp;
    ctx.fillStyle = healthRatio>0.3 ? '#44ff44' : '#ff4444';
    ctx.fillRect(u.x-7, u.y-10, hw*healthRatio, hh);
  }

  // Projectiles
  for(let p of projectiles){
    if(!p.active) continue;
    ctx.save();
    ctx.translate(p.x, p.y);
    const w = WEAPONS[p.weapon];
    ctx.fillStyle = w.color;
    ctx.shadowColor = w.color;
    ctx.shadowBlur = 10;
    ctx.beginPath();
    ctx.arc(0,0,3,0,Math.PI*2);
    ctx.fill();
    // Trail
    if(p.trail.length>0){
      ctx.beginPath();
      ctx.moveTo(p.trail[0].x-p.x, p.trail[0].y-p.y);
      for(let i=1;i<p.trail.length;i++){
        ctx.lineTo(p.trail[i].x-p.x, p.trail[i].y-p.y);
      }
      ctx.strokeStyle = w.color;
      ctx.lineWidth = 1.5;
      ctx.globalAlpha = 0.3;
      ctx.stroke();
      ctx.globalAlpha = 1;
    }
    ctx.restore();
  }

  // Particles
  for(let part of particles){
    ctx.save();
    ctx.translate(part.x, part.y);
    if(part.type==='debris'){
      ctx.rotate(part.angle);
      ctx.fillStyle = part.color;
      ctx.fillRect(-part.size/2, -part.size/2, part.size, part.size);
      part.angle += part.rotSpeed;
    } else {
      ctx.fillStyle = part.color;
      ctx.beginPath();
      ctx.arc(0,0,part.size,0,Math.PI*2);
      ctx.fill();
    }
    ctx.restore();
  }

  // Water surface effects
  for(let y=0;y<rows;y++){
    for(let x=0;x<cols;x++){
      if((terrain[y][x]||0)===3){
        const wx = x*pw + pw/2, wy = y*ph + ph/2;
        const rip = Math.sin((wx+Date.now()/300)*3)*0.5;
        ctx.fillStyle = 'rgba(255,255,255,0.1)';
        ctx.fillRect(wx-pw/2, wy-ph/2+rip, pw, 1);
      }
    }
  }

  ctx.restore(); // End camera

  // Floating combat text
  for(let ft of floatingTexts){
    ft.y += ft.vy; ft.life--;
    if(ft.life<=0) continue;
    ctx.save();
    ctx.font = 'bold 22px Bangers';
    ctx.fillStyle = ft.color;
    ctx.strokeStyle = '#000';
    ctx.lineWidth = 3;
    ctx.strokeText(ft.text, W/2 + (ft.x-cameraX*cameraScale) - W/2 + (ft.x-cameraX*cameraScale - W/2), H/2 - (ft.y-cameraY*cameraScale*0.7) + H/2 - (ft.y-cameraY*cameraScale*0.7 - H/2));
    // Simplify: screen-space projection
    const sx = (ft.x - cameraX)*cameraScale + W/2;
    const sy = (ft.y - cameraY*0.7)*cameraScale + H/2;
    ctx.globalAlpha = ft.life/60;
    ctx.fillText(ft.text, sx, sy);
    ctx.restore();
  }

  // Predictive aim line (drawn in world space, converted to screen)
  if(gameState==='playing'){
    drawAimLine();
  }

  // Impact freeze effect (flash)
  if(impactFreeze>0){
    ctx.fillStyle = `rgba(255,200,0,${impactFreeze/20})`;
    ctx.fillRect(0,0,W,H);
    impactFreeze--;
  }
}

function drawAimLine(){
  const activeU = units.find(u=>u.team===activeTeam && u.alive);
  if(!activeU) return;
  const w = WEAPONS[getSelectedWeaponIndex()];
  if(w.type==='utility') return;
  // Predict trajectory
  let px = activeU.x, py = activeU.y - 5;
  let vx = Math.cos(aimAngle)*aimPower;
  let vy = Math.sin(aimAngle)*aimPower;
  ctx.save();
  ctx.translate(W/2 - cameraX*cameraScale, H/2 - cameraY*cameraScale*0.7);
  if(screenShake>0){
    const sx = (Math.random()-0.5)*screenShake*2;
    const sy = (Math.random()-0.5)*screenShake*2;
    ctx.translate(sx,sy);
  }
  ctx.beginPath();
  ctx.moveTo(px, py);
  for(let step=0;step<30;step++){
    vx += (wind/100)*0.3; // wind affects trajectory
    vy += 0.8; // gravity
    px += vx; py += vy;
    // Check collision with terrain
    if(getTerrainAt(px, py)===1) break;
    // Draw dot
    ctx.lineTo(px, py);
  }
  ctx.strokeStyle = 'rgba(255,255,255,0.4)';
  ctx.setLineDash([4,3]);
  ctx.lineWidth = 1.5;
  ctx.stroke();
  ctx.restore();
}

// ============================================
// GAME LOOP
// ============================================

function getSelectedWeaponIndex(){
  return 0; // We'll manage selection via UI
}

// Update physics
function updatePhysics(dt){
  // Update wind gradually
  windTarget = Math.sin(Date.now()/5000)*0.6 + Math.random()*0.3 - 0.15;
  wind += (windTarget - wind) * 0.02;
  // Update wind UI
  document.getElementById('windVal').textContent = wind.toFixed(2);
  const rot = wind*20;
  document.getElementById('windArrow').style.transform = `rotate(${rot}deg)`;

  // Update particles
  for(let p=particles.length-1;p>=0;p--){
    const part = particles[p];
    part.x += part.vx; part.y += part.vy;
    part.vy += 0.1; // gravity on debris
    part.life--;
    if(part.life<=0 || part.y>90) particles.splice(p,1);
  }

  // Update floating texts
  for(let ft=floatingTexts.length-1;ft>=0;ft--){
    const text = floatingTexts[ft];
    text.life--;
    if(text.life<=0) floatingTexts.splice(ft,1);
  }

  // Update units
  for(let u of units){
    if(!u.alive) continue;
    // Apply wind to falling units
    u.vx += wind*0.05;
    // Gravity
    u.vy += 1.2;

    // Friction
    u.vx *= 0.98;

    // Movement (turn-based manual)
    // But for physics we apply velocity
    u.x += u.vx;
    u.y += u.vy;

    // Terrain collision
    // Sample multiple points
    const w=5, h=5;
    let colliding = false;
    for(let dy=0;dy<=h;dy+=h){
      for(let dx=0;dx<=w;dx+=w){
        const tx = u.x + dx - w/2;
        const ty = u.y + dy - h/2;
        const val = getTerrainAt(tx, ty);
        if(val===1){
          colliding = true;
          // Resolve
          u.vy = 0; u.vx = 0;
          // Push out upward
          u.y = Math.floor(ty/ (80/120))*(80/120) - h/2 - 0.5; // approximate
          // Actually let's use simple approach
        }
        if(val===3){
          // Water
          u.inWater = true;
          u.vy = Math.min(u.vy, 0.5); // float/slower fall
          if(Math.random()>0.98){
            // Splash particle
            particles.push({x:tx,y:ty,vx:0,vy:-1,life:15,color:'#0099ff',size:2,type:'fire'});
          }
        } else {
          u.inWater = false;
        }
      }
    }

    // If in water too long, drown
    if(u.inWater && Math.random()>0.99){
      // Actually let's apply damage over time
      if(Math.random()>0.995){
        u.hp -= 2;
        spawnFloatingText(u.x, u.y-8, '-2', '#0099ff');
      }
    }

    // Fall damage
    if(colliding && u.vy>4){
      const dmg = Math.floor(u.vy*2);
      u.hp -= dmg;
      spawnFloatingText(u.x, u.y-8, '-'+dmg, '#ffaa00');
      playSound('land');
      u.vy = -2; // bounce slightly
    }

    // Boundary
    if(u.x<2) u.x=2; if(u.x>158) u.x=158;
    if(u.y>85) u.y=85; // bottom boundary

    u.onGround = colliding;
    if(u.hp<=0){ u.alive=false; playSound('damage'); }
    if(u.hp>u.maxHp) u.hp=u.maxHp;
  }

  // Update projectiles
  for(let p=projectiles.length-1;p>=0;p--){
    const proj = projectiles[p];
    proj.life--;
    proj.vx += (wind/100)*0.3;
    proj.vy += 0.8;
    proj.x += proj.vx;
    proj.y += proj.vy;
    proj.trail.push({x:proj.x, y:proj.y});
    if(proj.trail.length>15) proj.trail.shift();

    // Collision
    if(getTerrainAt(proj.x, proj.y)===1 || proj.life<=0){
      spawnExplosion(proj.x, proj.y, WEAPONS[proj.weapon].radius, WEAPONS[proj.weapon].damage, proj.team);
      projectiles.splice(p,1);
      continue;
    }

    // Water interaction
    if(getTerrainAt(proj.x, proj.y)===3){
      // Small splash
      particles.push({x:proj.x, y:proj.y, vx:(Math.random()-0.5), vy:-2, life:10, color:'#0099ff', size:2, type:'fire'});
    }
  }

  // Turn timer
  if(gameState==='playing'){
    const now = Date.now();
    const elapsed = (now - turnStartTime)/1000;
    turnTimer = Math.max(0, 30 - elapsed);
    document.getElementById('turnTimer').textContent = Math.ceil(turnTimer);
    if(turnTimer<=5) document.getElementById('turnTimer').classList.add('pulse'); else document.getElementById('turnTimer').classList.remove('pulse');
    if(turnTimer<=0){
      turnTimer = 30;
      turnStartTime = now;
      nextTurn();
    }
  }
}

// ============================================
// TURN MANAGEMENT
// ============================================

function nextTurn(){
  // Find next alive unit in next team
  playSound('turnStart');
  do{
    activeUnitIndex = (activeUnitIndex+1)%units.length;
    activeTeam = units[activeUnitIndex].team;
    const teamUnits = units.filter(u=>u.team===activeTeam);
    if(teamUnits.every(u=>!u.alive)){
      // Skip dead team? Actually just continue
    }
  } while(!units[activeUnitIndex].alive);
  turnStartTime = Date.now();
  turnTimer = 30;
  // Update HUD
  document.getElementById('turnTeamName').textContent = 'TEAM '+(activeTeam+1);
  document.getElementById('turnTimer').classList.remove('pulse');
  document.getElementById('turnTimer').textContent = '30';
  // CPU turn if it's a bot team
  // For simplicity, we'll make team 2+ AI when teamCount>1 or all teams have AI except human
  if(isAITeam(activeTeam)){
    // Small delay then run AI
    setTimeout(runAITurn, 800);
  }
}

function isAITeam(team){
  // Even teams are AI if teamCount>1
  if(teamCount===2) return team===1;
  if(teamCount===4) return team>=1; // Team 0 human, 1-3 AI? Let's say all except first
  return false;
}

function runAITurn(){
  const activeU = units.find(u=>u.team===activeTeam && u.alive);
  if(!activeU) return;
  // Simple AI: jump sometimes, aim at closest enemy, fire
  // Find closest enemy
  let closest = null; let minD = Infinity;
  for(let u of units){
    if(u.team===activeTeam || !u.alive) continue;
    const d=Math.hypot(u.x-activeU.x, u.y-activeU.y);
    if(d<minD){ minD=d; closest=u; }
  }
  // Aim at closest
  if(closest){
    const dx = closest.x - activeU.x;
    const dy = closest.y - activeU.y;
    aimAngle = Math.atan2(dy, dx);
    // Random power
    aimPower = 10+Math.random()*25;
    // Fire a weapon with qty>0
    const weaponIndex = Math.floor(Math.random()*WEAPONS.length);
    const w = WEAPONS[weaponIndex];
    // Check if we have that weapon
    // We'll just always use first available
    const avail = WEAPONS.findIndex(w=>w.qty>0);
    if(avail>=0){
      fireWeapon(avail);
    }
    // Also move randomly sometimes
    if(Math.random()>0.5){
      if(Math.random()>0.5){
        // Jump
        if(activeU.onGround) activeU.vy = -6 - Math.random()*3;
      } else {
        activeU.vx = (Math.random()>0.5?1:-1)*3;
      }
    }
  }
  // AI takes shorter time? We'll just let timer run
  // Actually AI should act quickly
  setTimeout(()=>{
    turnTimer = 2; // Force quick end
  }, 400);
}

// ============================================
// INPUT
// ============================================

window.addEventListener('mousemove', e=>{
  mouseX = e.clientX; mouseY = e.clientY;
  // Calculate aim relative to active unit
  if(gameState==='playing'){
    const activeU = units.find(u=>u.team===activeTeam && u.alive);
    if(activeU){
      const screenU = {x:(activeU.x-cameraX)*cameraScale+W/2, y:(activeU.y-cameraY*0.7)*cameraScale+H/2};
      aimAngle = Math.atan2(mouseY-screenU.y, mouseX-screenU.x);
      aimPower = 5 + Math.hypot(mouseX-screenU.x, mouseY-screenU.y)*0.3;
      aimPower = Math.min(aimPower, 40);
    }
  }
});

window.addEventListener('click', e=>{
  // Only handle clicks in menu or weapon buttons
});

window.addEventListener('keydown', e=>{
  if(gameState!=='playing') return;
  const activeU = units.find(u=>u.team===activeTeam && u.alive);
  if(!activeU) return;

  if(e.key==='ArrowLeft' || e.key==='a'){
    activeU.vx = -3; activeU.faceDir = -1;
    playSound('jump');
  }
  if(e.key==='ArrowRight' || e.key==='d'){
    activeU.vx = 3; activeU.faceDir = 1;
    playSound('jump');
  }
  if((e.key==='ArrowUp' || e.key==='w') && activeU.onGround){
    activeU.vy = -7 - Math.random()*2;
    activeU.onGround = false;
    playSound('jump');
  }
  if(e.key===' '){
    // Fire selected weapon (default index based on selection)
    fireWeapon(getSelectedWeaponIndex());
  }
  if(e.key==='1') selectWeapon(0);
  if(e.key==='2') selectWeapon(1);
  if(e.key==='3') selectWeapon(2);
  if(e.key==='4') selectWeapon(3);
  if(e.key==='5') selectWeapon(4);
  if(e.key==='6') selectWeapon(5);
  if(e.key==='7') selectWeapon(6);
});

let selectedWeapon = 0;
function getSelectedWeaponIndex(){ return selectedWeapon; }
function selectWeapon(i){
  selectedWeapon = i;
  document.querySelectorAll('.weapon-btn').forEach(b=>b.classList.remove('active'));
  document.getElementById('wbtn'+i).classList.add('active');
  playSound('click');
}

function fireWeapon(wi){
  const w = WEAPONS[wi];
  if(w.qty<=0 || gameState!=='playing') return;
  const activeU = units.find(u=>u.team===activeTeam && u.alive);
  if(!activeU) return;
  w.qty--;
  playSound('shoot');

  if(w.type==='indirect' || w.type==='highaoe'){
    spawnProjectile(wi, activeU.x, activeU.y-5, Math.cos(aimAngle)*aimPower*0.8, Math.sin(aimAngle)*aimPower*0.6, activeTeam);
  } else if(w.type==='direct'){
    // Instant ray
    const steps = 50;
    for(let s=0;s<steps;s++){
      const tx = activeU.x + Math.cos(aimAngle)*(10+s*8);
      const ty = activeU.y -5 + Math.sin(aimAngle)*(10+s*8);
      if(getTerrainAt(tx,ty)===1){
        spawnExplosion(tx,ty,20,30,activeTeam);
        break;
      }
      // Damage units along path
      for(let u of units){
        if(!u.alive || u.team===activeTeam) continue;
        const d=Math.hypot(u.x-tx, u.y-ty);
        if(d<6){
          u.hp-=w.damage;
          spawnFloatingText(u.x,u.y-8,'-'+w.damage,'#ff4444');
        }
      }
    }
  } else if(w.type==='utility'){
    if(w.name.includes('SHIELD')){
      activeU.shielded = true;
      setTimeout(()=>{ activeU.shielded=false; }, 3000);
    } else if(w.name.includes('TELEPORT')){
      activeU.x += Math.cos(aimAngle)*15;
      activeU.y += Math.sin(aimAngle)*15 - 10;
      spawnExplosion(activeU.x, activeU.y, 10, 0, activeTeam);
    }
  }
  // End turn faster if fired
  turnTimer = 5;
}

function updateWeaponUI(){
  const panel = document.getElementById('weaponPanel');
  panel.innerHTML = '';
  WEAPONS.forEach((w,i)=>{
    const btn = document.createElement('button');
    btn.className = 'weapon-btn' + (i===selectedWeapon?' active':'') + (w.qty<=0?' disabled':'');
    btn.id = 'wbtn'+i;
    btn.innerHTML = `<span class="w-name">${w.name}</span><span class="w-qty">${w.qty}</span>`;
    btn.onclick = () => { if(w.qty>0) selectWeapon(i); };
    panel.appendChild(btn);
  });
}

function cycleTeams(){
  teamCount = teamCount===2?4:(teamCount===4?2:2);
  document.getElementById('teamCount').textContent = teamCount;
  playSound('click');
}

function setTerrain(t){
  terrainType = t;
  document.querySelectorAll('.terrain-card').forEach(c=>c.classList.remove('active'));
  event.target.closest('.terrain-card').classList.add('active');
  playSound('click');
}

function startGame(){
  document.getElementById('mainOverlay').classList.add('hidden');
  gameState = 'playing';
  generateTerrain(terrainType);
  spawnUnits();
  updateWeaponUI();
  turnStartTime = Date.now();
  updateHUD();
  loop();
}

function updateHUD(){
  const hud = document.getElementById('teamsHUD');
  hud.innerHTML = '';
  for(let t=0;t<teamCount;t++){
    const alive = units.filter(u=>u.team===t && u.alive);
    const card = document.createElement('div');
    card.className = 'team-card' + (t===activeTeam?' active':'');
    card.innerHTML = `
      <div class="team-icon" style="color:${TEAM_COLORS[t]};background:${TEAM_COLORS[t]};box-shadow:0 0 10px ${TEAM_COLORS[t]};"></div>
      <div class="team-info">
        <div class="team-name">TEAM ${t+1}</div>
        <div class="team-health">${alive.length} ALIVE</div>
        <div class="team-units">HP: ${alive.map(u=>u.hp).reduce((a,b)=>a+b,0)}/400</div>
      </div>
    `;
    hud.appendChild(card);
  }
  // Unit stats
  const activeU = units.find(u=>u.team===activeTeam && u.alive);
  if(activeU){
    document.getElementById('unitHealth').textContent = Math.floor(activeU.hp);
    document.getElementById('unitPos').textContent = Math.round(activeU.x)+','+Math.round(activeU.y);
  }
}

// ============================================
// MAIN LOOP
// ============================================

let lastTime = 0;
function loop(timestamp){
  if(gameState!=='playing' && !document.getElementById('mainOverlay').classList.contains('hidden')){
    // Menu scene: just draw stars and simple background
    ctx.fillStyle='#0b0c15'; ctx.fillRect(0,0,W,H);
    ctx.save();
    ctx.translate(W/2, H/2);
    for(let s of stars){
      ctx.fillStyle=`rgba(255,255,255,${0.1+s.r/10})`;
      ctx.fillRect(s.x - cameraX*0.05 + W/2, s.y - cameraY*0.05 + H/2, s.r, s.r);
    }
    ctx.restore();
    requestAnimationFrame(loop);
    return;
  }

  const dt = (timestamp - lastTime) / 1000;
  lastTime = timestamp;

  updatePhysics(dt);
  updateCamera();
  draw();
  updateHUD();
  updateWeaponUI();

  if(gameState==='playing'){
    // Check game over
    const aliveTeams = new Set(units.filter(u=>u.alive).map(u=>u.team));
    if(aliveTeams.size<=1 && units.some(u=>u.alive)){
      gameState = 'gameover';
      playSound('victory');
      setTimeout(()=>{
        alert('TEAM '+(Array.from(aliveTeams)[0]+1)+' WINS!');
        document.getElementById('mainOverlay').classList.remove('hidden');
        gameState='menu';
      }, 2000);
    }
  }

  requestAnimationFrame(loop);
}

loop();
</script>
</body>
</html>
<!-- agent-meta {"model":"thinkingmachines/inkling","provider":"openrouter","persona":"full","sessionId":"2a7e24e4-63e9-4979-a7d6-589b07790998","tokensIn":643619,"tokensOut":17379,"tokensTotal":660998,"turns":16,"toolCalls":15,"failedToolCalls":1,"timestamp":"2026-07-18T22:00:39.513Z"} -->