From 8d0eb4b5f969e9da6e5b8855692e65b3535c8de4 Mon Sep 17 00:00:00 2001 From: Dorian Date: Mon, 9 Mar 2026 08:00:06 +0000 Subject: [PATCH] feat: add narrations for all 16 challenge types, eliminate generic fallback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- server/src/engine/scoring.test.ts | 29 +++++++++++++++++++ server/src/engine/scoring.ts | 48 +++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+) diff --git a/server/src/engine/scoring.test.ts b/server/src/engine/scoring.test.ts index 11836f6..b6ef177 100644 --- a/server/src/engine/scoring.test.ts +++ b/server/src/engine/scoring.test.ts @@ -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() + 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) + } + }) +}) diff --git a/server/src/engine/scoring.ts b/server/src/engine/scoring.ts index ed4a2cc..2d8be53 100644 --- a/server/src/engine/scoring.ts +++ b/server/src/engine/scoring.ts @@ -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] || [