[object Object]

← back to Model Arena

Add Games Arcade: /arcade gallery + /api/arcade auto-discovery of crowned game challenges, /game/:slug standalone champions, DW+AA idea pool + all-night loop harness

0e921bc9edc4daf97b45971e49e695e0cf6ebe2a · 2026-07-24 23:18:24 -0700 · Steve Abrams

Files touched

Diff

commit 0e921bc9edc4daf97b45971e49e695e0cf6ebe2a
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Fri Jul 24 23:18:24 2026 -0700

    Add Games Arcade: /arcade gallery + /api/arcade auto-discovery of crowned game challenges, /game/:slug standalone champions, DW+AA idea pool + all-night loop harness
---
 data/arcade-games.json                             |   6 +
 data/artifacts/4a6e2cfce746/gemma3-12b.html        | 297 +++++++++++++
 data/artifacts/4a6e2cfce746/gemma3-12b.png         | Bin 0 -> 9726 bytes
 data/artifacts/4a6e2cfce746/hermes3-8b.html        | 282 ++++++++++++
 data/artifacts/4a6e2cfce746/hermes3-8b.png         | Bin 0 -> 8442 bytes
 data/artifacts/4a6e2cfce746/hf-qwen-coder-32b.html | 209 +++++++++
 data/artifacts/4a6e2cfce746/hf-qwen-coder-32b.png  | Bin 0 -> 6338 bytes
 data/artifacts/4a6e2cfce746/qwen25-7b.html         |  98 +++++
 data/artifacts/4a6e2cfce746/qwen25-7b.png          | Bin 0 -> 5368 bytes
 data/artifacts/4a6e2cfce746/qwen3-14b.html         | 315 +++++++++++++
 data/artifacts/4a6e2cfce746/qwen3-14b.png          | Bin 0 -> 14748 bytes
 data/challenges.json                               | 223 ++++++++++
 fleet-defender.html                                | 490 +++++++++++++++++++++
 idea-run/.dw-aa-pointer                            |   1 +
 idea-run/dw-aa-challenges.txt                      |  16 +
 idea-run/run-next.sh                               |  28 ++
 public/arcade.html                                 | 146 ++++++
 star-clock.html => public/games/star-clock.html    |   0
 tetris.html => public/games/tetris.html            |   0
 public/index.html                                  |   1 +
 server.js                                          |  58 +++
 21 files changed, 2170 insertions(+)

diff --git a/data/arcade-games.json b/data/arcade-games.json
new file mode 100644
index 0000000..7198b05
--- /dev/null
+++ b/data/arcade-games.json
@@ -0,0 +1,6 @@
+{
+  "featured": [
+    { "slug": "tetris", "title": "Tetris", "credit": "claude-code", "note": "Arena champion — All-Models Tetris" },
+    { "slug": "star-clock", "title": "Constellation Clock", "credit": "claude-code", "note": "Arena champion — Free-Roster Constellation Clock" }
+  ]
+}
diff --git a/data/artifacts/4a6e2cfce746/gemma3-12b.html b/data/artifacts/4a6e2cfce746/gemma3-12b.html
new file mode 100644
index 0000000..4a9acf0
--- /dev/null
+++ b/data/artifacts/4a6e2cfce746/gemma3-12b.html
@@ -0,0 +1,297 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <title>Fleet Defender - AI Command Center</title>
+  <style>
+    body {
+      margin: 0;
+      overflow: hidden;
+      background-color: navy;
+      font-family: monospace;
+      display: flex;
+      justify-content: center;
+      align-items: center;
+      height: 100vh;
+    }
+
+    #gameCanvas {
+      border: 1px solid cyan;
+      background-color: navy; /* Ensure canvas background is navy */
+    }
+
+    #hud {
+      position: absolute;
+      top: 10px;
+      left: 10px;
+      font-size: 16px;
+      color: cyan;
+      text-shadow: 2px 2px 4px black;
+    }
+
+    #gameoverScreen {
+        position: absolute;
+        top: 50%;
+        left: 50%;
+        transform: translate(-50%, -50%);
+        background-color: rgba(0, 0, 60, 0.8);
+        padding: 20px;
+        border: 2px solid cyan;
+        text-align: center;
+        display: none; /* Hidden initially */
+    }
+
+  </style>
+</head>
+<body>
+  <div id="hud">Score: <span id="score">0</span> | Wave: <span id="wave">1</span> | Lives: <span id="lives">3</span></div>
+  <canvas id="gameCanvas" width="640" height="480"></canvas>
+
+  <div id="gameoverScreen">
+    <h1>Fleet Defender - Game Over</h1>
+    <p id="finalScore">Your Score: <span id="gameOverScore">0</span></p>
+    <button onclick="restartGame()">Restart</button>
+  </div>
+
+
+  <script>
+    const canvas = document.getElementById('gameCanvas');
+    const ctx = canvas.getContext('2d');
+    ctx.canvas.width = 640;
+    ctx.canvas.height = 480;
+
+    let score = 0;
+    let wave = 1;
+    let lives = 3;
+
+    // Player Ship
+    const player = {
+      x: canvas.width / 2,
+      y: canvas.height - 50,
+      width: 30,
+      height: 20,
+      speed: 5,
+      color: 'cyan'
+    };
+
+    // Bug Enemies
+    const bugs = [];
+
+    // Projectiles (Player Shots)
+    const projectiles = [];
+
+    // Particle Effects
+    const particles = [];
+
+
+    function update() {
+        if(lives <= 0){
+            showGameOver();
+            return;
+        }
+      // Player Movement
+      if (keys['ArrowLeft']) player.x -= player.speed;
+      if (keys['ArrowRight']) player.x += player.speed;
+
+      player.x = Math.max(0, Math.min(player.x, canvas.width - player.width));
+
+
+      // Bug Spawning
+      if (Math.random() < 0.01 * wave) { // Increase spawn rate with wave
+        bugs.push({
+          x: Math.random() * canvas.width,
+          y: 0,
+          width: 20,
+          height: 20,
+          speed: 1 + wave * 0.5,  //Increase speed with wave
+          color: 'red'
+        });
+      }
+
+      // Update Bugs
+      for (let i = bugs.length - 1; i >= 0; i--) {
+        bugs[i].y += bugs[i].speed;
+        if (bugs[i].y > canvas.height) {
+          bugs.splice(i, 1);
+          lives--;
+            updateLivesDisplay();
+        }
+      }
+
+      // Update Projectiles
+      for (let i = projectiles.length - 1; i >= 0; i--) {
+        projectiles[i].y -= 8; //projectile speed
+        if (projectiles[i].y < 0) {
+          projectiles.splice(i, 1);
+        }
+      }
+
+      // Collision Detection - Player/Bug
+      for (let i = bugs.length - 1; i >= 0; i--) {
+        if (
+          player.x < bugs[i].x + bugs[i].width &&
+          player.x + player.width > bugs[i].x &&
+          player.y < bugs[i].y + bugs[i].height &&
+          player.y + player.height > bugs[i].y
+        ) {
+           bugs.splice(i, 1);
+            lives--;
+            updateLivesDisplay();
+        }
+      }
+
+      // Collision Detection - Projectile/Bug
+      for (let i = projectiles.length - 1; i >= 0; i--) {
+        for (let j = bugs.length - 1; j >= 0; j--) {
+          if (
+            projectiles[i].x < bugs[j].x + bugs[j].width &&
+            projectiles[i].x + projectiles[i].width > bugs[j].x &&
+            projectiles[i].y < bugs[j].y + bugs[j].height &&
+            projectiles[i].y + projectiles[i].height > bugs[j].y
+          ) {
+            // Collision!
+            score += 10 * wave;
+            updateScoreDisplay();
+
+            // Create Explosion Particles
+            const numParticles = 20;
+            for (let k = 0; k < numParticles; k++) {
+              particles.push({
+                x: bugs[j].x + Math.random() * bugs[j].width,
+                y: bugs[j].y + Math.random() * bugs[j].height,
+                size: Math.random() * 5,
+                color: 'white',
+                alpha: 1
+              });
+            }
+
+            bugs.splice(j, 1);
+            projectiles.splice(i, 1);
+            break; // Important to break inner loop after collision
+          }
+        }
+      }
+
+    // Update Particles (Explosions)
+    for (let i = particles.length - 1; i >= 0; i--) {
+      particles[i].alpha -= 0.05;
+      if (particles[i].alpha <= 0) {
+        particles.splice(i, 1);
+      }
+    }
+
+      //Increase Wave Every X Seconds
+      const startTime = Date.now();
+      if(Date.now() - startTime > 10000){ //every 10 seconds increase wave
+          wave++;
+          updateWaveDisplay();
+      }
+
+    }
+
+
+
+    function draw() {
+      ctx.clearRect(0, 0, canvas.width, canvas.height);
+
+
+      // Draw Player
+      ctx.fillStyle = player.color;
+      ctx.fillRect(player.x, player.y, player.width, player.height);
+
+      // Draw Bugs
+      for (const bug of bugs) {
+        ctx.fillStyle = bug.color;
+        ctx.fillRect(bug.x, bug.y, bug.width, bug.height);
+      }
+
+      // Draw Projectiles
+      for (const projectile of projectiles) {
+        ctx.fillStyle = 'cyan';
+        ctx.fillRect(projectile.x, projectile.y, 3, 5); // Thin rectangle for projectile
+      }
+
+
+      // Draw Particles
+      for (const particle of particles) {
+        ctx.globalAlpha = particle.alpha;
+        ctx.fillStyle = particle.color;
+        ctx.beginPath();
+        ctx.arc(particle.x, particle.y, particle.size, 0, Math.PI * 2);
+        ctx.fill();
+        ctx.globalAlpha = 1; // Reset alpha
+      }
+
+
+    }
+
+    function fire() {
+      projectiles.push({
+        x: player.x + player.width / 2 - 3,
+        y: player.y
+      });
+    }
+
+    function updateDisplay(){
+         document.getElementById('score').textContent = score;
+         document.getElementById('wave').textContent = wave;
+         document.getElementById('lives').textContent = lives;
+    }
+
+    function updateScoreDisplay(){
+        document.getElementById('score').textContent = score;
+    }
+
+    function updateWaveDisplay(){
+       document.getElementById('wave').textContent = wave;
+    }
+
+    function updateLivesDisplay(){
+        document.getElementById('lives').textContent = lives;
+    }
+
+
+    let keys = {};
+    window.addEventListener('keydown', (e) => {
+      keys[e.key] = true;
+      if (e.code === 'Space') {
+        fire();
+      }
+    });
+
+    window.addEventListener('keyup', (e) => {
+      keys[e.key] = false;
+    });
+
+
+     function restartGame() {
+        score = 0;
+        wave = 1;
+        lives = 3;
+        bugs.length = 0;
+        projectiles.length = 0;
+        particles.length = 0;
+
+        updateDisplay();
+        document.getElementById('gameoverScreen').style.display = 'none';
+    }
+
+    function showGameOver(){
+       document.getElementById('gameOverScore').textContent = score;
+       document.getElementById('gameoverScreen').style.display = 'block';
+
+    }
+
+
+    let animationFrameId;
+
+    function gameLoop() {
+      update();
+      draw();
+      animationFrameId = requestAnimationFrame(gameLoop);
+    }
+
+
+    gameLoop(); //Start the Game Loop
+  </script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/4a6e2cfce746/gemma3-12b.png b/data/artifacts/4a6e2cfce746/gemma3-12b.png
new file mode 100644
index 0000000..afe3037
Binary files /dev/null and b/data/artifacts/4a6e2cfce746/gemma3-12b.png differ
diff --git a/data/artifacts/4a6e2cfce746/hermes3-8b.html b/data/artifacts/4a6e2cfce746/hermes3-8b.html
new file mode 100644
index 0000000..a77e77d
--- /dev/null
+++ b/data/artifacts/4a6e2cfce746/hermes3-8b.html
@@ -0,0 +1,282 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <title>FLEET DEFENDER</title>
+  <style>
+    body {
+      margin: 0;
+      padding: 0;
+      background-color: #00008B;
+      overflow: hidden;
+    }
+    
+    canvas {
+      display: block;
+      background-color: #00FF00;
+    }
+    
+    .hud {
+      position: absolute;
+      top: 0;
+      left: 0;
+      right: 0;
+      bottom: 0;
+      display: flex;
+      justify-content: space-around;
+      align-items: center;
+      color: #FFA500;
+      font-size: 24px;
+    }
+    
+    .game-over {
+      position: absolute;
+      top: 50%;
+      left: 50%;
+      transform: translate(-50%, -50%);
+      font-size: 48px;
+      text-align: center;
+      color: #FFA500;
+    }
+  </style>
+</head>
+<body>
+  <canvas id="gameCanvas"></canvas>
+  <div class="hud">
+    <span id="score">Score: 0</span>
+    <span id="wave">Wave: 1</span>
+    <span id="lives">Lives: 3</span>
+  </div>
+  <div class="game-over" style="display: none;">Game Over! Click to Restart.</div>
+
+  <script>
+    const canvas = document.getElementById('gameCanvas');
+    const ctx = canvas.getContext('2d');
+
+    canvas.width = window.innerWidth * 0.8;
+    canvas.height = window.innerHeight * 0.7;
+
+    let score = 0;
+    let wave = 1;
+    let lives = 3;
+    let gameLoop;
+
+    class GameObject {
+      constructor(x, y, width, height, color) {
+        this.x = x;
+        this.y = y;
+        this.width = width;
+        this.height = height;
+        this.color = color;
+      }
+
+      draw() {
+        ctx.fillStyle = this.color;
+        ctx.fillRect(this.x, this.y, this.width, this.height);
+      }
+    }
+
+    class PlayerShip extends GameObject {
+      constructor() {
+        super(canvas.width / 2 - 25, canvas.height * 0.8, 50, 50, 'cyan');
+      }
+
+      moveLeft() {
+        if (this.x > 0) {
+          this.x -= 5;
+        }
+      }
+
+      moveRight() {
+        if (this.x < canvas.width - 50) {
+          this.x += 5;
+        }
+      }
+
+      draw() {
+        super.draw();
+        ctx.fillStyle = 'yellow';
+        ctx.fillRect(this.x + 15, this.y + 25, 20, 10);
+        ctx.fillRect(this.x + 30, this.y + 25, 20, 10);
+      }
+    }
+
+    class Bug extends GameObject {
+      constructor(x, y) {
+        super(x, y, 50, 50, 'red');
+      }
+    }
+
+    class Explosion extends GameObject {
+      constructor(x, y) {
+        super(x, y, 0, 0, 'yellow');
+        this.width = 20;
+        this.height = 20;
+        this.frame = 0;
+        this.frames = 5;
+        this.time = 0;
+        this.speed = 10;
+      }
+
+      update() {
+        if (this.frame === 0) {
+          this.width = Math.floor(this.width * 1.2);
+          this.height = Math.floor(this.height * 1.2);
+        }
+        if (++this.time === this.speed) {
+          this.frame++;
+          this.time = 0;
+        }
+        if (this.frame >= this.frames) {
+          this.width = 0;
+          this.height = 0;
+        }
+      }
+
+      draw() {
+        super.draw();
+        this.update();
+        ctx.fillRect(this.x, this.y, this.width, this.height);
+      }
+    }
+
+    const playerShip = new PlayerShip();
+    let bugs = [];
+    let explosions = [];
+
+    function createBug() {
+      const x = Math.random() * canvas.width;
+      const y = -Math.random() * 500;
+      const bugSize = Math.random() * 0.5 + 0.5;
+      bugs.push(new Bug(x, y));
+      bugs[bugs.length - 1].height *= bugSize;
+      bugs[bugs.length - 1].width *= bugSize;
+    }
+
+    function collisionDetection() {
+      for (let i = bugs.length - 1; i >= 0; i--) {
+        const bug = bugs[i];
+        if (
+          playerShip.x < bug.x + bug.width &&
+          playerShip.x + playerShip.width > bug.x &&
+          playerShip.y < bug.y + bug.height &&
+          playerShip.y + playerShip.height > bug.y
+        ) {
+          explosions.push(new Explosion(bug.x, bug.y));
+          bugs.splice(i, 1);
+          lives--;
+          if (lives === 0) {
+            stopGame();
+            return;
+          }
+        }
+      }
+    }
+
+    function drawBugs() {
+      for (const bug of bugs) {
+        bug.draw();
+      }
+    }
+
+    function checkBugBounds() {
+      for (let i = bugs.length - 1; i >= 0; i--) {
+        const bug = bugs[i];
+        if (bug.y + bug.height > canvas.height) {
+          explosions.push(new Explosion(bug.x, bug.y));
+          bugs.splice(i, 1);
+          score += 10;
+        }
+      }
+    }
+
+    function drawExplosions() {
+      for (const explosion of explosions) {
+        explosion.draw();
+      }
+    }
+
+    function stopGame() {
+      clearInterval(gameLoop);
+      document.querySelector('.game-over').style.display = 'flex';
+      document.querySelector('.game-over').addEventListener('click', resetGame);
+    }
+
+    function resetGame() {
+      location.reload();
+    }
+
+    function startGame() {
+      wave++;
+      score = 0;
+      lives = 3;
+      bugs = [];
+      explosions = [];
+
+      for (let i = 0; i < wave * 2; i++) {
+        createBug();
+      }
+      
+      gameLoop = setInterval(gameEngine, 20);
+    }
+
+    function gameEngine() {
+      ctx.clearRect(0, 0, canvas.width, canvas.height);
+
+      playerShip.draw();
+
+      drawBugs();
+
+      checkBugBounds();
+
+      collisionDetection();
+
+      drawExplosions();
+
+      ctx.fillStyle = 'cyan';
+      ctx.fillRect(playerShip.x, playerShip.y - 70, 50, 5);
+      ctx.fillRect(playerShip.x + 25, playerShip.y - 80, 10, 15);
+
+      for (const bug of bugs) {
+        if (
+          playerShip.x < bug.x + bug.width &&
+          playerShip.x + playerShip.width > bug.x &&
+          playerShip.y - 70 < bug.y + bug.height &&
+          playerShip.y - 70 + 5 > bug.y
+        ) {
+          score += 10;
+        }
+      }
+
+      document.getElementById('score').textContent = `Score: ${score}`;
+      document.getElementById('wave').textContent = `Wave: ${wave}`;
+      document.getElementById('lives').textContent = `Lives: ${lives}`;
+
+      if (bugs.length === 0) {
+        stopGame();
+      } else if (lives === 0) {
+        document.querySelector('.game-over').style.display = 'flex';
+      }
+    }
+
+    document.addEventListener('keydown', event => {
+      switch (event.key) {
+        case 'ArrowLeft':
+          playerShip.moveLeft();
+          break;
+        case 'ArrowRight':
+          playerShip.moveRight();
+          break;
+        case ' ':
+          if (bugs.length > 0 && bugs[0].y + bugs[0].height < playerShip.y) {
+            explosions.push(new Explosion(bugs[0].x, bugs[0].y));
+            bugs.splice(0, 1);
+            score += 50;
+          }
+          break;
+      }
+    });
+
+    startGame();
+  </script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/4a6e2cfce746/hermes3-8b.png b/data/artifacts/4a6e2cfce746/hermes3-8b.png
new file mode 100644
index 0000000..3bc899e
Binary files /dev/null and b/data/artifacts/4a6e2cfce746/hermes3-8b.png differ
diff --git a/data/artifacts/4a6e2cfce746/hf-qwen-coder-32b.html b/data/artifacts/4a6e2cfce746/hf-qwen-coder-32b.html
new file mode 100644
index 0000000..11e52d7
--- /dev/null
+++ b/data/artifacts/4a6e2cfce746/hf-qwen-coder-32b.html
@@ -0,0 +1,209 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<title>FLEET DEFENDER</title>
+<style>
+body { margin: 0; overflow: hidden; display: flex; justify-content: center; align-items: center; background: #002B36; color: cyan; font-family: 'Courier New', Courier, monospace; }
+canvas { border: 4px solid #586E75; }
+#hud { position: absolute; top: 10px; left: 10px; }
+#gameOver { display: none; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); background: rgba(0, 43, 54, 0.8); padding: 20px; border: 2px solid cyan; text-align: center; }
+</style>
+</head>
+<body>
+<div id="hud">
+    Score: <span id="score">0</span><br>
+    Wave: <span id="wave">1</span><br>
+    Lives: <span id="lives">3</span>
+</div>
+<canvas id="gameCanvas" width="800" height="600"></canvas>
+<div id="gameOver">
+    Game Over<br>
+    Score: <span id="finalScore">0</span><br>
+    Press Space to Restart
+</div>
+<script>
+const canvas = document.getElementById('gameCanvas');
+const ctx = canvas.getContext('2d');
+const scoreDisplay = document.getElementById('score');
+const waveDisplay = document.getElementById('wave');
+const livesDisplay = document.getElementById('lives');
+const finalScoreDisplay = document.getElementById('finalScore');
+const gameOverScreen = document.getElementById('gameOver');
+
+let score = 0;
+let wave = 1;
+let lives = 3;
+let lastTime = 0;
+let ship = { x: canvas.width / 2, y: canvas.height - 50, width: 40, height: 20, speed: 5 };
+let bullets = [];
+let enemies = [];
+let explosions = [];
+
+const keys = {
+    ArrowLeft: false,
+    ArrowRight: false,
+    Spacebar: false
+};
+
+window.addEventListener('keydown', function(e) { keys[e.key] = true; });
+window.addEventListener('keyup', function(e) { keys[e.key] = false; });
+
+function spawnEnemies() {
+    for (let i = 0; i < wave * 3; i++) {
+        enemies.push({ x: Math.random() * canvas.width, y: -Math.random() * 100, width: 20, height: 20, speedY: 2 + wave / 5 });
+    }
+}
+
+function drawShip() {
+    ctx.fillStyle = 'cyan';
+    ctx.fillRect(ship.x, ship.y, ship.width, ship.height);
+    ctx.beginPath();
+    ctx.moveTo(ship.x - 10, ship.y);
+    ctx.lineTo(ship.x + 20, ship.y + 10);
+    ctx.lineTo(ship.x - 10, ship.y + 20);
+    ctx.closePath();
+    ctx.fill();
+}
+
+function drawBullets() {
+    bullets.forEach(bullet => {
+        ctx.fillStyle = 'amber';
+        ctx.fillRect(bullet.x, bullet.y, bullet.width, bullet.height);
+    });
+}
+
+function moveShip() {
+    if (keys.ArrowLeft) ship.x -= ship.speed;
+    if (keys.ArrowRight) ship.x += ship.speed;
+    if (ship.x < 0) ship.x = 0;
+    if (ship.x + ship.width > canvas.width) ship.x = canvas.width - ship.width;
+}
+
+function moveBullets() {
+    bullets.forEach(bullet => bullet.y -= 5);
+    bullets = bullets.filter(bullet => bullet.y > 0);
+}
+
+function spawnBullet() {
+    if (!keys.Spacebar) return;
+    keys.Spacebar = false;
+    bullets.push({ x: ship.x + ship.width / 2 - 1, y: ship.y - 5, width: 2, height: 10 });
+}
+
+function drawEnemies() {
+    enemies.forEach(enemy => {
+        ctx.fillStyle = 'red';
+        ctx.fillRect(enemy.x, enemy.y, enemy.width, enemy.height);
+    });
+}
+
+function moveEnemies() {
+    enemies.forEach(enemy => enemy.y += enemy.speedY);
+    enemies = enemies.filter(enemy => enemy.y < canvas.height);
+}
+
+function checkCollisions() {
+    bullets.forEach((bullet, index) => {
+        enemies.forEach((enemy, eIndex) => {
+            if (bullet.x < enemy.x + enemy.width &&
+                bullet.x + bullet.width > enemy.x &&
+                bullet.y < enemy.y + enemy.height &&
+                bullet.y + bullet.height > enemy.y) {
+                score += 10;
+                bullets.splice(index, 1);
+                enemies.splice(eIndex, 1);
+                createExplosion(enemy.x + enemy.width / 2, enemy.y + enemy.height / 2);
+            }
+        });
+    });
+
+    enemies.forEach((enemy, index) => {
+        if (enemy.y + enemy.height > ship.y && 
+            enemy.x < ship.x + ship.width &&
+            enemy.x + enemy.width > ship.x) {
+            lives--;
+            if (lives <= 0) endGame();
+            else enemies.splice(index, 1);
+        }
+    });
+}
+
+function createExplosion(x, y) {
+    explosions.push({ x: x, y: y, radius: 2, maxRadius: 20 + Math.random() * 10, color: `rgba(255, 69, 0, ${Math.random()})` });
+}
+
+function drawExplosions() {
+    explosions.forEach(explosion => {
+        ctx.beginPath();
+        ctx.arc(explosion.x, explosion.y, explosion.radius, 0, Math.PI * 2);
+        ctx.fillStyle = explosion.color;
+        ctx.fill();
+        explosion.radius += 1.5;
+    });
+    explosions = explosions.filter(explosion => explosion.radius <= explosion.maxRadius);
+}
+
+function nextWave() {
+    wave++;
+    spawnEnemies();
+}
+
+function endGame() {
+    gameOverScreen.style.display = 'block';
+    finalScoreDisplay.textContent = score;
+    window.removeEventListener('keydown', function(e) { keys[e.key] = true; });
+    window.addEventListener('keydown', restart);
+}
+
+function restart(e) {
+    if (e.code === 'Space') {
+        lives = 3;
+        wave = 1;
+        score = 0;
+        bullets = [];
+        enemies = [];
+        explosions = [];
+        spawnEnemies();
+        gameOverScreen.style.display = 'none';
+        window.removeEventListener('keydown', restart);
+        requestAnimationFrame(gameLoop);
+    }
+}
+
+function gameLoop(currentTime) {
+    if (!lastTime) lastTime = currentTime;
+    let deltaTime = (currentTime - lastTime) / 1000;
+
+    ctx.clearRect(0, 0, canvas.width, canvas.height);
+
+    moveShip();
+    drawShip();
+
+    spawnBullet();
+    moveBullets();
+    drawBullets();
+
+    drawEnemies();
+    moveEnemies();
+
+    checkCollisions();
+
+    drawExplosions();
+
+    scoreDisplay.textContent = score;
+    waveDisplay.textContent = wave;
+    livesDisplay.textContent = lives;
+
+    if (enemies.length === 0) nextWave();
+
+    lastTime = currentTime;
+    requestAnimationFrame(gameLoop);
+}
+
+spawnEnemies();
+requestAnimationFrame(gameLoop);
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/4a6e2cfce746/hf-qwen-coder-32b.png b/data/artifacts/4a6e2cfce746/hf-qwen-coder-32b.png
new file mode 100644
index 0000000..c1300a3
Binary files /dev/null and b/data/artifacts/4a6e2cfce746/hf-qwen-coder-32b.png differ
diff --git a/data/artifacts/4a6e2cfce746/qwen25-7b.html b/data/artifacts/4a6e2cfce746/qwen25-7b.html
new file mode 100644
index 0000000..efdf237
--- /dev/null
+++ b/data/artifacts/4a6e2cfce746/qwen25-7b.html
@@ -0,0 +1,98 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>FLEET DEFENDER</title>
+    <style>
+        body { margin: 0; overflow: hidden; }
+        canvas { display: block; }
+        .hud {
+            position: absolute;
+            left: 5px; bottom: 20px; font-size: 18px; color: cyan;
+            pointer-events: none;
+        }
+    </style>
+</head>
+<body>
+<canvas id="gameCanvas" width="800" height="600"></canvas>
+<div class="hud">Score: <span id="score">0</span><br>Wave: <span id="wave">1</span><br>Lives: <span id="lives">3</span></div>
+
+<script>
+const canvas = document.getElementById('gameCanvas');
+const ctx = canvas.getContext('2d');
+
+canvas.width = 800;
+canvas.height = 600;
+
+let shipX = canvas.width / 2 - 15, shipY = 570, score = 0, wave = 1, lives = 3, bullets = [], bugs = [], bugCount = 5, bugSpeed = 4, bulletSpeed = 8, hitEffect = [];
+const bg = new ImageData(new Uint8ClampedArray(canvas.width * canvas.height * 4), canvas.width, canvas.height);
+bg.data.set([0, 255, 0, 255].repeat(bg.width * bg.height));
+
+function draw() {
+    ctx.putImageData(bg, 0, 0);
+    ctx.fillStyle = 'cyan';
+    ctx.fillRect(10, 10, 180, 40);
+
+    for (let i = 0; i < bullets.length; i++) {
+        const { x, y } = bullets[i];
+        ctx.fillStyle = 'white';
+        ctx.fillRect(x - 2, y - 2, 4, 4);
+        if (++bullets[i].y > canvas.height) bullets.splice(i--, 1);
+    }
+
+    for (let i = bugs.length - 1; i >= 0; --i) {
+        const { x, y } = bugs[i];
+        ctx.fillStyle = 'red';
+        ctx.fillRect(x - 5, y - 5, 10, 10);
+        if (++bugs[i].y > canvas.height) {
+            score += 10;
+            bugs.splice(i--, 1);
+        }
+    }
+
+    for (let i = hitEffect.length - 1; i >= 0; --i) {
+        const { x, y } = hitEffect[i];
+        ctx.fillStyle = 'yellow';
+        ctx.fillRect(x - 2, y - 2, 4, 4);
+        if (++hitEffect[i].y > canvas.height) hitEffect.splice(i--, 1);
+    }
+
+    ctx.fillStyle = 'white';
+    ctx.fillText(`Score: ${score}`, 20, 35);
+    ctx.fillText(`Wave: ${wave}`, 20, 60);
+    ctx.fillText(`Lives: ${lives}`, 20, 85);
+
+    if (bugs.length === 0) {
+        wave++;
+        bugCount += 1;
+        bugs = Array(bugCount).fill().map(() => ({ x: Math.random() * canvas.width, y: -30 }));
+    }
+
+    if (!lives || !bugs.length && wave > 1) endGame();
+
+    ctx.fillStyle = 'cyan';
+    ctx.fillRect(canvas.width / 2 - 15, shipY, 30, 10);
+
+    requestAnimationFrame(draw);
+}
+
+function moveShip(e) {
+    const speed = 6;
+    if (e.key === 'ArrowLeft') shipX -= speed;
+    else if (e.key === 'ArrowRight') shipX += speed;
+    if (shipX < 15) shipX = 15;
+    if (shipX > canvas.width - 30) shipX = canvas.width - 30;
+}
+
+function shootBullet() {
+    bullets.push({ x: shipX + 12, y: shipY });
+}
+
+document.addEventListener('keydown', moveShip);
+document.addEventListener('keyup', e => { if (e.key === 'Space') shootBullet(); });
+
+draw();
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/4a6e2cfce746/qwen25-7b.png b/data/artifacts/4a6e2cfce746/qwen25-7b.png
new file mode 100644
index 0000000..10fdc55
Binary files /dev/null and b/data/artifacts/4a6e2cfce746/qwen25-7b.png differ
diff --git a/data/artifacts/4a6e2cfce746/qwen3-14b.html b/data/artifacts/4a6e2cfce746/qwen3-14b.html
new file mode 100644
index 0000000..d6a2819
--- /dev/null
+++ b/data/artifacts/4a6e2cfce746/qwen3-14b.html
@@ -0,0 +1,315 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="UTF-8" />
+  <title>Fleet Defender</title>
+  <style>
+    html, body {
+      margin: 0;
+      padding: 0;
+      overflow: hidden;
+      background: #0a0f2c;
+      color: #00ffcc;
+      font-family: 'Courier New', monospace;
+      display: flex;
+      justify-content: center;
+      align-items: center;
+      height: 100vh;
+      user-select: none;
+    }
+    canvas {
+      display: block;
+      background: #0a0f2c;
+    }
+    .hud {
+      position: absolute;
+      top: 10px;
+      left: 10px;
+      font-size: 20px;
+      color: #00ffcc;
+      text-shadow: 0 0 10px #00ffcc;
+    }
+    .game-over {
+      position: absolute;
+      top: 50%;
+      left: 50%;
+      transform: translate(-50%, -50%);
+      font-size: 40px;
+      text-align: center;
+      color: #ffcc00;
+      text-shadow: 0 0 15px #ffcc00;
+    }
+    .restart {
+      position: absolute;
+      top: 60%;
+      left: 50%;
+      transform: translate(-50%, -50%);
+      font-size: 24px;
+      color: #00ffcc;
+      text-shadow: 0 0 10px #00ffcc;
+      cursor: pointer;
+    }
+  </style>
+</head>
+<body>
+  <canvas id="gameCanvas"></canvas>
+  <div class="hud" id="hud">Score: 0 | Wave: 1 | Lives: 3</div>
+  <div class="game-over" id="gameOver" style="display: none;">Game Over</div>
+  <div class="restart" id="restart" style="display: none;" onclick="init()">Press Space to Restart</div>
+  <script>
+    const canvas = document.getElementById('gameCanvas');
+    const ctx = canvas.getContext('2d');
+    const hud = document.getElementById('hud');
+    const gameOver = document.getElementById('gameOver');
+    const restart = document.getElementById('restart');
+
+    let width = canvas.width = window.innerWidth;
+    let height = canvas.height = window.innerHeight;
+    let score = 0;
+    let wave = 1;
+    let lives = 3;
+    let gameOverState = false;
+
+    const ship = {
+      x: width / 2,
+      y: height - 50,
+      width: 40,
+      height: 20,
+      speed: 5,
+      color: '#00ffff',
+      bullets: []
+    };
+
+    const bugs = [];
+    let spawnTimer = 0;
+    let spawnInterval = 1000;
+    let bugSpeed = 2;
+    let waveTimer = 0;
+    let waveInterval = 5000;
+
+    const explosions = [];
+
+    const keys = {};
+
+    window.addEventListener('keydown', (e) => {
+      keys[e.code] = true;
+    });
+
+    window.addEventListener('keyup', (e) => {
+      keys[e.code] = false;
+    });
+
+    function init() {
+      score = 0;
+      wave = 1;
+      lives = 3;
+      gameOverState = false;
+      gameOver.style.display = 'none';
+      restart.style.display = 'none';
+      hud.style.display = 'block';
+      bugs.length = 0;
+      explosions.length = 0;
+      ship.bullets.length = 0;
+      spawnTimer = 0;
+      waveTimer = 0;
+      requestAnimationFrame(gameLoop);
+    }
+
+    function spawnBug() {
+      const size = 20 + Math.random() * 10;
+      const x = Math.random() * (width - size);
+      bugs.push({
+        x,
+        y: -size,
+        width: size,
+        height: size,
+        speed: bugSpeed,
+        color: '#ff4444'
+      });
+    }
+
+    function spawnWave() {
+      for (let i = 0; i < wave * 2; i++) {
+        spawnBug();
+      }
+    }
+
+    function fireBullet() {
+      ship.bullets.push({
+        x: ship.x + ship.width / 2 - 2,
+        y: ship.y,
+        width: 4,
+        height: 15,
+        speed: 7,
+        color: '#00ffcc'
+      });
+    }
+
+    function update() {
+      // Move ship
+      if (keys['ArrowLeft']) {
+        ship.x -= ship.speed;
+        if (ship.x < 0) ship.x = 0;
+      }
+      if (keys['ArrowRight']) {
+        ship.x += ship.speed;
+        if (ship.x + ship.width > width) ship.x = width - ship.width;
+      }
+      if (keys['Space']) {
+        fireBullet();
+      }
+
+      // Update bullets
+      for (let i = ship.bullets.length - 1; i >= 0; i--) {
+        const bullet = ship.bullets[i];
+        bullet.y -= bullet.speed;
+        if (bullet.y < 0) {
+          ship.bullets.splice(i, 1);
+        }
+      }
+
+      // Update bugs
+      for (let i = bugs.length - 1; i >= 0; i--) {
+        const bug = bugs[i];
+        bug.y += bug.speed;
+
+        // Collision with bullet
+        for (let j = ship.bullets.length - 1; j >= 0; j--) {
+          const bullet = ship.bullets[j];
+          if (
+            bullet.x < bug.x + bug.width &&
+            bullet.x + bullet.width > bug.x &&
+            bullet.y < bug.y + bug.height &&
+            bullet.y + bullet.height > bug.y
+          ) {
+            explosions.push({
+              x: bug.x + bug.width / 2,
+              y: bug.y + bug.height / 2,
+              size: 20,
+              color: '#ffcc00',
+              alpha: 1,
+              duration: 0.5
+            });
+            score += 10;
+            ship.bullets.splice(j, 1);
+            bugs.splice(i, 1);
+            break;
+          }
+        }
+
+        // Collision with ship
+        if (
+          bug.x < ship.x + ship.width &&
+          bug.x + bug.width > ship.x &&
+          bug.y < ship.y + ship.height &&
+          bug.y + bug.height > ship.y
+        ) {
+          explosions.push({
+            x: ship.x + ship.width / 2,
+            y: ship.y + ship.height / 2,
+            size: 20,
+            color: '#ff4444',
+            alpha: 1,
+            duration: 0.5
+          });
+          lives--;
+          bugs.splice(i, 1);
+          if (lives <= 0) {
+            gameOverState = true;
+            gameOver.style.display = 'block';
+            restart.style.display = 'block';
+            hud.style.display = 'none';
+          }
+        }
+
+        // Remove bug if off screen
+        if (bug.y > height) {
+          bugs.splice(i, 1);
+        }
+      }
+
+      // Update explosions
+      for (let i = explosions.length - 1; i >= 0; i--) {
+        const exp = explosions[i];
+        exp.alpha -= 0.05;
+        if (exp.alpha <= 0) {
+          explosions.splice(i, 1);
+        }
+      }
+
+      // Spawn timer
+      spawnTimer += 1;
+      if (spawnTimer >= spawnInterval) {
+        spawnBug();
+        spawnTimer = 0;
+      }
+
+      // Wave timer
+      waveTimer += 1;
+      if (waveTimer >= waveInterval) {
+        wave++;
+        spawnWave();
+        waveTimer = 0;
+        spawnInterval = Math.max(200, spawnInterval * 0.95);
+        bugSpeed += 0.1;
+      }
+
+      // Update HUD
+      hud.textContent = `Score: ${score} | Wave: ${wave} | Lives: ${lives}`;
+    }
+
+    function draw() {
+      ctx.clearRect(0, 0, width, height);
+
+      // Draw ship
+      ctx.fillStyle = ship.color;
+      ctx.fillRect(ship.x, ship.y, ship.width, ship.height);
+
+      // Draw bullets
+      for (const bullet of ship.bullets) {
+        ctx.fillStyle = bullet.color;
+        ctx.fillRect(bullet.x, bullet.y, bullet.width, bullet.height);
+      }
+
+      // Draw bugs
+      for (const bug of bugs) {
+        ctx.fillStyle = bug.color;
+        ctx.fillRect(bug.x, bug.y, bug.width, bug.height);
+      }
+
+      // Draw explosions
+      for (const exp of explosions) {
+        ctx.fillStyle = `rgba(${exp.color.slice(1)}, ${exp.alpha})`;
+        ctx.beginPath();
+        ctx.arc(exp.x, exp.y, exp.size, 0, Math.PI * 2);
+        ctx.fill();
+      }
+
+      // Draw grid
+      ctx.strokeStyle = '#00ffcc';
+      ctx.lineWidth = 1;
+      for (let i = 0; i < 10; i++) {
+        ctx.beginPath();
+        ctx.moveTo(0, i * (height / 10));
+        ctx.lineTo(width, i * (height / 10));
+        ctx.stroke();
+      }
+      for (let i = 0; i < 10; i++) {
+        ctx.beginPath();
+        ctx.moveTo(i * (width / 10), 0);
+        ctx.lineTo(i * (width / 10), height);
+        ctx.stroke();
+      }
+    }
+
+    function gameLoop() {
+      if (gameOverState) return;
+      update();
+      draw();
+      requestAnimationFrame(gameLoop);
+    }
+
+    init();
+  </script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/4a6e2cfce746/qwen3-14b.png b/data/artifacts/4a6e2cfce746/qwen3-14b.png
new file mode 100644
index 0000000..e690a49
Binary files /dev/null and b/data/artifacts/4a6e2cfce746/qwen3-14b.png differ
diff --git a/data/challenges.json b/data/challenges.json
index d6a3a2d..cc7e775 100644
--- a/data/challenges.json
+++ b/data/challenges.json
@@ -11315,5 +11315,228 @@
     "judging": false,
     "aiPick": "qwen3-14b",
     "judged_at": "2026-07-23T19:54:52.473Z"
+  },
+  {
+    "id": "4a6e2cfce746",
+    "title": "Agent Abrams — Fleet Defender",
+    "prompt": "Build a single self-contained HTML file: a playable arcade game called FLEET DEFENDER themed as an AI-agent command center. The player pilots a glowing command ship at the bottom, moving left/right with arrow keys and firing with spacebar to defend the fleet from waves of descending 'bug' invaders. Neon command-center aesthetic (dark navy background, cyan/amber HUD, agent-node grid). Show score, wave number, and lives. Increasing difficulty per wave, particle explosions on hits, and a game-over + restart screen. 60fps canvas, keyboard controls, no external assets. Output ONLY the HTML.",
+    "category": "Games",
+    "designTools": false,
+    "created_at": "2026-07-25T06:11:28.221Z",
+    "winner": null,
+    "runs": [
+      {
+        "model": "qwen3-14b",
+        "status": "done",
+        "error": null,
+        "seconds": 149,
+        "cost": 0,
+        "started_at": "2026-07-25T06:11:28.290Z",
+        "finished_at": "2026-07-25T06:13:57.155Z",
+        "queued_at": "2026-07-25T06:11:28.260Z",
+        "bytes": 7711,
+        "thumb": true
+      },
+      {
+        "model": "gemma3-12b",
+        "status": "done",
+        "error": null,
+        "seconds": 118,
+        "cost": 0,
+        "started_at": "2026-07-25T06:13:57.160Z",
+        "finished_at": "2026-07-25T06:15:55.094Z",
+        "queued_at": "2026-07-25T06:11:28.266Z",
+        "bytes": 7432,
+        "thumb": true
+      },
+      {
+        "model": "hermes3-8b",
+        "status": "done",
+        "error": null,
+        "seconds": 24,
+        "cost": 0,
+        "started_at": "2026-07-25T06:15:55.099Z",
+        "finished_at": "2026-07-25T06:16:19.228Z",
+        "queued_at": "2026-07-25T06:11:28.271Z",
+        "bytes": 6555,
+        "thumb": true
+      },
+      {
+        "model": "qwen25-7b",
+        "status": "done",
+        "error": null,
+        "seconds": 25,
+        "cost": 0,
+        "started_at": "2026-07-25T06:11:28.294Z",
+        "finished_at": "2026-07-25T06:11:53.407Z",
+        "queued_at": "2026-07-25T06:11:28.278Z",
+        "bytes": 2988,
+        "thumb": true
+      },
+      {
+        "model": "hf-qwen-coder-32b",
+        "status": "done",
+        "error": null,
+        "seconds": 76,
+        "cost": 0,
+        "started_at": "2026-07-25T06:16:19.234Z",
+        "finished_at": "2026-07-25T06:17:35.300Z",
+        "queued_at": "2026-07-25T06:11:28.282Z",
+        "bytes": 6033,
+        "thumb": true
+      },
+      {
+        "model": "claude-code",
+        "status": "error",
+        "error": "no HTML document in model output (1686 chars)",
+        "seconds": 126,
+        "cost": null,
+        "started_at": "2026-07-25T06:11:28.296Z",
+        "finished_at": "2026-07-25T06:13:33.939Z",
+        "queued_at": "2026-07-25T06:11:28.286Z"
+      }
+    ],
+    "judging": true,
+    "judged_at": null
+  },
+  {
+    "id": "ea271db82cff",
+    "title": "Remix: Free-Roster — Lava Lamp",
+    "prompt": "Build a single-file HTML page: a metaball lava lamp. Blobs rise and fall and merge with smooth metaball rendering and a warm gradient; a color-theme selector and a blob-count slider. Canvas-based, self-contained, no external libraries.\n\nHere is a strong previous attempt at this exact challenge. IMPROVE on it — keep what works, fix its weaknesses, and elevate the visual quality and polish. Return a complete, improved single-file HTML document.\n\n=== PREVIOUS ATTEMPT (Claude Opus (Max plan)) ===\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"UTF-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n<title>Metaball Lava Lamp</title>\n<style>\n  * { margin: 0; padding: 0; box-sizing: border-box; }\n  html, body { width: 100%; height: 100%; overflow: hidden; background: #000; }\n  body { font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif; }\n  #lamp { display: block; width: 100vw; height: 100vh; }\n\n  #panel {\n    position: fixed;\n    top: 18px;\n    left: 18px;\n    padding: 16px 18px 18px;\n    border-radius: 16px;\n    background: rgba(20, 12, 30, 0.42);\n    backdrop-filter: blur(14px) saturate(140%);\n    -webkit-backdrop-filter: blur(14px) saturate(140%);\n    border: 1px solid rgba(255, 255, 255, 0.12);\n    box-shadow: 0 10px 40px rgba(0, 0, 0, 0.45);\n    color: #f4eef8;\n    width: 224px;\n    user-select: none;\n    z-index: 10;\n    transition: opacity .3s;\n  }\n  #panel h1 {\n    font-size: 13px;\n    font-weight: 600;\n    letter-spacing: 1.6px;\n    text-transform: uppercase;\n    opacity: 0.85;\n    margin-bottom: 14px;\n    display: flex;\n    align-items: center;\n    gap: 8px;\n  }\n  #panel h1 .dot {\n    width: 9px; height: 9px; border-radius: 50%;\n    background: radial-gradient(circle at 30% 30%, #ffe066, #ff2d55);\n    box-shadow: 0 0 10px #ff5e3a;\n  }\n  .row { margin-bottom: 14px; }\n  .row:last-child { margin-bottom: 0; }\n  label {\n    display: block;\n    font-size: 11px;\n    letter-spacing: 0.5px;\n    opacity: 0.7;\n    margin-bottom: 7px;\n    display: flex;\n    justify-content: space-between;\n  }\n  label b { opacity: 0.95; font-weight: 600; }\n  select {\n    width: 100%;\n    padding: 8px 10px;\n    border-radius: 9px;\n    background: rgba(255,255,255,0.07);\n    color: #f4eef8;\n    border: 1px solid rgba(255,255,255,0.14);\n    font-size: 13px;\n    outline: none;\n    cursor: pointer;\n    appearance: none;\n    -webkit-appearance: none;\n    background-image: url(\"data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'><path d='M2 4l4 4 4-4' stroke='%23f4eef8' stroke-width='1.6' fill='none' stroke-linecap='round' stroke-linejoin='round'/></svg>\");\n    background-repeat: no-repeat;\n    background-position: right 10px center;\n  }\n  select option { background: #1a1024; color: #f4eef8; }\n\n  input[type=range] {\n    width: 100%;\n    -webkit-appearance: none;\n    appearance: none;\n    height: 5px;\n    border-radius: 5px;\n    background: rgba(255,255,255,0.18);\n    outline: none;\n    cursor: pointer;\n  }\n  input[type=range]::-webkit-slider-thumb {\n    -webkit-appearance: none;\n    width: 17px; height: 17px;\n    border-radius: 50%;\n    background: radial-gradient(circle at 32% 30%, #ffe9a8, #ff7a00);\n    border: 2px solid rgba(255,255,255,0.5);\n    box-shadow: 0 0 8px rgba(255,120,40,0.7);\n  }\n  input[type=range]::-moz-range-thumb {\n    width: 15px; height: 15px;\n    border-radius: 50%;\n    background: radial-gradient(circle at 32% 30%, #ffe9a8, #ff7a00);\n    border: 2px solid rgba(255,255,255,0.5);\n    box-shadow: 0 0 8px rgba(255,120,40,0.7);\n  }\n  #hint {\n    position: fixed;\n    bottom: 14px;\n    left: 0; right: 0;\n    text-align: center;\n    color: rgba(255,255,255,0.28);\n    font-size: 11px;\n    letter-spacing: 1px;\n    z-index: 5;\n    pointer-events: none;\n  }\n</style>\n</head>\n<body>\n<canvas id=\"lamp\"></canvas>\n\n<div id=\"panel\">\n  <h1><span class=\"dot\"></span>Lava Lamp</h1>\n  <div class=\"row\">\n    <label>Color Theme</label>\n    <select id=\"theme\">\n      <option value=\"classic\">Classic Lava</option>\n      <option value=\"ember\">Ember Glow</option>\n      <option value=\"sunset\">Sunset</option>\n      <option value=\"amethyst\">Amethyst</option>\n      <option value=\"ocean\">Deep Ocean</option>\n      <option value=\"toxic\">Toxic</option>\n    </select>\n  </div>\n  <div class=\"row\">\n    <label>Blobs <b id=\"countVal\">12</b></label>\n    <input type=\"range\" id=\"count\" min=\"3\" max=\"26\" value=\"12\">\n  </div>\n</div>\n\n<div id=\"hint\">metaballs rise · fall · merge</div>\n\n<script>\n(function () {\n  \"use strict\";\n\n  const canvas = document.getElementById(\"lamp\");\n  const ctx = canvas.getContext(\"2d\");\n\n  // Offscreen low-res buffer for the metaball field\n  const off = document.createElement(\"canvas\");\n  const offCtx = off.getContext(\"2d\");\n\n  // ---- Themes ----------------------------------------------------------\n  // bg: vertical background gradient [top, bottom]\n  // stops: blob color ramp (edge -> core)\n  const THEMES = {\n    classic: {\n      bg: [\"#2a0a3a\", \"#160326\"],\n      stops: [[0,\"#7a0f3a\"],[0.35,\"#ff2d55\"],[0.7,\"#ff8a00\"],[1,\"#ffe066\"]]\n    },\n    ember: {\n      bg: [\"#220a05\", \"#120302\"],\n      stops: [[0,\"#7a1500\"],[0.4,\"#ff3d00\"],[0.72,\"#ff7a00\"],[1,\"#ffc46b\"]]\n    },\n    sunset: {\n      bg: [\"#1b1035\", \"#360d38\"],\n      stops: [[0,\"#5b1250\"],[0.35,\"#ff4d6d\"],[0.7,\"#ff9e5e\"],[1,\"#ffd76e\"]]\n    },\n    amethyst: {\n      bg: [\"#14052a\", \"#26063f\"],\n      stops: [[0,\"#5a1a8a\"],[0.4,\"#c04dff\"],[0.72,\"#ff5edb\"],[1,\"#ffd1f5\"]]\n    },\n    ocean: {\n      bg: [\"#041b2d\", \"#06263a\"],\n      stops: [[0,\"#053a5c\"],[0.38,\"#00c8ff\"],[0.72,\"#2b8cff\"],[1,\"#bfeaff\"]]\n    },\n    toxic: {\n      bg: [\"#071a07\", \"#0a250a\"],\n      stops: [[0,\"#2c5c00\"],[0.4,\"#7bd400\"],[0.72,\"#39ff14\"],[1,\"#eaffb0\"]]\n    }\n  };\n\n  let theme = THEMES.classic;\n  let ramp = new Uint8Array(256 * 3); // color lookup: edge->core\n\n  function hexToRgb(h) {\n    h = h.replace(\"#\", \"\");\n    return [parseInt(h.slice(0,2),16), parseInt(h.slice(2,4),16), parseInt(h.slice(4,6),16)];\n  }\n\n  function buildRamp(t) {\n    const stops = t.stops;\n    for (let i = 0; i < 256; i++) {\n      const p = i / 255;\n      let a = stops[0], b = stops[stops.length - 1];\n      for (let s = 0; s < stops.length - 1; s++) {\n        if (p >= stops[s][0] && p <= stops[s + 1][0]) { a = stops[s]; b = stops[s + 1]; break; }\n      }\n      const span = (b[0] - a[0]) || 1;\n      const f = (p - a[0]) / span;\n      const ca = hexToRgb(a[1]), cb = hexToRgb(b[1]);\n      ramp[i*3]   = ca[0] + (cb[0] - ca[0]) * f;\n      ramp[i*3+1] = ca[1] + (cb[1] - ca[1]) * f;\n      ramp[i*3+2] = ca[2] + (cb[2] - ca[2]) * f;\n    }\n  }\n  buildRamp(theme);\n\n  // ---- Sizing ----------------------------------------------------------\n  let W = 0, H = 0, minDim = 0;\n  let lowW = 0, lowH = 0, scale = 0.22;\n  let imgData = null, buf = null;\n  let bgGrad = null;\n\n  function resize() {\n    W = canvas.width = Math.max(1, window.innerWidth);\n    H = canvas.height = Math.max(1, window.innerHeight);\n    minDim = Math.min(W, H);\n\n    // Keep the low-res field around ~200px on the long edge\n    scale = Math.min(0.26, 200 / Math.max(W, H));\n    lowW = Math.max(2, Math.round(W * scale));\n    lowH = Math.max(2, Math.round(H * scale));\n    off.width = lowW;\n    off.height = lowH;\n    imgData = offCtx.createImageData(lowW, lowH);\n    buf = imgData.data;\n\n    bgGrad = ctx.createLinearGradient(0, 0, 0, H);\n    bgGrad.addColorStop(0, theme.bg[0]);\n    bgGrad.addColorStop(1, theme.bg[1]);\n\n    for (const b of blobs) recomputeRadius(b);\n  }\n\n  // ---- Blobs -----------------------------------------------------------\n  const blobs = [];\n\n  function recomputeRadius(b) {\n    b.r = b.rFrac * minDim;\n  }\n\n  function makeBlob() {\n    const b = {\n      x: Math.random() * (W || 800),\n      y: Math.random() * (H || 600),\n      rFrac: 0.05 + Math.random() * 0.055,\n      vs: 0.25 + Math.random() * 0.5,   // vertical osc speed\n      vp: Math.random() * Math.PI * 2,  // vertical phase\n      vamp: 45 + Math.random() * 70,    // vertical amplitude (px/s)\n      hs: 0.15 + Math.random() * 0.35,  // horizontal speed\n      hp: Math.random() * Math.PI * 2,\n      hamp: 18 + Math.random() * 34,    // horizontal drift\n      ps: 0.4 + Math.random() * 0.8,    // pulse speed\n      pp: Math.random() * Math.PI * 2,\n      r: 40\n    };\n    recomputeRadius(b);\n    return b;\n  }\n\n  function setCount(n) {\n    while (blobs.length < n) blobs.push(makeBlob());\n    while (blobs.length > n) blobs.pop();\n  }\n\n  // ---- Physics ---------------------------------------------------------\n  function update(t, dt) {\n    for (const b of blobs) {\n      const vy = Math.sin(t * b.vs + b.vp) * b.vamp;\n      const vx = Math.sin(t * b.hs + b.hp) * b.hamp;\n      b.x += vx * dt;\n      b.y += vy * dt;\n\n      const pad = b.r * 0.35;\n      if (b.x < pad) { b.x = pad; b.hp += Math.PI; }\n      else if (b.x > W - pad) { b.x = W - pad; b.hp += Math.PI; }\n      if (b.y < pad) { b.y = pad; b.vp += Math.PI; }\n      else if (b.y > H - pad) { b.y = H - pad; b.vp += Math.PI; }\n\n      b.rr = b.r * (1 + Math.sin(t * b.ps + b.pp) * 0.14); // pulsating radius\n    }\n  }\n\n  // ---- Metaball render -------------------------------------------------\n  function renderField(t) {\n    // Precompute low-res blob params\n    const n = blobs.length;\n    const bx = new Float32Array(n), by = new Float32Array(n), br2 = new Float32Array(n);\n    for (let i = 0; i < n; i++) {\n      const b = blobs[i];\n      bx[i] = b.x * scale;\n      by[i] = b.y * scale;\n      const rl = b.rr * scale;\n      br2[i] = rl * rl;\n    }\n\n    let p = 0;\n    for (let y = 0; y < lowH; y++) {\n      for (let x = 0; x < lowW; x++) {\n        let sum = 0;\n        for (let i = 0; i < n; i++) {\n          const dx = x - bx[i], dy = y - by[i];\n          const d2 = dx * dx + dy * dy + 0.0001;\n          sum += br2[i] / d2;\n        }\n        let a;\n        if (sum < 0.65) {\n          a = 0;\n        } else {\n          a = sum < 1.05 ? (sum - 0.65) / 0.4 : 1;\n          let ct = (sum - 0.7) / 2.6;         // color param edge->core\n          if (ct < 0) ct = 0; else if (ct > 1) ct = 1;\n          const ci = (ct * 255) | 0;\n          const r3 = ci * 3;\n          buf[p]     = ramp[r3];\n          buf[p + 1] = ramp[r3 + 1];\n          buf[p + 2] = ramp[r3 + 2];\n        }\n        buf[p + 3] = a * 255;\n        p += 4;\n      }\n    }\n    offCtx.putImageData(imgData, 0, 0);\n  }\n\n  // ---- Main loop -------------------------------------------------------\n  let last = performance.now();\n  function frame(now) {\n    let dt = (now - last) / 1000;\n    if (dt > 0.05) dt = 0.05; // clamp after tab switch\n    last = now;\n    const t = now / 1000;\n\n    update(t, dt);\n    renderField(t);\n\n    // Background gradient\n    ctx.globalCompositeOperation = \"source-over\";\n    ctx.fillStyle = bgGrad;\n    ctx.fillRect(0, 0, W, H);\n\n    // Upscale the metaball layer smoothly on top\n    ctx.imageSmoothingEnabled = true;\n    ctx.imageSmoothingQuality = \"high\";\n    ctx.drawImage(off, 0, 0, lowW, lowH, 0, 0, W, H);\n\n    // Soft glow pass\n    ctx.globalCompositeOperation = \"lighter\";\n    ctx.globalAlpha = 0.28;\n    ctx.drawImage(off, 0, 0, lowW, lowH, 0, 0, W, H);\n    ctx.globalAlpha = 1;\n    ctx.globalCompositeOperation = \"source-over\";\n\n    requestAnimationFrame(frame);\n  }\n\n  // ---- Controls --------------------------------------------------------\n  const themeSel = document.getElementById(\"theme\");\n  const countInp = document.getElementById(\"count\");\n  const countVal = document.getElementById(\"countVal\");\n\n  themeSel.addEventListener(\"change\", function () {\n    theme = THEMES[themeSel.value] || THEMES.classic;\n    buildRamp(theme);\n    bgGrad = ctx.createLinearGradient(0, 0, 0, H);\n    bgGrad.addColorStop(0, theme.bg[0]);\n    bgGrad.addColorStop(1, theme.bg[1]);\n  });\n\n  countInp.addEventListener(\"input\", function () {\n    countVal.textContent = countInp.value;\n    setCount(parseInt(countInp.value, 10));\n  });\n\n  window.addEventListener(\"resize\", resize);\n\n  // ---- Boot ------------------------------------------------------------\n  setCount(parseInt(countInp.value, 10));\n  resize();\n  requestAnimationFrame(frame);\n})();\n</script>\n</body>\n</html>",
+    "remixOf": "78dd0efa6a47",
+    "remixSource": "claude-code",
+    "designTools": false,
+    "created_at": "2026-07-25T06:17:24.143Z",
+    "winner": null,
+    "runs": [
+      {
+        "model": "qwen3-14b",
+        "status": "running",
+        "error": null,
+        "seconds": 0,
+        "cost": null,
+        "started_at": "2026-07-25T06:17:53.534Z",
+        "finished_at": null,
+        "queued_at": "2026-07-25T06:17:53.526Z",
+        "resumes": 1
+      },
+      {
+        "model": "gemma3-12b",
+        "status": "queued",
+        "error": null,
+        "seconds": 0,
+        "cost": null,
+        "started_at": null,
+        "finished_at": null,
+        "queued_at": "2026-07-25T06:17:53.528Z",
+        "resumes": 1
+      },
+      {
+        "model": "hermes3-8b",
+        "status": "queued",
+        "error": null,
+        "seconds": 0,
+        "cost": null,
+        "started_at": null,
+        "finished_at": null,
+        "queued_at": "2026-07-25T06:17:53.529Z",
+        "resumes": 1
+      },
+      {
+        "model": "qwen25-7b",
+        "status": "running",
+        "error": null,
+        "seconds": 0,
+        "cost": null,
+        "started_at": "2026-07-25T06:17:53.541Z",
+        "finished_at": null,
+        "queued_at": "2026-07-25T06:17:53.531Z",
+        "resumes": 1
+      },
+      {
+        "model": "hf-qwen-coder-32b",
+        "status": "queued",
+        "error": null,
+        "seconds": 0,
+        "cost": null,
+        "started_at": null,
+        "finished_at": null,
+        "queued_at": "2026-07-25T06:17:53.532Z",
+        "resumes": 1
+      }
+    ],
+    "aiPick": null,
+    "category": "Games"
+  },
+  {
+    "id": "dfaee602df0d",
+    "title": "Agent Abrams — Agent Swarm Snake",
+    "prompt": "Build a single self-contained HTML file: a Snake game reimagined as an AI agent swarm. The snake is a chain of glowing agent nodes collecting 'tasks' (pulsing tokens); each task grows the swarm and bumps a score/tokens-processed counter. Neon dark UI, smooth grid movement (arrow/WASD), self-collision game-over, restart. 60fps, no external assets. Output ONLY the HTML.",
+    "category": "Games",
+    "designTools": false,
+    "created_at": "2026-07-25T06:18:24.454Z",
+    "winner": null,
+    "runs": [
+      {
+        "model": "qwen3-14b",
+        "status": "queued",
+        "error": null,
+        "seconds": null,
+        "cost": null,
+        "started_at": null,
+        "finished_at": null,
+        "queued_at": "2026-07-25T06:18:24.456Z"
+      },
+      {
+        "model": "gemma3-12b",
+        "status": "queued",
+        "error": null,
+        "seconds": null,
+        "cost": null,
+        "started_at": null,
+        "finished_at": null,
+        "queued_at": "2026-07-25T06:18:24.458Z"
+      },
+      {
+        "model": "hermes3-8b",
+        "status": "queued",
+        "error": null,
+        "seconds": null,
+        "cost": null,
+        "started_at": null,
+        "finished_at": null,
+        "queued_at": "2026-07-25T06:18:24.460Z"
+      },
+      {
+        "model": "qwen25-7b",
+        "status": "queued",
+        "error": null,
+        "seconds": null,
+        "cost": null,
+        "started_at": null,
+        "finished_at": null,
+        "queued_at": "2026-07-25T06:18:24.462Z"
+      },
+      {
+        "model": "hf-qwen-coder-32b",
+        "status": "queued",
+        "error": null,
+        "seconds": null,
+        "cost": null,
+        "started_at": null,
+        "finished_at": null,
+        "queued_at": "2026-07-25T06:18:24.463Z"
+      },
+      {
+        "model": "claude-code",
+        "status": "running",
+        "error": null,
+        "seconds": null,
+        "cost": null,
+        "started_at": "2026-07-25T06:18:24.466Z",
+        "finished_at": null,
+        "queued_at": "2026-07-25T06:18:24.465Z"
+      }
+    ]
   }
 ]
\ No newline at end of file
diff --git a/fleet-defender.html b/fleet-defender.html
new file mode 100644
index 0000000..463778e
--- /dev/null
+++ b/fleet-defender.html
@@ -0,0 +1,490 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<title>FLEET DEFENDER — AI Command Center</title>
+<style>
+  :root{
+    --navy:#050a1a; --navy2:#0a1430; --cyan:#22e0ff; --amber:#ffb028;
+    --grid:#0f2247; --pink:#ff4d7d; --green:#5affc0;
+  }
+  *{margin:0;padding:0;box-sizing:border-box}
+  html,body{height:100%;background:var(--navy);overflow:hidden;
+    font-family:"Courier New",ui-monospace,monospace;color:var(--cyan)}
+  #wrap{position:fixed;inset:0;display:flex;align-items:center;justify-content:center}
+  #stage{position:relative;width:min(100vw,calc(100vh*0.66));height:100%;
+    max-height:100vh;box-shadow:0 0 60px #0a1e4a inset}
+  canvas{display:block;width:100%;height:100%;
+    background:radial-gradient(120% 90% at 50% 8%,#0b1636 0%,#050a1a 70%)}
+  .hud{position:absolute;top:0;left:0;right:0;display:flex;justify-content:space-between;
+    padding:14px 18px;pointer-events:none;font-size:14px;letter-spacing:2px;
+    text-transform:uppercase;text-shadow:0 0 8px currentColor}
+  .hud .lbl{opacity:.55;font-size:10px;display:block}
+  .hud .amber{color:var(--amber)}
+  .hud .val{font-size:20px;font-weight:bold}
+  .lives i{display:inline-block;width:12px;height:12px;margin-left:5px;
+    background:var(--cyan);clip-path:polygon(50% 0,100% 78%,50% 60%,0 78%);
+    box-shadow:0 0 6px var(--cyan)}
+  .overlay{position:absolute;inset:0;display:flex;flex-direction:column;
+    align-items:center;justify-content:center;text-align:center;gap:18px;
+    background:radial-gradient(60% 60% at 50% 45%,rgba(8,16,40,.82),rgba(5,10,26,.96));
+    backdrop-filter:blur(2px)}
+  .overlay.hide{display:none}
+  h1{font-size:clamp(30px,7vw,58px);letter-spacing:6px;color:var(--cyan);
+    text-shadow:0 0 18px var(--cyan),0 0 40px #0af}
+  h1 span{color:var(--amber);text-shadow:0 0 18px var(--amber)}
+  .sub{color:var(--amber);letter-spacing:3px;font-size:13px;opacity:.85}
+  .panel{border:1px solid #17335f;background:rgba(10,22,52,.5);padding:16px 22px;
+    max-width:80%;line-height:1.9;font-size:13px;letter-spacing:1px;color:#9fd6ff}
+  .k{color:var(--amber)}
+  button{pointer-events:auto;cursor:pointer;font-family:inherit;font-size:15px;
+    letter-spacing:3px;text-transform:uppercase;color:var(--navy);font-weight:bold;
+    background:linear-gradient(180deg,var(--cyan),#12a6c9);border:none;
+    padding:14px 34px;border-radius:2px;box-shadow:0 0 20px var(--cyan)}
+  button:hover{background:linear-gradient(180deg,#8ff3ff,var(--cyan))}
+  .go{color:var(--pink);text-shadow:0 0 18px var(--pink),0 0 44px #900}
+  .flash{color:var(--amber);animation:bl 1s steps(2) infinite}
+  @keyframes bl{50%{opacity:.25}}
+</style>
+</head>
+<body>
+<div id="wrap">
+<div id="stage">
+  <canvas id="c"></canvas>
+  <div class="hud">
+    <div><span class="lbl">Score</span><span class="val" id="score">0</span></div>
+    <div style="text-align:center"><span class="lbl amber">Wave</span><span class="val amber" id="wave">1</span></div>
+    <div style="text-align:right"><span class="lbl">Fleet</span><span class="lives" id="lives"></span></div>
+  </div>
+
+  <div class="overlay" id="startScreen">
+    <h1>FLEET <span>DEFENDER</span></h1>
+    <div class="sub">// AI AGENT COMMAND CENTER //</div>
+    <div class="panel">
+      Rogue <span class="k">BUG</span> processes are descending on the agent grid.<br>
+      Pilot the command ship. Purge every bug before it breaches the fleet.<br><br>
+      <span class="k">← →</span> move &nbsp;·&nbsp; <span class="k">SPACE</span> fire &nbsp;·&nbsp; <span class="k">P</span> pause
+    </div>
+    <button id="startBtn">Initialize</button>
+  </div>
+
+  <div class="overlay hide" id="overScreen">
+    <h1 class="go">SYSTEM BREACH</h1>
+    <div class="sub" id="overStats"></div>
+    <div class="panel" id="overMsg"></div>
+    <button id="restartBtn">Redeploy</button>
+  </div>
+
+  <div class="overlay hide" id="pauseScreen">
+    <h1 class="flash">PAUSED</h1>
+    <div class="sub">Press P to resume</div>
+  </div>
+</div>
+</div>
+
+<script>
+(function(){
+"use strict";
+const canvas=document.getElementById("c"), ctx=canvas.getContext("2d");
+const $=id=>document.getElementById(id);
+let W=0,H=0,DPR=1;
+
+// logical resolution — we scale drawing to fit
+const VW=660, VH=1000;
+
+function resize(){
+  const r=canvas.getBoundingClientRect();
+  DPR=Math.min(window.devicePixelRatio||1,2);
+  canvas.width=Math.round(r.width*DPR);
+  canvas.height=Math.round(r.height*DPR);
+  W=canvas.width; H=canvas.height;
+}
+window.addEventListener("resize",resize);
+
+// ---- input ----
+const keys={};
+addEventListener("keydown",e=>{
+  if(["ArrowLeft","ArrowRight","Space","ArrowUp"].includes(e.code))e.preventDefault();
+  keys[e.code]=true;
+  if(e.code==="KeyP"&&state==="play")togglePause();
+  else if(e.code==="KeyP"&&state==="pause")togglePause();
+});
+addEventListener("keyup",e=>{keys[e.code]=false});
+
+// ---- game state ----
+let state="start"; // start | play | pause | over
+let score=0, wave=1, lives=3, best=0;
+let ship, bugs=[], pShots=[], eShots=[], parts=[], stars=[];
+let fireCd=0, waveClearTimer=0, flashTimer=0, shakeT=0;
+let time=0;
+
+function rnd(a,b){return a+Math.random()*(b-a)}
+
+function initStars(){
+  stars=[];
+  for(let i=0;i<80;i++)stars.push({x:rnd(0,VW),y:rnd(0,VH),z:rnd(.3,1),s:rnd(.5,1.6)});
+}
+
+function newShip(){
+  return {x:VW/2,y:VH-70,w:46,h:34,speed:7,cool:0};
+}
+
+function spawnWave(n){
+  bugs=[];
+  const cols=Math.min(6+Math.floor(n/2),10);
+  const rows=Math.min(2+Math.floor((n+1)/2),6);
+  const marginX=70, gapX=(VW-marginX*2)/(cols-1||1);
+  const startY=130, gapY=64;
+  const hpBonus=Math.floor(n/4);
+  for(let r=0;r<rows;r++){
+    for(let c=0;c<cols;c++){
+      bugs.push({
+        x:marginX+c*gapX, y:startY+r*gapY,
+        bx:marginX+c*gapX, // base anchor for sway
+        w:34,h:28,
+        hp:1+ (r===0?hpBonus:0),
+        maxhp:1+(r===0?hpBonus:0),
+        row:r,col:c,
+        phase:rnd(0,Math.PI*2),
+        alive:true, dead:false
+      });
+    }
+  }
+  // horde motion params scale with wave
+  horde.dir=1;
+  horde.speed=0.5+n*0.18;
+  horde.drop=14+n*1.5;
+  horde.fireRate=Math.max(0.010,0.006+n*0.0016);
+  horde.diveChance=Math.min(0.002+n*0.0006,0.008);
+}
+const horde={dir:1,speed:1,drop:16,fireRate:0.01,diveChance:0.002,offY:0};
+
+function startGame(){
+  score=0;wave=1;lives=3;
+  ship=newShip();pShots=[];eShots=[];parts=[];
+  fireCd=0;waveClearTimer=0;flashTimer=0;
+  initStars();
+  spawnWave(wave);
+  state="play";
+  $("startScreen").classList.add("hide");
+  $("overScreen").classList.add("hide");
+  $("pauseScreen").classList.add("hide");
+  updateHUD();
+}
+function togglePause(){
+  if(state==="play"){state="pause";$("pauseScreen").classList.remove("hide");}
+  else if(state==="pause"){state="play";$("pauseScreen").classList.add("hide");}
+}
+function gameOver(){
+  state="over";
+  best=Math.max(best,score);
+  $("overStats").textContent="SCORE "+score+"  ·  WAVE "+wave+"  ·  BEST "+best;
+  $("overMsg").innerHTML="The bug swarm overran the agent grid.<br>Recompile the fleet and defend again.";
+  $("overScreen").classList.remove("hide");
+}
+
+function updateHUD(){
+  $("score").textContent=score;
+  $("wave").textContent=wave;
+  let h="";for(let i=0;i<lives;i++)h+="<i></i>";
+  $("lives").innerHTML=h;
+}
+
+// ---- particles ----
+function burst(x,y,color,n,spd){
+  for(let i=0;i<n;i++){
+    const a=rnd(0,Math.PI*2),v=rnd(.4,1)*spd;
+    parts.push({x,y,vx:Math.cos(a)*v,vy:Math.sin(a)*v,
+      life:1,decay:rnd(.02,.05),color,size:rnd(1.5,3.5)});
+  }
+}
+
+// ---- update ----
+function update(dt){
+  time+=dt;
+  // stars
+  for(const s of stars){s.y+=s.z*0.6*dt;if(s.y>VH){s.y=0;s.x=rnd(0,VW);}}
+
+  // ship
+  if(keys.ArrowLeft)ship.x-=ship.speed*dt;
+  if(keys.ArrowRight)ship.x+=ship.speed*dt;
+  ship.x=Math.max(ship.w/2,Math.min(VW-ship.w/2,ship.x));
+  if(ship.cool>0)ship.cool-=dt;
+  if((keys.Space||keys.ArrowUp)&&ship.cool<=0){
+    pShots.push({x:ship.x,y:ship.y-ship.h/2,vy:-12,w:4,h:14});
+    pShots.push({x:ship.x-10,y:ship.y-ship.h/2+6,vy:-12,w:3,h:10});
+    pShots.push({x:ship.x+10,y:ship.y-ship.h/2+6,vy:-12,w:3,h:10});
+    ship.cool=Math.max(9-wave*0.4,4);
+    burst(ship.x,ship.y-ship.h/2,"#22e0ff",4,2);
+  }
+
+  // player shots
+  for(const s of pShots){s.y+=s.vy*dt;}
+  pShots=pShots.filter(s=>s.y>-20);
+
+  // horde movement (classic sweep + drop)
+  const live=bugs.filter(b=>b.alive);
+  if(live.length){
+    let minX=Infinity,maxX=-Infinity;
+    for(const b of live){minX=Math.min(minX,b.bx);maxX=Math.max(maxX,b.bx);}
+    let move=horde.dir*horde.speed*dt;
+    if(maxX+move>VW-40||minX+move<40){
+      horde.dir*=-1;
+      horde.offY+=horde.drop;
+    }else{
+      for(const b of bugs)b.bx+=move;
+    }
+    // apply position with sway
+    for(const b of bugs){
+      if(!b.alive)continue;
+      b.x=b.bx+Math.sin(time*0.03+b.phase)*6;
+      b.y=b.baseY(horde.offY,b);
+    }
+  }
+
+  // enemy fire + occasional dive
+  for(const b of live){
+    if(Math.random()<horde.fireRate*0.02*dt){
+      eShots.push({x:b.x,y:b.y+b.h/2,vy:3+wave*0.15,w:4,h:12});
+    }
+    if(!b.diving && Math.random()<horde.diveChance*0.02*dt){
+      b.diving=true;b.dvx=(ship.x-b.x)*0.008;
+    }
+    if(b.diving){
+      b.divY=(b.divY||0)+ (2.4+wave*0.1)*dt;
+      b.x+=b.dvx*dt;
+    }
+  }
+
+  for(const s of eShots)s.y+=s.vy*dt;
+  eShots=eShots.filter(s=>s.y<VH+20);
+
+  // collisions: player shots vs bugs
+  for(const s of pShots){
+    for(const b of live){
+      if(Math.abs(s.x-b.x)<b.w/2+3 && Math.abs(s.y-getBugY(b))<b.h/2+6){
+        b.hp--; s.dead=true;
+        burst(s.x,getBugY(b),"#ffb028",6,2.5);
+        if(b.hp<=0){
+          b.alive=false;
+          score+=10*wave + b.row*5;
+          burst(b.x,getBugY(b),"#ff4d7d",18,4);
+          burst(b.x,getBugY(b),"#22e0ff",10,3);
+          shakeT=Math.min(shakeT+3,8);
+          updateHUD();
+        }
+        break;
+      }
+    }
+  }
+  pShots=pShots.filter(s=>!s.dead);
+
+  // enemy shots vs ship
+  for(const s of eShots){
+    if(Math.abs(s.x-ship.x)<ship.w/2 && Math.abs(s.y-ship.y)<ship.h/2){
+      s.dead=true; hitShip();
+    }
+  }
+  // bug reaches ship / bottom
+  for(const b of live){
+    const by=getBugY(b);
+    if(by>VH-40){ hitShip(); b.alive=false; burst(b.x,by,"#ff4d7d",20,4);}
+    else if(Math.abs(b.x-ship.x)<ship.w/2+b.w/2 && Math.abs(by-ship.y)<ship.h/2+b.h/2){
+      b.alive=false; burst(b.x,by,"#ff4d7d",20,4); hitShip();
+    }
+  }
+  eShots=eShots.filter(s=>!s.dead);
+
+  // particles
+  for(const p of parts){p.x+=p.vx*dt;p.y+=p.vy*dt;p.vy+=0.05*dt;p.life-=p.decay*dt;}
+  parts=parts.filter(p=>p.life>0);
+
+  if(shakeT>0)shakeT-=dt*0.5;
+  if(flashTimer>0)flashTimer-=dt;
+
+  // wave clear
+  if(bugs.length && bugs.every(b=>!b.alive)){
+    if(waveClearTimer===0)waveClearTimer=1;
+    waveClearTimer+=dt;
+    if(waveClearTimer>60){
+      wave++; horde.offY=0;
+      spawnWave(wave);
+      waveClearTimer=0;
+      updateHUD();
+    }
+  }
+}
+
+// bug vertical position helper (base + drop + dive)
+function getBugY(b){ return b.startY + horde.offY + (b.divY||0); }
+function hitShip(){
+  lives--; flashTimer=20;
+  burst(ship.x,ship.y,"#22e0ff",26,5);
+  shakeT=10;
+  updateHUD();
+  ship.x=VW/2;
+  eShots=[];
+  if(lives<=0)gameOver();
+}
+
+// patch spawnWave to set startY & helper — redefine spawn to include startY
+function fixBugs(){/* handled inline below */}
+
+// override: assign startY on spawn
+const _spawn=spawnWave;
+spawnWave=function(n){
+  _spawn(n);
+  for(const b of bugs){ b.startY=b.y; b.divY=0; b.diving=false; b.baseY=(off,bb)=>bb.startY+off+(bb.divY||0);}
+};
+
+// ---- render ----
+function drawGrid(){
+  ctx.save();
+  ctx.strokeStyle="rgba(30,70,140,0.22)";ctx.lineWidth=1;
+  const step=VW/11;
+  for(let x=0;x<=VW;x+=step){ctx.beginPath();ctx.moveTo(x,90);ctx.lineTo(x,VH);ctx.stroke();}
+  for(let y=90;y<=VH;y+=step){ctx.beginPath();ctx.moveTo(0,y);ctx.lineTo(VW,y);ctx.stroke();}
+  // agent nodes at intersections
+  ctx.fillStyle="rgba(34,224,255,0.14)";
+  for(let x=0;x<=VW;x+=step*2){for(let y=110;y<=VH;y+=step*2){
+    ctx.beginPath();ctx.arc(x,y,2,0,7);ctx.fill();
+  }}
+  ctx.restore();
+}
+
+function drawShip(){
+  ctx.save();
+  ctx.translate(ship.x,ship.y);
+  const blink = flashTimer>0 && Math.floor(flashTimer/3)%2===0;
+  ctx.globalAlpha = blink?0.35:1;
+  // glow
+  ctx.shadowColor="#22e0ff";ctx.shadowBlur=18;
+  ctx.fillStyle="#22e0ff";
+  ctx.beginPath();
+  ctx.moveTo(0,-ship.h/2);
+  ctx.lineTo(ship.w/2,ship.h/2);
+  ctx.lineTo(ship.w*0.18,ship.h*0.3);
+  ctx.lineTo(-ship.w*0.18,ship.h*0.3);
+  ctx.lineTo(-ship.w/2,ship.h/2);
+  ctx.closePath();ctx.fill();
+  // core
+  ctx.shadowBlur=10;ctx.fillStyle="#eafcff";
+  ctx.beginPath();ctx.arc(0,-2,5,0,7);ctx.fill();
+  // engine flare
+  ctx.shadowColor="#ffb028";ctx.fillStyle="#ffb028";
+  const f=6+Math.sin(time*0.4)*3;
+  ctx.beginPath();ctx.moveTo(-6,ship.h/2);ctx.lineTo(0,ship.h/2+f);ctx.lineTo(6,ship.h/2);ctx.fill();
+  ctx.restore();
+}
+
+function drawBug(b){
+  const y=getBugY(b);
+  ctx.save();
+  ctx.translate(b.x,y);
+  const pulse=0.5+0.5*Math.sin(time*0.08+b.phase);
+  ctx.shadowColor="#ff4d7d";ctx.shadowBlur=10;
+  // body — hexagonal bug node
+  ctx.fillStyle= b.maxhp>1? "#ff6a4d":"#ff4d7d";
+  ctx.beginPath();
+  const r=b.w/2;
+  for(let i=0;i<6;i++){const a=i/6*Math.PI*2+time*0.01;
+    const px=Math.cos(a)*r, py=Math.sin(a)*r*0.8;
+    i?ctx.lineTo(px,py):ctx.moveTo(px,py);}
+  ctx.closePath();ctx.fill();
+  // eye
+  ctx.shadowBlur=6;ctx.fillStyle="#0a1430";
+  ctx.beginPath();ctx.arc(0,0,r*0.42,0,7);ctx.fill();
+  ctx.fillStyle=`rgba(90,255,192,${0.5+pulse*0.5})`;
+  ctx.beginPath();ctx.arc(0,0,r*0.22,0,7);ctx.fill();
+  // legs
+  ctx.strokeStyle="#ff4d7d";ctx.lineWidth=2;ctx.shadowBlur=4;
+  for(const sx of [-1,1]){
+    ctx.beginPath();
+    ctx.moveTo(sx*r*0.6,0);ctx.lineTo(sx*(r+6),-4+Math.sin(time*0.1+b.phase)*3);
+    ctx.moveTo(sx*r*0.6,3);ctx.lineTo(sx*(r+6),8+Math.sin(time*0.1+b.phase)*3);
+    ctx.stroke();
+  }
+  ctx.restore();
+}
+
+function render(){
+  ctx.save();
+  ctx.scale(W/VW,H/VH);
+  ctx.clearRect(0,0,VW,VH);
+
+  if(shakeT>0){ctx.translate(rnd(-shakeT,shakeT),rnd(-shakeT,shakeT));}
+
+  // stars
+  for(const s of stars){
+    ctx.globalAlpha=s.z;ctx.fillStyle="#7fd8ff";
+    ctx.fillRect(s.x,s.y,s.s,s.s);
+  }
+  ctx.globalAlpha=1;
+
+  drawGrid();
+
+  // player shots
+  ctx.shadowColor="#22e0ff";ctx.shadowBlur=8;
+  for(const s of pShots){ctx.fillStyle="#eafcff";ctx.fillRect(s.x-s.w/2,s.y-s.h/2,s.w,s.h);}
+  // enemy shots
+  ctx.shadowColor="#ffb028";
+  for(const s of eShots){ctx.fillStyle="#ffb028";ctx.fillRect(s.x-s.w/2,s.y-s.h/2,s.w,s.h);}
+  ctx.shadowBlur=0;
+
+  for(const b of bugs)if(b.alive)drawBug(b);
+  if(ship)drawShip();
+
+  // particles
+  for(const p of parts){
+    ctx.globalAlpha=Math.max(0,p.life);
+    ctx.fillStyle=p.color;
+    ctx.fillRect(p.x-p.size/2,p.y-p.size/2,p.size,p.size);
+  }
+  ctx.globalAlpha=1;
+
+  // wave banner
+  if(waveClearTimer>0){
+    ctx.fillStyle="rgba(34,224,255,0.9)";ctx.font="bold 34px 'Courier New',monospace";
+    ctx.textAlign="center";ctx.shadowColor="#22e0ff";ctx.shadowBlur=20;
+    ctx.fillText("WAVE "+(wave)+" CLEARED",VW/2,VH/2);
+    ctx.font="14px 'Courier New',monospace";ctx.fillStyle="#ffb028";
+    ctx.fillText("INCOMING: WAVE "+(wave+1),VW/2,VH/2+30);
+    ctx.shadowBlur=0;
+  }
+  ctx.restore();
+
+  // subtle breach vignette flash
+  if(flashTimer>0){
+    ctx.save();ctx.globalAlpha=Math.min(flashTimer/40,0.4);
+    ctx.fillStyle="#ff4d7d";ctx.fillRect(0,0,W,H);ctx.restore();
+  }
+}
+
+// ---- loop (fixed-step for consistent 60fps feel) ----
+let last=performance.now(),acc=0;
+const STEP=1000/60;
+function loop(now){
+  let delta=now-last;last=now;
+  if(delta>100)delta=100;
+  if(state==="play"){
+    acc+=delta;
+    while(acc>=STEP){update(1);acc-=STEP;}
+  }
+  render();
+  requestAnimationFrame(loop);
+}
+
+// ---- boot ----
+$("startBtn").onclick=startGame;
+$("restartBtn").onclick=startGame;
+resize();
+initStars();
+ship=newShip();
+spawnWave(1);
+// draw idle background behind start screen
+requestAnimationFrame(loop);
+})();
+</script>
+</body>
+</html>
diff --git a/idea-run/.dw-aa-pointer b/idea-run/.dw-aa-pointer
new file mode 100644
index 0000000..00750ed
--- /dev/null
+++ b/idea-run/.dw-aa-pointer
@@ -0,0 +1 @@
+3
diff --git a/idea-run/dw-aa-challenges.txt b/idea-run/dw-aa-challenges.txt
new file mode 100644
index 0000000..235836d
--- /dev/null
+++ b/idea-run/dw-aa-challenges.txt
@@ -0,0 +1,16 @@
+Agent Abrams — Fleet Defender|Build a single self-contained HTML file: a playable arcade game FLEET DEFENDER themed as an AI-agent command center. Player pilots a glowing command ship at the bottom (arrow keys move, spacebar fires) defending the fleet from descending 'bug' invaders. Neon command-center look (dark navy, cyan/amber HUD, agent-node grid), score, wave number, lives, rising difficulty, particle explosions, game-over + restart. 60fps canvas, keyboard controls, no external assets. Output ONLY the HTML.
+Agent Abrams — Agent Swarm Snake|Build a single self-contained HTML file: a Snake game reimagined as an AI agent swarm. The snake is a chain of glowing agent nodes collecting 'tasks' (pulsing tokens); each task grows the swarm and bumps a score/tokens-processed counter. Neon dark UI, smooth grid movement (arrow/WASD), self-collision game-over, restart. 60fps, no external assets. Output ONLY the HTML.
+Designer Wallcoverings — Pattern Tile Match|Build a single self-contained HTML file: a relaxing tile-matching puzzle where the player swaps adjacent wallpaper-swatch tiles (elegant damask/floral/geometric CSS-drawn patterns) to make rows of 3+; matches clear with a shimmer and add score. 6x6 grid, mouse/touch swap, move counter, restart. Luxury muted palette (oatmeal, sage, blush, charcoal). Output ONLY the HTML.
+Designer Wallcoverings — Room Repaint Visualizer|Build a single self-contained HTML file: an interactive room visualizer. A CSS/canvas-drawn living room with a large feature wall; a side palette of 8 wallcovering swatches (CSS-generated damask/grasscloth/geometric textures); clicking a swatch instantly re-skins the feature wall with a smooth crossfade. Include a day/night lighting toggle. Luxury interior aesthetic. Output ONLY the HTML.
+Designer Wallcoverings — Seamless Pattern Studio|Build a single self-contained HTML file: a generative seamless-wallpaper pattern maker. Sliders control motif type (dots/leaves/diamonds/waves), scale, rotation, and a 5-color luxury palette; the canvas renders a live tiling repeat that visibly seams correctly. A 'Randomize' and 'Download PNG' button. 60fps. Output ONLY the HTML.
+Designer Wallcoverings — Color Story Builder|Build a single self-contained HTML file: an interior color-palette tool. Pick a base hue on a wheel and it generates a curated 5-swatch wallcovering color story (analogous/complementary/triadic toggle) with tasteful names (Oatmeal, Celadon, Alabaster, Greige). Copy-hex on click, live preview strip. Elegant editorial layout. Output ONLY the HTML.
+Agent Abrams — Fleet Command Dashboard|Build a single self-contained HTML file: an AI-agent fleet command-center dashboard mockup. A dark grid of ~12 agent status cards (name, uptime, tasks/hr, a live-updating mini sparkline via random walk), a top KPI ribbon (agents online, tasks today, $ saved), and a pulsing 'healthy/degraded' status dot per card. Cyan/amber neon on navy. Pure CSS/JS, self-animating. Output ONLY the HTML.
+Agent Abrams — Agent Network Constellation|Build a single self-contained HTML file: an animated force-directed constellation of AI agents. Nodes (agents) connected by glowing edges (message passing); nodes gently drift and edges pulse when a 'message' travels along them. Hover a node to highlight its neighbors. Dark space aesthetic, cyan glow, 60fps canvas. Output ONLY the HTML.
+Designer Wallcoverings — Sortable Product Catalog|Build a single self-contained HTML file: a luxury wallcovering product catalog grid of 12 sample products (CSS-drawn swatch, name, collection, price). Include a working sort dropdown (Newest, Price low-high, Name A-Z, Color) AND a density slider that changes the grid column count, both persisted. Elegant editorial card design, hover lift. Output ONLY the HTML.
+Designer Wallcoverings — Wallpaper Calculator|Build a single self-contained HTML file: a wallpaper-quantity calculator. Inputs for wall width, height, and number of walls, plus roll dimensions and pattern-repeat; it computes rolls needed (with 10% waste), total coverage, and estimated cost. Clean luxury form UI, live recompute, print-friendly result card. Output ONLY the HTML.
+Agent Abrams — Task Pipeline Kanban|Build a single self-contained HTML file: a self-animating agent task-pipeline kanban board (Queued -> Running -> Done -> Shipped). ~10 task cards auto-advance columns over time with a smooth slide; each card shows an agent avatar dot, title, and elapsed timer. Dark neon UI, counts per column. Pure CSS/JS. Output ONLY the HTML.
+Designer Wallcoverings — Damask Kaleidoscope|Build a single self-contained HTML file: a mesmerizing damask kaleidoscope generator. A symmetric canvas renders an ever-morphing luxury damask motif in gold/charcoal/blush; mouse movement warps the symmetry, click cycles palettes. Elegant, hypnotic, 60fps. Output ONLY the HTML.
+Agent Abrams — Reaction Time Trainer|Build a single self-contained HTML file: a reflex mini-game 'Agent Reflex'. Glowing target nodes pop up at random positions; click them before they fade to score. 30-second round, combo multiplier, hits/misses, best score in localStorage, restart. Neon command-center theme. 60fps. Output ONLY the HTML.
+Designer Wallcoverings — Swatch Memory Match|Build a single self-contained HTML file: a memory card-flip game using pairs of luxury wallcovering swatches (CSS-drawn patterns). 4x4 grid, flip two to match, moves + timer, win screen, restart. Refined muted palette, smooth 3D flip animation. Output ONLY the HTML.
+Agent Abrams — Cost Savings Ticker|Build a single self-contained HTML file: a cinematic 'value delivered' dashboard for an AI agent fleet. A big animated odometer counts up total $ saved and eng-hours saved; below, a scrolling feed of recent 'wins' (title + $ impact) and a bar chart of savings by category. Dark premium UI, count-up easing. Pure CSS/JS. Output ONLY the HTML.
+Designer Wallcoverings — Texture Weave Simulator|Build a single self-contained HTML file: an interactive grasscloth/weave texture simulator on canvas. Sliders control weave density, fiber color (2-tone), and sheen; the canvas renders a realistic woven wallcovering texture that updates live, with a subtle light sweep. Luxury natural palette. 60fps. Output ONLY the HTML.
diff --git a/idea-run/run-next.sh b/idea-run/run-next.sh
new file mode 100755
index 0000000..91e70c9
--- /dev/null
+++ b/idea-run/run-next.sh
@@ -0,0 +1,28 @@
+#!/usr/bin/env bash
+# run-next.sh — fire the next DW/Agent-Abrams challenge (all $0 local/CLI models),
+# advance a resumable pointer, wrap around the idea file to run all night.
+set -euo pipefail
+ROOT="$HOME/Projects/model-arena"
+IDEAS="$ROOT/idea-run/dw-aa-challenges.txt"
+PTR="$ROOT/idea-run/.dw-aa-pointer"
+N=$(grep -c '' "$IDEAS")
+
+IDX=$(cat "$PTR" 2>/dev/null || echo 2)   # line 1 (Fleet Defender) fired manually
+[ "$IDX" -gt "$N" ] && IDX=1              # wrap: loop the pool all night
+
+LINE=$(sed -n "${IDX}p" "$IDEAS")
+TITLE="${LINE%%|*}"; PROMPT="${LINE#*|}"
+
+python3 - "$TITLE" "$PROMPT" > /tmp/dwaa-payload.json <<'PY'
+import json,sys
+json.dump({"title":sys.argv[1],"prompt":sys.argv[2],
+           "models":["qwen3-14b","gemma3-12b","hermes3-8b","qwen25-7b","hf-qwen-coder-32b","claude-code"]},
+          sys.stdout)
+PY
+
+ID=$(curl -s -m 25 -u admin:DW2024! -X POST http://127.0.0.1:9758/api/challenges \
+  -H 'Content-Type: application/json' -d @/tmp/dwaa-payload.json \
+  | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('id') or ('ERR:'+str(d.get('error'))))")
+
+echo "$((IDX+1))" > "$PTR"
+echo "FIRED idx=$IDX/$N  id=$ID  title=$TITLE"
diff --git a/public/arcade.html b/public/arcade.html
new file mode 100644
index 0000000..7d3d7dc
--- /dev/null
+++ b/public/arcade.html
@@ -0,0 +1,146 @@
+<!doctype html>
+<html lang="en">
+<head>
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<title>Model Arena — Games Arcade</title>
+<style>
+  :root { --cols: 4; }
+  * { box-sizing: border-box; }
+  body { margin:0; background:#07080f; color:#dfe4ff; font:14px/1.5 ui-monospace,Menlo,Consolas,monospace; }
+  a { color:#00e5ff; text-decoration:none; }
+  header { display:flex; align-items:center; gap:16px; flex-wrap:wrap;
+    padding:18px 22px; border-bottom:1px solid #1a1f3a; position:sticky; top:0; background:#07080fdd; backdrop-filter:blur(6px); z-index:5; }
+  h1 { font-size:18px; letter-spacing:3px; margin:0; }
+  h1 b { color:#00e5ff; }
+  .sub { color:#7d84ad; font-size:12px; }
+  .spacer { flex:1; }
+  .ctrl { display:flex; align-items:center; gap:7px; color:#7d84ad; font-size:12px; }
+  select, input[type=range] { accent-color:#00e5ff; }
+  select { background:#0d0f1c; color:#dfe4ff; border:1px solid #26305c; border-radius:6px; padding:4px 8px; font:inherit; }
+  .grid { display:grid; grid-template-columns:repeat(var(--cols), 1fr); gap:16px; padding:22px; }
+  .tile { border:1px solid #1e2342; background:#0d0f1c; border-radius:10px; overflow:hidden; cursor:pointer;
+    transition:transform .12s ease, border-color .12s ease, box-shadow .12s ease; display:flex; flex-direction:column; }
+  .tile:hover { transform:translateY(-3px); border-color:#00e5ff; box-shadow:0 10px 30px -12px #00e5ff55; }
+  .tile.crowned { border-color:#ffc72e55; }
+  .shot { aspect-ratio:16/10; background:#05060c center/cover no-repeat; position:relative; border-bottom:1px solid #1e2342; }
+  .shot .ph { position:absolute; inset:0; display:flex; align-items:center; justify-content:center; font-size:34px; opacity:.6; }
+  .shot .play { position:absolute; inset:0; display:flex; align-items:center; justify-content:center;
+    font-size:40px; background:#05060caa; opacity:0; transition:opacity .12s; }
+  .tile:hover .shot .play { opacity:1; }
+  .meta { padding:10px 12px; }
+  .meta .t { font-size:13px; color:#eef1ff; margin:0 0 5px; line-height:1.35; }
+  .badges { display:flex; gap:6px; flex-wrap:wrap; font-size:10.5px; }
+  .b { padding:1px 7px; border-radius:20px; border:1px solid #26305c; color:#9aa2cf; }
+  .b.win { border-color:#ffc72e; color:#ffc72e; }
+  .b.ai { border-color:#00e5ff55; color:#00e5ff; }
+  .b.feat { border-color:#b06bff77; color:#c79bff; }
+  .empty { padding:60px 22px; color:#7d84ad; text-align:center; }
+  /* play overlay */
+  #ov { position:fixed; inset:0; background:#05060cf2; z-index:20; display:none; flex-direction:column; }
+  #ov.on { display:flex; }
+  #ovbar { display:flex; align-items:center; gap:14px; padding:12px 18px; border-bottom:1px solid #1a1f3a; }
+  #ovbar .x { cursor:pointer; color:#7d84ad; font-size:20px; border:1px solid #26305c; border-radius:6px; padding:1px 11px; }
+  #ovbar .x:hover { color:#fff; border-color:#00e5ff; }
+  #ovframe { flex:1; width:100%; border:0; background:#000; }
+  footer { color:#4c5378; font-size:11px; padding:16px 22px; border-top:1px solid #1a1f3a; }
+</style>
+</head>
+<body>
+<header>
+  <h1><b>ARENA</b> ARCADE 🎮</h1>
+  <span class="sub" id="count">loading…</span>
+  <span class="spacer"></span>
+  <span class="ctrl">sort
+    <select id="sort">
+      <option value="newest">Newest</option>
+      <option value="title">Title A→Z</option>
+      <option value="model">Model</option>
+      <option value="score">AI Score ↓</option>
+    </select>
+  </span>
+  <span class="ctrl">density
+    <input type="range" id="density" min="2" max="7" step="1" value="4">
+  </span>
+  <a href="/" class="sub">← arena</a>
+</header>
+
+<div class="grid" id="grid"></div>
+<div class="empty" id="empty" style="display:none">No games crowned yet — run a Games challenge in the arena and crown a winner.</div>
+
+<div id="ov">
+  <div id="ovbar">
+    <b id="ovtitle" style="color:#00e5ff"></b>
+    <span class="sub" id="ovmeta"></span>
+    <span class="spacer" style="flex:1"></span>
+    <a id="ovnew" class="sub" target="_blank">open ↗</a>
+    <span class="x" id="ovx">✕ close</span>
+  </div>
+  <iframe id="ovframe" sandbox="allow-scripts allow-pointer-lock"></iframe>
+</div>
+
+<footer>Games are untrusted model output, played in a sandboxed frame (no network). Crowned 👑 = human pick · 🤖 = AI referee pick. Auto-updates as new Games challenges are won.</footer>
+
+<script>
+const $ = s => document.querySelector(s);
+let GAMES = [];
+
+// persisted controls (Steve's hard rule: every grid has sort + density, localStorage-persisted)
+const sortEl = $('#sort'), densEl = $('#density');
+sortEl.value = localStorage.getItem('arcade.sort') || 'newest';
+densEl.value = localStorage.getItem('arcade.density') || '4';
+document.documentElement.style.setProperty('--cols', densEl.value);
+sortEl.onchange = () => { localStorage.setItem('arcade.sort', sortEl.value); render(); };
+densEl.oninput  = () => { localStorage.setItem('arcade.density', densEl.value);
+  document.documentElement.style.setProperty('--cols', densEl.value); };
+
+function sortGames(list) {
+  const s = sortEl.value, a = list.slice();
+  if (s === 'title') a.sort((x, y) => x.title.localeCompare(y.title));
+  else if (s === 'model') a.sort((x, y) => (x.label || '').localeCompare(y.label || ''));
+  else if (s === 'score') a.sort((x, y) => (y.aiScore || 0) - (x.aiScore || 0));
+  else a.sort((x, y) => String(y.created_at || '').localeCompare(String(x.created_at || ''))); // newest
+  return a;
+}
+
+function render() {
+  const grid = $('#grid'); grid.innerHTML = '';
+  const list = sortGames(GAMES);
+  $('#empty').style.display = list.length ? 'none' : 'block';
+  for (const g of list) {
+    const el = document.createElement('div');
+    el.className = 'tile' + (g.crowned ? ' crowned' : '');
+    const badges = [
+      g.standalone ? '<span class="b feat">★ FEATURED</span>' : '',
+      g.crowned && !g.standalone ? '<span class="b win">👑 WON</span>' : '',
+      g.aiPick ? '<span class="b ai">🤖 AI PICK</span>' : '',
+      g.label ? `<span class="b">${g.label}</span>` : '',
+      g.aiScore != null ? `<span class="b">🤖 ${g.aiScore}</span>` : '',
+    ].filter(Boolean).join('');
+    el.innerHTML =
+      `<div class="shot" ${g.thumb ? `style="background-image:url('${g.thumb}')"` : ''}>
+         ${g.thumb ? '' : '<div class="ph">🎮</div>'}<div class="play">▶</div></div>
+       <div class="meta"><p class="t">${g.title}</p><div class="badges">${badges}</div></div>`;
+    el.onclick = () => play(g);
+    grid.appendChild(el);
+  }
+}
+
+function play(g) {
+  $('#ovtitle').textContent = g.title;
+  $('#ovmeta').textContent = (g.label ? g.label + (g.crowned && !g.standalone ? ' 👑' : '') : '') + (g.aiScore != null ? ' · 🤖 ' + g.aiScore : '');
+  $('#ovnew').href = g.play;
+  $('#ovframe').src = g.play;
+  $('#ov').classList.add('on');
+}
+$('#ovx').onclick = () => { $('#ov').classList.remove('on'); $('#ovframe').src = 'about:blank'; };
+document.addEventListener('keydown', e => { if (e.key === 'Escape') $('#ovx').click(); });
+
+fetch('/api/arcade').then(r => r.json()).then(d => {
+  GAMES = d.games || [];
+  $('#count').textContent = GAMES.length + ' playable games';
+  render();
+}).catch(() => { $('#count').textContent = 'failed to load'; });
+</script>
+</body>
+</html>
diff --git a/star-clock.html b/public/games/star-clock.html
similarity index 100%
rename from star-clock.html
rename to public/games/star-clock.html
diff --git a/tetris.html b/public/games/tetris.html
similarity index 100%
rename from tetris.html
rename to public/games/tetris.html
diff --git a/public/index.html b/public/index.html
index dac9e3d..9a219b9 100644
--- a/public/index.html
+++ b/public/index.html
@@ -157,6 +157,7 @@ tr.clk{cursor:pointer}tr.clk:hover td{background:rgba(0,229,255,.06)}
     <button id="nav-challenges" class="on">Challenges</button>
     <button id="nav-new">+ New Battle</button>
     <button id="nav-board">Leaderboard</button>
+    <a href="/arcade" style="text-decoration:none"><button type="button">🎮 Arcade</button></a>
   </nav>
 </header>
 <div class="costbar" id="costbar" title="Total metered API spend across the arena (local models + Claude Max CLI are $0)">
diff --git a/server.js b/server.js
index cc09fe0..7899618 100644
--- a/server.js
+++ b/server.js
@@ -860,6 +860,64 @@ iframe{width:100%;height:460px;border:0;background:#000}.c.win iframe{height:560
     return res.end(fs.readFileSync(fp));
   }
 
+  // ── Games Arcade ────────────────────────────────────────────────
+  // Auto-discovers every crowned/AI-picked Games-category challenge as a
+  // playable tile, plus the hand-picked standalone champions in public/games.
+  if (p === '/api/arcade') {
+    const GAMEY = /\b(game|tetris|snake|maze|breakout|pong|invader|defender|flappy|asteroid|platformer|puzzle|arcade|shoot|2048|memory|driving|dragon|fireworks|synthwave|starfield|conway|life|neon|visualizer|particle|physics|domino|bounce|rope|pathfind)\b/i;
+    const games = [];
+    for (const c of challenges) {
+      const isGame = (c.category === 'Games') || GAMEY.test((c.title || '') + ' ' + (c.prompt || ''));
+      if (!isGame) continue;
+      // champion = human crown → AI pick → highest AI score among done runs
+      let champ = c.winner || c.aiPick;
+      if (!champ) {
+        const done = (c.runs || []).filter(r => r.status === 'done' && r.thumb);
+        done.sort((a, b) => (b.aiScore || 0) - (a.aiScore || 0));
+        champ = done[0] && done[0].model;
+      }
+      if (!champ) continue;
+      if (!fs.existsSync(path.join(ART, c.id, champ + '.html'))) continue;
+      const run = (c.runs || []).find(r => r.model === champ) || {};
+      games.push({
+        id: c.id, title: c.title, model: champ, label: mLabelServer(champ),
+        created_at: c.created_at, category: c.category || 'Games',
+        crowned: c.winner === champ, aiPick: c.aiPick === champ,
+        aiScore: typeof run.aiScore === 'number' ? run.aiScore : null,
+        play: '/artifact/' + c.id + '/' + champ, thumb: '/thumb/' + c.id + '/' + champ,
+        standalone: false,
+      });
+    }
+    // featured standalone champions
+    let feat = { featured: [] };
+    try { feat = JSON.parse(fs.readFileSync(path.join(DATA, 'arcade-games.json'), 'utf8')); } catch {}
+    for (const g of (feat.featured || [])) {
+      if (!fs.existsSync(path.join(DIR, 'public', 'games', g.slug + '.html'))) continue;
+      games.push({
+        id: 'featured-' + g.slug, title: g.title, model: g.credit || '', label: mLabelServer(g.credit || ''),
+        created_at: null, category: 'Featured', crowned: true, aiPick: false, aiScore: null,
+        play: '/game/' + g.slug, thumb: null, standalone: true, note: g.note || '',
+      });
+    }
+    return send(res, 200, { games, count: games.length });
+  }
+  if (p === '/arcade' || p === '/arcade.html') {
+    const fp = path.join(DIR, 'public', 'arcade.html');
+    if (!fs.existsSync(fp)) { res.writeHead(404); return res.end('no arcade'); }
+    res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
+    return res.end(fs.readFileSync(fp));
+  }
+  if ((m = p.match(/^\/game\/([a-z0-9_-]+)$/i))) {
+    const fp = path.join(DIR, 'public', 'games', path.basename(m[1]) + '.html');
+    if (!fs.existsSync(fp)) { res.writeHead(404); return res.end('no game'); }
+    res.writeHead(200, {
+      'Content-Type': 'text/html; charset=utf-8',
+      'Content-Security-Policy': "default-src 'none'; script-src 'unsafe-inline'; style-src 'unsafe-inline'; img-src data:; media-src data:; font-src data:",
+      'X-Frame-Options': 'SAMEORIGIN',
+    });
+    return res.end(fs.readFileSync(fp));
+  }
+
   const f = p === '/' ? 'index.html' : p.replace(/^\//, '');
   const fp = path.join(DIR, 'public', path.basename(f));
   if (fs.existsSync(fp) && fs.statSync(fp).isFile()) {

← 5c09122 auto-save: 2026-07-24T20:59:44 (4 files) — data/artifacts/7b  ·  back to Model Arena  ·  night-loop: cycle 23:19 — judged=4a6e2cfce746 · (backlog 2 — 5917c15 →