Skip to content

Ranked Ladder & Matchmaking Service #2001

@cursorial

Description

@cursorial

Ranked Matchmaking and MMR: Comprehensive Design and Integration Plan

Goal

  • Introduce a ranked matchmaking system with seasons, MMR, and a queue-based matchmaker, fully integrated with the existing client and server.
  • Prioritize correctness, fairness, speed of delivery, and a clean migration path from MVP to scale.

High-level architecture

  • Ranked Matchmaking Service (RMS): Accepts queue tickets, widens search over time, creates balanced matches, coordinates accept/ready checks, hands off to game creation.
  • Rating Service: Maintains player MMR per season, applies updates post-match, persists history, exposes leaderboards.
  • Lobby Coordinator: Spins up the game instance/lobby with assigned players/mode, returns server connection details, enforces accept/ready windows.
  • Data Store: Relational DB for durability/auditability; Redis optional for fast, ephemeral queue state.
  • Observability: Metrics (wait times, fairness deltas), structured logs, SLOs.

Recommended initial deployment

  • Phase 1: In-process ranked module in the existing server, single worker.
  • Phase 2: Extract to a separate process sharing DB; optional Redis for queue state and accept windows.
  • Phase 3: Horizontal scale, sharded by region/mode.

MMR system

  • Default: Glicko-2 per season
    • Store per player per season: rating r, rating deviation RD, volatility σ.
    • Init: r=1500, RD=350, σ=0.06; provisional for first 10 matches.
    • Team rating: team_rating = mean(player_ratings), team_RD = sqrt(sum(RD^2))/team_size.
    • Expected score E uses g(RD) dampening.
    • Update per standard Glicko-2: compute v, Δ, a=ln(σ^2), solve for a’, then r’ and RD’; apply time-based RD inflation between matches.
    • Placement: higher RD floor until N matches.
    • Decay: increase RD after inactivity; do not forcibly lower r.
    • Parties: update each member separately with their own r/RD and team result.
  • Ranking tiers
    • Map rating to tiers (e.g., Bronze 0–1199, Silver 1200–1399, Gold 1400–1599, etc.) with 100-point sub-divisions.
    • Soft season reset: r’ = mean + α*(r - mean), α≈0.75; RD bumped to 200.
  • Anti-abuse
    • Dodge penalties: escalating queue timeouts; no rating change if match not started.
    • Leaver penalties: ranked loss on early leave and confidence constraints on the remaining team’s MMR updates.
    • Smurf detection: large deltas and rapid rating changes flagged.

Alternative MMR choices

  • TrueSkill 2: Bayesian skill with teams/draws natively; superior for team games; more complex.
  • Elo with dynamic K: Simple and fast; add K scaling by uncertainty/opponent delta.
  • Glicko-1: Simpler than Glicko-2; slightly less adaptive.

Matchmaking algorithm

  • Queue tickets
    • Attributes: player_id/party_id, mode_id, region, mmr_snapshot, ping, team_size, constraints (crossplay, map pool).
    • Search window: (MMR_min, MMR_max), ping_max, party size compatibility.
  • Widening search over time
    • T0–30s: ±100 MMR, ping ≤50ms.
    • 30–90s: expand ±50 MMR and +25ms every 15s.
    • 90–180s: expand up to ±400 MMR and ping ≤120ms.
    • Cap by mode’s fairness targets and regional population.
  • Candidate matching
    • Partition queue by region → mode → party size bins.
    • Find candidate sets that satisfy team size/constraints.
    • Balance teams by minimizing |team_avg_mmr - global_avg_mmr|.
  • Ready/accept check
    • Send MatchFound with accept tokens; 10–15s accept window.
    • Decline/timeout: apply dodge penalty; re-queue remaining or cancel all.
  • Hand-off to game
    • Create lobby, return endpoint to clients, record ranked_matches.
  • Post-match updates
    • Ingest result, compute ratings, write ranked_match_participants and ranked_rating_history, update player_ranked_ratings.

Alternative matchmaking strategies

  • Strict banded queues: Fixed ±MMR brackets; lower fairness drift; longer waits.
  • Global pool with region weighting: Cross-region fallback with ping caps; better low-population handling.
  • Role-based MMR: Track role-specific ratings; higher fairness at increased complexity.

Client integration (files to touch and flows)

  • UI/UX
    • Ranked panel with queue button and mode/region selectors in src/client/graphics/layers/PlayerPanel.ts or src/client/graphics/layers/OptionsMenu.ts.
    • Queue state widget in src/client/graphics/layers/GameRightSidebar.ts: status, elapsed time, estimated wait.
    • Match accept modal with countdown; display accept/decline states in src/client/graphics/layers/ExitConfirmModal.ts or a new AcceptMatchModal.ts.
    • Post-game: rating delta, tier, streak in src/client/graphics/layers/WinModal.ts.
    • Settings: ranked toggle and region lock in src/client/graphics/layers/SettingsModal.ts.
  • Networking messages (client → server)
    • RankedQueueJoin: { modeId, region, partyId?, constraints? }
    • RankedQueueLeave: { ticketId }
    • RankedAcceptMatch: { ticketId, matchId, acceptToken }
    • RankedDeclineMatch: { ticketId, matchId, acceptToken }
  • Notifications (server → client)
    • RankedQueued: { ticketId, estWait, searchWindow }
    • RankedSearchUpdate: { searchWindow, position?, estWait }
    • RankedMatchFound: { matchId, acceptDeadline, opponentsSummary }
    • RankedMatchCancelled: { reason }
    • RankedLobbyReady: { matchId, connect }
    • RankedRatingUpdate: { matchId, delta, newRating, newTier }
  • Server handlers to extend
    • Add message handlers in src/server/worker/websocket/handler/message/PreJoinHandler.ts and src/server/worker/websocket/handler/message/PostJoinHandler.ts for join/leave/accept/decline.
    • Introduce RankedQueueHandler.ts consolidating matchmaking commands/events.
    • Wire lobby creation through src/core/GameRunner.ts with a ranked flag and metadata.

Alternative client integration approaches

  • Minimal MVP UI: Single Ranked button and one accept modal; expand later.
  • Out-of-game queue screen: Dedicated waiting room with tips/leaderboards.
  • Background queue: Allow casual play while queued; prompt accept on match found.

Server integration (modules and flow)

  • New modules
    • RankedQueueService: ticket lifecycle, search widening, candidate selection.
    • AcceptCoordinator: match found → ready check → finalization.
    • RatingService: Glicko-2 updates, placement logic, history write.
    • RankedController: websocket message endpoints.
  • Game lifecycle hooks
    • On lobby ready: persist ranked_matches with roster and mmr snapshot.
    • On game end: compute outcome, rating updates, record history, notify clients.
  • Persistence and idempotency
    • State transitions transactional; dedupe by natural keys (match_id, player_id).
    • Rating updates isolated and retriable.

Alternative service architecture

  • Separate service process communicating via HTTP/gRPC and Redis streams; scales independently.
  • Monolithic module in the existing server process; fastest initial delivery.
  • Managed service (e.g., PlayFab/Steam): fastest to validate; least control.

Database schema (core tables)

  • ranked_seasons
    • id (pk), name, start_at, end_at, soft_reset_factor, created_at
    • idx: (start_at), (end_at)
  • player_ranked_ratings
    • player_id (pk fk), season_id (pk fk), rating, rd, volatility, matches_played, wins, losses, streak, last_active_at, last_match_id
    • idx: (season_id, rating desc), (player_id)
  • ranked_matches
    • id (pk), season_id (fk), mode_id, region, map_id, created_at, started_at, finished_at, state, average_mmr, team_size
    • idx: (season_id, mode_id, created_at), (region, state)
  • ranked_match_participants
    • match_id (pk fk), player_id (pk fk), team, rating_before, rd_before, volatility_before, rating_after, rd_after, volatility_after, outcome, dodged, left_early, duration_seconds
    • idx: (player_id, match_id), (match_id, team)
  • ranked_rating_history
    • id (pk), player_id (fk), season_id (fk), match_id (fk), delta, rating_after, rd_after, volatility_after, reason, created_at
    • idx: (player_id, season_id, created_at desc)
  • ranked_parties
    • id (pk), leader_id (fk), created_at, disbanded_at
    • idx: (leader_id)
  • ranked_party_members
    • party_id (pk fk), player_id (pk fk), joined_at, left_at
    • idx: (player_id)
  • ranked_queue_tickets
    • id (pk), player_id or party_id, is_party, season_id, mode_id, region, mmr_snapshot, ping_ms, created_at, state, search_json
    • idx: (state, region, mode_id, created_at), (player_id), (party_id)
  • optional: ranked_leaderboards
    • Materialized view or cached denormalization for fast top-N.

Alternative persistence approaches

  • Event-sourced: Append-only rating events, materialize projections.
  • Key-value + periodic compaction: Redis primary, DB as sink.
  • Analytics sidecar: Columnar store (e.g., ClickHouse) for telemetry.

API contracts (examples)

Client → Server

{
  "type": "RankedQueueJoin",
  "modeId": "1v1",
  "region": "eu-west",
  "partyId": null,
  "constraints": {"maxPingMs": 80}
}

Server → Client

{
  "type": "RankedMatchFound",
  "matchId": "m_abc",
  "acceptDeadline": 1712345678,
  "opponentsSummary": {"avgMmr": 1520, "teamSizes": [1,1]}
}

Post-match rating update

{
  "type": "RankedRatingUpdate",
  "matchId": "m_abc",
  "delta": 18,
  "newRating": 1538,
  "newTier": "Gold-1"
}

Ready/accept and penalties

  • Accept window: 10–15s; all must accept.
  • Declines/timeouts: Decliner flagged, escalating queue lock (e.g., 2m, 5m, 10m).
  • No rating changes: If match never started; invalidate match.
  • AFK early leave: Mark loss; apply reduced rating protection to teammates.

Seasons and leaderboards

  • Season roll: Soft reset, RD inflation; optionally maintain all-time rating separately.
  • Leaderboards: Per season; top N with tier filters; weekly highlights.
  • Rewards: Snapshot tiers at end_of_season for cosmetics.

Testing and rollout

  • Unit tests
    • Rating math: golden tests vs reference Glicko-2 vectors.
    • Matchmaking selection under synthetic queues; fairness and time-to-match.
  • Simulation
    • Agent-based simulation of populations and ping/MMR distributions; track fairness/wait time distributions.
  • Load tests
    • Burst queue and accept storm; DB contention and Redis TTL churn.
  • Observability
    • Metrics: p50/p90 queue times, fairness delta by mode, accept rate, dodge rate, rating error drift.
  • Feature flags
    • Enable by mode/region; shadow compute MMR and compare to control.
  • Rollback
    • Gate rating writes; ability to invalidate a season quickly.

Implementation sequence

  • Step 1: Schema migrations, read models, admin tools for seasons.
  • Step 2: Queue join/leave plumbing and simple same-MMR matches end-to-end (dev region).
  • Step 3: Ready/accept workflow; lobby creation and back-out safety.
  • Step 4: Post-match ingestion and rating updates; emit deltas to client.
  • Step 5: Search widening and fairness balancing.
  • Step 6: Placement, tiers, and leaderboards; inactivity RD inflation.
  • Step 7: Scaling: move queue state to Redis; optional service split.
  • Step 8: Telemetry SLOs and alerting; anti-dodge and anti-smurf policies.

Specific changes in this codebase

  • Server
    • Add RankedQueueService, AcceptCoordinator, RatingService modules.
    • Extend websocket message handling in src/server/worker/websocket/handler/message/PreJoinHandler.ts and src/server/worker/websocket/handler/message/PostJoinHandler.ts with ranked join/leave/accept.
    • Add ranked-aware lobby creation path in src/core/GameRunner.ts.
    • Add post-game hook to emit result into RatingService.
  • Client
    • Add Ranked UI in src/client/graphics/layers/PlayerPanel.ts and src/client/graphics/layers/OptionsMenu.ts.
    • Add queue status in src/client/graphics/layers/GameRightSidebar.ts.
    • Add accept modal; wire to src/client/graphics/layers/ExitConfirmModal.ts or a new layer.
    • Post-game rating in src/client/graphics/layers/WinModal.ts.
  • Tooling
    • Add DB migrations for ranked tables.
    • Add endpoint schemas in src/core/ExpressSchemas.ts or src/core/WorkerSchemas.ts.
    • Add analytics events for queue lifecycle.

Operations and safeguards

  • Idempotent rating writes keyed by (player_id, match_id).
  • Ticket TTLs for stale entries.
  • Strict state machine: ticket → candidate → matchFound → accepted → lobbyReady → started → finished/cancelled.
  • Privacy: Only expose public ranking data.

Concrete configuration defaults

  • Glicko-2 init: r=1500, RD=350, σ=0.06, RD floor 50, RD inflation 1.5/month inactivity.
  • Queue widening: ±100 MMR, +50 every 30s up to ±400; ping cap 50→120ms.
  • Accept window: 12s, dodge lockouts: 2m, 5m, 10m; reset daily.
  • Placement matches: First 10 with higher RD floor.

Fastest MVP cut

  • Scope: Single mode (1v1) and single region.
  • State: In-process queue with array-based candidates.
  • MMR: Elo with dynamic K; upgrade to Glicko-2 in Phase 2.
  • Constraints: No parties initially; no cross-region fallback.

Alternatives to major decisions

  • Architecture
    • In-process module first, split later.
    • Separate service with HTTP/gRPC from day one.
    • Managed matchmaking platform integration for rapid validation.
  • Queue state
    • In-memory plus periodic checkpoints.
    • Redis primary with consumer groups for accept windows.
    • Database-only with careful indexing.
  • Ready checks
    • Instant start without accept for low ranks.
    • Double-confirm accept to reduce dodges.
    • Pre-commit ready (soft match) then finalize after lobby allocation.
  • Season management
    • Hard reset each season.
    • Soft reset with tier floors.
    • Rolling seasons per mode.
  • Leaderboards
    • Live computed from player_ranked_ratings.
    • Materialized leaderboard refreshed every minute.
    • Cached in Redis with write-through on updates.

Risks and mitigations

  • Low-population queues: Gradual cross-region with ping caps and clear UX.
  • Party fairness: Avoid large parties in small modes; optionally split parties.
  • Rating inflation/deflation: Monitor drift; tune σ and RD floors per season.
  • Cheating/abuse: Escalate penalties; detect anomaly deltas.

Metadata

Metadata

Labels

No labels
No labels

Projects

Status

Triage

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions