Sister Brawl — Technical Reference
Complete technical reference. Architecture → Architecture. Systems → Systems. Deployment → Deployment.
📁 Complete File Inventory
Frontend (games/sisterbrawl/src/)
| File | LOC | Purpose | Key Exports |
|---|---|---|---|
Game.svelte | 2,616 | Main component — dual 2D/3D renderer, solo practice, bot AI, arena themes, progression UI, VictoryScreen, TutorialOverlay | isSoloMode, startSoloPractice(), draw2DFrame(), detectStateChanges(), platformBus handlers |
stores/gameStore.svelte.ts | 891 | Reactive store — prediction, reconciliation, interpolation, hit-stop, progression, combo logic | state, applyInput(), reconcile(), interpolateRemotes(), hitStopTicks, progression |
stores/spectatorStore.svelte.ts | ~200 | Spectator state management | spectatorState, camera, joinSpectator() |
types/index.ts | 733 | Core types — Entity, CharacterDef, AbilityDef, MatchState, EntityState enum, input bitmasks | Entity, CharacterDef, AbilityDef, MatchState, EntityState, BTN_*, InputState |
lib/audio.ts | 440 | Web Audio API — 14+ SFX, BGM loop, master/SFX/music gain | AudioEngine, playSFX(), playMusic(), setVolume() |
lib/particleSystem.ts | 84 | Data-only particle pool (200 max) — sparks, bursts, dust, trails, specials | ParticleSystem, emit(), update(), render() |
lib/screenShake.ts | ~150 | Stackable screen shake — per-attack profiles, quadratic decay, directional | ScreenShake, addShake(), update(), getOffset() |
lib/arena-themes.ts | ~120 | 3 deterministic themes + hazards (lava, ice, trees, moving platforms) | ArenaTheme, selectArenaTheme(), HazardDef |
components/CharacterSelect.svelte | ~350 | Character grid, ability tooltips, ready up, arena theme selector | onCharacterSelect, onReadyUp, onArenaThemeChange |
components/GameHUD.svelte | ~280 | HP bars, combo counter, damage numbers (throttled), super meter, timer | combo, damageNumbers, superMeter |
components/CountdownOverlay.svelte | ~120 | Match start countdown 3-2-1-FIGHT | countdown, onCountdownComplete |
components/ResultsOverlay.svelte | ~200 | Victory/defeat screen with XP/levels/stats | results, progression |
components/ReplayControls.svelte | ~250 | Playback scrub for 10Hz snapshots — speed, frame step, ghost overlay | playback, speed, ghostToggle |
components/SpectatorViewport.svelte | ~300 | Free camera (WASD + mouse drag), HP bars, team colors, kill feed | camera, entities, killFeed |
components/TutorialOverlay.svelte | ~300 | Interactive tutorial — movement, attack, block, special, win condition | step, onStepComplete, onTutorialComplete |
components/VictoryScreen.svelte | ~220 | Silhouette, taunt phrase, announcer SFX, particles, 4s auto-dismiss | winner, taunt, onDismiss |
Backend (games/sisterbrawl/server/)
| File | LOC | Purpose | Key Exports |
|---|---|---|---|
match_handler.ts | 3,127 | Authoritative Nakama match handler — 60Hz tick, 6 character specials, hazards, progression, Elo, replay, spectator | matchInit, matchJoinAttempt, matchJoin, matchLeave, matchLoop, matchTerminate, stepPhysics(), resolveCombat(), awardMatchProgression() |
metrics.ts | 199 | Prometheus metrics collection — 4 metrics | sisterbrawl_active_matches, sisterbrawl_tick_latency_ms, sisterbrawl_avg_players, sisterbrawl_player_kills |
Nakama Module Registration (nakama-modules/index.ts)
| RPC | Handler | Purpose |
|---|---|---|
sisterbrawl_get_replay | rpcGetReplay | Fetch replay snapshots for match |
sisterbrawl_metrics | rpcGetMetrics | JSON metrics for internal use |
sisterbrawl_metrics_plain | rpcGetMetricsPlain | Prometheus text format |
sisterbrawl_spectate | rpcMatchSpectate | Get spectate token for match |
sisterbrawl_find_spectatable | rpcFindSpectatableMatch | Find match with open:true label |
sisterbrawl_get_leaderboard | rpcGetLeaderboard | Ranked leaderboard (Elo) |
sisterbrawl_get_player_stats | rpcGetPlayerStats | Player profile (XP, level, wins, losses) |
sisterbrawl_get_referral_info | rpcGetReferralInfo | Referral code + rewards |
sisterbrawl_record_referral | rpcRecordReferral | Record referral attribution |
sisterbrawl_get_active_counts | rpcGetActiveCounts | Active players/matches for dashboard |
sisterbrawl_create_match | rpcCreateMatch | Create custom match (host flow) |
🔌 API Contracts
Platform Bridge (Injected Props)
// Injected by NativeGameHost.svelte via platformBus
interface PlatformProps {
matchId: string | null
userId: string
username: string
sessionToken: string
isHost: boolean
matchLabel: MatchLabel
onMatchState: (state: MatchState) => void
onMatchEnd: (results: MatchResults) => void
onPlayerJoin: (player: PlayerInfo) => void
onPlayerLeave: (userId: string) => void
onError: (error: string) => void
sendOpcode: (opcode: number, data: any) => void
rpc: (id: string, payload: any) => Promise<any>
}Server → Client Opcodes (match_loop broadcasts)
| Opcode | Name | Payload | Description |
|---|---|---|---|
| 0 | STATE_UPDATE | { entities, projectiles, tick, matchId, arenaTheme } | 60Hz authoritative state |
| 1 | GAME_OVER | { winner, results, progression, eloChanges } | Match ended |
| 2 | PLAYER_JOINED | { userId, username, character, team } | New player |
| 3 | PLAYER_LEFT | { userId, reason } | Player disconnected |
| 4 | COUNTDOWN | { phase, remaining } | 3-2-1-FIGHT |
| 5 | LOBBY_UPDATE | { players, matchLabel } | Character select changes |
| 6 | SPECTATOR_JOINED | { userId, username } | Spectator joined |
| 7 | HIT_EVENT | { attacker, target, damage, type, hitstopFrames } | For juice sync |
| 8 | ARENA_THEME | { arenaTheme } | Host theme selection |
Client → Server Opcodes (via sendOpcode)
| Opcode | Name | Payload | Description |
|---|---|---|---|
| 0 | INPUT | { tick, inputs: InputState, sequence } | 60Hz input frame |
| 1 | READY_UP | { characterId, skinId } | Character select ready |
| 2 | CHANGE_CHARACTER | { characterId } | Switch character in lobby |
| 3 | SELECT_ARENA | { arenaTheme } | Host selects theme |
| 4 | USE_SPECIAL | { direction } | Special ability input |
| 5 | EMOTE | { emoteId } | Victory/defeat emote |
| 6 | REQUEST_REMATCH | {} | Vote for rematch |
| 7 | SPECTATOR_CAMERA | { x, y, zoom } | Spectator camera sync |
Match Label Schema (Nakama)
interface MatchLabel {
open: "true" | "false"
mode: "ranked" | "casual" | "custom"
arenaTheme: "volcanic" | "frozen" | "forest" | "auto"
hostId: string
playerCount: number
maxPlayers: number
version: string
}RPC Payloads
// sisterbrawl_create_match
{ mode: 'ranked'|'casual'|'custom', arenaTheme?: string, maxPlayers?: number }
// → { matchId: string, matchToken: string, label: MatchLabel }
// sisterbrawl_find_spectatable
{} // → { matchId: string, label: MatchLabel }[]
// sisterbrawl_spectate
{ matchId: string } // → { spectateToken: string }
// sisterbrawl_get_leaderboard
{ limit?: number, offset?: number }
// → { leaderboard: [{ userId, username, rating, rank, wins, losses }] }
// sisterbrawl_get_player_stats
{ userId?: string }
// → { xp, level, wins, losses, streak, characters: { [charId]: { mastery, wins } } }
// sisterbrawl_get_replay
{ matchId: string, fromTick?: number, toTick?: number }
// → { snapshots: ReplaySnapshot[] }✅ Verification Checklists
Pre-Deploy Checklist
| Check | Command | Expected |
|---|---|---|
| TypeScript clean | tsc --noEmit | 0 errors |
| Lint clean | eslint src/ | 0 warnings |
| Vite build success | npx vite build --mode production | Success |
| Nakama module build | cd nakama-modules && npm run build | dist/index.js created |
| Health check (local) | curl -sf http://127.0.0.1:3000/health | 200 OK |
| Game loads (prod) | curl -sf https://funday.gg/play/sisterbrawl | grep canvas | Contains canvas |
Post-Deploy Verification (Browser)
| Check | Tool | Expected |
|---|---|---|
| Canvas alive | browser_console | canvas.width > 0 && non_black_pixels > 1% |
| No JS errors | browser_console | console.errors.length === 0 |
| Feature renders | browser_vision | Vision confirms CharacterSelect → Countdown → Gameplay |
| 60fps sustained | browser_console | requestAnimationFrame delta < 18ms for 30s |
| WebSocket connects | browser_console | socket.onopen logged, STATE_UPDATE received |
| Solo mode works | browser_play | 2s timeout → bot spawns, 2D canvas renders |
Fun Verify Scorecard (Per Phase)
| Metric | Target | Method |
|---|---|---|
| Hitstop feel | 2f regular, 5f combo, 3f KO | Frame-count in browser console |
| Shake stacking | No judder, directional | Visual + screenShake.ts unit test |
| Input latency | <16ms (1 frame) | performance.now() in prediction loop |
| 60fps sustained | 0 drops in 5min match | Chrome Performance tab |
| Audio zero-lag | SFX on frame 0 | detectStateChanges() logs |
| Particle cap | ≤200 active, no GC spikes | particleSystem.ts pool size |
| Elo integrity | ±16 equal, floor respected | competitive-integrity-recipes.md tests |
| Forfeit progression | Winner gets full XP | awardMatchProgression() in matchLoop & matchLeave |
Ship Gate: fun-verify scorecard avg ≥ 3.5, no 🔴 killers
🔗 Integration Touchpoints
| System | Hook | Validation |
|---|---|---|
| Nakama match handler | matchLoop/matchLeave call awardMatchProgression() | Forfeit winner gets XP |
| Leaderboard RPC | sisterbrawl_get_leaderboard → nakama-modules | pluginId: sisterbrawl, method prefix |
| Spectator | joinMatch(id, undefined, {spectator:'true'}) | Label open:false after matchInit |
| Elo/Rank | metrics.ts + bridge → Prometheus → Grafana | Alert on active_matches gauge leak |
| Frontend deploy | build-atomic.sh → funday-frontend.service restart | Hash match verification |
| Nakama deploy | npm run build → kubectl rollout restart | Startup done in logs |
🎯 Key Constants (Single Source)
| Constant | Value | Location |
|---|---|---|
TICK_RATE | 60 Hz | match_handler.ts, gameStore.svelte.ts |
MATCH_TIMEOUT | 300s (18,000 ticks) | match_handler.ts |
KILL_THRESHOLD | 10 kills | match_handler.ts |
ARENA_SIZE | 56×56 (±28 units) | match_handler.ts, Game.svelte |
GRAVITY | -20 m/s² | match_handler.ts, Game.svelte |
ATTACK_RANGE | 1.5 units | match_handler.ts |
ATTACK_COOLDOWN | 15 ticks (250ms) | match_handler.ts |
JUMP_VELOCITY | 10 m/s | match_handler.ts |
KNOCKBACK | 8 m/s | match_handler.ts |
HITSTUN | 20 ticks (333ms) | match_handler.ts |
BLOCK_REDUCTION | 80% | match_handler.ts |
BASE_K (Elo) | 32 | match_handler.ts, metrics.ts |
MIN_RATING (Elo) | 100 | match_handler.ts |
PROVISIONAL_K | 64 | match_handler.ts |
PROVISIONAL_MATCHES | 10 | match_handler.ts |
PARTICLE_POOL_MAX | 200 | particleSystem.ts |
DAMAGE_NUMBER_POOL | 50 | Game.svelte |
🛡️ Risks & Mitigations
| Risk | Mitigation |
|---|---|
| Build breaks from other games | git stash && npx vite build isolate; commit before deploy |
Circular $state in Svelte 5 | untrack() in setters; helper $derived null-safety pattern |
| WebGL black in headless | HTML/Svelte overlay for screenshots; CDP injection |
| AudioContext leak | cleanup() in onDestroy; null all refs |
Stale import.meta.glob | Rebuild on any game file change |
| Label propagation gap | updateMatchLabel() after every state change |
| Forfeit progression gap | Shared helper awardMatchProgression() |
| Client-server contract drift | Unit test server math; import in client vitest |
| Memory pressure build | --clean flag; verify free -h > 4GB |
📋 Rollout Commands (Quick Reference)
| Stage | Command | Verification |
|---|---|---|
| Local dev | cd games/sisterbrawl && npm run dev | Hot reload, console clean |
| Nakama test | cd nakama-modules && npm run build && sudo kubectl rollout restart -n funday-platform deployment/nakama | Startup done in logs |
| Frontend test | bash scripts/build-atomic.sh --clean | grep "new-change" build/client/_app/immutable/chunks/*.js |
| Staging smoke | curl https://funday.gg/play/sisterbrawl | 200, canvas renders, WS connects |
| Live | Deploy to prod | fun-verify scorecard ≥ 3.5 |
Related Pages
- Architecture — System diagram, data flow, match lifecycle
- Systems — Core systems deep-dive
- Deployment — Build pipeline, health checks, monitoring
funday-play-shellskill →references/game-feel-patterns.mdfunday-play-shellskill →references/victory-screen-pattern.mdfunday-play-shellskill →references/arena-theme-pattern.mdfunday-play-shellskill →references/elo-rating-pattern.mdfunday-play-shellskill →references/nakama-label-propagation-pitfall.mdfunday-play-shellskill →references/forfeit-progression-gap.mdfunday-play-shellskill →references/client-server-contract-pitfalls.md