Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/ValveSoftware/counter-strike_regional_standings/llms.txt

Use this file to discover all available pages before exploring further.

Before the Glicko rating phase runs, four factors are computed for each team and combined into a single seed value. These factors are derived from the team’s match history: which events they participated in, how much prize money they won, who they beat, and whether those matches were played on LAN. Each factor is normalized to a value between 0 and 1 before being combined. All four use the same bucket mechanism — only the top 10 results contribute — to limit the impact of any single outlier result.

Shared mechanics

Two design decisions apply across all four factors: Top-N bucket: Only the best 10 results count toward each factor (bucketSize = 10 in team.js). Results are ranked by their scaled value before slicing. Age weighting: Each result is multiplied by a timestampModifier from RankingContext.getTimestampModifier(), which linearly remaps match timestamps into [0, 1] based on a configured time window. Recent matches score closer to 1; older matches score closer to 0. Outlier normalization: Each raw factor total is divided by the 5th-highest value across all teams (rankingContext.setOutlierCount(5)), then clamped to 1. This prevents a single dominant team from compressing everyone else toward zero.
// ranking.js
rankingContext.setOutlierCount(5);
// team.js — normalization in phase 2
let referenceWinnings      = nthHighest( teams.map( t => t.scaledWinnings ), context.getOutlierCount() );
let referenceOpponentCount = nthHighest( teams.map( t => t.distinctTeamsDefeated ), context.getOutlierCount() );
let referenceLanWins       = nthHighest( teams.map( t => t.scaledLanWins ), context.getOutlierCount() );

Bounty offered

bountyOffered measures the team’s own prize winnings — how much money the team has earned by finishing in the money at events. Calculation:
  1. For each event where the team earned prize money, compute scaledWinnings = baseWinnings × ageWeight.
  2. Sort all scaled winnings descending and take the top 10.
  3. Sum those top-10 values.
  4. Divide by referenceWinnings (the 5th-highest sum across all rosters) and clamp to 1.
  5. Apply the curve function.
// team.js — phase 1
team.eventMap.forEach( teamEvent => {
    let timestampModifier = context.getTimestampModifier( teamEvent.event.lastMatchTime );
    let baseWinnings      = teamEvent.getTeamWinnings();
    let scaledWinnings    = baseWinnings * timestampModifier;
    if ( baseWinnings > 0 )
        winnings.push( { age: timestampModifier, base: baseWinnings, val: scaledWinnings } );
} );
winnings.sort( (a,b) => b.val - a.val );
team.winnings       = winnings.slice(0, bucketSize);
team.scaledWinnings = team.winnings.map( el => el.val ).reduce( (a,b) => a + b, 0 );

// phase 2 — normalize
team.bountyOffered = Math.min( team.scaledWinnings / referenceWinnings, 1 );

// phase 3 — apply curve
team.modifiers.bountyOffered = curveFunction( team.bountyOffered );
The curve function compresses extreme values:
function curveFunction( x ) {
    return Math.pow( 1 / ( 1 + Math.abs(Math.log10(x)) ), 1 );
}
At x = 1 (as good as the reference team) the curve returns 1. For x significantly below 1, log10(x) becomes negative and large in magnitude, pulling the output toward 0.

Bounty collected

bountyCollected measures the quality of opponents a team has beaten, weighted by those opponents’ own prize winnings. Beating a team with high bountyOffered is worth more than beating an unknown team. Calculation:
  1. For each won match, score it as: opponent.bountyOffered × ageWeight × stakesModifier.
  2. stakesModifier = curveFunction(prizePool / 1,000,000) — events with higher prize pools carry more weight, capped at $1M.
  3. Sort all won-match scores descending, take the top 10.
  4. Sum the top 10, divide by bucketSize (10) to get an average.
  5. Apply the curve function.
// team.js — phase 3
team.wonMatches.forEach( teamMatch => {
    let timestampModifier = context.getTimestampModifier( teamMatch.match.matchStartTime );
    let cappedPrizePool   = Math.min( Math.max(1, prizePool), 1000000 );
    let stakesModifier    = curveFunction( cappedPrizePool / 1000000 );
    let matchContext      = timestampModifier * stakesModifier;

    let scaledBounty = teamMatch.opponent.bountyOffered * matchContext;
    bounties.push( { context: stakesModifier, base: teamMatch.opponent.bountyOffered, val: scaledBounty } );
} );

bounties.sort( (a,b) => b.val - a.val );
team.bounties         = bounties.slice(0, bucketSize);
team.opponentBounties = team.bounties.map( el => el.val ).reduce( (a,b) => a+b, 0 ) / bucketSize;

team.modifiers.bountyCollected = curveFunction( team.opponentBounties );

Opponent network

opponentNetwork measures the breadth of quality opponents a team has defeated. It uses the same pipeline as bountyCollected, but substitutes opponent.ownNetwork in place of opponent.bountyOffered. ownNetwork for each opponent is:
ownNetwork = distinctTeamsDefeated (time-decay weighted) / referenceOpponentCount
Where distinctTeamsDefeated counts only the most recent win against each unique opponent, each scaled by how recently it occurred.
// team.js — phase 1 (ownNetwork source)
opponentMap.forEach( ( lastWinTime, opp ) => {
    team.distinctTeamsDefeated += context.getTimestampModifier( lastWinTime );
} );

// phase 2 — normalize
team.ownNetwork = Math.min( team.distinctTeamsDefeated / referenceOpponentCount, 1 );
// team.js — phase 3 (opponentNetwork calculation)
let scaledNetwork = teamMatch.opponent.ownNetwork * matchContext;
network.push( { context: stakesModifier, base: teamMatch.opponent.ownNetwork, val: scaledNetwork } );

network.sort( (a,b) => b.val - a.val );
team.network         = network.slice(0, bucketSize);
team.opponentNetwork = team.network.map( el => el.val ).reduce( (a,b) => a+b, 0 ) / bucketSize;

team.modifiers.opponentNetwork = team.opponentNetwork; // powerFunction(x) = x
Note that opponentNetwork uses powerFunction (identity) rather than curveFunction, so it is not compressed the way bounty factors are.

LAN wins

lanFactor rewards winning matches played on LAN, weighted by how recently they occurred. Calculation:
  1. For each won match, score it as isLAN × ageWeight (1 if LAN, 0 if online).
  2. Sort descending and take the top 10.
  3. Sum the top 10, divide by bucketSize (10) to get a proportion.
  4. Normalize against referenceLanWins (the 5th-highest LAN score across all teams) and clamp to 1.
// team.js — phase 1
// getLAN helper: function getLAN(x) { return x.team.eventMap.get(x.match.eventId).event.lan ? 1 : 0 }
team.wonMatches.forEach( wonMatch => {
    let matchTime         = wonMatch.match.matchStartTime;
    let timestampModifier = context.getTimestampModifier( matchTime );
    let lan               = getLAN( wonMatch );   // 1 if LAN event, 0 if online
    let scaledLan         = lan * timestampModifier;
    lanWins.push( { id: wonMatch.match.umid, context: timestampModifier, base: lan, val: scaledLan } );
} );

lanWins.sort( (a,b) => b.val - a.val );
team.lanWins       = lanWins.slice(0, bucketSize);
team.scaledLanWins = team.lanWins.map( el => el.val ).reduce( (a,b) => a+b, 0 ) / bucketSize;

// phase 2 — normalize
team.lanParticipation = Math.min( team.scaledLanWins / referenceLanWins, 1 );

// final modifier
team.modifiers.lanFactor = team.lanParticipation; // powerFunction(x) = x

Factor summary

FactorSource dataCurve appliedCoefficient in seed
bountyOfferedTeam’s own prize winningsYes (curveFunction)1
bountyCollectedOpponents’ bountyOffered, weighted by stakesYes (curveFunction)1
opponentNetworkOpponents’ ownNetwork, weighted by stakesNo (powerFunction)1
ownNetworkDistinct opponents defeatedNo (powerFunction)0 (unused)
lanFactorLAN winsNo (powerFunction)1
Every factor uses a top-10 bucket, so participating in more events can only help. A weak result in a low-stakes match will not displace a stronger earlier result because results are sorted before the bucket is applied.

Build docs developers (and LLMs) love