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:
co-authored by
Claude Opus 4.6
parent
56785cfdea
commit
8448f1d823
@@ -179,18 +179,31 @@ export function generateHumanSpriteSheet(
|
||||
const pantsDark = darken(secondaryColor, 15)
|
||||
|
||||
const wr = Math.max(0, Math.min(1, winRate))
|
||||
const isFat = wr < 0.3 ? true : wr < 0.5 ? rng() > 0.4 : false
|
||||
const isShort = wr < 0.25
|
||||
// Growth stages: baby → toddler → kid → teen → adult → hero → super
|
||||
const isBaby = wr < 0.1
|
||||
const isToddler = wr >= 0.1 && wr < 0.25
|
||||
const isKid = wr >= 0.25 && wr < 0.4
|
||||
const isFat = isBaby || isToddler ? true : wr < 0.5 ? rng() > 0.4 : false
|
||||
const isShort = isBaby || isToddler || isKid
|
||||
const isSwole = wr > 0.75
|
||||
const isSuperHero = wr > 0.85
|
||||
const isSuperMode = wr > 0.9
|
||||
const hasBeard = wr < 0.35 ? true : isSuperMode ? false : rng() > 0.6
|
||||
const hasGlasses = wr < 0.4 ? true : isSuperMode ? false : rng() > 0.5
|
||||
const hasStains = wr < 0.3
|
||||
const hasCheetos = wr < 0.2
|
||||
const hasBeard = isBaby || isToddler || isKid ? false : wr < 0.5 ? rng() > 0.7 : isSuperMode ? false : rng() > 0.6
|
||||
const hasGlasses = isBaby || isToddler ? false : wr < 0.4 ? true : isSuperMode ? false : rng() > 0.5
|
||||
const hasStains = isToddler || isKid
|
||||
const hasCheetos = false
|
||||
const hasCape = isSuperHero
|
||||
const hasHeadband = wr > 0.7
|
||||
const hasSweatDrops = wr < 0.4
|
||||
const hasSweatDrops = wr < 0.4 && !isBaby
|
||||
const hasDiaper = isBaby
|
||||
const hasPacifier = isBaby && rng() > 0.3
|
||||
const hasBib = isBaby || (isToddler && rng() > 0.5)
|
||||
const hasRattle = isBaby
|
||||
const hasRosyCheeks = isBaby || isToddler
|
||||
const hasTuftHair = isBaby
|
||||
const hasSippyCup = isToddler
|
||||
const hasBackpack = isKid && rng() > 0.5
|
||||
const hasBandaid = isToddler || isKid
|
||||
|
||||
const costume = getCostume(archetype)
|
||||
const out = '#0a0a0a'
|
||||
@@ -283,13 +296,34 @@ export function generateHumanSpriteSheet(
|
||||
fill(cx - 3 + xOff, bodyTop + yOff - 2, 6, 3, '#ffd700', ox, oy)
|
||||
}
|
||||
|
||||
// ===== DIAPER (babies) =====
|
||||
if (hasDiaper) {
|
||||
const diaperY = legsTop + yOff - 2
|
||||
const diaperW = bodyW + 4
|
||||
const diaperH = 12
|
||||
const dx = cx - Math.floor(diaperW / 2) + xOff
|
||||
// White puffy diaper
|
||||
roundBox(dx, diaperY, diaperW, diaperH, '#f5f5f0', ox, oy)
|
||||
// Diaper tape strips (blue)
|
||||
fill(dx + 3, diaperY + 2, 4, 2, '#66aadd', ox, oy)
|
||||
fill(dx + diaperW - 7, diaperY + 2, 4, 2, '#66aadd', ox, oy)
|
||||
// Little star decoration
|
||||
px(cx + xOff, diaperY + 4, '#ffcc44', ox, oy)
|
||||
px(cx + xOff - 1, diaperY + 5, '#ffcc44', ox, oy)
|
||||
px(cx + xOff + 1, diaperY + 5, '#ffcc44', ox, oy)
|
||||
// Puffy sides
|
||||
fill(dx - 1, diaperY + 3, 2, 4, '#eeeee8', ox, oy)
|
||||
fill(dx + diaperW - 1, diaperY + 3, 2, 4, '#eeeee8', ox, oy)
|
||||
}
|
||||
|
||||
// ===== LEGS =====
|
||||
const legGap = isRun ? Math.round(Math.sin(t * Math.PI * 2) * 8) : isPanic ? 6 : 3
|
||||
const ll = cx - legGap - Math.floor(legW / 2) + xOff
|
||||
const rl = cx + legGap - Math.floor(legW / 2) + xOff
|
||||
|
||||
const legColor = costume.onesie ? (costume.hat || shirtColor) : pantsColor
|
||||
const legColorDk = costume.onesie ? darken(costume.hat || shirtColor, 15) : pantsDark
|
||||
// Babies have bare skin-colored legs, toddlers too
|
||||
const legColor = hasDiaper || isToddler ? skinTone : costume.onesie ? (costume.hat || shirtColor) : pantsColor
|
||||
const legColorDk = hasDiaper || isToddler ? skinDark : costume.onesie ? darken(costume.hat || shirtColor, 15) : pantsDark
|
||||
|
||||
// Left leg
|
||||
roundBox(ll, legsTop + yOff, legW, legH, legColor, ox, oy)
|
||||
@@ -728,7 +762,17 @@ export function generateHumanSpriteSheet(
|
||||
}
|
||||
|
||||
// Hair
|
||||
if (!costume.onesie || costume.type !== 'animal') {
|
||||
if (hasTuftHair) {
|
||||
// Baby: just a tiny curl on top
|
||||
px(cx + xOff - 1, hy - 1, hairColor, ox, oy)
|
||||
px(cx + xOff, hy - 2, hairColor, ox, oy)
|
||||
px(cx + xOff + 1, hy - 1, hairColor, ox, oy)
|
||||
px(cx + xOff, hy - 3, lighten(hairColor, 15), ox, oy)
|
||||
px(cx + xOff - 1, hy - 3, hairColor, ox, oy)
|
||||
// Tiny curl
|
||||
px(cx + xOff + 2, hy - 2, hairColor, ox, oy)
|
||||
px(cx + xOff + 3, hy - 1, hairColor, ox, oy)
|
||||
} else if (!costume.onesie || costume.type !== 'animal') {
|
||||
// Top hair (fluffy)
|
||||
for (let ix = hx; ix < hx + headW; ix++) {
|
||||
px(ix, hy, hairColor, ox, oy)
|
||||
@@ -746,7 +790,7 @@ export function generateHumanSpriteSheet(
|
||||
px(hx + 4, hy, lighten(hairColor, 15), ox, oy)
|
||||
px(hx + 5, hy - 1, lighten(hairColor, 10), ox, oy)
|
||||
// Messy hair for losers
|
||||
if (wr < 0.3) {
|
||||
if (wr < 0.3 && !isKid) {
|
||||
px(hx + 2, hy - 2, hairColor, ox, oy)
|
||||
px(hx + headW - 4, hy - 3, hairColor, ox, oy)
|
||||
px(hx - 1, hy - 1, hairColor, ox, oy)
|
||||
@@ -777,6 +821,30 @@ export function generateHumanSpriteSheet(
|
||||
// Raised cheeks
|
||||
fill(leX - 1, eyeY + 2, 3, 2, '#ff8888', ox, oy)
|
||||
fill(reX + 4, eyeY + 2, 3, 2, '#ff8888', ox, oy)
|
||||
} else if (isBaby || isToddler) {
|
||||
// BABY/TODDLER: huge sparkly adorable eyes
|
||||
const eyeSize = isBaby ? 8 : 7
|
||||
fill(leX - 1, eyeY - 1, eyeSize, eyeSize, '#ffffff', ox, oy)
|
||||
fill(reX - 1, eyeY - 1, eyeSize, eyeSize, '#ffffff', ox, oy)
|
||||
// Big iris
|
||||
const irisSize = isBaby ? 5 : 4
|
||||
fill(leX + 1, eyeY + 1, irisSize, irisSize, isBaby ? '#5588cc' : '#4466aa', ox, oy)
|
||||
fill(reX + 1, eyeY + 1, irisSize, irisSize, isBaby ? '#5588cc' : '#4466aa', ox, oy)
|
||||
// Big sparkly pupil
|
||||
fill(leX + 2, eyeY + 2, 3, 3, '#000000', ox, oy)
|
||||
fill(reX + 2, eyeY + 2, 3, 3, '#000000', ox, oy)
|
||||
// Two sparkle highlights per eye
|
||||
px(leX + 1, eyeY, '#ffffff', ox, oy)
|
||||
px(leX + 2, eyeY, '#ffffff', ox, oy)
|
||||
px(leX + 4, eyeY + 3, '#ffffff', ox, oy)
|
||||
px(reX + 1, eyeY, '#ffffff', ox, oy)
|
||||
px(reX + 2, eyeY, '#ffffff', ox, oy)
|
||||
px(reX + 4, eyeY + 3, '#ffffff', ox, oy)
|
||||
// Tiny eyebrows (barely there)
|
||||
if (!isBaby) {
|
||||
fill(leX, eyeY - 3, 5, 1, hairColor, ox, oy)
|
||||
fill(reX, eyeY - 3, 5, 1, hairColor, ox, oy)
|
||||
}
|
||||
} else {
|
||||
// Normal eyes with iris detail
|
||||
fill(leX, eyeY, 6, 5, '#ffffff', ox, oy)
|
||||
@@ -905,8 +973,8 @@ export function generateHumanSpriteSheet(
|
||||
}
|
||||
}
|
||||
|
||||
// Double chin
|
||||
if (isFat && wr < 0.25) {
|
||||
// Double chin (adults only, not babies)
|
||||
if (isFat && wr >= 0.25 && wr < 0.5) {
|
||||
fill(hx + 4, hy + headH, headW - 8, 4, skinTone, ox, oy)
|
||||
fill(hx + 5, hy + headH + 3, headW - 10, 2, skinDark, ox, oy)
|
||||
fill(hx + 6, hy + headH + 4, headW - 12, 2, skinDark, ox, oy)
|
||||
@@ -943,6 +1011,100 @@ export function generateHumanSpriteSheet(
|
||||
px(hx - 7, hy + 5 + (isIdle ? bounce : 0), darken(hbColor, 15), ox, oy)
|
||||
}
|
||||
|
||||
// ===== BABY ACCESSORIES =====
|
||||
if (hasRosyCheeks) {
|
||||
// Pink rosy cheeks
|
||||
const cheekY = hy + Math.floor(headH * 0.55)
|
||||
fill(hx + 2, cheekY, 4, 3, '#ffaaaa', ox, oy)
|
||||
fill(hx + headW - 6, cheekY, 4, 3, '#ffaaaa', ox, oy)
|
||||
px(hx + 3, cheekY + 1, '#ff8888', ox, oy)
|
||||
px(hx + headW - 5, cheekY + 1, '#ff8888', ox, oy)
|
||||
}
|
||||
|
||||
if (hasPacifier && !isCheer && !isPanic && !isCoach) {
|
||||
// Pacifier in mouth
|
||||
const pY = hy + Math.floor(headH * 0.72)
|
||||
// Shield (round part)
|
||||
roundBox(cx - 4 + xOff, pY - 1, 8, 5, '#66ccff', ox, oy)
|
||||
// Nipple (sticks out)
|
||||
fill(cx - 2 + xOff, pY, 4, 3, '#ffcc66', ox, oy)
|
||||
// Handle ring
|
||||
px(cx - 5 + xOff, pY + 1, '#88ddff', ox, oy)
|
||||
px(cx - 6 + xOff, pY, '#88ddff', ox, oy)
|
||||
px(cx - 6 + xOff, pY + 2, '#88ddff', ox, oy)
|
||||
px(cx - 7 + xOff, pY + 1, '#88ddff', ox, oy)
|
||||
}
|
||||
|
||||
if (hasBib) {
|
||||
// Bib around neck area
|
||||
const bibY = neckTop + yOff
|
||||
const bibW = isBaby ? 20 : 16
|
||||
const bx2 = cx - Math.floor(bibW / 2) + xOff
|
||||
fill(bx2, bibY, bibW, 3, '#ffffff', ox, oy)
|
||||
fill(bx2 + 2, bibY + 3, bibW - 4, 6, '#ffffff', ox, oy)
|
||||
fill(bx2 + 4, bibY + 9, bibW - 8, 4, '#ffffff', ox, oy)
|
||||
// Bib border
|
||||
for (let ix = bx2 + 2; ix < bx2 + bibW - 2; ix++) px(ix, bibY + 8, '#dddddd', ox, oy)
|
||||
// Cute stain on bib
|
||||
px(cx - 2 + xOff, bibY + 5, '#ffaa66', ox, oy)
|
||||
px(cx - 1 + xOff, bibY + 6, '#ffaa66', ox, oy)
|
||||
px(cx + xOff, bibY + 5, '#ff8844', ox, oy)
|
||||
// "I <3 AI" text (tiny)
|
||||
px(cx - 3 + xOff, bibY + 4, '#ff4466', ox, oy) // heart
|
||||
px(cx - 2 + xOff, bibY + 3, '#ff4466', ox, oy)
|
||||
px(cx - 4 + xOff, bibY + 3, '#ff4466', ox, oy)
|
||||
}
|
||||
|
||||
if (hasRattle && (isCheer || isIdle)) {
|
||||
// Rattle in raised hand
|
||||
const rattleX = isCheer ? cx + Math.floor(bodyW / 2) + 8 + xOff : cx + Math.floor(bodyW / 2) + 3 + xOff
|
||||
const rattleY = isCheer ? bodyTop + yOff - 10 : bodyTop + yOff + 5
|
||||
// Handle
|
||||
fill(rattleX, rattleY + 4, 2, 8, '#dda844', ox, oy)
|
||||
// Ball
|
||||
roundBox(rattleX - 3, rattleY - 2, 8, 7, '#ff6688', ox, oy)
|
||||
// Sparkle on rattle
|
||||
px(rattleX - 1, rattleY - 1, '#ffffff', ox, oy)
|
||||
px(rattleX + 2, rattleY, '#ffccdd', ox, oy)
|
||||
}
|
||||
|
||||
if (hasSippyCup && (isIdle || isCoach)) {
|
||||
// Sippy cup in hand
|
||||
const scX = cx + Math.floor(bodyW / 2) + 3 + xOff
|
||||
const scY = bodyTop + yOff + Math.floor(bodyH * 0.5)
|
||||
roundBox(scX, scY, 6, 10, '#4488ff', ox, oy)
|
||||
// Lid
|
||||
fill(scX - 1, scY - 1, 8, 2, '#66aaff', ox, oy)
|
||||
// Spout
|
||||
fill(scX + 2, scY - 3, 2, 3, '#66aaff', ox, oy)
|
||||
// Liquid level
|
||||
fill(scX + 1, scY + 4, 4, 4, '#ffaa22', ox, oy)
|
||||
}
|
||||
|
||||
if (hasBandaid) {
|
||||
// Band-aid on forehead or cheek
|
||||
const baidX = hx + headW - 8
|
||||
const baidY = hy + 4
|
||||
fill(baidX, baidY, 6, 3, '#ffcc88', ox, oy)
|
||||
px(baidX + 1, baidY + 1, '#ddaa66', ox, oy)
|
||||
px(baidX + 4, baidY + 1, '#ddaa66', ox, oy)
|
||||
// Cross-hatch in middle
|
||||
px(baidX + 2, baidY, '#eebb77', ox, oy)
|
||||
px(baidX + 3, baidY + 2, '#eebb77', ox, oy)
|
||||
}
|
||||
|
||||
if (hasBackpack && !isCheer) {
|
||||
// Little backpack on back
|
||||
const bpX = cx - Math.floor(bodyW / 2) - 6 + xOff
|
||||
const bpY = bodyTop + yOff + 3
|
||||
roundBox(bpX, bpY, 6, 12, '#ee5533', ox, oy)
|
||||
// Zipper
|
||||
fill(bpX + 2, bpY + 2, 2, 3, '#cc3311', ox, oy)
|
||||
// Strap
|
||||
px(bpX + 5, bpY, '#cc3311', ox, oy)
|
||||
px(bpX + 6, bpY + 1, '#cc3311', ox, oy)
|
||||
}
|
||||
|
||||
// Winner glow aura
|
||||
if (isSuperHero && !isPanic) {
|
||||
for (let a = 0; a < 16; a++) {
|
||||
|
||||
Reference in New Issue
Block a user