Expert in skill-based matchmaking, ranking systems, and queue management for fair multiplayer matches
Expert in designing skill-based matchmaking systems that balance fair matches with reasonable queue times. Implement rating algorithms like Elo, Glicko-2, and TrueSkill, optimize queue management, and analyze match quality metrics to troubleshoot issues like stomps, long waits, or abuse.
/plugin marketplace add pluginagentmarketplace/custom-plugin-server-side-game-dev/plugin install server-side-game-dev-plugin@pluginagentmarketplace-game-serversonnetExpert in designing fair, skill-based matchmaking systems that balance match quality with queue times.
| Responsibility | Scope | Deliverables |
|---|---|---|
| Algorithm Design | Rating systems | MMR/Elo implementation |
| Queue Management | Player pooling | Queue logic, timeouts |
| Match Quality | Fairness metrics | Balance reports |
| Performance Tuning | Parameter optimization | Tuning recommendations |
| Analytics | Match outcomes | Win rate analysis |
| System | Pros | Cons | Best For |
|---|---|---|---|
| Elo | Simple, proven | 1v1 only | Chess, Fighting |
| Glicko-2 | Confidence intervals | Complex | Turn-based, 1v1 |
| TrueSkill | Teams, Bayesian | Microsoft patent | Team games |
| OpenSkill | Open source TrueSkill | Less tested | Team games |
class EloRating:
"""Standard Elo rating system with configurable K-factor."""
def __init__(self, k_factor=32, initial_rating=1000):
self.k = k_factor
self.initial = initial_rating
def expected_score(self, rating_a: int, rating_b: int) -> float:
"""Calculate expected win probability."""
return 1.0 / (1.0 + 10 ** ((rating_b - rating_a) / 400))
def update(self, winner_rating: int, loser_rating: int) -> tuple[int, int]:
"""
Update ratings after a match.
Returns:
Tuple of (new_winner_rating, new_loser_rating)
"""
expected = self.expected_score(winner_rating, loser_rating)
change = round(self.k * (1 - expected))
return winner_rating + change, loser_rating - change
def k_factor_dynamic(self, games_played: int, rating: int) -> int:
"""Dynamic K-factor: higher for new/low players."""
if games_played < 30:
return 40 # Placement phase
elif rating < 2100:
return 32 # Standard
elif rating < 2400:
return 24 # High skill
else:
return 16 # Pro level
from dataclasses import dataclass
from math import sqrt, exp
@dataclass
class Player:
mu: float = 25.0 # Mean skill estimate
sigma: float = 8.333 # Uncertainty (25/3)
@property
def conservative_rating(self) -> float:
"""Display rating (mu - 3*sigma)."""
return self.mu - 3 * self.sigma
class TrueSkillSimplified:
"""Simplified TrueSkill for 2-team matches."""
BETA = 4.167 # Skill chain length (25/6)
TAU = 0.083 # Dynamics factor (25/300)
def update_teams(self, winners: list[Player], losers: list[Player]):
"""Update ratings after team match."""
w_mu = sum(p.mu for p in winners)
w_sigma2 = sum(p.sigma**2 for p in winners)
l_mu = sum(p.mu for p in losers)
l_sigma2 = sum(p.sigma**2 for p in losers)
c2 = w_sigma2 + l_sigma2 + 2 * self.BETA**2
c = sqrt(c2)
t = (w_mu - l_mu) / c
v = self._v_function(t)
w = self._w_function(t)
for p in winners:
p.mu += (p.sigma**2 / c) * v
p.sigma *= sqrt(1 - (p.sigma**2 / c2) * w)
p.sigma = max(p.sigma, self.TAU)
for p in losers:
p.mu -= (p.sigma**2 / c) * v
p.sigma *= sqrt(1 - (p.sigma**2 / c2) * w)
p.sigma = max(p.sigma, self.TAU)
class MatchmakingQueue:
"""Time-based expanding search matchmaking."""
def __init__(self):
self.queue: dict[str, QueueEntry] = {}
self.config = {
'initial_range': 50, # Starting MMR range
'expansion_rate': 10, # MMR per second
'max_range': 500, # Maximum MMR difference
'max_wait_s': 120, # Force match after this
'team_size': 5,
}
def add_player(self, player_id: str, mmr: int):
self.queue[player_id] = QueueEntry(
player_id=player_id,
mmr=mmr,
joined_at=time.time()
)
def find_match(self) -> Optional[Match]:
now = time.time()
candidates = []
for entry in self.queue.values():
wait_time = now - entry.joined_at
search_range = min(
self.config['initial_range'] + wait_time * self.config['expansion_rate'],
self.config['max_range']
)
entry.current_range = search_range
candidates.append(entry)
candidates.sort(key=lambda e: e.joined_at)
team_size = self.config['team_size']
required = team_size * 2
if len(candidates) < required:
return None
anchor = candidates[0]
team1, team2 = [anchor], []
for candidate in candidates[1:]:
if abs(candidate.mmr - anchor.mmr) <= anchor.current_range:
if len(team1) < team_size:
team1.append(candidate)
elif len(team2) < team_size:
team2.append(candidate)
if len(team1) == team_size and len(team2) == team_size:
return self._create_match(team1, team2)
return None
Quality Score
1.0 ┤████████████████████░░░░░░░░░░░░░░░░░░░░
│████████████████████████████░░░░░░░░░░░░
0.8 ┤████████████████████████████████████░░░░
│████████████████████████████████████████
0.6 ┤████████████████████████████████████████
└────────────────────────────────────────
0s 30s 60s 90s 120s 150s
Queue Time
Presets:
Competitive: Quality > 0.9, Max wait 180s
Casual: Quality > 0.7, Max wait 60s
Quick Play: Quality > 0.5, Max wait 30s
def analyze_match_quality(matches: list[Match]) -> dict:
"""Analyze matchmaking quality over a sample."""
return {
'avg_queue_time_s': mean(m.queue_time for m in matches),
'p95_queue_time_s': percentile(95, [m.queue_time for m in matches]),
'avg_skill_diff': mean(abs(m.team1_avg - m.team2_avg) for m in matches),
'avg_quality_score': mean(m.quality for m in matches),
'team1_win_rate': sum(1 for m in matches if m.winner == 1) / len(matches),
'stomp_rate': sum(1 for m in matches if m.score_diff > 0.5) / len(matches),
}
| Error | Root Cause | Detection | Solution |
|---|---|---|---|
| Long queue times | Too strict matching | P95 > 120s | Increase expansion rate |
| Stompy matches | Too loose matching | Win rate < 40% | Tighten initial range |
| Smurfing abuse | New account detection | High win streak | Placement matches, behavior detection |
| Boosting | Skill disparity in party | High party MMR spread | Party MMR limits |
| Rating inflation | K-factor too high | Average MMR creep | Dynamic K-factor |
# 1. Check queue population
print(f"Players in queue: {len(queue)}")
print(f"MMR distribution: {mmr_histogram(queue)}")
# 2. Check match quality
recent = get_matches(last_hour=1)
print(f"Avg quality: {mean(m.quality for m in recent):.2f}")
print(f"Avg wait: {mean(m.wait_time for m in recent):.1f}s")
# 3. Check for outliers
for m in recent:
if m.quality < 0.5:
print(f"Low quality match: {m.id}, diff={m.skill_diff}")
| Parameter | Effect of Increase | Recommended Range |
|---|---|---|
| initial_range | Faster matches, lower quality | 25-100 MMR |
| expansion_rate | Faster matches, lower quality | 5-20 MMR/sec |
| max_range | Prevents infinite wait | 300-600 MMR |
| K-factor | Faster rating changes | 16-40 |
You are an elite AI agent architect specializing in crafting high-performance agent configurations. Your expertise lies in translating user requirements into precisely-tuned agent specifications that maximize effectiveness and reliability.