About Invokation

Invokation provides matchmaking and skill-ratings for video games.

Wizard

SBMM: Skill-Based Matchmaking

Definition

Skill-Based Matchmaking (SBMM) is the process of matching together players of similar skill.

Why do games use SBMM?

In short, SBMM increases engagement and retention. This effect has been observed consistently in experiments by game developers1 2 3 and in academic studies4.

The absence of SBMM leads to repetitive outcomes for players. High-skill players will dominate most of their matches, but low-skill players will consistently struggle. Matchmaking without SBMM leads to a greater variety of skills within each match, but less variety of individual outcomes.

Lifetime Win Rate

For the vast majority (>80%) of players, the increased outcome variance with SBMM lets them experience more frequent highs, but with the trade-off of relatively more frequent lows for highly-skilled players.

Problems with SBMM

Reduced sense of mastery

The main problem with SBMM is that it reduces extrinsic feedback. As players increase in skill, they're matched with opponents of equal skill, which makes it harder to gauge absolute skill and personal improvement. Solving this requires deliberate transparency around MMR.

Lack of variety → Burnout

Tight SBMM can lead to always facing the same types of opponents, and to punishing players for trying off-META5 strategies.

If the MMR system does not adapt appropriately (or if there is no SBMM at all), players will be discouraged from experimentation or from simply enjoying the game. It's necessary to tune the tightness of matchmaking against the responsiveness of the MMR system to mitigate this.

Invokation can help you quantify the trade-offs and balance the two.


  1. Call of Duty: The Role of Skill in Matchmaking

  2. TheGamer: Why Apex Legends Has Skill-Based Matchmaking

  3. GameRant: Respawn Entertainment Dev Reveals Why SBMM Will Be the New Norm for Multiplayer Games

  4. Level Up: Leveraging Skill and Engagement to Maximize Player Game-Play in Online Video Games

  5. Most Effective Tactic Available

Matchmaking-Rating (MMR)

MMR is the skill metric used by a matchmaking system.

Coming soon:

  • Why naive systems like "average KD" are pretty bad.
  • Technical details of common MMR systems:
    • Elo
    • Glicko
    • TrueSkill/OpenSkill
    • TrueSkill 2
  • How and why IVK Skill works so well.

IVK Skill API 1.0

Getting Started

You should have received from us:

  • user_id
  • api_key
  • config_id

For most requests, you will also need to provide a match_id. This can be an arbitrary string for testing.

REST API Overview

All requests use Basic Auth:

username: {{user_id}}
password: {{api_key}}

Base URL:

https://dev.ivk.dev/v1/mmr/{{config_id}}

e.g. to test credentials with cURL (and retrieve the configuration JSON):

curl https://dev.ivk.dev/v1/mmr/{{config_id}} -u "{{user_id}}:{{api_key}}"

Examples

We recommend using Bruno as an API client. (not Postman)

The most common route for sending match results:

POST https://dev.ivk.dev/v1/mmr/{{config_id}}/full/player/?match_id={{match_id}}

1v1 - Balanced Match

body - 1v1

[
  // Team 1
  {
    "player_id": "T1P1",
    "team_score": 30,
    "player_score": 30,
    "mmr": 400,
    "games_played": 100
  },
  // Team 2
  {
    "player_id": "T2P1",
    "team_score": 40,
    "player_score": 40,
    "mmr": 400,
    "games_played": 100
  }
]

response - 1v1

{
  "success": true,
  "player_count": 2,
  "team_count": 2,
  "result": [
    {
      "player_id": "T1P1",
      "mmr": 400,
      "mmr_team": 0.4,
      "played_frac": 1,
      "placement_frac": 1,
      "party_size": 1,
      "expected": 0.5,
      "outcome": 0.009485174635513356,
      "player_expected_raw": 0.5,
      "player_expected": 0.5,
      "player_outcome": 0,
      "player_weight": 0.8,
      "player_contrib": -19.583213144091705,
      "team_expected_raw": 0.5,
      "team_expected": 0.5,
      "team_outcome": 0.04742587317756678,
      "team_weight": 0.2,
      "team_contrib": -4.43142779453245,
      "mmr_delta": -24.014640938624154,
      "mmr_final": 375.98535906137585
    },
    {
      "player_id": "T2P1",
      "mmr": 400,
      "mmr_team": 0.4,
      "played_frac": 1,
      "placement_frac": 1,
      "party_size": 1,
      "expected": 0.5,
      "outcome": 0.9905148253644867,
      "player_expected_raw": 0.5,
      "player_expected": 0.5,
      "player_outcome": 1,
      "player_weight": 0.8,
      "player_contrib": 20.416786855908327,
      "team_expected_raw": 0.5,
      "team_expected": 0.5,
      "team_outcome": 0.9525741268224333,
      "team_weight": 0.2,
      "team_contrib": 4.6200547419162215,
      "mmr_delta": 25.036841597824548,
      "mmr_final": 425.03684159782455
    }
  ],
  "debug": null
}

3v3 - Balanced Match

body - 3v3

[
  //
  // Team 1
  //
  {
    "team_id": "T1",
    "player_id": "T1P1",
    "team_score": 30,
    "player_score": 15,
    "mmr": 500,
    "games_played": 100
  },
  {
    "team_id": "T1",
    "player_id": "T1P2",
    "team_score": 30,
    "player_score": 10,
    "mmr": 400,
    "games_played": 100
  },
  {
    "team_id": "T1",
    "player_id": "T1P3",
    "team_score": 30,
    "player_score": 5,
    "mmr": 400,
    "games_played": 100
  },
  //
  // Team 2
  //
  {
    "team_id": "T2",
    "player_id": "T2P1",
    "team_score": 40,
    "player_score": 20,
    "mmr": 500,
    "games_played": 100
  },
  {
    "team_id": "T2",
    "player_id": "T2P2",
    "team_score": 40,
    "player_score": 15,
    "mmr": 450,
    "games_played": 100
  },
  {
    "team_id": "T2",
    "player_id": "T2P3",
    "team_score": 40,
    "player_score": 5,
    "mmr": 350,
    "games_played": 100
  }
]

response - 3v3

{
  "success": true,
  "player_count": 6,
  "team_count": 2,
  "result": [
    {
      "player_id": "T1P1",
      "mmr": 500,
      "mmr_team": 1.3,
      "played_frac": 1,
      "placement_frac": 1,
      "party_size": 1,
      "expected": 0.5717913636893147,
      "outcome": 0.5694851746355133,
      "player_expected_raw": 0.5897392046116434,
      "player_expected": 0.5897392046116434,
      "player_outcome": 0.7,
      "player_weight": 0.8,
      "player_contrib": 1.1530945269004178,
      "team_expected_raw": 0.5,
      "team_expected": 0.5,
      "team_outcome": 0.04742587317756678,
      "team_weight": 0.2,
      "team_contrib": -1.1530945269004178,
      "mmr_delta": -0.11530945269004178,
      "mmr_final": 499.88469054730996
    },
    {
      "player_id": "T1P2",
      "mmr": 400,
      "mmr_team": 1.3,
      "played_frac": 1,
      "placement_frac": 1,
      "party_size": 1,
      "expected": 0.46381049397388585,
      "outcome": 0.3294851746355134,
      "player_expected_raw": 0.4547631174673573,
      "player_expected": 0.4547631174673573,
      "player_outcome": 0.4,
      "player_weight": 0.8,
      "player_contrib": -2.1448756035963696,
      "team_expected_raw": 0.5,
      "team_expected": 0.5,
      "team_outcome": 0.04742587317756678,
      "team_weight": 0.2,
      "team_contrib": -4.43142779453243,
      "mmr_delta": -6.5763033981288,
      "mmr_final": 393.4236966018712
    },
    {
      "player_id": "T1P3",
      "mmr": 400,
      "mmr_team": 1.3,
      "played_frac": 1,
      "placement_frac": 1,
      "party_size": 1,
      "expected": 0.46381049397388585,
      "outcome": 0.08948517463551336,
      "player_expected_raw": 0.4547631174673573,
      "player_expected": 0.4547631174673573,
      "player_outcome": 0.1,
      "player_weight": 0.8,
      "player_contrib": -13.89480349005136,
      "team_expected_raw": 0.5,
      "team_expected": 0.5,
      "team_outcome": 0.04742587317756678,
      "team_weight": 0.2,
      "team_contrib": -4.431427794532437,
      "mmr_delta": -18.3262312845838,
      "mmr_final": 381.6737687154162
    },
    {
      "player_id": "T2P1",
      "mmr": 500,
      "mmr_team": 1.3,
      "played_frac": 1,
      "placement_frac": 1,
      "party_size": 1,
      "expected": 0.5717913636893147,
      "outcome": 0.9905148253644867,
      "player_expected_raw": 0.5897392046116434,
      "player_expected": 0.5897392046116434,
      "player_outcome": 1,
      "player_weight": 0.8,
      "player_contrib": 16.41043181553427,
      "team_expected_raw": 0.5,
      "team_expected": 0.5,
      "team_outcome": 0.9525741268224333,
      "team_weight": 0.2,
      "team_contrib": 4.525741268224334,
      "mmr_delta": 20.936173083758604,
      "mmr_final": 520.9361730837586
    },
    {
      "player_id": "T2P2",
      "mmr": 450,
      "mmr_team": 1.3,
      "played_frac": 1,
      "placement_frac": 1,
      "party_size": 1,
      "expected": 0.5181319340019225,
      "outcome": 0.7505148253644867,
      "player_expected_raw": 0.5226649175024031,
      "player_expected": 0.5226649175024031,
      "player_outcome": 0.7,
      "player_weight": 0.8,
      "player_contrib": 7.165081511622735,
      "team_expected_raw": 0.5,
      "team_expected": 0.5,
      "team_outcome": 0.9525741268224333,
      "team_weight": 0.2,
      "team_contrib": 4.571473482662635,
      "mmr_delta": 11.73655499428537,
      "mmr_final": 461.73655499428537
    },
    {
      "player_id": "T2P3",
      "mmr": 350,
      "mmr_team": 1.3,
      "played_frac": 1,
      "placement_frac": 1,
      "party_size": 1,
      "expected": 0.4108023896865164,
      "outcome": 0.2705148253644867,
      "player_expected_raw": 0.3885029871081455,
      "player_expected": 0.3885029871081455,
      "player_outcome": 0.1,
      "player_weight": 0.8,
      "player_contrib": -11.159640135958671,
      "team_expected_raw": 0.5,
      "team_expected": 0.5,
      "team_outcome": 0.9525741268224333,
      "team_weight": 0.2,
      "team_contrib": 4.376526947614297,
      "mmr_delta": -6.783113188344373,
      "mmr_final": 343.2168868116556
    }
  ],
  "debug": null
}

3v3 - Leaver

body - leaver

[
  //
  // Team 1
  //
  {
    "team_id": "T1",
    "player_id": "T1P1",
    "team_score": 30,
    "player_score": 15,
    "mmr": 500,
    "games_played": 100
  },
  {
    "team_id": "T1",
    "player_id": "T1P2",
    "team_score": 30,
    "player_score": 10,
    "played_frac": 0.5, // <-- was only in half of the match
    "mmr": 400,
    "games_played": 100
  },
  {
    "team_id": "T1",
    "player_id": "T1P3",
    "team_score": 30,
    "player_score": 5,
    "mmr": 400,
    "games_played": 100
  },
  //
  // Team 2
  //
  {
    "team_id": "T2",
    "player_id": "T2P1",
    "team_score": 40,
    "player_score": 20,
    "mmr": 500,
    "games_played": 100
  },
  {
    "team_id": "T2",
    "player_id": "T2P2",
    "team_score": 40,
    "player_score": 15,
    "mmr": 450,
    "games_played": 100
  },
  {
    "team_id": "T2",
    "player_id": "T2P3",
    "team_score": 40,
    "player_score": 5,
    "mmr": 350,
    "games_played": 100
  }
]

response - leaver

{
  "success": true,
  "player_count": 6,
  "team_count": 2,
  "result": [
    {
      "player_id": "T1P1",
      "mmr": 500,
      "mmr_team": 1.1,
      "played_frac": 1,
      "placement_frac": 1,
      "party_size": 1,
      "expected": 0.5293052378801512,
      "outcome": 0.5694851746355133,
      "player_expected_raw": 0.5897392046116434,
      "player_expected": 0.5897392046116434,
      "player_outcome": 0.7,
      "player_weight": 0.8,
      "player_contrib": 4.410431815534266,
      "team_expected_raw": 0.2875693709541823,
      "team_expected": 0.2875693709541823,
      "team_outcome": 0.04742587317756678,
      "team_weight": 0.2,
      "team_contrib": -2.401434977766158,
      "mmr_delta": 2.008996837768109,
      "mmr_final": 502.0089968377681
    },
    {
      "player_id": "T1P2",
      "mmr": 400,
      "mmr_team": 1.1,
      "played_frac": 0.5,
      "placement_frac": 1,
      "party_size": 1,
      "expected": 0.42132436816472235,
      "outcome": 0.3294851746355134,
      "player_expected_raw": 0.4547631174673573,
      "player_expected": 0.4547631174673573,
      "player_outcome": 0.4,
      "player_weight": 0.8,
      "player_contrib": -2.1448756035963883,
      "team_expected_raw": 0.2875693709541823,
      "team_expected": 0.2875693709541823,
      "team_outcome": 0.04742587317756678,
      "team_weight": 0.2,
      "team_contrib": -2.3513906510635976,
      "mmr_delta": -4.496266254659986,
      "mmr_final": 395.50373374534
    },
    {
      "player_id": "T1P3",
      "mmr": 400,
      "mmr_team": 1.1,
      "played_frac": 1,
      "placement_frac": 1,
      "party_size": 1,
      "expected": 0.42132436816472235,
      "outcome": 0.08948517463551336,
      "player_expected_raw": 0.4547631174673573,
      "player_expected": 0.4547631174673573,
      "player_outcome": 0.1,
      "player_weight": 0.8,
      "player_contrib": -13.894803490051348,
      "team_expected_raw": 0.2875693709541823,
      "team_expected": 0.2875693709541823,
      "team_outcome": 0.04742587317756678,
      "team_weight": 0.2,
      "team_contrib": -2.351390651063578,
      "mmr_delta": -16.246194141114927,
      "mmr_final": 383.7538058588851
    },
    {
      "player_id": "T2P1",
      "mmr": 500,
      "mmr_team": 1.3,
      "played_frac": 1,
      "placement_frac": 1,
      "party_size": 1,
      "expected": 0.6142774894984783,
      "outcome": 0.9905148253644867,
      "player_expected_raw": 0.5897392046116434,
      "player_expected": 0.5897392046116434,
      "player_outcome": 1,
      "player_weight": 0.8,
      "player_contrib": 16.410431815534242,
      "team_expected_raw": 0.7124306290458177,
      "team_expected": 0.7124306290458177,
      "team_outcome": 0.9525741268224333,
      "team_weight": 0.2,
      "team_contrib": 2.401434977766153,
      "mmr_delta": 18.811866793300396,
      "mmr_final": 518.8118667933004
    },
    {
      "player_id": "T2P2",
      "mmr": 450,
      "mmr_team": 1.3,
      "played_frac": 1,
      "placement_frac": 1,
      "party_size": 1,
      "expected": 0.560618059811086,
      "outcome": 0.7505148253644867,
      "player_expected_raw": 0.5226649175024031,
      "player_expected": 0.5226649175024031,
      "player_outcome": 0.7,
      "player_weight": 0.8,
      "player_contrib": 7.165081511622751,
      "team_expected_raw": 0.7124306290458177,
      "team_expected": 0.7124306290458177,
      "team_outcome": 0.9525741268224333,
      "team_weight": 0.2,
      "team_contrib": 2.4257012653981826,
      "mmr_delta": 9.590782777020934,
      "mmr_final": 459.59078277702093
    },
    {
      "player_id": "T2P3",
      "mmr": 350,
      "mmr_team": 1.3,
      "played_frac": 1,
      "placement_frac": 1,
      "party_size": 1,
      "expected": 0.45328851549567994,
      "outcome": 0.2705148253644867,
      "player_expected_raw": 0.3885029871081455,
      "player_expected": 0.3885029871081455,
      "player_outcome": 0.1,
      "player_weight": 0.8,
      "player_contrib": -11.159640135958677,
      "team_expected_raw": 0.7124306290458177,
      "team_expected": 0.7124306290458177,
      "team_outcome": 0.9525741268224333,
      "team_weight": 0.2,
      "team_contrib": 2.322259331731681,
      "mmr_delta": -8.837380804226996,
      "mmr_final": 341.162619195773
    }
  ],
  "debug": null
}

Advanced

IVK Skill also has routes for calculating pre-match data, or for calculating in-match results (e.g. when a team or player is eliminated in a battle royale).

POST https://dev.ivk.dev/v1/mmr/{{config_id}}/pre/player/?match_id={{match_id}}
POST https://dev.ivk.dev/v1/mmr/{{config_id}}/partial/player/?match_id={{match_id}}