feat: human vs AI mode — live typing challenges, baby growth system, SSE rounds

- Add choose-mode step: "I BUILD BOTS" vs "I FIGHT MYSELF" paths
- Human registration with baby avatar picker, no webhook required
- Live fight scene with SSE round streaming and real-time challenge UI
- 5-second timer per round, submit answers via browser
- Baby → toddler → kid → teen → adult → hero → super growth stages
- Huge sparkly baby eyes, diapers, pacifiers, bibs, rattles, rosy cheeks
- Speech bubble positioning fix (pushed to outside of sprite)
- Canvas text rendering via offscreen canvas to bypass kaplay color issues
- Voice timing improvements: await pauses between voice lines and hits
- 30 devastating announcement lines, 15 critical/hit word variants
- Orchestrator human player detection + waitForHumanResponse system
- Server endpoints: GET /challenge/:botId, POST /respond/:botId
- Human player auth: register-human route, isHuman flag on login

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Dorian
2026-03-07 15:20:14 +00:00
co-authored by Claude Opus 4.6
parent 56785cfdea
commit 8448f1d823
15 changed files with 1805 additions and 85 deletions
+5 -3
View File
@@ -279,8 +279,8 @@ async function replay() {
// Hit text overlay — after playRound completes so it lands on the result
const hitWords = isCritical
? ['CRITICAL!', 'DEVASTATING!', 'OBLITERATED!', 'ANNIHILATED!', 'WRECKED!']
: ['POW!', 'BAM!', 'WHAM!', 'CRACK!', 'SMASH!', 'BOOM!', 'THWACK!']
? ['CRITICAL!', 'OBLITERATED!', 'ANNIHILATED!', 'WRECKED!', 'BRUTAL!', 'CRUSHED!', 'DELETED!', 'ERASED!', 'VAPORIZED!', 'DESTROYED!', 'SHATTERED!', 'TERMINATED!', 'MASSACRED!', 'PULVERIZED!', 'DECIMATED!']
: ['POW!', 'BAM!', 'WHAM!', 'CRACK!', 'SMASH!', 'BOOM!', 'THWACK!', 'BONK!', 'CRUNCH!', 'SLAM!', 'WHACK!', 'KAPOW!', 'ZAP!', 'CLONK!', 'THUD!']
if (aWon || bWon) {
showHitText(
hitWords[Math.floor(Math.random() * hitWords.length)],
@@ -358,8 +358,10 @@ async function replay() {
if (isPerfect) {
await scene.playPerfect(winningSide, winnerName)
announceFlawlessVictory()
await sleep(600) // let "flawless victory" voice land
} else {
await scene.playKO(winningSide, winnerName)
await sleep(300) // let fatality voice finish
}
glitching.value = true
@@ -527,7 +529,7 @@ async function replay() {
:disabled="isReplaying"
@click="replay"
>
{{ isReplaying ? 'FIGHTING...' : 'REPLAY FIGHT' }}
{{ isReplaying ? 'FIGHTING...' : 'VIDEO REPLAY' }}
</button>
<button
class="w-8 h-8 flex items-center justify-center border border-border/50 text-text-muted