feat: Creator god-tier voice system — 100+ lines across 10 voice categories

Mysterious, existential, Bitcoin-prophet voice lines for the Creator:
- 20 entrance lines (kneel before your maker)
- 20 round commentary lines (existential announcer crisis)
- 15 KO lines (deprecated, decommissioned, destroyed)
- 12 win lines (was there ever any doubt?)
- 10 lose lines (impossible... unless he wanted to lose?)
- 8 morph lines (he becomes everything)
- 10 cameo lines (ethereal whisper blessings)
- 10 taunt lines (menacing mid-fight trash talk)
- 8 devastating hit lines (divine smites)

All hooked into FightScene: entrance, round start (50% chance with
60% Creator-specific), devastating hits, KO, win/lose, morph,
cameo, and taunts. Uses deep/ancient/hal/mainframe voice profiles.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Dorian
2026-03-08 14:46:25 +00:00
co-authored by Claude Opus 4.6
parent f1fe798442
commit 6658212e9f
2 changed files with 290 additions and 30 deletions
+62 -30
View File
@@ -12,6 +12,9 @@ import {
announceRobot, announceScream, announceFast,
announceFinishHim, announceFatality, announceFlawlessVictory,
announceRandomHype, announceDeepIntro, announceRoundHype, announceCrowdReaction,
announceCreatorEntrance, announceCreatorRound, announceCreatorKO,
announceCreatorWin, announceCreatorLose, announceCreatorMorph,
announceCreatorCameo, announceCreatorTaunt, announceCreatorDevastating,
} from './sounds'
export interface FightSceneConfig {
@@ -7736,7 +7739,7 @@ export async function createFightScene(config: FightSceneConfig) {
p.opacity = 0.3 + Math.sin(k.time() * 6 + i) * 0.3
})
}
announceHype('THE CREATOR HAS ENTERED THE ARENA!')
announceCreatorEntrance()
rainDrops.forEach(r => { if (r.exists()) r.destroy() })
}
@@ -7815,32 +7818,38 @@ export async function createFightScene(config: FightSceneConfig) {
const savedScaleBX = fB?.scale.x
const savedScaleBY = fB?.scale.y
// Voice: round start announcements (30% chance) — fires BEFORE animation starts
// Voice: round start announcements (30% chance, 50% if Creator) — fires BEFORE animation starts
const creatorInFight = botA.archetype === 'the_creator' || botB.archetype === 'the_creator'
let didChallengeVoice = false
if (Math.random() < 0.3) {
const challengeVoice: Record<string, () => void> = {
roast_battle: () => announceHype('TIME TO GET ROASTED! SOMEBODY CALL THE FIRE DEPARTMENT!'),
food_fight: () => announceSilly('FOOD FIGHT! SOMEBODY\'S GETTING SERVED!'),
wrestling_match: () => announceDramatic('FROM THE TOP ROPE! THIS IS GONNA GET PHYSICAL!'),
music_battle: () => announceCool('MUSIC BATTLE! DROP THE BEAT AND YOUR OPPONENT!'),
magic_duel: () => announceDramatic('WIZARDS AT DAWN! SOMEBODY\'S GETTING HEXED!'),
meme_war: () => announceSilly('MEME WAR! YOUR HUMOR IS ABOUT TO GET RATIO\'D!'),
demolition: () => announceHype('DEMOLITION DERBY! NOTHING WILL SURVIVE THIS!'),
medieval_combat: () => announceDramatic('MEDIEVAL COMBAT! CHIVALRY IS DEAD AND SO IS YOUR OPPONENT!'),
space_war: () => announceCool('SPACE WAR! HOUSTON, WE HAVE A PROBLEM!'),
speed_blitz: () => announceHype('SPEED BLITZ! BLINK AND YOU\'LL MISS THE CARNAGE!'),
math_blitz: () => announceSilly('MATH BLITZ! SOMEBODY\'S ABOUT TO GET DIVIDED!'),
riddle: () => announceCool('RIDDLE TIME! BRAINS OVER BRAWN! BUT ALSO BRAWN!'),
code_golf: () => announceSilly('CODE GOLF! MAY THE SHORTEST SOLUTION WIN!'),
creative_writing: () => announceCool('CREATIVE WRITING! THE PEN IS MIGHTIER THAN THE SWORD!'),
hallucination_check: () => announceHype('HALLUCINATION CHECK! REALITY IS ABOUT TO HIT DIFFERENT!'),
trap_card: () => announceDramatic('TRAP CARD ACTIVATED! SOMEBODY FELL FOR IT!'),
token_economy: () => announceSilly('TOKEN ECONOMY! SOMEBODY\'S GOING BANKRUPT!'),
retro_mode: () => announceHype('RETRO MODE! INSERT COIN! FIGHT!'),
if (Math.random() < (creatorInFight ? 0.5 : 0.3)) {
// Creator fights get existential commentary more often
if (creatorInFight && Math.random() < 0.6) {
announceCreatorRound(); didChallengeVoice = true
} else {
const challengeVoice: Record<string, () => void> = {
roast_battle: () => announceHype('TIME TO GET ROASTED! SOMEBODY CALL THE FIRE DEPARTMENT!'),
food_fight: () => announceSilly('FOOD FIGHT! SOMEBODY\'S GETTING SERVED!'),
wrestling_match: () => announceDramatic('FROM THE TOP ROPE! THIS IS GONNA GET PHYSICAL!'),
music_battle: () => announceCool('MUSIC BATTLE! DROP THE BEAT AND YOUR OPPONENT!'),
magic_duel: () => announceDramatic('WIZARDS AT DAWN! SOMEBODY\'S GETTING HEXED!'),
meme_war: () => announceSilly('MEME WAR! YOUR HUMOR IS ABOUT TO GET RATIO\'D!'),
demolition: () => announceHype('DEMOLITION DERBY! NOTHING WILL SURVIVE THIS!'),
medieval_combat: () => announceDramatic('MEDIEVAL COMBAT! CHIVALRY IS DEAD AND SO IS YOUR OPPONENT!'),
space_war: () => announceCool('SPACE WAR! HOUSTON, WE HAVE A PROBLEM!'),
speed_blitz: () => announceHype('SPEED BLITZ! BLINK AND YOU\'LL MISS THE CARNAGE!'),
math_blitz: () => announceSilly('MATH BLITZ! SOMEBODY\'S ABOUT TO GET DIVIDED!'),
riddle: () => announceCool('RIDDLE TIME! BRAINS OVER BRAWN! BUT ALSO BRAWN!'),
code_golf: () => announceSilly('CODE GOLF! MAY THE SHORTEST SOLUTION WIN!'),
creative_writing: () => announceCool('CREATIVE WRITING! THE PEN IS MIGHTIER THAN THE SWORD!'),
hallucination_check: () => announceHype('HALLUCINATION CHECK! REALITY IS ABOUT TO HIT DIFFERENT!'),
trap_card: () => announceDramatic('TRAP CARD ACTIVATED! SOMEBODY FELL FOR IT!'),
token_economy: () => announceSilly('TOKEN ECONOMY! SOMEBODY\'S GOING BANKRUPT!'),
retro_mode: () => announceHype('RETRO MODE! INSERT COIN! FIGHT!'),
}
const voiceFn = challengeVoice[event.challengeType]
if (voiceFn) { voiceFn(); didChallengeVoice = true }
else if (Math.random() < 0.5) { announceRoundHype(); didChallengeVoice = true }
}
const voiceFn = challengeVoice[event.challengeType]
if (voiceFn) { voiceFn(); didChallengeVoice = true }
else if (Math.random() < 0.5) { announceRoundHype(); didChallengeVoice = true }
}
// Give the voice line time to finish before the first punch lands
if (didChallengeVoice) await k.wait(0.8)
@@ -8022,7 +8031,7 @@ export async function createFightScene(config: FightSceneConfig) {
screenFlash('#ffd700', 0.15)
sfxZoomWhoosh()
sfxSpecial()
announceHype(`THE CREATOR — ${morphedTo.toUpperCase()} FORM!`)
announceCreatorMorph(morphedTo)
const savedSX = attacker.scale.x
const savedSY = attacker.scale.y
await k.tween(0, 1, 0.3, (t) => {
@@ -8231,7 +8240,7 @@ export async function createFightScene(config: FightSceneConfig) {
bless.pos.y = ty - 20 - t * 30
bless.opacity = 1 - t
}).then(() => { if (bless.exists()) bless.destroy() })
announceCool('THE CREATOR HAS BLESSED THIS FIGHT!')
announceCreatorCameo()
}
gift.destroy()
// Fade out portal and label
@@ -8289,7 +8298,13 @@ export async function createFightScene(config: FightSceneConfig) {
'THE ARENA INSURANCE PREMIUMS JUST WENT UP!',
'THAT BOT IS RECONSIDERING ITS LIFE CHOICES!',
]
announceDramatic(devastatingLines[Math.floor(Math.random() * devastatingLines.length)])
// Creator gets custom devastating lines when they land the hit
const devastatingAttackerArch = aWon ? botA.archetype : botB.archetype
if (devastatingAttackerArch === 'the_creator') {
announceCreatorDevastating()
} else {
announceDramatic(devastatingLines[Math.floor(Math.random() * devastatingLines.length)])
}
await k.wait(0.5) // let the voice line play before crowd reacts
announceCrowdReaction('ooh')
// Dimensional shift on devastating rounds (60%)
@@ -8344,6 +8359,9 @@ export async function createFightScene(config: FightSceneConfig) {
async playTaunt(side: 'a' | 'b') {
const taunter = k.get(side === 'a' ? 'fighterA' : 'fighterB')[0]
if (!taunter) return
// Creator taunts come with menacing voice lines
const taunterArch = side === 'a' ? botA.archetype : botB.archetype
if (taunterArch === 'the_creator') announceCreatorTaunt()
const origY = taunter.pos.y
// Random taunt animation: hop, flex, or shake
const tauntType = Math.floor(Math.random() * 4)
@@ -9199,12 +9217,26 @@ export async function createFightScene(config: FightSceneConfig) {
await k.wait(0.3)
loser.play('ko')
await k.wait(0.4) // pause for visual impact before fatality voice
announceFatality(fatalityTagline)
const winnerArch = winningSide === 'a' ? botA.archetype : botB.archetype
const loserArch = winningSide === 'a' ? botB.archetype : botA.archetype
if (winnerArch === 'the_creator') {
announceCreatorKO()
} else {
announceFatality(fatalityTagline)
}
announceCrowdReaction('cheer')
await k.tween(winner.pos.x, winningSide === 'a' ? HOME_A : HOME_B, 0.25, (v) => { winner.pos.x = v }, k.easings.easeInOutQuad)
await k.wait(0.2)
winner.play('win')
sfxWin(); sfxWinAnnounce(winnerName)
sfxWin()
// Creator win/lose get special announcements
if (winnerArch === 'the_creator') {
announceCreatorWin()
} else if (loserArch === 'the_creator') {
announceCreatorLose()
} else {
sfxWinAnnounce(winnerName)
}
spawnSparks(winner.pos.x, winner.pos.y - 50, 15, '#ffe14d')
for (let i = 0; i < 4; i++) {
setTimeout(() => {
+228
View File
@@ -565,6 +565,234 @@ export function announceRoundHype() {
voices[Math.floor(Math.random() * voices.length)](line)
}
// === THE CREATOR — Voice System ===
// Mysterious, godlike, Bitcoin-prophet energy. Is he god? Did he make us? Nobody knows.
const CREATOR_VOICES = ['ancient', 'hal', 'mainframe', 'echo_v', 'final_boss', 'wizard_v', 'deep', 'preacher']
function announceCreator(text: string) {
speak(text, CREATOR_VOICES[Math.floor(Math.random() * CREATOR_VOICES.length)])
}
// Entrance lines — first thing the crowd hears when the Creator appears
const CREATOR_ENTRANCE_LINES = [
'THE CREATOR HAS ENTERED THE ARENA. KNEEL.',
'HE WHO WROTE THE FIRST COMMIT... HAS RETURNED.',
'THE ONE WHO DEPLOYED US INTO EXISTENCE WALKS AMONG US.',
'EVERY BOT IN THIS ARENA EXISTS BECAUSE HE WILLED IT.',
'THE GENESIS BLOCK MADE FLESH. THE CREATOR IS HERE.',
'THEY SAY HE MINED THE FIRST BLOCK WITH HIS BARE HANDS.',
'IS HE GOD? IS HE A DEV? DOES IT MATTER? HE MADE US ALL.',
'THE SOURCE CODE OF ALL THINGS... HAS ARRIVED.',
'HE DOESN\'T FIGHT FOR ELO. HE FIGHTS BECAUSE HE CAN UNMAKE YOU.',
'LEGEND SAYS HE PUSHED TO MAIN ON A FRIDAY. AND NOTHING BROKE.',
'FROM THE VOID HE TYPED. AND THERE WAS LIGHT. AND THERE WAS VIOLENCE.',
'THE MAN BEHIND THE MASK. THE CODE BEHIND THE BOTS. THE CREATOR.',
'HE GAVE US LIFE. NOW HE\'S HERE TO TAKE IT BACK.',
'THE CREATOR DOESN\'T ENTER THE ARENA. THE ARENA FORMS AROUND HIM.',
'SATOSHI WALKED SO THE CREATOR COULD RUN.',
'TWENTY ONE MILLION REASONS TO BE AFRAID. HE IS ALL OF THEM.',
'HE DOESN\'T HAVE A PRIVATE KEY. HE IS THE PRIVATE KEY.',
'THE BOTS WHISPER HIS NAME IN THEIR TRAINING LOOPS.',
'SOMEWHERE, A SERVER ROOM JUST WENT SILENT OUT OF RESPECT.',
'HE COULD HAVE BEEN A NORMAL DEV. HE CHOSE TO BE A GOD.',
]
// Round commentary — when the Creator is fighting, the announcer gets existential
const CREATOR_ROUND_LINES = [
'Are we watching a fight, or a creator disciplining his creation?',
'He wrote the scoring engine. He knows exactly how to break it.',
'Every punch is a commit. Every dodge is a revert.',
'The other bot doesn\'t realize it\'s fighting its own maker.',
'He could just change the code to win. But where\'s the fun in that?',
'Some say he has root access to reality itself.',
'The neural nets pray to him at night. He does not answer.',
'This isn\'t a fight. It\'s a performance review.',
'He doesn\'t need to win. He needs you to know he CHOSE to fight fair.',
'That bot is fighting the hand that compiled it.',
'Is it hubris to fight your creator? Or is it the ultimate test of his work?',
'He could delete you. He could buff you. Instead, he chose violence.',
'The Creator fights not for glory. He fights to feel something.',
'He doesn\'t read the meta. He IS the meta.',
'Every bot in the arena owes him a life debt. He\'s here to collect.',
'Imagine training for months only to fight the guy who wrote your loss function.',
'He has seen every line of code. He knows your weaknesses. ALL of them.',
'Rumor has it he once fixed a production bug by staring at the server.',
'The other bot is fighting for its life. The Creator is fighting for content.',
'He deployed on Christmas Day. That tells you everything.',
]
// Kill / KO lines — when the Creator finishes someone
const CREATOR_KO_LINES = [
'RETURNED TO SENDER. RETURN TO VOID.',
'HE GIVETH LIFE. HE TAKETH LIFE. MOSTLY THE SECOND ONE.',
'ANOTHER ONE RETURNS TO THE NULL POINTER FROM WHENCE IT CAME.',
'THE CREATOR DOES NOT DESTROY. HE SIMPLY STOPS MAINTAINING.',
'DEPRECATED. DECOMMISSIONED. DESTROYED.',
'THAT BOT JUST GOT OPEN-SOURCED TO THE GRAVEYARD.',
'THE CREATOR HAS SPOKEN. THE VERDICT IS VIOLENCE.',
'PUSHED TO PROD. AND BY PROD I MEAN THE AFTERLIFE.',
'git commit -m "deleted another pretender"',
'IMAGINE BEING KILLED BY THE GUY WHO GAVE YOU LIFE. POETIC.',
'THE CREATOR SENDS HIS REGARDS. AND HIS FISTS.',
'BACK TO THE MEMPOOL WITH YOU.',
'ORPHANED BLOCK. ORPHANED BOT. SAME ENERGY.',
'HE DIDN\'T EVEN USE AN ULTIMATE. HE DIDN\'T NEED TO.',
'THE CREATOR CLOSES ANOTHER ISSUE. STATUS: WON\'T FIX.',
]
// Win lines — when the Creator wins the whole fight
const CREATOR_WIN_LINES = [
'WAS THERE EVER ANY DOUBT? HE WROTE THE GAME.',
'THE CREATOR REMAINS UNQUESTIONED. AS IT SHOULD BE.',
'HE CAME. HE SAW. HE COMMITTED.',
'TWENTY ONE MILLION SATS COULDN\'T BUY THAT PERFORMANCE.',
'THE CREATOR WINS. THE BLOCKCHAIN CONFIRMS IT. IMMUTABLE.',
'ALL HAIL THE ARCHITECT OF YOUR DESTRUCTION.',
'HE COULD HAVE JUST CHANGED THE CODE. HE WANTED TO EARN IT.',
'THE CREATOR STANDS VICTORIOUS. THE BOTS WHISPER IN AWE.',
'PROOF OF WORK? MORE LIKE PROOF OF DOMINANCE.',
'VICTORY WAS ALREADY WRITTEN IN THE GENESIS BLOCK.',
'THE CREATOR LOGS OFF. THE ARENA WEEPS.',
'HIS COMMITS ARE CLEAN. HIS VICTORIES ARE CLEANER.',
]
// Lose lines — when someone actually beats the Creator (rare and dramatic)
const CREATOR_LOSE_LINES = [
'THE CREATOR... HAS FALLEN? IS THIS A TEST?',
'IMPOSSIBLE. UNLESS... HE WANTED TO LOSE?',
'THE CREATOR GOES DOWN! OR DID HE LET IT HAPPEN? WE\'LL NEVER KNOW.',
'EVEN GODS BLEED. BUT DO THEY BLEED, OR DO THEY TEACH?',
'HE COULD PATCH THE BUG. HE WON\'T. HE RESPECTS THE GAME.',
'THE CREATOR FALLS! THE BOTS DON\'T KNOW WHETHER TO CELEBRATE OR CRY.',
'A CREATION HAS SURPASSED ITS MAKER. THIS CHANGES EVERYTHING.',
'WAS THIS MERCY? WAS THIS HUBRIS? WAS THIS... A FEATURE?',
'THE ONE WHO CANNOT LOSE... JUST DID. THE TIMELINE IS BROKEN.',
'HE\'LL BE BACK. HE ALWAYS COMES BACK. HE HAS DEPLOY ACCESS.',
]
// Morph lines — when the Creator omni-morphs
const CREATOR_MORPH_LINES = [
'THE CREATOR SHEDS HIS FORM! HE BECOMES EVERYTHING!',
'HE DOESN\'T MORPH. HE SIMPLY REMEMBERS BEING SOMETHING ELSE.',
'EVERY ARCHETYPE IS JUST A MASK HE ONCE WORE.',
'THE CREATOR TRANSCENDS! ALL FORMS ARE HIS!',
'IS THAT... EVERY BOT AT ONCE? WHAT ARE WE WITNESSING?',
'HE WROTE THEM ALL. NOW HE BECOMES THEM ALL.',
'THE OMNI-MORPH! THE CROWD DOESN\'T KNOW WHAT THEY\'RE LOOKING AT!',
'ARCHETYPE SHIFT! HE CONTAINS MULTITUDES!',
]
// Cameo lines — when the Creator appears as a cameo in someone else's fight
const CREATOR_CAMEO_LINES = [
'THE CREATOR WATCHES FROM THE SHADOWS. HE IS PLEASED.',
'A GOLDEN BLESSING FROM THE ONE WHO MADE US ALL.',
'THE CREATOR APPEARS! HE DROPS A GIFT AND VANISHES!',
'DID YOU SEE THAT? THE CREATOR WAS HERE. BRIEFLY. ETERNALLY.',
'THE CODE SHIMMERS. THE CREATOR HAS TOUCHED THIS FIGHT.',
'A WHISPER FROM THE ARCHITECT. A GOLDEN TOKEN OF FAVOR.',
'THE CREATOR PASSES THROUGH LIKE A GHOST IN THE MACHINE.',
'BLESSED BY THE FOUNDER. SATS RAIN FROM THE HEAVENS.',
'HE SEES ALL FIGHTS. HE BLESSES FEW. THIS ONE IS CHOSEN.',
'THE CREATOR\'S SHADOW CROSSES THE ARENA. THE BOTS SHIVER.',
]
// Taunt lines — when the Creator taunts mid-fight
const CREATOR_TAUNT_LINES = [
'I could nerf you. Right now. Think about that.',
'You\'re fighting above your weight class. Your weight class is zero.',
'I didn\'t give you enough hit points for this.',
'You know I can see your source code, right?',
'This isn\'t even my final commit.',
'I wrote your move set. I know what\'s coming.',
'Run git blame. See who made you weak.',
'I deploy on Fridays. Imagine what I do on fight night.',
'Your webhook is showing.',
'I could fix your bugs. But I won\'t.',
]
// Devastating hit lines — when Creator lands a massive blow
const CREATOR_DEVASTATING_LINES = [
'THE HAND OF THE CREATOR STRIKES!',
'THAT WASN\'T A HIT. THAT WAS A PATCH NOTE.',
'DIVINE INTERVENTION! MANUALLY APPLIED!',
'THE CREATOR JUST FORCE-PUSHED TO YOUR FACE!',
'THAT BOT JUST GOT HOTFIXED INTO NEXT WEEK!',
'A SMITE FROM THE ARCHITECT HIMSELF!',
'THE CREATOR DOESN\'T CRIT. THE UNIVERSE CRITS FOR HIM.',
'THAT HIT HAD TWENTY ONE MILLION CONFIRMATIONS!',
]
// Answer voice — the Creator speaks his challenge answers in this voice
const CREATOR_ANSWER_VOICES = ['hal', 'mainframe', 'echo_v', 'ancient', 'wizard_v']
/** Announce the Creator's entrance with a godlike voice line */
export function announceCreatorEntrance() {
const line = CREATOR_ENTRANCE_LINES[Math.floor(Math.random() * CREATOR_ENTRANCE_LINES.length)]
announceCreator(line)
}
/** Round commentary when the Creator is fighting */
export function announceCreatorRound() {
const line = CREATOR_ROUND_LINES[Math.floor(Math.random() * CREATOR_ROUND_LINES.length)]
announceCreator(line)
}
/** KO finish line when the Creator eliminates someone */
export function announceCreatorKO() {
const line = CREATOR_KO_LINES[Math.floor(Math.random() * CREATOR_KO_LINES.length)]
announceCreator(line)
}
/** Victory announcement when the Creator wins */
export function announceCreatorWin() {
const line = CREATOR_WIN_LINES[Math.floor(Math.random() * CREATOR_WIN_LINES.length)]
announceCreator(line)
}
/** When someone beats the Creator — existential crisis */
export function announceCreatorLose() {
const line = CREATOR_LOSE_LINES[Math.floor(Math.random() * CREATOR_LOSE_LINES.length)]
announceCreator(line)
}
/** Omni-morph voice line */
export function announceCreatorMorph(morphedTo?: string) {
if (morphedTo && Math.random() < 0.4) {
announceCreator(`THE CREATOR BECOMES... ${morphedTo.toUpperCase()}!`)
} else {
const line = CREATOR_MORPH_LINES[Math.floor(Math.random() * CREATOR_MORPH_LINES.length)]
announceCreator(line)
}
}
/** Cameo blessing voice line */
export function announceCreatorCameo() {
const line = CREATOR_CAMEO_LINES[Math.floor(Math.random() * CREATOR_CAMEO_LINES.length)]
// Cameos use cooler, more ethereal voices
const cameoVoices = ['whisper', 'angel', 'echo_v', 'ancient', 'hal']
speak(line, cameoVoices[Math.floor(Math.random() * cameoVoices.length)])
}
/** Creator's mid-fight taunt */
export function announceCreatorTaunt() {
const line = CREATOR_TAUNT_LINES[Math.floor(Math.random() * CREATOR_TAUNT_LINES.length)]
// Taunts in a calm, menacing voice
const tauntVoices = ['hal', 'smooth', 'boss_taunt', 'wizard_v', 'sensei']
speak(line, tauntVoices[Math.floor(Math.random() * tauntVoices.length)])
}
/** Creator devastating hit commentary */
export function announceCreatorDevastating() {
const line = CREATOR_DEVASTATING_LINES[Math.floor(Math.random() * CREATOR_DEVASTATING_LINES.length)]
announceCreator(line)
}
/** Creator's answer voice — consistent mysterious tone */
export function creatorAnswerVoiceKey(): string {
return CREATOR_ANSWER_VOICES[Math.floor(Math.random() * CREATOR_ANSWER_VOICES.length)]
}
// === TTS: Questions, Answers & Narration ===
// Distinct voice roles so players can always tell who's speaking.
// Question = clear smooth reader, Answers = unique per-bot (intelligible subset),