Products Docs Help Store Discord

ELO Configuration

Configure the rating system that determines player rankings.

The heart of competitive ranking. Configure how player ratings change after each battle.

Rating System Selection

# elo.yaml
ratingSystem: POKEMON_SHOWDOWN  # or GLICKO2
startingElo: 1000
floorElo: 1000
SettingDefaultDescription
ratingSystemPOKEMON_SHOWDOWNRating algorithm to use
startingElo1000Rating for new players
floorElo1000Minimum possible rating

Pokemon Showdown Mode (Default)

Uses the classic Elo formula with K-Factor adjustments. Same system as Pokemon Showdown and chess.

# elo.yaml
pokemonShowdown:
  newPlayerGames: 30
  newPlayerKFactor: 50
  kFactorBands:
    - maxElo: 1500
      kFactor: 48
    - maxElo: 1700
      kFactor: 36
    - maxElo: 2000
      kFactor: 24
    - maxElo: 2500
      kFactor: 18
    - maxElo: 999999
      kFactor: 12
  streakBonus:
    enabled: false
    threshold3Wins: 3
    threshold5Wins: 5
K-Factor Bands Explained

K-Factor determines how much rating changes per match. Higher K = bigger swings.

Player TypeK-FactorReason
New players (first 30 games)50Quickly find true skill level
Low rating (< 1500)48Easier to climb out
Mid rating (1500-1700)36Balanced progression
Mid-high (1700-2000)24More stable
High (2000-2500)18Stable rankings
Top (2500+)12Very stable, small changes

Example:

  • Player A (1800 ELO, K=24) beats Player B (1800 ELO, K=24)
  • Player A gains: 24 × (1 - 0.5) = +12 ELO
Win Streak Bonus

Players on winning streaks receive a K-Factor boost. Disabled by default.

streakBonus:
  enabled: false       # Enable to boost K-Factor on streaks
  threshold3Wins: 3    # Bonus equal to this value after 3 wins
  threshold5Wins: 5    # Bonus equal to this value after 5+ wins
Win StreakK-Factor Bonus
3 wins+3
5+ wins+5

Glicko-2 Mode (Advanced)

Tracks rating and uncertainty (Rating Deviation). Better for servers where players have varying activity.

# elo.yaml
ratingSystem: GLICKO2

glicko2:
  startingRating: 1500       # Glicko-2 starts at 1500 (matches Showdown's internal scale)
  startingRD: 150.0
  startingVolatility: 0.06
  systemConstant: 0.5
  rdDecayDays: 30
  maxRatingChange: 100
SettingDefaultDescription
startingRating1500Starting rating for new players in Glicko-2 mode
startingRD150.0Rating Deviation (uncertainty in rating)
startingVolatility0.06Expected rating fluctuation
systemConstant0.5Controls volatility changes (0.3-1.2)
rdDecayDays30Days of inactivity before RD increases
maxRatingChange100Maximum rating change per match
RD vs K-Factor
SystemWhat ChangesHow
Pokemon ShowdownK-Factor (fixed bands)Based on rating only
Glicko-2RD (dynamic per player)Based on certainty + activity

When to use Glicko-2:

  • Players have varying activity levels
  • You want more accurate matchmaking for returning players

When to use Pokemon Showdown:

  • Simpler to understand for players
  • More predictable rating changes

ELO Decay

Inactive players lose ELO over time to keep the leaderboard competitive. Forces high-rated players to stay active to maintain their ranking. Inspired by Pokemon Showdown, Chess (FIDE), and League of Legends systems.

# elo.yaml
eloDecay:
  enabled: false
  thresholdDays: 7        # Days before decay starts
  minimumElo: 1500        # Must be above this to decay
  decayPerDay: 10         # ELO lost per full day
  checkIntervalHours: 6   # How often to check
SettingDefaultDescription
enabledfalseEnable/disable ELO decay
thresholdDays7Days of inactivity before decay starts
minimumElo1500Only players above this ELO decay
decayPerDay10ELO lost per full day of inactivity
checkIntervalHours6How often the decay task runs

Note: Decay stops at the top-level floorElo setting (default: 1000).

How Decay Works

  1. Players above minimumElo who haven’t played in thresholdDays days start decaying
  2. Every full day of inactivity = decayPerDay ELO lost
  3. Decay stops when player reaches floorElo
  4. Playing any ranked match resets the inactivity timer

Example:

  • Player has 1800 ELO
  • Config: minimumElo: 1500, decayPerDay: 10, floorElo: 1000 (top-level setting)
  • Player doesn’t play for 20 days
  • Decay starts after 7 days = 13 days of decay
  • ELO loss: 13 × 10 = 130 ELO
  • Final ELO: 1800 - 130 = 1670
Cross-Server Setup

For multi-server networks, the decay task runs on one server only using a Redis lock. This prevents duplicate ELO reductions across your network.

Requirements:

No extra configuration needed. The lock is automatic when Redis is enabled.

Database Schema

ELO decay requires tracking last match time per player per format.

SQLite/MySQL: last_match_at column in format_stats table

-- Auto-created on startup
ALTER TABLE format_stats ADD COLUMN last_match_at BIGINT;

MongoDB: last_match_at field in format_stats documents (automatic)

Rank Tiers

Cosmetic ranks displayed in GUI and leaderboard. Tiers are per rating system: Pokémon Showdown mode uses rankTiers, Glicko-2 mode uses glicko2RankTiers. The correct set is selected automatically based on your active ratingSystem.

# elo.yaml
# Tiers for Pokémon Showdown mode (centered on the 1000 start)
rankTiers:
  - { name: "POKEBALL",   displayName: "Poké Ball",   minElo: 0 }
  - { name: "GREATBALL",  displayName: "Great Ball",  minElo: 1200 }
  - { name: "ULTRABALL",  displayName: "Ultra Ball",  minElo: 1400 }
  - { name: "MASTERBALL", displayName: "Master Ball", minElo: 1600 }
  - { name: "BEASTBALL",  displayName: "Beast Ball",  minElo: 1800 }
  - { name: "CHERISH",    displayName: "Cherish Ball", minElo: 2000 }

# Separate tiers for Glicko-2 mode (centered on the 1500 start, spaced by σ)
glicko2RankTiers:
  - { name: "POKEBALL",   displayName: "Poké Ball",   minElo: 0 }
  - { name: "GREATBALL",  displayName: "Great Ball",  minElo: 1410 }
  - { name: "ULTRABALL",  displayName: "Ultra Ball",  minElo: 1500 }
  - { name: "MASTERBALL", displayName: "Master Ball", minElo: 1675 }
  - { name: "BEASTBALL",  displayName: "Beast Ball",  minElo: 1850 }
  - { name: "CHERISH",    displayName: "Cherish Ball", minElo: 2020 }

📝 Tiers affect display and rank rewards only; they don’t change matchmaking. Use LuckPerms Integration for rank-based permissions.

Daily Elo Gain Limit

Caps positive rating gains per day to curb smurfing and boosting. Players can still battle after hitting the limit, but wins grant no positive rating. Losses are never capped.

# elo.yaml
dailyEloGainLimit: 200   # 0 = unlimited

The reset time of day follows the global config.yaml -> timezone.

SettingDefaultDescription
dailyEloGainLimit200Max positive Elo gain per day (0 = unlimited)
Example
  • Player starts at 1500 Elo
  • Plays 10 matches: wins 7 (+140 Elo), loses 3 (-30 Elo)
  • Remaining allowance: 200 - 140 = 60 Elo
  • After hitting 200 net positive gain: wins grant 0 Elo, losses still apply

Draws

By default, a draw (double-KO / indeterminate outcome) is rating-neutral. You can opt into a standard 0.5-score update for both players instead:

# elo.yaml
draws:
  applyRatingChange: false   # true applies a 0.5-score update on draws

When enabled, draws apply a half-win/half-loss rating update (works with both rating systems), respect the daily gain cap, and fire rank-change/tier-reward checks as usual.


See Also