plugins/game-dev/engineering/matchmaking-system/SKILL.md
Use when implementing player matchmaking, skill-based match selection, lobby systems, queue management, or rank-based grouping. Triggers: matchmaking, lobby, queue, ELO, skill-based, rank, match players, pairing, bracket.
npx skillsauth add fcsouza/agent-skills engineering-matchmaking-systemInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Rank-based and skill-based player matching, lobby lifecycle management, and queue orchestration for multiplayer games.
Trigger: matchmaking, lobby, queue, ELO, Glicko, skill-based matchmaking, rank, match players, pairing, bracket, queue management, wait time, search radius, lobby state
bullmq-game-queues — priority queues, delayed jobs, worker patternsredis-game-patterns — Redis connection, pub/sub for lobby eventspostgres-game-schema — persisting match history, player ratingsRaph Koster: "Matchmaking is the invisible hand that shapes every player's experience. Get it wrong, and no amount of great game design can save you." Will Wright: "The best systems match complexity to the player — beginners face beginners, experts face experts, and everyone stays in flow."
bun add bullmq ioredis
bun add -d @types/node
Create dedicated BullMQ queues for matchmaking. Use the patterns from bullmq-game-queues.
import { Queue, Worker } from 'bullmq';
import type { Redis } from 'ioredis';
const matchmakingQueue = new Queue('matchmaking', {
connection: redis,
defaultJobOptions: {
removeOnComplete: 100,
removeOnFail: 500,
attempts: 3,
backoff: { type: 'exponential', delay: 1000 },
},
});
// Separate queue for search radius expansion
const radiusExpansionQueue = new Queue('matchmaking:radius-expand', {
connection: redis,
});
Choose ELO for simplicity or Glicko-2 for accuracy. Glicko-2 tracks rating deviation (confidence) and volatility.
interface PlayerRating {
rating: number; // ELO/Glicko-2 rating (default: 1500)
deviation: number; // Glicko-2 RD (default: 350, lower = more confident)
volatility: number; // Glicko-2 sigma (default: 0.06)
gamesPlayed: number;
}
// K-factor decreases as confidence increases
function getKFactor(gamesPlayed: number): number {
if (gamesPlayed < 10) return 40; // New players: high adjustment
if (gamesPlayed < 30) return 24; // Learning players
return 16; // Established players
}
See boilerplate/matchmaker.ts for the full MatchmakingEngine class. Players enter the queue with their rating, region, and game mode.
import { MatchmakingEngine } from './matchmaker';
const engine = new MatchmakingEngine(redis);
await engine.enqueue({
playerId: 'player_123',
rating: 1500,
deviation: 100,
gameMode: 'ranked-1v1',
region: 'us-east',
queuedAt: Date.now(),
searchRadius: 50,
});
The engine scores potential matches and picks the best one. Quality score considers rating difference, deviation overlap, wait time, and region latency.
// Inside BullMQ worker
const worker = new Worker('matchmaking', async (job) => {
const player = job.data as PlayerQueueEntry;
const match = await engine.findMatch(player);
if (match) {
await lobbyManager.createLobby(match);
} else {
// Schedule radius expansion after 10s
await radiusExpansionQueue.add('expand', {
playerId: player.playerId,
}, { delay: 10_000 });
}
}, { connection: redis, concurrency: 10 });
See boilerplate/lobby-manager.ts for the full LobbyManager class. Lobbies use Redis for state storage and pub/sub for real-time events.
import { LobbyManager } from './lobby-manager';
const lobbyManager = new LobbyManager(redis);
// Match found -> create lobby
const lobby = await lobbyManager.createLobby(match);
// Players ready up
await lobbyManager.readyUp(lobby.lobbyId, 'player_123');
// All ready -> countdown starts automatically
// Countdown complete -> IN_PROGRESS
// Game ends -> COMPLETE
// Player disconnects during countdown
await lobbyManager.leaveLobby(lobbyId, disconnectedPlayerId);
// Remaining players return to queue front with priority boost
// Player cancels queue
await engine.dequeue('player_123');
// Search radius expansion (called by delayed job)
engine.widenSearchRadius('player_123');
See boilerplate files:
boilerplate/matchmaker.ts — Core MatchmakingEngine class with enqueue, dequeue, match finding, and quality scoringboilerplate/lobby-manager.ts — LobbyManager class with state machine, Redis storage, and pub/sub eventstemplates/matchmaking-config.md — Configuration template for queue settings, rating parameters, and anti-abuse rulesRaph Koster: Matchmaking defines the difficulty curve more than any level designer ever could. A good matchmaker keeps players in Csikszentmihalyi's "flow channel" — challenged enough to be engaged, but never so outmatched that they feel helpless. The system is invisible when it works, infuriating when it doesn't.
Will Wright: The matchmaker is a simulation of social dynamics. It must model not just skill but confidence, frustration tolerance, and play patterns. A player on a losing streak needs an easier match, even if their rating says otherwise. Systems that serve the math instead of the human always fail.
tools
Use when implementing client-server state synchronization, delta compression, optimistic updates, rollback netcode, or real-time game state reconciliation. Triggers: state sync, netcode, delta, rollback, interpolation, prediction.
testing
Use when designing virtual economies, currencies, sink/faucet balance, loot tables, crafting systems, shops, or inflation control. Triggers: economy, currency, sinks, loot, inflation, crafting, shop.
development
Audits existing game code against design principles — checks server-authority, schema conventions, auth security, payment safety, narrative coherence, and MVP scope drift. Extract the optional component name or path from the user's message (defaults to entire src/). Use after building components or before committing.
testing
Designs a single quest end-to-end — coherence check, objective tree, quest brief, and registry entry. Extract the quest name from the user's message. Requires docs/world-lore.md and docs/quest-registry.md.