Elemental Skirmish Queue Bot
A competitive queue + matchmaking bot used inside the Elemental Esports Discord to run fair, fast in-house Valorant games with account linking, party queues, base + seasonal MMR, and seasonal leaderboards.
Note: Valorant data is currently fetched via HenrikDev (unofficial) while the official Riot API application is under review.
What it does
- Queues: join solo or as a party; region/server-aware; configurable queue types
- Matchmaking: targets equal total MMR; caps team gap (≤ 350)
- MMR model: Base MMR (from current/peak Valorant rank) + Seasonal MMR (earned)
- Seasons & ranks: tiered seasonal ranks, reset controls, history, leaderboards
- Admin ops: start/cancel matches, set results, migrate season, audit rating changes
Why it matters
- Fair games: underdog/favorite balancing, upset bonuses, anti-farm dampening
- Less admin overhead: automated queues, results, deltas, and persistence
- Visible progress: seasonal ranks with meaningful climbs; history you can point to
Flow (end to end)
- Link account → user verifies their Valorant profile (Discord ↔ Valorant)
- Base MMR → conservative seed from current/peak rank (+ tiny RR)
- Join queue → solo or party; region-compatibility & MMR window enforced
- Matchmaking → two teams formed with similar (base + seasonal) totals
- Play & report → admins set score / winner; draws & cancels supported
- Apply deltas → adaptive K-factor updates seasonal MMR; history recorded
- Leaderboards → live seasonal standings + per-queue ladders
Matchmaking & MMR (short version)
- Total MMR = Base (1000–3000 cap at link) + Seasonal (unbounded)
- Gap scaling: at 350 MMR diff → underdog +25%, favorite −35% gain/penalty
- Upset bonus: underdog win → +15%
- Premades: full 3-stack −5% dampen (coordination edge)
- Spread dampening: big internal spread reduces win gains (up to −40%)
- K-factor: newcomers 50; experienced 16–64 (adaptive by form/consistency)
Core snippet
TS
function expected(rA: number, rB: number) {
return 1 / (1 + Math.pow(10, -(rA - rB) / 400));
}
const MAX_DIFF = 350;
function gapScale(teamAvg: number, oppAvg: number) {
const diff = Math.min(Math.abs(teamAvg - oppAvg), MAX_DIFF);
const ratio = diff / MAX_DIFF;
const underdog = teamAvg < oppAvg;
const maxUnderdog = 0.25;
const maxFavorite = 0.35;
return underdog ? 1 + maxUnderdog * ratio : 1 - maxFavorite * ratio;
}