[object Object]

← back to Model Arena

model-arena loop: 16-category creative COMPETITION bank + weighted rotation

272a37d4cccac715af965faeee203dbc135b9994 · 2026-09-18 14:46:35 -0700 · Steve Abrams

Broaden the prompt bank to 52 vivid, surprising prompts across 16 categories
(Art/Culture/News/Political Cartoon emphasized ~2x, plus Games, Real Work,
Data Viz, Science, Music, Sports, History, Nature & Space, Comedy, Demoscene,
Finance, Design) in one editable PROMPT_BANK const. Even category rotation via
a schedule (emphasis cats get an extra slot); prompts cycle per-category. Frame
every cycle as a named COMPETITION titled "<Category> Competition #N — <theme>"
with a per-category counter persisted in yolo/arena-loop-state.json. Categories
flow through to the existing per-category leaderboard (/api/ledger) unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HTnyDUiR2At9Aj5f6RGRav

Files touched

Diff

commit 272a37d4cccac715af965faeee203dbc135b9994
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Fri Sep 18 14:46:35 2026 -0700

    model-arena loop: 16-category creative COMPETITION bank + weighted rotation
    
    Broaden the prompt bank to 52 vivid, surprising prompts across 16 categories
    (Art/Culture/News/Political Cartoon emphasized ~2x, plus Games, Real Work,
    Data Viz, Science, Music, Sports, History, Nature & Space, Comedy, Demoscene,
    Finance, Design) in one editable PROMPT_BANK const. Even category rotation via
    a schedule (emphasis cats get an extra slot); prompts cycle per-category. Frame
    every cycle as a named COMPETITION titled "<Category> Competition #N — <theme>"
    with a per-category counter persisted in yolo/arena-loop-state.json. Categories
    flow through to the existing per-category leaderboard (/api/ledger) unchanged.
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
    Claude-Session: https://claude.ai/code/session_01HTnyDUiR2At9Aj5f6RGRav
---
 scripts/arena-loop.js | 156 ++++++++++++++++++++++++++++++++++++++------------
 1 file changed, 118 insertions(+), 38 deletions(-)

diff --git a/scripts/arena-loop.js b/scripts/arena-loop.js
index ad532f0..2b68842 100644
--- a/scripts/arena-loop.js
+++ b/scripts/arena-loop.js
@@ -1,17 +1,20 @@
 #!/usr/bin/env node
 // Continuous themed Model Arena loop — always-on generator (pm2: model-arena-loop).
-// Every cycle it creates ONE new $0 battle on a rotating prompt drawn from a themed
-// bank focused on ART, CULTURE, NEWS, and POLITICAL CARTOONS, fanning it out to a
-// rotating window of the DISCOVERED local models (all 3 hosts) so every local model
-// competes over time and each battle spans an ollama + an openai-local model. It never
-// crowns (the human 👑 vote stays pure) — it just accrues AI-refereed battles and logs
-// each cycle to yolo/daily-log.jsonl. A wedged/OOM model times out via the server
-// watchdog and the battle still settles, so the loop never deadlocks.
+// Every cycle it creates ONE new $0 battle on a creative, rotating prompt drawn from a
+// themed bank spanning many categories (Art/Culture/News/Political Cartoons emphasized,
+// plus Games, Real Work, Data Viz, Science, Music, Sports, History, Nature & Space,
+// Comedy, Demoscene, Finance, Design). It fans each battle to a rotating window of the
+// DISCOVERED local models (all 3 hosts) so every local model competes over time and each
+// battle spans an ollama + an openai-local model. It never crowns (the human 👑 vote
+// stays pure) — it just accrues AI-refereed battles and logs each cycle to
+// yolo/daily-log.jsonl. A wedged/OOM model times out via the server watchdog and the
+// battle still settles, so the loop never deadlocks.
 //
 // Env: BASE (http://127.0.0.1:9758), AUTH (admin:DW2024!), ARENA_LOOP_CAP (models per
 //      battle, default 6), ARENA_LOOP_GAP_MS (gap after a battle settles, default 300000),
 //      ARENA_LOOP_SETTLE_CAP_MS (max wait for a battle to settle, default 2700000),
-//      ONESHOT=1 (run one cycle then exit — used by the verify step).
+//      ONESHOT=1 (run one cycle then exit — used by the verify step),
+//      PROMPT_ID=<id> (force a specific prompt this cycle).
 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!';
@@ -21,32 +24,106 @@ const CAP = parseInt(process.env.ARENA_LOOP_CAP || '6', 10);
 const GAP_MS = parseInt(process.env.ARENA_LOOP_GAP_MS || '300000', 10);
 const SETTLE_CAP_MS = parseInt(process.env.ARENA_LOOP_SETTLE_CAP_MS || '2700000', 10);
 
-// themed prompt bank — art / culture / news / political cartoons, tagged so
-// inferCategory + the per-category leaderboards bucket them correctly.
-const POOL = [
-  // ---- ART ----
-  ['gen-art', 'Art', 'Generative Art Canvas', 'An interactive generative-art piece (flow field or particle system) on a canvas, with live controls to reshape palette, density, and motion, plus a snapshot/export. Make it feel like a real artwork, not a demo.'],
-  ['mondrian', 'Art', 'Neoplastic Composition Studio', 'An interactive Mondrian-style neoplastic composition generator: adjustable grid, primary-color palette, line weight, randomize, and a save-as-image action.'],
-  ['kinetic-type', 'Art', 'Kinetic Typography Poster', 'An animated typographic art poster on a strong editorial grid that reacts to the cursor with restrained, tasteful motion and respects prefers-reduced-motion.'],
-  ['fractal', 'Art', 'Fractal Explorer', 'An interactive Mandelbrot/Julia fractal explorer with smooth zoom, palette cycling, a coordinate/iteration readout, and a reset.'],
-  // ---- CULTURE ----
-  ['museum', 'Culture', 'Interactive Museum Explainer', 'An interactive museum-style explainer of an INVENTED art movement: a timeline, artifact cards with detail views, a guided reading order, and accessible keyboard navigation.'],
-  ['culture-map', 'Culture', 'World Culture Map', 'An interactive cultural map of INVENTED festivals across world regions, with region filters, a detail panel per region, and accessible navigation.'],
-  ['languages', 'Culture', 'Endangered Languages Explainer', 'An interactive explainer visualizing an INVENTED endangered-languages dataset with a simple map, a filter, and story cards that expand.'],
-  ['heritage', 'Culture', 'Heritage Site Storyscroll', 'A cinematic, accessible scrollytelling page about an INVENTED heritage site, with chapter navigation, restrained motion, and reduced-motion support.'],
-  // ---- NEWS ----
-  ['election-viz', 'News', 'Election Results Visualization', 'A data-driven news visualization of INVENTED election results: a region map or bar breakdown, a filter, a clear headline takeaway, and a source/methodology note.'],
-  ['econ-brief', 'News', 'Economic Indicators Briefing', 'A newsroom data dashboard of INVENTED economic indicators with charts, KPI cards, a time-range filter, empty/error states, and a source note.'],
-  ['news-ticker', 'News', 'Live News Landing', 'A responsive breaking-news landing with a live-updating ticker, clear story hierarchy, a category filter, and an accessible reading order.'],
-  ['climate-story', 'News', 'Climate Data Story', 'A data-journalism scrollytelling piece charting an INVENTED climate dataset, with annotated charts, a clear narrative arc, and reduced-motion support.'],
-  // ---- POLITICAL CARTOONS ----
-  ['editorial-cartoon', 'Political Cartoon', 'Editorial Cartoon', 'Render an ORIGINAL editorial/political cartoon entirely in canvas or SVG about an INVENTED civic issue, with a caption and a caption show/hide toggle. Keep it satirical but non-partisan and non-defamatory — use invented figures and symbols only.'],
-  ['satire-strip', 'Political Cartoon', 'Three-Panel Satire Strip', 'A three-panel political-satire comic strip drawn in SVG about an INVENTED policy debate, with panel navigation and per-panel captions. Invented characters only; keep it good-natured.'],
-  ['metaphor', 'Political Cartoon', 'Political Metaphor Illustration', 'An SVG editorial illustration built around a single visual metaphor for an INVENTED political theme, with an explanatory caption and a light/dark toggle.'],
-  ['civic-explainer', 'Political Cartoon', 'Civic Explainer Cartoon', 'An illustrated (SVG) civic explainer cartoon that walks panel-by-panel through how an INVENTED civic process works, with navigation and captions.'],
+// ================== THE ONE PLACE TO EDIT ==================
+// Themed prompt bank — every prompt is [id, category, title, brief]. Categories are
+// passed to the arena verbatim, so the per-category leaderboards bucket by whatever
+// label you use here (inferCategory is only a fallback when no category is sent). Add
+// or remove prompts / categories freely; EMPHASIS below controls extra weighting.
+// Briefs are written to be imaginative and surprising — weird mashups, bold visual
+// concepts, clever interactions — while staying buildable as ONE self-contained file.
+const EMPHASIS = ['Art', 'Culture', 'News', 'Political Cartoon']; // Steve's focus — rotated ~2x more often
+const PROMPT_BANK = [
+  // ---- ART (emphasis) ----
+  ['gen-art', 'Art', 'Living Ink Canvas', "A generative 'living ink' canvas where cursor speed is brush pressure: fast strokes splatter, slow strokes bloom into calligraphic petals. The palette drifts with an imagined time-of-day, and one keypress freezes the piece into a framed 'gallery print' with an invented placard."],
+  ['mondrian', 'Art', 'Playable Mondrian', "A Mondrian composition you can play like an instrument: clicking each colored rectangle triggers a tone and a ripple, and a 'let it compose itself' mode slowly rearranges the grid to a generative rhythm."],
+  ['kinetic-type', 'Art', 'Swarm Typography Poster', "A kinetic-type poster for an INVENTED avant-garde festival where the headline letters behave like a flocking swarm — scattering from the cursor and re-forming the title — with a slider that morphs the mood from 'whisper' to 'scream'."],
+  ['fractal', 'Art', 'Deep-Sea Fractal Dive', "A fractal explorer disguised as a deep-sea dive: descend into a Julia set as if sinking through bioluminescent water, a depth gauge ticking, the palette cooling with depth, and a 'creature spotted' note at interesting zoom levels."],
+  // ---- CULTURE (emphasis) ----
+  ['museum', 'Culture', 'Museum of a Lost Civilization', "A pocket museum for a civilization that never existed: wander a floor-plan of rooms, click artifacts to reveal INVENTED lore, and assemble a shard-puzzle that unlocks the civilization's 'lost anthem' (Web Audio)."],
+  ['culture-map', 'Culture', 'Festivals of Nowhere', "A world map of INVENTED holidays where hovering a region launches its signature celebration in miniature (lanterns, confetti, fireworks) and a little passport stamps each one you visit."],
+  ['languages', 'Culture', 'The Last Speaker', "An interactive 'sound of a dying language' explainer for an INVENTED tongue: click words on a fading map to hear invented phonemes, watch the speaker-count clock tick down, and 'save' words into a phrasebook."],
+  ['heritage', 'Culture', 'Lantern Temple by Night', "A candle-lit scrollytelling tour of an INVENTED heritage temple where scrolling moves a lantern's glow across carvings, revealing a story chapter by chapter, and a constellation completes at the end."],
+  // ---- NEWS (emphasis) ----
+  ['election-viz', 'News', 'Election Night: Floating Isles', "An election-night 'war room' for an INVENTED nation of floating islands: a live map colors in as returns land, a needle swings, invented pundits drop one-liners, and a 'what if' slider re-runs the result."],
+  ['econ-brief', 'News', 'Teletext Econ Briefing', "A retro-teletext economic briefing for an INVENTED country: blocky CRT panels tick with fake indicators, a crawler scrolls, a big red/green mood dial summarizes the day — with scanlines and a channel-flip."],
+  ['news-ticker', 'News', 'Absurd 24-Hour News', "A satirical INVENTED 24-hour news channel landing where three breaking stories escalate absurdly in real time via a live ticker, a 'BREAKING' klaxon toggle, and a category filter that changes the channel's whole color mood."],
+  ['climate-story', 'News', 'The City That Floods As You Scroll', "A data-journalism scrollytelling piece where an INVENTED city floods frame by frame as you drag a rising-sea slider, annotated charts surface, and a final interaction lets the reader 'plant' defenses and watch the chart bend."],
+  // ---- POLITICAL CARTOON (emphasis) ----
+  ['editorial-cartoon', 'Political Cartoon', 'The Seesaw Debate', "An original SVG editorial cartoon about an INVENTED debate ('should robots get weekends?'): a seesaw of absurd figures the viewer can tip, with a caption that rewrites itself for each side. Non-partisan, invented symbols only."],
+  ['satire-strip', 'Political Cartoon', 'The Form to Request a Form', "A three-panel SVG satire strip about an INVENTED bureaucratic saga (a form that requires a form to request a form). Click to advance panels; a 'plot twist' button reshuffles the punchline. Good-natured, invented characters."],
+  ['metaphor', 'Political Cartoon', 'Ship of State by Committee', "A single bold SVG metaphor illustration for an INVENTED political theme — 'the ship of state steered by committee' — where dragging elements changes the metaphor's meaning and the caption adapts. Light/dark toggle."],
+  ['civic-explainer', 'Political Cartoon', 'How a Law Gets Passed (Rube Goldberg)', "A cheerful SVG cartoon explaining how an INVENTED law gets passed as a Rube-Goldberg contraption: click to send a marble through each stage of 'the process', with captions and a gleeful finale."],
+  // ---- GAMES ----
+  ['snake', 'Games', 'Comet Snake in a Dying Galaxy', "Snake, but the snake is a comet and the arena is a shrinking galaxy: eating stars grows your tail AND bends gravity, near-misses trigger slow-mo, and the high score is logged as a named 'constellation'."],
+  ['breakout', 'Games', 'Wake the Dragon Breakout', "Brick-breaker where the bricks are a sleeping dragon's scales — knock them loose and the dragon slowly wakes (eyes open, breathing quickens). The paddle is a shield; a caught power-up briefly lets you paint new bricks."],
+  ['tower', 'Games', 'Tower on a Walking Giant', "A block-stacker built on the back of a walking giant: each drop sways the tower with real wobble physics, wind gusts nudge your timing, and reaching cloud height unlocks a tiny weather system at the summit."],
+  // ---- REAL WORK (dashboards / utilities) ----
+  ['crm', 'Real Work', 'Fishing-Dock CRM', "A CRM where each lead is a fish on a line you drag between stages (Nibble -> Hooked -> Landed), per-stage totals shown as a catch tally, with search, quick-add, and validation. Playful but genuinely usable."],
+  ['timesheet', 'Real Work', 'Timesheet Garden', "A timesheet that grows your week as a garden — each logged hour sprouts a plant in the right plot, overtime wilts, and an 'invoice' view harvests it into a clean printable total with rate math and validation."],
+  ['pomodoro', 'Real Work', 'Lighthouse Focus Timer', "A focus timer styled as a tiny lighthouse: work sessions sweep the beam and calm the sea, breaks bring gentle waves, a task list is the ships to guide home, and session stats show the night's safe passages."],
+  // ---- DATA VIZ / INFOGRAPHICS ----
+  ['multi-chart', 'Data Viz', 'City of Data Dashboard', "An INVENTED 'city of data' dashboard where bar charts are skyscrapers, a donut is a roundabout, and a line chart is the dusk skyline — one shared filter re-zones the whole city, with a legend and an empty 'ghost town' state."],
+  ['flow-infographic', 'Data Viz', 'Bean-to-Cup Sankey', "An SVG sankey where an INVENTED coffee empire's beans flow from farm to cup — hover any stream to trace it, click to reroute supply and watch downstream totals update, with an accessible table fallback."],
+  ['scatter', 'Data Viz', 'Planet Dating Show', "A scatter-plot 'dating show' for INVENTED planets: each dot is a world, axis pickers change what makes a 'good match', lasso-select to form a shortlist, and a summary narrates the cluster you drew."],
+  // ---- SCIENCE & EDUCATION ----
+  ['solar', 'Science', 'Conduct the Solar System', "An INVENTED solar system you conduct like an orchestra — each planet's orbit is a looping note, speed sets the tempo, clicking a world solos its sound with a fact card, and a 'supernova' resets it. Reduced-motion aware."],
+  ['pendulum', 'Science', 'The Painting Pendulum', "A double-pendulum that paints: its chaotic tip leaves luminous trails, so tiny tweaks to gravity/length spin wildly different artworks — with a 'twin start' mode that races two near-identical pendulums into divergence."],
+  ['cell', 'Science', 'Cell as a Factory-City', "A cell explainer as a bustling factory-city (SVG): click organelles to read their 'job posting' and watch a glucose delivery truck route through the city to make ATP, with a guided tour and a quiz-yourself mode."],
+  // ---- MUSIC & AUDIO ----
+  ['sequencer', 'Music', 'Constellation Sequencer', "A Web Audio step sequencer where the grid is a night sky and each active step is a star that flickers on its beat (patterns = constellations); tempo, swing, per-track mute, a shooting-star fill, and a safe muted default."],
+  ['synth', 'Music', 'The Singing Creature Synth', "A playable Web Audio synth built as a creature that sings: its mouth opens with amplitude, eyes track pitch, filter sweeps ruffle its fur, and the on-screen keys are its teeth. Waveform/envelope controls; genuinely playable."],
+  ['audioviz', 'Music', 'Skyline Audio Visualizer', "An audio visualizer that renders an INVENTED city skyline pulsing to Web Audio oscillators — windows light on transients, traffic streaks on the beat, weather shifts with a 'mood' control. No external files; mute default."],
+  // ---- SPORTS ----
+  ['standings', 'Sports', 'Zero-G Relay League', "A standings tracker for an INVENTED sport (zero-gravity relay) with a sortable table, a form guide of tiny animated results, and a team page that shows their home 'arena' as a themed backdrop."],
+  ['bracket', 'Sports', 'Mascot Clash Bracket', "A single-elim bracket for INVENTED mascots where clicking a winner triggers a 2-second SVG 'clash' animation before advancing, with seeding, highlighted upsets, and a champion confetti reveal."],
+  ['statcard', 'Sports', 'Holographic Stat Cards', "A trading-card stat viewer for INVENTED athletes: cards tilt in 3D to the cursor with a holographic sheen, flip to reveal radar stats, and a head-to-head mode slams two cards together for a winner readout."],
+  // ---- HISTORY ----
+  ['timeline-hist', 'History', 'Walk-Through Age of Invention', "A scrollable timeline of an INVENTED age of invention where each era is a room you walk through, gadgets you can click to 'switch on', and a filter that dims everything except one thread of progress."],
+  ['empire-map', 'History', 'An Empire That Breathes', "An INVENTED empire that breathes across a map as you drag a century slider — borders bloom and recede, cities rise as glowing dots, and event notes fade in at the turning points, with a legend."],
+  ['artifact', 'History', 'Spotlit Relic Viewer', "A museum artifact viewer where an INVENTED relic rotates as a chunky CSS-3D object under a movable spotlight; click facets to reveal provenance and era context, with next/prev and a 'restore vs weathered' toggle."],
+  // ---- NATURE & SPACE ----
+  ['starmap', 'Nature & Space', 'Invent Your Own Constellations', "A night sky where YOU invent the myths: drag between stars to draw your own constellation, name it, and the sky remembers it with a little origin story; hover known INVENTED constellations for lore."],
+  ['ecosystem', 'Nature & Space', 'Terrarium in a Jar', "A jar-terrarium ecosystem of INVENTED critters with live population graphs — feed them, introduce a predator, or trigger a drought and watch the food web wobble toward chaos or balance, with a reset."],
+  ['weather', 'Nature & Space', 'Shake-the-Snow-Globe Weather', "A snow-globe weather visualizer you shake (click-drag) to trigger storms over an INVENTED tiny town; a dial cycles sun/rain/storm/snow with smooth transitions and the town reacts (umbrellas, lights). Reduced-motion aware."],
+  // ---- COMEDY & MEMES ----
+  ['meme', 'Comedy', 'The Meme Generator That Fights Back', "A meme generator that judges you: a sarcastic INVENTED critic meter rates your caption, suggests a worse one, and lets you export the final over selectable CSS-drawn backdrops with font controls."],
+  ['excuse', 'Comedy', 'The Excuse Slot Machine', "An 'Excuse Engine' slot machine that spins three reels (who / what / cosmic reason) into gloriously implausible excuses, with a believability meter, a 'too far' lever, favorites, and copy-to-clipboard."],
+  ['roast', 'Comedy', 'Roast or Toast Campfire', "A good-natured 'Roast or Toast' campfire where a toggle sends compliments or playful roasts at escalating heat levels shown as literal flames, with shareable text and a 'mercy' button. Keep it kind."],
+  // ---- RETRO / DEMOSCENE / GENERATIVE ----
+  ['plasma', 'Demoscene', 'Lava-Lamp Boot Screen', "A demoscene plasma pretending to be a lava-lamp OS boot screen — fake loading messages scroll over palette-cycling plasma, a 'sync to beat' toggle pulses it, and an fps counter sits in the corner. Reduced-motion fallback."],
+  ['tunnel', 'Demoscene', 'Wormhole to Anywhere', "An infinite tunnel that flies through an INVENTED wormhole to shifting destinations (neon city, coral reef, circuit board) selectable on the fly, with texture/palette controls and auto-motion."],
+  ['metaballs', 'Demoscene', 'Slime Creature Terrarium', "Gooey metaballs staged as a terrarium of glowing 'slime creatures' that merge and split; controls for count/threshold/palette, a cursor that repels them, and a snapshot button."],
+  // ---- FINANCE & MARKETS ----
+  ['portfolio', 'Finance', 'Galactic Exchange Portfolio', "A portfolio tracker for an INVENTED galactic exchange (holdings in 'moon deeds' and 'comet futures'): allocation donut as a planet system, a gain/loss aura, a sortable table, and a what-if rebalance slider that re-orbits everything."],
+  ['candles', 'Finance', 'Meme-Stock Chart With a Personality', "An INVENTED meme-stock candlestick chart that reacts to a 'hype' slider (rockets, panic), draws a moving average, shows a crosshair readout and volume, and has a fake pundit narrate the big moves."],
+  ['budget', 'Finance', 'Budget as Jars of Coins', "A budget planner as a household of jars you pour income into — drag coins between category jars, a 'remaining' jar shows overflow/empty states, validation guards overspend, and a savings-goal jar fills toward a treat."],
+  // ---- DESIGN / UI COMPONENTS ----
+  ['components', 'Design', 'Design System Safari', "A UI component kit staged as a 'design system safari' — each component (buttons, inputs, tabs, modal, toast, cards) lives in a themed habitat card, a live light/dark toggle changes the biome, and every accessible state is shown."],
+  ['tokens', 'Design', 'Palette & Type Mad-Lab', "A palette + type 'mad scientist lab' where you mix two seed colors and a mood to brew a full token set, preview it live on a sample product card, copy the CSS variables, and hit 'randomize a happy accident'."],
+  ['formkit', 'Design', 'Pre-Flight Checklist Form Kit', "An accessible form kit framed as a spaceship pre-flight checklist — validation states light up like console gauges, an inline error summary is the 'do not launch' panel, an optional multi-step flow, and rock-solid keyboard focus."],
 ].map(([id, cat, t, p]) => ({ id, cat, t, p }));
 
-const brief = (pick) => `Build this as one complete, self-contained HTML file: ${pick.p} Use realistic invented content and a strong information hierarchy. Every visible control must work and give clear feedback; include relevant empty, error, and completion states where applicable. Make it cohesive from 390px to 1440px, keyboard accessible with visible focus, and respectful of reduced motion.`;
+// Category rotation schedule: every category appears once per pass; EMPHASIS categories
+// get an extra slot so they rotate ~2x as often, while coverage across ALL categories
+// stays even. Within a category, prompts cycle in order (tracked per-category in state).
+function categorySchedule() {
+  const cats = [...new Set(PROMPT_BANK.map(p => p.cat))]; // first-seen order
+  const sched = cats.slice();
+  for (const c of EMPHASIS) if (cats.includes(c)) sched.push(c);
+  return sched;
+}
+function pickPrompt(state) {
+  const sched = categorySchedule();
+  const cat = sched[(state.catIdx || 0) % sched.length];
+  state.catIdx = ((state.catIdx || 0) + 1) % sched.length;
+  const inCat = PROMPT_BANK.filter(p => p.cat === cat);
+  state.perCat = state.perCat || {};
+  const i = (state.perCat[cat] || 0) % inCat.length;
+  state.perCat[cat] = i + 1;
+  return inCat[i];
+}
+
+const brief = (pick) => `Build this as one complete, self-contained HTML file: ${pick.p} Use realistic invented content and a strong information hierarchy. Every visible control must work and give clear feedback; include relevant empty, error, and completion states where applicable. Make it cohesive from 390px to 1440px, keyboard accessible with visible focus, and respectful of reduced motion. Be genuinely creative and surprising — surprise me with the execution, not just the concept.`;
 
 function req(method, p, body) {
   return new Promise((resolve, reject) => {
@@ -58,7 +135,7 @@ function req(method, p, body) {
   });
 }
 const sleep = ms => new Promise(r => setTimeout(r, ms));
-function loadState() { try { return JSON.parse(fs.readFileSync(STATE, 'utf8')); } catch { return { promptIdx: 0, modelOffset: 0 }; } }
+function loadState() { try { return JSON.parse(fs.readFileSync(STATE, 'utf8')); } catch { return { catIdx: 0, perCat: {}, modelOffset: 0 }; } }
 function saveState(s) { try { fs.mkdirSync(path.dirname(STATE), { recursive: true }); fs.writeFileSync(STATE, JSON.stringify(s)); } catch {} }
 
 // all discovered/curated LOCAL models (ollama + openai-local), interleaved by host so a
@@ -91,14 +168,17 @@ async function oneCycle(state) {
   const inter = await localModelsInterleaved();
   const hostCount = new Set(inter.map(m => m.host || m.id)).size;
   if (inter.length < 2) { console.error('[loop] only ' + inter.length + ' local models available — waiting'); return false; }
-  const pick = POOL[state.promptIdx % POOL.length];
+  const pick = process.env.PROMPT_ID ? (PROMPT_BANK.find(p => p.id === process.env.PROMPT_ID) || pickPrompt(state)) : pickPrompt(state);
   const models = process.env.MODELS_OVERRIDE ? process.env.MODELS_OVERRIDE.split(',').map(s => s.trim()).filter(Boolean) : pickModels(inter, state.modelOffset);
-  const title = `Arena · ${pick.cat}: ${pick.t}`;
+  // frame every cycle as a named COMPETITION, numbered per category (persisted).
+  state.compCount = state.compCount || {};
+  const compN = (state.compCount[pick.cat] || 0) + 1;
+  state.compCount[pick.cat] = compN;
+  const title = `${pick.cat} Competition #${compN} — ${pick.t}`;
   const c = await req('POST', '/api/challenges', { title, prompt: brief(pick), models, category: pick.cat });
   if (!c || !c.id) { console.error('[loop] create failed', JSON.stringify(c).slice(0, 200)); return false; }
   console.log('[loop] created', c.id, '—', pick.cat + ':', pick.t, '| models:', models.length, '| hosts:', hostCount);
-  // advance rotation only on a successful create
-  state.promptIdx = (state.promptIdx + 1) % POOL.length;
+  // advance the model window only on a successful create (pickPrompt already advanced the category rotation)
   state.modelOffset = (state.modelOffset + CAP) % Math.max(1, inter.length);
   saveState(state);
   // poll until settled (or cap) — never deadlock; server watchdog fails wedged runs
@@ -120,7 +200,7 @@ async function oneCycle(state) {
 (async () => {
   const state = loadState();
   if (process.env.ONESHOT === '1') { const ok = await oneCycle(state); process.exit(ok ? 0 : 1); }
-  console.log('[loop] continuous themed arena loop starting — cap', CAP, 'gap', GAP_MS + 'ms');
+  console.log('[loop] continuous themed arena loop starting — cap', CAP, 'gap', GAP_MS + 'ms', '| bank', PROMPT_BANK.length, 'prompts /', new Set(PROMPT_BANK.map(p => p.cat)).size, 'categories');
   // eslint-disable-next-line no-constant-condition
   while (true) {
     try { const ok = await oneCycle(state); await sleep(ok ? GAP_MS : 60000); }

← 9f431e6 model-arena: HOT arena sampling temperature (ARENA_TEMP, def  ·  back to Model Arena  ·  auto-data-snapshot: 2026-09-18T14:54:24 (3 data files) — dat 933886f →