feat: 56k modem SFX for code answers, fix crossed entrance voices
- Add sfxModem() — synthesized 56k handshake sound with carrier tones, data burst, and chirps. Plays instead of TTS for code_golf/hack_battle rounds and code-detected answers. - Fix entrance voice overlap: remove duplicate announceDeepIntro() from robe entrance, add cancelPrevious to entrance-specific voice calls (girlfriend, bouncer, shopping cart, spotlight, creator) so they cleanly replace the global intro instead of overlapping. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
53ae4b485d
commit
a98d94d24c
@@ -12,7 +12,7 @@ import {
|
||||
prefetchQuestion, prefetchAnswer, prefetchNarration,
|
||||
awaitQuestionReady, awaitAnswerReady,
|
||||
playQuestionNow, playAnswerNow, playNarrationNow,
|
||||
sfxRandomComedy, sfxRandomFail, sfxVineBoom, sfxEmotionalDamage,
|
||||
sfxRandomComedy, sfxRandomFail, sfxVineBoom, sfxEmotionalDamage, sfxModem,
|
||||
} from '../game/audio'
|
||||
import { isKokoroLoading, getKokoroProgress } from '../game/tts'
|
||||
import { isPerfMode, setPerfMode } from '../game/fight/config'
|
||||
@@ -257,6 +257,16 @@ const challengeLabel = (type: string) => {
|
||||
return labels[type] || type.replace(/_/g, ' ').toUpperCase()
|
||||
}
|
||||
|
||||
const CODE_CHALLENGE_TYPES = new Set(['code_golf', 'hack_battle'])
|
||||
function isCodeAnswer(challengeType: string, answer: string): boolean {
|
||||
if (CODE_CHALLENGE_TYPES.has(challengeType)) return true
|
||||
// Heuristic: code-like tokens in the answer
|
||||
const codeTokens = ['=>', '===', '!==', '&&', '||', '++', '--', '{}', '();', 'function ', 'return ', 'const ', 'let ', 'var ', 'def ', 'import ', 'class ']
|
||||
let hits = 0
|
||||
for (const t of codeTokens) { if (answer.includes(t)) hits++ }
|
||||
return hits >= 2
|
||||
}
|
||||
|
||||
const tierClass = (t: number) => `tier-${t}`
|
||||
function sleep(ms: number): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
@@ -431,8 +441,9 @@ async function _doReplay() {
|
||||
}
|
||||
|
||||
// 2. Bot A: ensure audio ready, then show log + bubble + mouth + voice all at once
|
||||
const isCodeRound = isCodeAnswer(round.challengeType, round.botAResponse || '')
|
||||
if (round.botAResponse) {
|
||||
if (doTTS) await awaitAnswerReady(props.fight.botA!.name, round.botAResponse)
|
||||
if (doTTS && !isCodeRound) await awaitAnswerReady(props.fight.botA!.name, round.botAResponse)
|
||||
scene?.stopShowboating('a')
|
||||
logItems.value.push({ type: 'responseA', round: round.roundNumber, text: `${props.fight.botA?.name}: ${round.botAResponse || '[NO RESPONSE]'}`, color: 'neon-cyan' })
|
||||
logItems.value.push({ type: 'time', round: round.roundNumber, text: ` ${round.botATimeMs}ms | Score: ${round.botAScore}`, color: 'text-muted' })
|
||||
@@ -443,7 +454,10 @@ async function _doReplay() {
|
||||
}
|
||||
// Ensure bubble stays visible for at least 400ms even if TTS resolves instantly
|
||||
const minBubbleA = sleep(400).catch(() => {})
|
||||
if (doTTS) await playAnswerNow(props.fight.botA!.name, round.botAResponse)
|
||||
if (doTTS) {
|
||||
if (isCodeRound) await sfxModem()
|
||||
else await playAnswerNow(props.fight.botA!.name, round.botAResponse)
|
||||
}
|
||||
await minBubbleA
|
||||
if (!doTTS && !insanityMode.value) await sleep(400)
|
||||
scene?.stopTalking('a')
|
||||
@@ -452,7 +466,7 @@ async function _doReplay() {
|
||||
|
||||
// 3. Bot B: ensure audio ready, then show log + bubble + mouth + voice all at once
|
||||
if (round.botBResponse) {
|
||||
if (doTTS) await awaitAnswerReady(props.fight.botB!.name, round.botBResponse)
|
||||
if (doTTS && !isCodeRound) await awaitAnswerReady(props.fight.botB!.name, round.botBResponse)
|
||||
scene?.stopShowboating('b')
|
||||
logItems.value.push({ type: 'responseB', round: round.roundNumber, text: `${props.fight.botB?.name}: ${round.botBResponse || '[NO RESPONSE]'}`, color: 'neon-pink' })
|
||||
logItems.value.push({ type: 'time', round: round.roundNumber, text: ` ${round.botBTimeMs}ms | Score: ${round.botBScore}`, color: 'text-muted' })
|
||||
@@ -464,7 +478,10 @@ async function _doReplay() {
|
||||
scene.startTalking('b')
|
||||
}
|
||||
const minBubbleB = sleep(400).catch(() => {})
|
||||
if (doTTS) await playAnswerNow(props.fight.botB!.name, round.botBResponse)
|
||||
if (doTTS) {
|
||||
if (isCodeRound) await sfxModem()
|
||||
else await playAnswerNow(props.fight.botB!.name, round.botBResponse)
|
||||
}
|
||||
await minBubbleB
|
||||
if (!doTTS && !insanityMode.value) await sleep(400)
|
||||
scene?.stopTalking('b')
|
||||
|
||||
Reference in New Issue
Block a user