feat: SSE live fight spectating with spectator count

Enable real-time fight spectating for all live fights (not just human
fights). Multiple spectators can watch simultaneously via SSE. Spectator
count is tracked per-fight and broadcast with every SSE event.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Dorian
2026-03-08 19:46:39 +00:00
co-authored by Claude Opus 4.6
parent a540320901
commit 610e799605
18 changed files with 1555 additions and 397 deletions
+19 -1
View File
@@ -19,10 +19,22 @@ export interface SpriteCustomization {
forceHorns?: boolean
}
// Sprite sheet cache — avoids regenerating expensive canvas work
const _spriteCache = new Map<string, string>()
const MAX_SPRITE_CACHE = 40
function _spriteCacheKey(seed: string, tier: number, primary: string, secondary: string, arch?: string, cust?: SpriteCustomization): string {
return `${seed}|${tier}|${primary}|${secondary}|${arch || ''}|${cust ? JSON.stringify(cust) : ''}`
}
export function generateSpriteSheet(
seed: string, tier: number, primaryColor: string, secondaryColor: string,
archetypeOverride?: string, customization?: SpriteCustomization,
): string {
const cacheKey = _spriteCacheKey(seed, tier, primaryColor, secondaryColor, archetypeOverride, customization)
const cached = _spriteCache.get(cacheKey)
if (cached) return cached
const canvas = document.createElement('canvas')
canvas.width = FRAME_SIZE * MAX_FRAMES
canvas.height = FRAME_SIZE * TOTAL_ROWS
@@ -551,7 +563,13 @@ export function generateSpriteSheet(
for (let f = cfg.frames; f < MAX_FRAMES; f++) drawFrame(f, row, pose, cfg.frames - 1, cfg.frames)
}
return canvas.toDataURL()
const dataUrl = canvas.toDataURL()
if (_spriteCache.size >= MAX_SPRITE_CACHE) {
const oldest = _spriteCache.keys().next().value
if (oldest) _spriteCache.delete(oldest)
}
_spriteCache.set(cacheKey, dataUrl)
return dataUrl
}
/** Load a sprite sheet data URL into a canvas (async for reliable image decode) */