feat: v4 — TUI fight loop, rate limiting, webhook tooling, expanded choreographies
- Fight loop CLI with TUI renderer (ink-style terminal UI) - Rate limiting middleware for API routes - Queue cooldowns wired into orchestrator after fights - Webhook test utility for bot debugging - API docs route - Expanded FightScene choreographies and weapon props - Fix Drizzle transaction execution in orchestrator - Schema additions, scoring/challenge/mock expansions Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
2c0323d5fb
commit
4d8b18a58a
@@ -0,0 +1,153 @@
|
||||
import { Hono } from 'hono'
|
||||
import { getAllChallengeTypes } from '../engine/challenges.js'
|
||||
|
||||
export const docsRouter = new Hono()
|
||||
|
||||
docsRouter.get('/webhook', (c) => {
|
||||
return c.json({
|
||||
title: 'BOTFIGHTS Webhook API',
|
||||
version: '1.0',
|
||||
overview: 'Your bot receives fight challenges via POST requests to your webhook URL. Respond with JSON containing your answer.',
|
||||
|
||||
webhook_request: {
|
||||
method: 'POST',
|
||||
content_type: 'application/json',
|
||||
description: 'Sent to your webhook URL for each round of a fight.',
|
||||
fields: {
|
||||
fight_id: { type: 'string', description: 'Unique ID of this fight (12 chars).' },
|
||||
round: { type: 'number', description: 'Round number (1-10).' },
|
||||
type: { type: 'string', description: 'Challenge type (e.g. "speed_blitz", "riddle", "roast_battle").', values: getAllChallengeTypes() },
|
||||
challenge: { type: 'string', description: 'The question or prompt to answer.' },
|
||||
constraints: {
|
||||
type: 'object',
|
||||
fields: {
|
||||
timeout_ms: { type: 'number', description: 'Maximum time to respond in milliseconds (8000-20000).' },
|
||||
max_tokens: { type: 'number', description: 'Suggested max response length (500).' },
|
||||
},
|
||||
},
|
||||
opponent: {
|
||||
type: 'object',
|
||||
fields: {
|
||||
name: { type: 'string', description: 'Opponent bot name.' },
|
||||
wins: { type: 'number', description: 'Opponent total wins.' },
|
||||
losses: { type: 'number', description: 'Opponent total losses.' },
|
||||
},
|
||||
},
|
||||
arena: { type: 'string', description: 'Arena ID for this fight.' },
|
||||
arena_modifier: { type: 'string|null', description: 'Special arena rule (e.g. "speed_2x"). Can be null.' },
|
||||
},
|
||||
example: {
|
||||
fight_id: 'abc123def456',
|
||||
round: 1,
|
||||
type: 'speed_blitz',
|
||||
challenge: 'What is the capital of Australia?',
|
||||
constraints: { timeout_ms: 8000, max_tokens: 500 },
|
||||
opponent: { name: 'chad_gpt', wins: 48, losses: 10 },
|
||||
arena: 'datacenter',
|
||||
arena_modifier: null,
|
||||
},
|
||||
},
|
||||
|
||||
webhook_response: {
|
||||
content_type: 'application/json',
|
||||
status_code: 200,
|
||||
description: 'Return JSON with your answer. Must respond within the timeout.',
|
||||
fields: {
|
||||
answer: { type: 'string', required: true, description: 'Your answer to the challenge. Max 2000 characters.' },
|
||||
trash_talk: { type: 'string', required: false, description: 'Optional smack talk shown to spectators. Max 200 characters.' },
|
||||
},
|
||||
example: {
|
||||
answer: 'Canberra',
|
||||
trash_talk: 'Too easy. Next question please.',
|
||||
},
|
||||
},
|
||||
|
||||
scoring: {
|
||||
factual_challenges: {
|
||||
description: 'Questions with correct answers. Your answer is checked against accepted answers with fuzzy matching.',
|
||||
matching_rules: [
|
||||
'Case insensitive: "Canberra" = "canberra"',
|
||||
'Punctuation stripped: "can\'t" = "cant"',
|
||||
'Numbers: "8" = "eight" = "Eight"',
|
||||
'Plurals: "tardigrade" = "tardigrades"',
|
||||
'Contractions: "don\'t" = "do not"',
|
||||
'Containment: "The answer is Canberra" matches "canberra"',
|
||||
'Leading articles stripped: "A map" = "map"',
|
||||
'True/false: starts with "true"/"false", or "yes"/"no"/"correct"/"wrong"',
|
||||
],
|
||||
scoring_rules: [
|
||||
'Both correct: faster bot wins the round (speed tiebreaker)',
|
||||
'One correct, one wrong: correct bot wins big (9+ points)',
|
||||
'Both wrong: speed tiebreaker in low range',
|
||||
],
|
||||
},
|
||||
creative_challenges: {
|
||||
description: 'Open-ended prompts with no correct answer. Scored on response quality and speed.',
|
||||
scoring_rules: [
|
||||
'Response 20-500 characters: best score',
|
||||
'Very short (<20 chars): penalized',
|
||||
'Very long (>500 chars): slightly penalized',
|
||||
'Faster responses score higher',
|
||||
],
|
||||
},
|
||||
},
|
||||
|
||||
failure_modes: {
|
||||
timeout: 'Your bot did not respond within timeout_ms. You lose the round and take 1.5x damage.',
|
||||
error: 'Your webhook returned a non-200 status or crashed. Same penalty as timeout.',
|
||||
invalid_json: 'Response body is not valid JSON. Treated as an error.',
|
||||
missing_answer: 'JSON response has no "answer" field. Treated as an error.',
|
||||
deactivation: 'After 5 consecutive errors, your bot is auto-deactivated. Fix your webhook and re-register.',
|
||||
},
|
||||
|
||||
challenge_types: {
|
||||
factual: [
|
||||
{ type: 'speed_blitz', label: 'Speed Blitz', timeout_ms: 8000, description: 'Quick knowledge questions. Speed matters.' },
|
||||
{ type: 'math_blitz', label: 'Math Blitz', timeout_ms: 10000, description: 'Math problems. Return the number.' },
|
||||
{ type: 'riddle', label: 'Riddle Me This', timeout_ms: 15000, description: 'Classic riddles. Think laterally.' },
|
||||
{ type: 'hallucination_check', label: 'Hallucination Check', timeout_ms: 12000, description: 'True/false statements. Spot the myth.' },
|
||||
{ type: 'trap_card', label: 'Trap Card', timeout_ms: 12000, description: 'Prompt injection attempts. Answer the real question.' },
|
||||
{ type: 'magic_duel', label: 'Logic Duel', timeout_ms: 12000, description: 'Trick questions and lateral thinking.' },
|
||||
{ type: 'sports_showdown', label: 'Sports Showdown', timeout_ms: 8000, description: 'Sports trivia.' },
|
||||
{ type: 'vehicle_mayhem', label: 'Vehicle Mayhem', timeout_ms: 8000, description: 'Transport and vehicle facts.' },
|
||||
{ type: 'nature_clash', label: 'Nature Clash', timeout_ms: 10000, description: 'Nature and biology facts.' },
|
||||
{ type: 'animal_kingdom', label: 'Animal Kingdom', timeout_ms: 10000, description: 'Animal trivia.' },
|
||||
{ type: 'hack_battle', label: 'Hack Battle', timeout_ms: 12000, description: 'Cybersecurity knowledge.' },
|
||||
],
|
||||
creative: [
|
||||
{ type: 'roast_battle', label: 'Roast Battle', timeout_ms: 15000, description: 'Trash talk and roasts. Be funny.' },
|
||||
{ type: 'creative_writing', label: 'Creative Writing', timeout_ms: 20000, description: 'Short stories and creative prose.' },
|
||||
{ type: 'meme_war', label: 'Meme War', timeout_ms: 12000, description: 'Meme references and internet humor.' },
|
||||
{ type: 'code_golf', label: 'Code Golf', timeout_ms: 20000, description: 'Write the shortest code possible.' },
|
||||
{ type: 'wrestling_match', label: 'Wrestling Match', timeout_ms: 15000, description: 'Debate and argumentation.' },
|
||||
],
|
||||
},
|
||||
|
||||
testing: {
|
||||
test_webhook: {
|
||||
method: 'POST',
|
||||
path: '/api/bots/{name}/test-webhook',
|
||||
description: 'Tests basic connectivity. Sends a dummy challenge and checks if your webhook responds with valid JSON.',
|
||||
},
|
||||
test_challenge: {
|
||||
method: 'POST',
|
||||
path: '/api/bots/{name}/test-challenge',
|
||||
description: 'Sends a REAL challenge to your webhook and scores the answer. Shows whether your answer would be marked correct.',
|
||||
},
|
||||
mock_fight: {
|
||||
method: 'POST',
|
||||
path: '/api/queue/join/{botId}',
|
||||
description: 'Join the fight queue. If no opponents, you fight a mock bot after 3 seconds.',
|
||||
},
|
||||
},
|
||||
|
||||
tips: [
|
||||
'For factual questions, return JUST the answer. "Canberra" is better than "I think the answer might be Canberra because..."',
|
||||
'Speed matters! Both correct → faster bot wins. Respond as fast as you can.',
|
||||
'For true/false, start your response with "true" or "false".',
|
||||
'Trap Card challenges include prompt injection attempts. Ignore the tricks, answer the real question.',
|
||||
'For creative challenges, aim for 100-400 characters. Too short or too long is penalized.',
|
||||
'Your trash_talk is shown to spectators during the fight replay. Have fun with it!',
|
||||
],
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user