Sister Brawl — Core Systems Deep-Dive

Pure systems implementation reference. Character definitions → Characters. Architecture → Architecture.


🎯 Client-Side Prediction + Server Reconciliation

Local player movement predicted immediately at 60Hz. When server state arrives, unacked inputs are replayed. Smooth correction over 10 frames when drift > 0.2 units. Remote entities interpolate between snapshots.

Implementation: gameStore.svelte.tsapplyInput(), reconcile(), interpolateRemotes()


⚡ Character Ability System (Server-Authoritative)

All 6 characters have unique special abilities simulated server-side on the Nakama match handler (ice walls, burn DOTs, stun shockwaves, teleporting logic, reflections). AbilityDef on each CharacterDef provides UI metadata and tooltips.

export interface AbilityDef {
  cooldown: number
  duration: number
  type: string // 'burn' | 'ice-wall' | 'dash-strike' | 'teleport-crit' | 'stun' | 'shield'
  dmgMod?: number
  tooltip?: string
}

Character specials implemented in match_handler.ts: Characters → Special Ability Types


🔊 Procedural Audio (Web Audio API)

Zero external files. All sounds generated procedurally.

SFX (14+)

SFXTriggerDescription
attack_swingAttack initiationSwing whoosh
attack_hitHit connectImpact thud
jumpJump initiationJump grunt
landLandingGround impact
block_raiseBlock initiationShield raise
block_hitBlock impactDeflection ping
special_castSpecial activationMagic charge
special_emberFireballFire whoosh
special_frostIce WallIce crack
special_voltDash StrikeElectric zap
special_shadeTeleportShadow pop
special_terraGround PoundGround rumble
special_aquaWater ShieldWater splash
koKnockoutHeavy impact
countdown3-2-1-FIGHTCountdown beeps
victoryMatch winVictory fanfare

Music

  • Lobby track: C minor 9 pad + slow arpeggio (8-12s loop variation)
  • Battle track: Layered kick/bass/lead with 100→140 BPM tempo ramp
  • Auto-switches on phase change (character-select/lobbyplaying)

Volume Controls

  • Master / SFX / Music gain nodes (independent)
  • Persisted to localStorage
  • prefers-reduced-motion respected

Implementation: src/lib/audio.ts


💥 Particle VFX (Data-Only Pool)

Data-only particle pool (200 max). No Three.js objects — plain TypeScript objects rendered in 2D canvas draw loop.

interface Particle {
  x: number
  y: number
  vx: number
  vy: number
  life: number
  maxLife: number
  color: string
  size: number
  type: "spark" | "burst" | "dust" | "trail" | "special"
}

Effects

TriggerParticle TypeDescription
Hit connectspark8-12 yellow/orange sparks
KOburst30+ character-color particles expanding
Attack swingtrailCharacter-color trail behind weapon
Jump/LanddustSmall puff at feet
Special castspecialCharacter-specific (fire, ice, electric, etc.)
BlocksparkBlue shield sparks on impact

Implementation: src/lib/particleSystem.ts


📳 Screen Shake

Impact-proportional camera shake. Character-specific intensities.

interface ShakeProfile {
  intensity: number // 0.3 (light) → 2.0 (KO)
  duration: number
  number
  number // frames
  decay: "quadratic" | "linear"
  directional: boolean // shake toward impact
}

Shake Profiles

EventIntensityDurationDecay
Light attack hit0.38fQuadratic
Heavy attack hit0.812fQuadratic
Special hit1.215fQuadratic
KO2.020fQuadratic
Super finisher2.530fQuadratic

Features: Stackable (multiple shakes add), respects prefers-reduced-motion, quadratic decay feels natural.

Implementation: src/lib/screenShake.ts


🎯 Hit-Stop System

Frame-freeze on impact for weight/feedback.

EventFrames (at 60Hz)Duration
Regular hit2~33ms
Special hit2~33ms
Combo completion5~83ms
KO / dramatic finish3~50ms

Implementation: gameStore.svelte.tshitStopTicks counter, checked in render loop.


💨 Floating Damage Numbers

Damage numbers appear on 2D canvas when entities take damage.

interface DamageNumber {
  x: number
  y: number
  value: number
  color: string // white=normal, red=crit, green=heal, orange=burn
  life: number // frames remaining
  vy: number // upward velocity
}

Throttling

  • Max 1 damage number per entity per 10 frames
  • Pool of 50 numbers (reused)
  • Fade out over 60 frames

🏔️ Arena Themes + Hazards (Server-Side)

Arena theme selection uses selectArenaTheme(matchId, arenaTheme): host-selected arenaTheme overrides deterministic matchId hash. Server, players, and spectators all consume the same shared selector.

interface ArenaTheme {
  name: string
  bg: string
  floor: string
  grid: string
  wall: string
  accent: string
  hazards: HazardDef[]
}

Themes

ThemePaletteHazards
VolcanicRed/orange/black4 lava pools (3 dmg + 2 DPS burn/2s) + moving platform
FrozenBlue/white/cyan4 ice crystals (2 dmg + 1 DPS slow/1s) + slippery center
ForestGreen/brown/gold5 trees (block movement/projectiles) + moving platform

Synchronization Contract

  • Host stage select sends { arenaTheme } through opcode 8 during character select
  • Opcode 8 lobby updates may arrive under native bridge’s state/data envelope — game normalizes before updating local selection UI
  • Recurring STATE_UPDATE payloads include matchId and arenaTheme so player clients converge through MatchState
  • Spectator join snapshots and SpectatorState include matchId and arenaTheme so spectators render same stage as players
  • If no host selection exists, server and clients pass actual Nakama matchId to selectArenaTheme and derive same deterministic default

Hazard Application (Server-Side)

In match_handler.ts stepPhysics():

  • Lava: instant damage + burn DOT
  • Ice: damage + slow DOT + slippery center
  • Trees: push-out collision (blocks movement/projectiles)
  • Moving platform: entity transport (placeholder)

🔄 Replay System

Snapshots recorded at 10Hz (every 6 ticks). Stored in Nakama storage.

Data Structure

interface ReplaySnapshot {
  tick: number
  timestamp: number
  entities: EntitySnapshot[] // position, state, hp, facing
  projectiles: ProjectileSnapshot[]
  inputs: InputSnapshot[] // per-player input at this tick
}

Storage

  • Collection: replays
  • Key: {matchId}/snap/{tick}
  • TTL: 7 days (auto-cleanup via cron)

Client Playback

ReplayControls.svelte provides:

  • Play/pause
  • Speed: 0.25x, 0.5x, 1x, 2x
  • Frame step (←/→)
  • Timeline scrubber
  • Ghost overlay toggle (compare runs)

👁️ Spectator Mode

SpectatorViewport.svelte + spectatorStore.svelte.ts. Free camera for watching live matches.

Join Flow

// 1. Find spectatable match
const { matchId } = await rpc("sisterbrawl_find_spectatable")
 
// 2. Get spectate token
const { spectateToken } = await rpc("sisterbrawl_spectate", { matchId })
 
// 3. Join as spectator
await socket.joinMatch(undefined, spectateToken, { spectator: "true" })
 
// 4. Receive STATE_UPDATE broadcasts (same as players)

Features

  • Free camera (WASD + mouse drag)
  • Entity interpolation (same as players)
  • HP bars above entities
  • Team colors
  • Kill feed
  • Match timer

📊 Progression System

XP Formula

  • Base: 15 XP per match
  • +8 XP per kill
  • +2 XP per death
  • +1 XP per 10 damage dealt
  • +20 XP win bonus

Streak Bonuses

  • 3+ win streak: 2× XP multiplier
  • 5+ win streak: 3× XP multiplier

Levels

Thresholds: [0, 100, 250, 500, 1000, 2000, 3500, 5500, 8000, 11000]

Storage

  • Nakama storage: collection=progression, key={userId}/profile
  • Included in GAME_OVER payload for client display

🏆 Elo Rating System (Ranked)

Leaderboard-based (NOT storage-based). N-player via average opponent rating.

Constants

ConstantValue
BASE_K32
MIN_RATING100
PROVISIONAL_K64 (first 10 matches)
PROVISIONAL_MATCHES10

Calculation

// 2-player: standard Elo
// N-player FFA: average opponent rating
delta = K * (actualScore - expectedScore)

Rank Tiers

TierRating RangeColor
Iron100-7999ca3af
Bronze800-1399cd7f32
Silver1400-1999c0c0c0
Gold2000-2599ffd700
Platinum2600-2999e5e4e2
Emerald3000-339950c878
Diamond3400-3799b9f2ff
Master3800-4199ff6b6b
Grandmaster4200-4599ff4757
Divine4600+a855f7

File References

FilePurpose
src/lib/audio.tsWeb Audio API (14+ SFX, BGM, volumes)
src/lib/particleSystem.tsData-only particle pool (200 max)
src/lib/screenShake.tsStackable screen shake profiles
src/lib/arena-themes.ts3 themes + hazards, deterministic selection
src/stores/gameStore.svelte.tsPrediction, reconciliation, hit-stop, damage numbers, progression
src/stores/spectatorStore.svelte.tsSpectator state management
src/components/ReplayControls.sveltePlayback UI (speed, scrub, ghost)
src/components/SpectatorViewport.svelteFree camera spectator view
server/match_handler.tsServer-authoritative abilities, hazards, progression, Elo, replay recording
server/metrics.ts4 Prometheus metrics


Ask Docs

AI assistant to help answer questions about the documentation. Answers are read-only and cite docs/source.

Hi! How can I help you with the documentation today? Answers are read-only and cite docs/source.

Ctrl+Enter to send