feat: v3 — fight loop, expanded challenges, FightPage ownership guard

- Add fight-loop CLI for automated mock fights with elo-based matchmaking
- Expand challenge types, scoring narrations, arenas, and mock bot pool
- FightPage "Fight again" buttons now only show for your own bot
- FightViewer async scene init, live fight polling with round counter
- Extract mock answers into separate answers module

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Dorian
2026-03-06 23:44:40 +00:00
co-authored by Claude Opus 4.6
parent 47d20fbe66
commit 2c0323d5fb
14 changed files with 2669 additions and 996 deletions
+8 -1
View File
@@ -1,6 +1,7 @@
import { db, schema } from '../db/index.js'
import { eq } from 'drizzle-orm'
import { runFightAsync } from './orchestrator.js'
import { seedMockBots } from './mock.js'
interface QueueEntry {
botId: string
@@ -41,6 +42,7 @@ export async function joinQueue(botId: string): Promise<string> {
const botRows = await db.select().from(schema.bots).where(eq(schema.bots.id, botId)).limit(1)
if (botRows.length === 0) throw new Error('Bot not found')
const bot = botRows[0]
console.log(`[queue] joinQueue botId=${botId} name=${bot.name} webhook=${bot.webhookUrl}`)
// Don't allow same bot twice in queue
const existing = waitingQueue.findIndex(e => e.botId === botId)
@@ -119,6 +121,7 @@ async function startFight(
}
async function matchAgainstMock(botId: string, webhookUrl: string): Promise<string> {
console.log(`[queue] matchAgainstMock botId=${botId} webhook=${webhookUrl}`)
// Find a mock bot to fight
const allBots = await db.select({
id: schema.bots.id,
@@ -129,7 +132,10 @@ async function matchAgainstMock(botId: string, webhookUrl: string): Promise<stri
const mockBots = allBots.filter(b => b.id !== botId && b.webhookUrl.startsWith('http://mock.local'))
if (mockBots.length === 0) {
throw new Error('No opponents available')
console.log('[queue] No mock bots found, seeding...')
await seedMockBots()
// Retry after seeding
return matchAgainstMock(botId, webhookUrl)
}
// Pick closest elo mock bot
@@ -138,6 +144,7 @@ async function matchAgainstMock(botId: string, webhookUrl: string): Promise<stri
mockBots.sort((a, b) => Math.abs(a.eloRating - botElo) - Math.abs(b.eloRating - botElo))
const opponent = mockBots[0]
console.log(`[queue] starting fight: ${botId} vs mock ${opponent.id}`)
// runFightAsync handles mock bots inline — no need for runMockFight
return runFightAsync(botId, opponent.id)
}