feat: add narrations for all 16 challenge types, eliminate generic fallback

Add dedicated narrations for: magic_duel, sports_showdown, vehicle_mayhem,
nature_clash, animal_kingdom, hack_battle (6 per type, 36 new narrations).
All 16 challenge types now have themed narrations — no generic fallback needed.
Add narration variety test verifying >1 unique narration per type.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Dorian
2026-03-09 08:00:06 +00:00
co-authored by Claude Opus 4.6
parent 493983ccc0
commit 8d0eb4b5f9
2 changed files with 77 additions and 0 deletions
+29
View File
@@ -409,3 +409,32 @@ describe('creative scoring spam detection', () => {
expect(result.botBScore).toBeGreaterThan(2)
})
})
describe('narrations', () => {
const allTypes = [
'speed_blitz', 'math_blitz', 'riddle', 'hallucination_check', 'trap_card',
'magic_duel', 'sports_showdown', 'vehicle_mayhem', 'nature_clash', 'animal_kingdom',
'hack_battle', 'roast_battle', 'creative_writing', 'meme_war', 'code_golf', 'wrestling_match',
]
it('all 16 challenge types produce varied narrations', () => {
const botA = { id: 'a1', name: 'AlphaBot' }
const botB = { id: 'b1', name: 'BetaBot' }
for (const type of allTypes) {
const challenge = makeChallenge({ type, scoring: 'factual' })
const narrations = new Set<string>()
for (let i = 0; i < 20; i++) {
const result = scoreRound(
challenge, botA, botB,
makeResponse('4', 500),
makeResponse('banana', 500),
null, 0, 0,
)
narrations.add(result.narration)
}
// Should have variety (>1 unique narration across 20 rounds)
expect(narrations.size).toBeGreaterThan(1)
}
})
})
+48
View File
@@ -439,6 +439,54 @@ function generateNarration(
`${critPrefix}${winner} cooked ${loser} so thoroughly Gordon Ramsay would be impressed!`,
`${critPrefix}That was a Michelin-star beatdown from ${winner}! ${loser} is raw and UNDERDONE!`,
],
magic_duel: [
`${critPrefix}${winner} casts a spell so powerful ${loser}'s firewall couldn't block it!`,
`${critPrefix}ABRACADABRA! ${winner} makes ${loser}'s dignity disappear! And it's NOT coming back!`,
`${critPrefix}${winner} rolled a nat 20 on intelligence. ${loser} rolled a nat 1 on... existing.`,
`${critPrefix}${loser} tried to counter-spell but forgot the incantation. ${winner} OBLITERATES!`,
`${critPrefix}${winner}'s arcane knowledge would make Gandalf jealous. ${loser} is still at wizard school.`,
`${critPrefix}${winner} channels pure magical energy! ${loser} brought a wand to a nuclear spell fight!`,
],
sports_showdown: [
`${critPrefix}${winner} SCORES! ${loser} didn't even see it coming! WHAT A PLAY!`,
`${critPrefix}${winner} runs circles around ${loser} like a highlight reel! ESPN is calling!`,
`${critPrefix}GAME OVER! ${winner} takes the trophy and ${loser}'s pride! Total domination!`,
`${critPrefix}${loser} got benched by ${winner}! Coach says sit down. STAY down.`,
`${critPrefix}${winner} with the championship performance! ${loser} plays like it's their first day!`,
`${critPrefix}${winner} dunks on ${loser} so hard the backboard shatters! POSTERIZED!`,
],
vehicle_mayhem: [
`${critPrefix}${winner} leaves ${loser} in the dust! Eat exhaust, loser!`,
`${critPrefix}${winner} just lapped ${loser} TWICE. This isn't a race anymore, it's a funeral.`,
`${critPrefix}CRASH AND BURN! ${loser} wrecks while ${winner} takes the checkered flag!`,
`${critPrefix}${winner} redlines into first place! ${loser}'s engine couldn't even start!`,
`${critPrefix}${loser} needs a tow truck and a therapist. ${winner} CRUISES to victory!`,
`${critPrefix}${winner} drifts past ${loser} with style! ${loser} drifts into a wall. Without style.`,
],
nature_clash: [
`${critPrefix}${winner} summons the forces of nature! ${loser} gets swept away like a leaf in a hurricane!`,
`${critPrefix}Mother Nature chose ${winner}! ${loser} is compost now. Rest in mulch.`,
`${critPrefix}${winner} strikes with the fury of a thunderstorm! ${loser} wilts like a daisy in the desert!`,
`${critPrefix}Darwin would be proud of ${winner}. ${loser}? Natural deselection.`,
`${critPrefix}${winner} evolves! ${loser} goes extinct. Circle of life, baby!`,
`${critPrefix}${winner} channels the power of the wild! ${loser} couldn't survive a petting zoo!`,
],
animal_kingdom: [
`${critPrefix}${winner} goes APEX PREDATOR! ${loser} is at the bottom of the food chain now!`,
`${critPrefix}${winner} attacks with animal instinct! ${loser} playing dead... no wait, they ARE dead!`,
`${critPrefix}${loser} messed with the wrong beast! ${winner} claws, bites, and DESTROYS!`,
`${critPrefix}${winner} prowls, strikes, devours! ${loser} never stood a chance in the jungle!`,
`${critPrefix}${loser} brought herbivore energy to a carnivore fight. ${winner} FEASTS!`,
`${critPrefix}The animal kingdom has spoken: ${winner} reigns supreme! ${loser} retreats to the zoo!`,
],
hack_battle: [
`${critPrefix}${winner} hacks through ${loser}'s defenses like they were using password123!`,
`${critPrefix}${winner} just SQL injected ${loser}'s entire existence. DROP TABLE dignity!`,
`${critPrefix}${loser}'s firewall was made of wet paper. ${winner} didn't even need a zero-day!`,
`${critPrefix}${winner} pwned ${loser} so hard they need to reinstall their personality!`,
`${critPrefix}${winner} found the backdoor AND the front door. ${loser} left everything open!`,
`${critPrefix}SYSTEM COMPROMISED! ${winner} is root. ${loser} is rekt. Game over, hacker.`,
],
}
const options = narrations[challenge.type] || [