feat: practice button on profile, fix rate limiter, fix mobile sprite rendering

- Add Practice button to BotProfilePage for quick sparring
- Fix rate limiter bug: all rateLimit() instances shared one counter map,
  causing global and per-route limits to corrupt each other. Each limiter
  now gets its own isolated map.
- Replace 8-digit hex colors (#ffd70066) with rgba() in sprite rendering
  for mobile browser compatibility (iOS Safari renders them as black boxes)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Dorian
2026-03-09 12:14:07 +00:00
co-authored by Claude Opus 4.6
parent d0f0a84a57
commit ad96d1158f
4 changed files with 77 additions and 21 deletions
+4 -4
View File
@@ -1115,8 +1115,8 @@ export function generateHumanSpriteSheet(
const r = 40 + Math.sin(t * Math.PI * 4 + a * 0.8) * 8
const gx = cx + xOff + Math.round(Math.cos(angle) * r)
const gy = by + Math.floor(bodyH / 2) + Math.round(Math.sin(angle) * r)
fill(gx - 1, gy - 1, 3, 3, '#ffcc4430', ox, oy)
px(gx, gy, '#ffcc4460', ox, oy)
fill(gx - 1, gy - 1, 3, 3, 'rgba(255,204,68,0.19)', ox, oy)
px(gx, gy, 'rgba(255,204,68,0.38)', ox, oy)
}
}
@@ -1141,7 +1141,7 @@ export function generateHumanSpriteSheet(
}
fill(hx + 2, hy - 4, 4, 2, hatLt, ox, oy) // highlight
// Visor
fill(hx + 2, hy + 1, headW - 4, 3, '#33333388', ox, oy)
fill(hx + 2, hy + 1, headW - 4, 3, 'rgba(51,51,51,0.53)', ox, oy)
} else if (costume.hatShape === 'pointy') {
for (let h = 0; h < 18; h++) {
const w = Math.max(1, Math.floor((headW + 2) * (1 - h / 18)))
@@ -1847,7 +1847,7 @@ export function generateHumanFighterSpriteSheet(
const r = Math.floor(bodyW / 2) + 6
const gx = cx + hOff + Math.round(Math.cos(angle) * r)
const gy = by + Math.floor(bodyH / 2) + Math.round(Math.sin(angle) * r * 0.6)
if ((frame + a) % 3 !== 0) px(gx, gy, a % 2 === 0 ? '#ffd700' : '#ffcc4460', ox, oy)
if ((frame + a) % 3 !== 0) px(gx, gy, a % 2 === 0 ? '#ffd700' : 'rgba(255,204,68,0.38)', ox, oy)
}
}
}
+6 -6
View File
@@ -210,10 +210,10 @@ export function generateSpriteSheet(
// Tier 4+: full golden ₿ with glow
if (tier >= 4) {
// Golden glow behind
px(ec - 2, ey, '#ffd70066', ox, oy)
px(ec + 2, ey, '#ffd70066', ox, oy)
px(ec, ey - 2, '#ffd70066', ox, oy)
px(ec, ey + 2, '#ffd70066', ox, oy)
px(ec - 2, ey, 'rgba(255,215,0,0.4)', ox, oy)
px(ec + 2, ey, 'rgba(255,215,0,0.4)', ox, oy)
px(ec, ey - 2, 'rgba(255,215,0,0.4)', ox, oy)
px(ec, ey + 2, 'rgba(255,215,0,0.4)', ox, oy)
}
const bc = tier >= 4 ? '#ffd700' : tier >= 2 ? '#ff8c00' : '#ff6600'
// Vertical stroke
@@ -298,8 +298,8 @@ export function generateSpriteSheet(
px(projX + 1, projY + 1, '#ffaa00', ox, oy)
px(projX + 2, projY, '#ffcc00', ox, oy)
px(projX - 1, projY, '#ff2200', ox, oy)
px(projX - 2, projY + 1, '#ff440066', ox, oy)
px(projX - 3, projY, '#ff220044', ox, oy)
px(projX - 2, projY + 1, 'rgba(255,68,0,0.4)', ox, oy)
px(projX - 3, projY, 'rgba(255,34,0,0.27)', ox, oy)
} else {
px(projX, projY, '#00eeff', ox, oy)
px(projX + 1, projY - 1, '#44ffff', ox, oy)