← back to Model Arena
yolo idea 6: daily auto-challenge generator (accrues real judged data, $0 local) + AI-judged; launchd install drafted to pending-approval (gated, not installed)
b15a98bc0aa490d6cc2d45efcd5b50a68bd43f7f · 2026-07-22 23:45:20 -0700 · Steve
Files touched
A data/artifacts/08d1c975f6db/gemma3-12b.htmlA data/artifacts/08d1c975f6db/gemma3-12b.pngA data/artifacts/08d1c975f6db/hermes3-8b.htmlA data/artifacts/08d1c975f6db/hermes3-8b.pngA data/artifacts/08d1c975f6db/qwen25-7b.htmlA data/artifacts/08d1c975f6db/qwen25-7b.pngM data/challenges.jsonA scripts/com.steve.model-arena-daily.plistA scripts/daily-challenge.jsM yolo/IDEAS.mdA yolo/daily-log.jsonl
Diff
commit b15a98bc0aa490d6cc2d45efcd5b50a68bd43f7f
Author: Steve <steve@designerwallcoverings.com>
Date: Wed Jul 22 23:45:20 2026 -0700
yolo idea 6: daily auto-challenge generator (accrues real judged data, $0 local) + AI-judged; launchd install drafted to pending-approval (gated, not installed)
---
data/artifacts/08d1c975f6db/gemma3-12b.html | 110 ++++++++++++++++++++++++++++
data/artifacts/08d1c975f6db/gemma3-12b.png | Bin 0 -> 11354 bytes
data/artifacts/08d1c975f6db/hermes3-8b.html | 85 +++++++++++++++++++++
data/artifacts/08d1c975f6db/hermes3-8b.png | Bin 0 -> 6321 bytes
data/artifacts/08d1c975f6db/qwen25-7b.html | 80 ++++++++++++++++++++
data/artifacts/08d1c975f6db/qwen25-7b.png | Bin 0 -> 3828 bytes
data/challenges.json | 60 +++++++++++++++
scripts/com.steve.model-arena-daily.plist | 14 ++++
scripts/daily-challenge.js | 59 +++++++++++++++
yolo/IDEAS.md | 1 +
yolo/daily-log.jsonl | 1 +
11 files changed, 410 insertions(+)
diff --git a/data/artifacts/08d1c975f6db/gemma3-12b.html b/data/artifacts/08d1c975f6db/gemma3-12b.html
new file mode 100644
index 0000000..e6c3a47
--- /dev/null
+++ b/data/artifacts/08d1c975f6db/gemma3-12b.html
@@ -0,0 +1,110 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Bouncing Balls</title>
+ <style>
+ body { margin: 0; overflow: hidden; }
+ canvas { background-color: #eee; display: block; }
+ </style>
+</head>
+<body>
+ <canvas id="myCanvas"></canvas>
+
+ <script>
+ const canvas = document.getElementById("myCanvas");
+ const ctx = canvas.getContext("2d");
+
+ let width = window.innerWidth;
+ let height = window.innerHeight;
+ canvas.width = width;
+ canvas.height = height;
+
+ let balls = [];
+ let gravity = 0.5;
+
+ function init() {
+ for (let i = 0; i < 20; i++) {
+ balls.push({
+ x: Math.random() * width,
+ y: Math.random() * height,
+ radius: Math.random() * 10 + 5,
+ vx: (Math.random() - 0.5) * 4,
+ vy: (Math.random() - 0.5) * 4,
+ color: `rgba(${Math.random()*255}, ${Math.random()*255}, ${Math.random()*255}, 0.8)`
+ });
+ }
+
+ const gravitySlider = document.createElement('input');
+ gravitySlider.type = 'range';
+ gravitySlider.min = 0;
+ gravitySlider.max = 10;
+ gravitySlider.value = 0.5;
+ gravitySlider.onchange = function() {
+ gravity = parseFloat(this.value);
+ };
+
+ document.body.appendChild(gravitySlider);
+ }
+
+
+ function draw() {
+ ctx.clearRect(0, 0, width, height);
+
+ for (let i = 0; i < balls.length; i++) {
+ const ball = balls[i];
+
+ // Update position
+ ball.y += ball.vy;
+ ball.x += ball.vx;
+
+ // Bounce off walls
+ if (ball.x + ball.radius > width || ball.x - ball.radius < 0) {
+ ball.vx = -ball.vx;
+ }
+ if (ball.y + ball.radius > height || ball.y - ball.radius < 0) {
+ ball.vy = -ball.vy;
+ }
+
+ // Apply gravity
+ ball.vy += gravity;
+
+ // Draw trail
+ ctx.beginPath();
+ ctx.arc(ball.x, ball.y, ball.radius, 0, Math.PI * 2);
+ ctx.fillStyle = ball.color;
+ ctx.fill();
+
+
+ //Draw ball
+ ctx.beginPath();
+ ctx.arc(ball.x, ball.y, ball.radius, 0, Math.PI * 2);
+ ctx.fillStyle = ball.color;
+ ctx.fill();
+
+ }
+ }
+
+ function resize() {
+ width = window.innerWidth;
+ height = window.innerHeight;
+ canvas.width = width;
+ canvas.height = height;
+ }
+
+ init();
+
+ let animationFrameId;
+
+ function animate() {
+ draw();
+ animationFrameId = requestAnimationFrame(animate);
+ }
+
+
+ resize();
+ window.addEventListener('resize', resize);
+ animate();
+
+ </script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/08d1c975f6db/gemma3-12b.png b/data/artifacts/08d1c975f6db/gemma3-12b.png
new file mode 100644
index 0000000..7c98c1b
Binary files /dev/null and b/data/artifacts/08d1c975f6db/gemma3-12b.png differ
diff --git a/data/artifacts/08d1c975f6db/hermes3-8b.html b/data/artifacts/08d1c975f6db/hermes3-8b.html
new file mode 100644
index 0000000..755a1af
--- /dev/null
+++ b/data/artifacts/08d1c975f6db/hermes3-8b.html
@@ -0,0 +1,85 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>20 Bouncing Balls</title>
+ <style>
+ body {
+ margin: 0;
+ overflow: hidden;
+ }
+ canvas {
+ display: block;
+ background-color: #f5gsg;
+ }
+ slider {
+ position: fixed;
+ bottom: 10px;
+ left: 10px;
+ }
+ </style>
+</head>
+<body>
+ <canvas id="gameCanvas"></canvas>
+ <input type="range" min="0.1" max="1" step="0.01" value="0.5" class="slider" id="gravitySlider">
+
+ <script>
+ const canvas = document.getElementById('gameCanvas');
+ const ctx = canvas.getContext('2d');
+
+ let gravity = 0.5;
+ const balls = [];
+ for (let i = 0; i < 20; i++) {
+ balls.push({
+ x: Math.random() * canvas.width,
+ y: Math.random() * canvas.height,
+ radius: Math.random() * 10 + 2,
+ speedX: (Math.random() - 0.5) * 4,
+ speedY: (Math.random() - 0.5) * 4,
+ color: `hsl(${(i*360)/20}, 100%, 50%)`,
+ trailLength: Math.random() * 10 + 2
+ });
+ }
+
+ function drawBalls() {
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
+
+ for (let i = 0; i < balls.length; i++) {
+ const ball = balls[i];
+
+ ctx.beginPath();
+ ctx.arc(ball.x, ball.y, ball.radius, 0, Math.PI * 2);
+ ctx.fillStyle = ball.color;
+ ctx.fill();
+
+ ctx.strokeStyle = `rgba(${ball.color}, ${ball.trailLength})`;
+ ctx.lineWidth = ball.radius / 5;
+ ctx.moveTo(ball.x - ball.radius, ball.y + ball.radius);
+ for (let j = 0; j < ball.trailLength; j++) {
+ const x = ball.x - Math.sin(j * Math.PI / ball.trailLength) * ball.radius;
+ const y = ball.y - Math.cos(j * Math.PI / ball.trailLength) * ball.radius;
+ ctx.lineTo(x, y);
+ }
+ ctx.stroke();
+
+ balls[i].x += ball.speedX;
+ balls[i].y += ball.speedY + gravity;
+
+ if (balls[i].x < 0 || balls[i].x > canvas.width - ball.radius)
+ balls[i].speedX *= -1;
+ if (balls[i].y < 0 || balls[i].y > canvas.height - ball.radius)
+ balls[i].speedY *= -1;
+ }
+ }
+
+ function updateGravity(newGravity) {
+ gravity = newGravity;
+ }
+
+ setInterval(drawBalls, 20);
+
+ document.getElementById('gravitySlider').oninput = function() {
+ updateGravity(this.value);
+ };
+ </script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/08d1c975f6db/hermes3-8b.png b/data/artifacts/08d1c975f6db/hermes3-8b.png
new file mode 100644
index 0000000..a71a32c
Binary files /dev/null and b/data/artifacts/08d1c975f6db/hermes3-8b.png differ
diff --git a/data/artifacts/08d1c975f6db/qwen25-7b.html b/data/artifacts/08d1c975f6db/qwen25-7b.html
new file mode 100644
index 0000000..af303f7
--- /dev/null
+++ b/data/artifacts/08d1c975f6db/qwen25-7b.html
@@ -0,0 +1,80 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <title>Bouncing Balls</title>
+ <style>
+ body { margin: 0; overflow: hidden; background-color: #2b2b2b; }
+ canvas { display: block; }
+ </style>
+</head>
+<body>
+<canvas id="canvas" width="800" height="600"></canvas>
+<script>
+const canvas = document.getElementById('canvas');
+const ctx = canvas.getContext('2d');
+
+let balls = [];
+for (let i = 0; i < 20; i++) {
+ balls.push({
+ x: Math.random() * canvas.width,
+ y: 0,
+ dx: (Math.random() - 0.5) * 3,
+ dy: 0,
+ radius: Math.random() * 10 + 10
+ });
+}
+
+function drawBall(ball, trail = false) {
+ ctx.beginPath();
+ ctx.arc(ball.x, ball.y, ball.radius, 0, Math.PI * 2);
+ if (trail) ctx.strokeStyle = 'rgba(255,255,255,' + (1 - ball.y / canvas.height) + ')';
+ else ctx.fillStyle = 'white';
+ if (trail) ctx.stroke();
+ else ctx.fill();
+}
+
+function draw() {
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
+ const gravity = 0.5;
+ for (let i = balls.length - 1; i >= 0; i--) {
+ let ball = balls[i];
+ if (ball.y + ball.radius > canvas.height) {
+ ball.dy = -(ball.dy * 0.7);
+ ball.y = canvas.height - ball.radius;
+ } else {
+ ball.dy += gravity;
+ }
+ if (ball.x + ball.radius > canvas.width || ball.x - ball.radius < 0) {
+ ball.dx = -ball.dx;
+ }
+ for (let j = i; j < balls.length; j++) {
+ let otherBall = balls[j];
+ if ((i !== j) && (Math.hypot(ball.x - otherBall.x, ball.y - otherBall.y) < (ball.radius + otherBall.radius))) {
+ let angle = Math.atan2(otherBall.y - ball.y, otherBall.x - ball.x);
+ let speed = 0.75 * ((ball.dx * ball.dx + ball.dy * ball.dy) ** 0.5 + otherBall.dx * otherBall.dx + otherBall.dy * otherBall.dy) ** 0.5;
+ balls[i] = {
+ x: canvas.width / 2,
+ y: canvas.height / 2,
+ dx: speed * Math.cos(angle),
+ dy: speed * -Math.sin(angle)
+ };
+ balls[j] = {
+ x: canvas.width / 2,
+ y: canvas.height / 2,
+ dx: -speed * Math.cos(angle),
+ dy: -speed * Math.sin(angle)
+ };
+ }
+ }
+ ball.x += ball.dx;
+ ball.y += ball.dy;
+ drawBall(ball, true);
+ }
+}
+
+setInterval(draw, 1000/60);
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/08d1c975f6db/qwen25-7b.png b/data/artifacts/08d1c975f6db/qwen25-7b.png
new file mode 100644
index 0000000..1d5cbe1
Binary files /dev/null and b/data/artifacts/08d1c975f6db/qwen25-7b.png differ
diff --git a/data/challenges.json b/data/challenges.json
index a458866..ea213c2 100644
--- a/data/challenges.json
+++ b/data/challenges.json
@@ -824,5 +824,65 @@
"judging": false,
"aiPick": "gemma3-12b",
"judged_at": "2026-07-23T06:40:30.864Z"
+ },
+ {
+ "id": "08d1c975f6db",
+ "title": "Daily: Bouncing Balls",
+ "prompt": "Single-file HTML canvas with 20 balls bouncing under gravity with collisions and trails; a slider controls gravity.",
+ "created_at": "2026-07-23T06:41:58.764Z",
+ "winner": null,
+ "runs": [
+ {
+ "model": "qwen3-14b",
+ "status": "error",
+ "error": "no HTML document in model output (0 chars)",
+ "seconds": 132,
+ "cost": null,
+ "started_at": "2026-07-23T06:41:58.771Z",
+ "finished_at": "2026-07-23T06:44:10.361Z"
+ },
+ {
+ "model": "gemma3-12b",
+ "status": "done",
+ "error": null,
+ "seconds": 21,
+ "cost": 0,
+ "started_at": "2026-07-23T06:44:10.364Z",
+ "finished_at": "2026-07-23T06:44:31.681Z",
+ "bytes": 2535,
+ "thumb": true,
+ "aiScore": 7,
+ "aiReason": "The model demonstrates the basic functionality of balls bouncing under gravity and colliding, but lacks visual polish or additional features."
+ },
+ {
+ "model": "hermes3-8b",
+ "status": "done",
+ "error": null,
+ "seconds": 10,
+ "cost": 0,
+ "started_at": "2026-07-23T06:44:31.683Z",
+ "finished_at": "2026-07-23T06:44:41.667Z",
+ "bytes": 2327,
+ "thumb": true,
+ "aiScore": 7,
+ "aiReason": "The model demonstrates basic functionality but lacks visual appeal and detail."
+ },
+ {
+ "model": "qwen25-7b",
+ "status": "done",
+ "error": null,
+ "seconds": 18,
+ "cost": 0,
+ "started_at": "2026-07-23T06:41:58.773Z",
+ "finished_at": "2026-07-23T06:42:16.559Z",
+ "bytes": 2572,
+ "thumb": true,
+ "aiScore": 4,
+ "aiReason": "The canvas is empty and no balls or gravity are visible."
+ }
+ ],
+ "judging": false,
+ "aiPick": "gemma3-12b",
+ "judged_at": "2026-07-23T06:44:54.324Z"
}
]
\ No newline at end of file
diff --git a/scripts/com.steve.model-arena-daily.plist b/scripts/com.steve.model-arena-daily.plist
new file mode 100644
index 0000000..04adc24
--- /dev/null
+++ b/scripts/com.steve.model-arena-daily.plist
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0"><dict>
+ <key>Label</key><string>com.steve.model-arena-daily</string>
+ <key>ProgramArguments</key>
+ <array>
+ <string>/opt/homebrew/bin/node</string>
+ <string>/Users/macstudio3/Projects/model-arena/scripts/daily-challenge.js</string>
+ </array>
+ <key>StartCalendarInterval</key><dict><key>Hour</key><integer>7</integer><key>Minute</key><integer>15</integer></dict>
+ <key>StandardOutPath</key><string>/tmp/model-arena-daily.log</string>
+ <key>StandardErrorPath</key><string>/tmp/model-arena-daily.err</string>
+ <key>RunAtLoad</key><false/>
+</dict></plist>
diff --git a/scripts/daily-challenge.js b/scripts/daily-challenge.js
new file mode 100644
index 0000000..df355c5
--- /dev/null
+++ b/scripts/daily-challenge.js
@@ -0,0 +1,59 @@
+#!/usr/bin/env node
+// Daily auto-challenge — creates one $0 all-local battle on a rotating prompt,
+// waits for it to render, triggers the AI vision referee, and logs it. This
+// accrues REAL judged data over time so the leaderboard stops being "one vote".
+// It deliberately does NOT auto-crown (the human 👑 vote stays pure); it just
+// builds a backlog of AI-scored battles for Steve to crown at his leisure.
+//
+// Usage: node scripts/daily-challenge.js (uses day-of-year rotation)
+// PICK=3 node scripts/daily-challenge.js (force a specific prompt)
+// Env: BASE (default http://127.0.0.1:9758), AUTH (default admin:DW2024!)
+const http = require('http'), fs = require('fs'), path = require('path');
+const BASE = process.env.BASE || 'http://127.0.0.1:9758';
+const AUTH = process.env.AUTH || 'admin:DW2024!';
+const LOG = path.join(__dirname, '..', 'yolo', 'daily-log.jsonl');
+const LOCAL = ['qwen3-14b', 'gemma3-12b', 'hermes3-8b', 'qwen25-7b'];
+
+const POOL = [
+ { t: 'Daily: Particle Fireworks', p: 'Interactive fireworks in one HTML file: click to launch rockets that burst into physics particles with trails, gravity, glow; include an auto-show mode.' },
+ { t: 'Daily: Sortable Catalog', p: 'Single-file HTML catalog of 8 invented wallcovering SKUs (sku, pattern, colorway, price, stock) as a responsive card grid with a live search box and click-to-sort columns.' },
+ { t: 'Daily: Bouncing Balls', p: 'Single-file HTML canvas with 20 balls bouncing under gravity with collisions and trails; a slider controls gravity.' },
+ { t: 'Daily: Luxury Product Page', p: 'Single-file HTML luxury wallpaper product page: hero (CSS gradient), serif display type, price, Add to Cart, spec table. High-end interior-design aesthetic.' },
+ { t: 'Daily: Starfield Warp', p: 'Single-file HTML animated starfield warp effect on canvas; mouse position steers the warp direction; a slider controls speed.' },
+ { t: 'Daily: Sample-Sale Email', p: 'Single-file email-safe HTML marketing email for a wallpaper sample sale: header, 3 featured patterns in a responsive grid (gradient placeholders), CTA button, compliant footer. Inline CSS only.' },
+ { t: 'Daily: Conway Life', p: "Single-file HTML Conway's Game of Life on canvas with play/pause, randomize, and a speed slider." },
+];
+
+function req(method, p, body) {
+ return new Promise((res, rej) => {
+ const u = new URL(BASE + p);
+ const r = http.request(u, { method, headers: { 'Content-Type': 'application/json', Authorization: 'Basic ' + Buffer.from(AUTH).toString('base64') } }, resp => {
+ let b = ''; resp.on('data', d => b += d); resp.on('end', () => { try { res(JSON.parse(b)); } catch { res({}); } });
+ });
+ r.on('error', rej); if (body) r.write(JSON.stringify(body)); r.end();
+ });
+}
+const sleep = ms => new Promise(r => setTimeout(r, ms));
+
+(async () => {
+ const idx = process.env.PICK != null ? (+process.env.PICK % POOL.length)
+ : Math.floor((Date.now() / 86400000)) % POOL.length; // day-of-epoch rotation
+ const pick = POOL[idx];
+ const c = await req('POST', '/api/challenges', { title: pick.t, prompt: pick.p, models: LOCAL });
+ if (!c.id) { console.error('create failed', c); process.exit(1); }
+ console.log('created', c.id, '—', pick.t);
+ // poll until settled + judged (cap ~20 min)
+ for (let i = 0; i < 150; i++) {
+ await sleep(8000);
+ const cur = await req('GET', '/api/challenges/' + c.id);
+ const settled = cur.runs.every(r => r.status === 'done' || r.status === 'error');
+ if (settled && !cur.judging && cur.judged_at) {
+ const done = cur.runs.filter(r => r.status === 'done').length;
+ const entry = { ts: new Date().toISOString(), id: c.id, title: pick.t, done, aiPick: cur.aiPick };
+ try { fs.appendFileSync(LOG, JSON.stringify(entry) + '\n'); } catch {}
+ console.log('DONE', JSON.stringify(entry));
+ return;
+ }
+ }
+ console.error('timed out waiting for battle to settle'); process.exit(2);
+})().catch(e => { console.error(e); process.exit(1); });
diff --git a/yolo/IDEAS.md b/yolo/IDEAS.md
index c40b86b..60310c9 100644
--- a/yolo/IDEAS.md
+++ b/yolo/IDEAS.md
@@ -18,3 +18,4 @@ Answering the /contrarian critique ("one builder vote = theater", "toy demos not
- [DONE] #3 ELO + cost-efficiency + AI-agreement ledger — ELO (K=24, crowned beats all rendered) as headline rank, $/win, avg 🤖 score, and 'AI referee agrees with human X% of N battles' meta-metric. Grok leads @1118.
- [DONE] #4 Blind judging — toggle hides model names as 'Model A/B/C' until a winner is crowned (also hides AI pick), reveal on crown. Strips brand bias from the vote. localStorage-persisted.
- [DONE] #5 Real-workload presets (Strategist answer) — added a '🏭 Real Work' preset category (luxury product page, sortable catalog, sample-sale email, room visualizer, JSON→grid) alongside '🎮 Games'. Verified full stack (gen→render→AI-judge) on the product-page task; AI reasons were task-aware. Moves the arena toward Steve's real decisions, not just toy demos.
+- [DONE] #6 Daily auto-challenge generator — scripts/daily-challenge.js creates a $0 all-local battle on a day-rotating prompt, renders + AI-judges, logs to yolo/daily-log.jsonl (no auto-crown, keeps human vote pure). Test-run OK. launchd install DRAFTED to pending-approval (GATED, not installed).
diff --git a/yolo/daily-log.jsonl b/yolo/daily-log.jsonl
new file mode 100644
index 0000000..f4e7d92
--- /dev/null
+++ b/yolo/daily-log.jsonl
@@ -0,0 +1 @@
+{"ts":"2026-07-23T06:44:54.850Z","id":"08d1c975f6db","title":"Daily: Bouncing Balls","done":3,"aiPick":"gemma3-12b"}
← 3854099 yolo idea 5: real-workload preset pack (Strategist's fix) —
·
back to Model Arena
·
auto-save: 2026-07-22T23:47:26 (3 files) — data/challenges.j 956ad95 →