Ruleset Reference
1. Overview
This document contains two key aspects:
- A Ruleset Example - Helps you concisely understand the syntax and core components of a Ruleset through practical demonstration.
- Ruleset Schema Specifications - Provides detailed technical explanations of the syntax rules, structural elements, and implementation requirements.
2. Ruleset Example
Here is a sample ruleset with detailed comments.
- It demonstrates two player attributes:
skill
: suggests the skill points of a player, which is defined on PGOS Player Data and its value will be automatically retrieved by PGOS backend.mode
: suggests the game mode selected by player before entering matchmaking, which is defined in the ruleset and its value should be passed by game client through PGOS SDK.
// The section of playerAttributes describes the player attributes used in the matching rules
"playerAttributes": [
{
"name": "skill", // The skill value of a player, the most common case is calculated by ELO algorithm
"key": "skill", // With this key, this attribute will be automatically retrieved from Player KVData in which key is "skill"
"type": "number", // Data type of attribute, can be "number" or "string"
"default": 0,
"partyAggregation": "avg" // Use the avg value for multiplayer matchmaking ticket
},
{
"name": "mode", // The game mode such as PvP or PvE, which must be submited from game client
"type": "string", // Data type of attribute, can be "number" or "string"
"default": "solo"
}
],
- It defines two teams:
- red: 4~8 players.
- blue: 4~8 players.
// The section of "teams" describes the team structure of the game
// The matchmaking system will try to gather as many players as the maxPlayers indicates. And it will compromise to minPlayers if there are not enough players in matchmaking pool.
"teams": [
{
"name": "red", // The size of red team is 4~8 players
"maxPlayers": 8,
"minPlayers": 4
},
{
"name": "blue", // The size of blue team is 4~8 players
"maxPlayers": 8,
"minPlayers": 4
}
],
- It has four rules:
- skillRule: makes the skill value of players in a battle as close as possible.
- modeRule: guarantees that mode value players selected must be the same.
- countRule1: guarantees that both red and blue team has equal number of players.
- countRule2: effects the same as countRule1, but demonstrates another way to configure.
// You can add multiple rule descriptions to the section of "rules", and PGOS searches for players based on these rules
"rules": [
{
// Makes the skill value of players in a battle as close as possible.
"name": "skillRule",
"type": "distanceRule",
"measurements": [
"flatten(teams[*].players.playerAttributes[skill])"
],
"referenceValue": "avg(flatten(teams[*].players.playerAttributes[skill]))",
"maxDistance": 50
},
{
// Guarantees that mode value players selected must be the same.
"name": "modeRule",
"type": "comparisonRule",
"measurements": [
"flatten(teams[*].players.playerAttributes[mode])"
],
"operation": "="
},
{
// Guarantees that both red and blue team has equal number of players.
"name": "countRule1",
"type": "comparisonRule",
"measurements": [
"count(teams[*].players)"
],
"operation": "="
},
{
// Effects the same as countRule1, but demonstrates another way to configure.
"name": "countRule2",
"type": "comparisonRule",
"measurements": [
"count(teams[blue].players)"
],
"referenceValue": "count(teams[red].players)",
"operation": "="
}
],
- It has two expansions:
- If no matches are found in 5 seconds, the maxDistance of
skillRule
will be expanded to 100, and 10 seconds for 200. - If no matches are found in 5 seconds, the minimum team size will be decreased to 2 players, and 10 seconds for just 1 player.
- If no matches are found in 5 seconds, the maxDistance of
// The section of "expansions" is "rules" expansions
// The meaning of the first expansion is:
// (1). Wait for 5s, if thereis no matchmaking result, then modify rules[skillRule].maxDistance to 100,
// modify rules[distanceRuleExample].maxDistance to 15
// (2). Wait for 10s,if thereis no matchmaking result,then modify rules[skillRule].maxDistance to 200,
"expansions": [
{
// If no matches are found after 5 seconds, the maxDistance of `skillRule` is expaned to 100, and 10 seconds for 200.
"target": "rules[skillRule].maxDistance",
"steps": [
{
"waitTimeSeconds": 5,
"value": 100
},
{
"waitTimeSeconds": 10,
"value": 200
}
]
},
{
// If no matches are found after 5 seconds, the minimum team size is decreased to 2 players, and 10 seconds for just 1 player.
"target": "teams[*].minPlayers",
"steps": [
{
"waitTimeSeconds": 5,
"value": 2
},
{
"waitTimeSeconds": 10,
"value": 1
}
]
}
]
Finally, the whole complete ruleset is as follows:
{
"version": "v1.0",
"playerAttributes": [
{
"name": "skill",
"type": "number",
"default": 0
},
{
"name": "mode",
"type": "string",
"default": "solo"
}
],
"teams": [
{
"name": "red",
"maxPlayers": 8,
"minPlayers": 4
},
{
"name": "blue",
"maxPlayers": 8,
"minPlayers": 4
}
],
"rules": [
{
"name": "skillRule",
"type": "distanceRule",
"measurements": [
"flatten(teams[*].players.playerAttributes[skill])"
],
"referenceValue": "avg(flatten(teams[*].players.playerAttributes[skill]))",
"maxDistance": 50
},
{
"name": "modeRule",
"type": "comparisonRule",
"measurements": [
"flatten(teams[*].players.playerAttributes[mode])"
],
"operation": "="
},
{
"name": "countRule1",
"type": "comparisonRule",
"measurements": [
"count(teams[*].players)"
],
"operation": "="
},
{
"name": "countRule2",
"type": "comparisonRule",
"measurements": [
"count(teams[blue].players)"
],
"referenceValue": "count(teams[red].players)",
"operation": "="
}
],
"expansions": [
{
"target": "rules[skillRule].maxDistance",
"steps": [
{
"waitTimeSeconds": 5,
"value": 100
},
{
"waitTimeSeconds": 10,
"value": 200
}
]
},
{
"target": "teams[*].minPlayers",
"steps": [
{
"waitTimeSeconds": 5,
"value": 2
},
{
"waitTimeSeconds": 10,
"value": 1
}
]
}
]
}
3. Ruleset Schema
3.1 Overview of the ruleset
PGOS supports matchmaking ruleset descriptions with JSON script.
Structure of ruleset script
Field | Description |
---|---|
version | Version of ruleset script, must be "v1.0" |
playerAttributes | Player attributes that can be used in rules |
teams | Team definitions which can be used in rules |
rules | Multiple matchmaking constraints which utilize player attributes and team data |
expansions | Expansion of rules to relax the constraints |
e.g
{
"playerAttributes":[
...
],
"teams":[
...
],
"rules":[
...
],
"expansions":[
...
],
}
3.2 PlayerAttributes
This contains descriptions of multiple sets of players, which allow the PGOS service to retrieve these data.
Structure of script
Field | Type | Description | Required | Note |
---|---|---|---|---|
name | string | Name of attribute | Yes | A string of length 32. Value range: a~z, A~Z, 0~9, _ |
type | string | Type of attribute | Yes | "number”, “string” |
default | string / number | Default value of attribute | No | Must match with the type of the attribute |
key | string | The key defined in Player Data | No | The key of player-data in PGOS Player Data |
bitmap | bool | Whether treat number as bitmap | No | Bitmap can only be true on number type attribute |
partyAggregation | string | The strategy to merge attribute value of multiple players in one ticket | No | Default to "each". Possible values can be "each", "avg", "min", "max", "any", "and", "or" |
- If the key field is empty, it means that the attribute is taken from the item with the same name in the match_attributes field of the matchmaking request.
- If the key field is not empty, it means that the data comes from the item with the same key in the Player Data.
- All player attributes described in ruleset script will be filled to
ServerPlayerDesc::match_attributes
when a new battle session is produced from the ruleset. Please read this doc to learn how to handle battle session placement notification on your game server. - If a number attribute will be used for bit operations in following rules, set bitmap to true. In any other scenario, leave it to false.
The partyAggregation parameter is used in attribute to merge the attribute values of the player in a multiplayer request.
For example "partyAggregation" = "max" means that the attribute values of all members in a multiplayer matchmaking request are regarded as the highest one.
each
: treat each player individually.avg
: combine players and select the average value of their attributes.min
: combine players and select the minimum value of their attributes.max
: combine players and select the maximum value of their attributes.any
: combine players and randomly select one of their attributes.and
: perform bitwise AND operation on the value, only available for bitmap attributes.or
: perform bitwise OR operation on the value, only available for bitmap attributes.
It is recommended to define partyAggregation
in the player attribute. Although for backward compatibility, it can also be set in the rule, setting it in the attribute is a better approach. This facilitates PGOS in sorting match tickets based on attributes.
e.g
"playerAttributes": [
{
"name": "age", // Attribute from match_attributes which key is "age"
"type": "number",
"default": 10,
"partyAggregation": "avg"
},
{
"name": "skill", // Attribute from match_attributes which key is "skill"
"type": "number"
},
{
"name": "kda", // Attribute from Player properties which key is "kda"
"key":"kda",
"type": "number",
"default": 10
},
{
"name": "mode", // Attribute from match_attributes which key is "mode"
"type": "number",
"bitmap": true,
"partyAggregation": "and"
}
],
3.3 Teams
Structure of script
Field | Description | Required | Note |
---|---|---|---|
name | Name of team | Yes | A string of length 32. Value range a~z,A~Z,0~9 |
minPlayers | Minimum number of players | Yes | An integer between 1-40 |
maxPlayers | Maximum number of players | Yes | An integer between 1-40; maxPlayers must be greater than or equal to minPlayers |
minQuantity | Minimum quantity of teams | No | An integer between 1-999; default is 1 |
maxQuantity | Maximum quantity of teams | No | An integer between 1-999; default is 1; maxQuantity must be greater than or equal to minQuantity |
The matchmaking system will try to gather as many players as the maxPlayers
indicates. And it will compromise to minPlayers
if there are not enough players in matchmaking pool.
Variable team quantity is an advanced feature. When matchmaking system finds a proposal that satisfy minQuantity
, it will continue to fill more players to the proposal and form more teams. With sufficient players in pool, the matchmaking system will return a proposal that reaches the maxQuantity
requirement.
The minPlayers
and maxPlayers
can also be the same, and it makes a team with fixed size. The minQuantity
and maxQuantity
can also be the same, and it makes fixed quantity of this team.
When maxQuantity
is set >1, serial number will be joined to the team name with underscore, which starts from "001" to "999".
For example:
"teams":[
{
"name":"squad",
"minPlayers":1,
"maxPlayers":1,
"minQuantity":4,
"maxQuantity":4
}
]
Then in the matchmaking result, there will be for teams: squad_001
, squad_002
, squad_003
, squad_004
.
e.g
// 5vs5 fair play
"teams":[
{
"name":"Darkness",
"minPlayers":5,
"maxPlayers":5
},
{
"name":"Light",
"minPlayers":5,
"maxPlayers":5
}
]
// 1vs1vs1vs1vs1 solo
"teams":[
{
"name":"soloTeam",
"minPlayers":1,
"maxPlayers":1
}
]
// 4v4v4v... battle royale
"teams":[
{
"name":"squad",
"minPlayers":4,
"maxPlayers":4,
"minQuantity":10,
"maxQuantity":25
}
]
If the two features: variable players and variable team quantities are both utilized, there are actually two tendencies that the matchmaking try to match players:
- Find as more players as
maxPlayers
requires - Find as more teams as
maxQuantity
requires
For example:
"teams":[
{
"name":"squad",
"minPlayers":1,
"maxPlayers":4,
"minQuantity":1,
"maxQuantity":4
}
]
Given 4 players in pool, 1v1v1v1
, 2v2
, 4 as a team
are all legal matchmaking results that meet the requirements above.
Currently the matchmaking system tends to meet the minQuantity
and maxPlayers
first, when minimum enough teams are created, it tries to raise the team quantity by one, still tries to meet maxPlayers
for this team. The matchmaking continues cycling until no sufficient players in pool for the last raised team, or the whole team quantity reaches maxQuantity
limit.
So it will give a 4 as a team
as the final result by default. If 1v1v1v1
is wanted, the developer can utilize the "Rules Expansion" feature below to relax the minQuantity
value by steps to deal with 1~4 players that are meant to be assigned each one as a team, which is demonstrated as below:
"teams":[
{
"name":"squad",
"minPlayers":1,
"maxPlayers":4,
"minQuantity":4,
"maxQuantity":4
}
],
"expansions": [
{
"target": "teams[*].minQuantity",
"steps": [
{
"waitTimeSeconds": 5,
"value": 3
}
]
},
{
"target": "teams[*].minQuantity",
"steps": [
{
"waitTimeSeconds": 10,
"value": 2
}
]
},
{
"target": "teams[*].minQuantity",
"steps": [
{
"waitTimeSeconds": 15,
"value": 1
}
]
}
]
3.4 Rules
3.4.1 Structure of script
Field | Description | Required | Note |
---|---|---|---|
name | Name of rule | Yes | A string of length 32. Value range a~z,A~Z,0~9,_ |
description | Rule description | No | String of length 128 |
type | Type of rule | Yes | Valid values are "distanceRule", "comparisonRule", and "latencyRule" |
The number of rules in a ruleset should not exceed 10.
3.4.2 Expressions of rules
Syntax Specification
The following statements shows how to identify the team and the players in the team.
// Meaning:
// Darkness team
// Example of results:
// [[Darkness]]
teams[Darkness]
// Meaning:
// All teams
// Example of results:
// [[Darkness] [Light]]
teams[*]
// Meaning:
// Players in Darkness team
// Example of results:
// [[player-1 player-2]]
teams[Darkness].players
// Meaning:
// All players group by team
// Example of results:
// [[player-1 player-2] [player-3 player-4]]
teams[*].players
The following statements shows how to get player attribute data.
// Meaning:
// Playerid of players in Darkness Team
// Example of results:
// [[player-1 player-2]]
teams[Darkness].players[playerid]
// Meaning:
// List of attribute named skill in Darkness Team
// Example of results:
// ["solo" "solo" "solo"] or [1 1 1]
teams[Darkness].players.playerAttributes[skill]
// Meaning:
// Attribute named skill of all players(Group by team)
// Example of results:
// [["solo" "solo" "solo"] ["solo" "solo" "solo"]] or [[1 1 1] [1 1 1]]
teams[*].players.playerAttributes[skill]
3.4.3 Functions of expressions
Function name | Input | Meaning | output |
---|---|---|---|
flatten | List | Turn the collection of nested lists into a single list containing all elements | List |
avg | List | Get the average of all numbers in a list | Number |
min | List | Get the minimum of all numbers in a list | Number |
max | List | Get the maximum of all numbers in a list | Number |
sum | List | Get the sum of all numbers in a list | Number |
count | List | Get the number of all elements in a list | Number |
and | List | Perform bitwise AND operation on the numbers in the list. | Number |
Note that when avg
always acts on one-dimensional list, and when the input is a nested lists, it will recurse.
Deal with nested lists
// One dimensional list condition
ListA = [[1, 2, 3]]
avg(ListA) = [2]
// Nested list condition
ListB = [[1,2,3], [3,4,5]]
avg(ListB) = [2, 4]
If you have nested lists like ListB
in the case above, and you want calculate the average of all elements, you should use flatten
to break nested lists into one-dimensional list first.
// Symbolic explanation
ListA = [[1, 2, 3]]
ListB = [[3, 4, 5]]
ListC = [ListA, ListB]
flatten(ListC) = [[1, 2, 3, 3, 4, 5]]
// Meaning:
// List of attribute named kda of all players
// Type of results:
// List<number>
// If
team[A].players.playerAttributes[skill] = [[1, 2, 3]]
team[B].players.playerAttributes[skill] = [[3, 4, 5]]
// Then
teams[*].players.playerAttributes[skill] = [[1,2,3], [3,4,5]]
flatten(teams[*].players.playerAttributes[skill]) = [[1, 2, 3, 3, 4, 5]]
// When combined with "avg", the difference of using "flatten" is:
avg(teams[*].players.playerAttributes[skill]) = [[2, 4]]
avg(flatten(teams[*].players.playerAttributes[skill])) = avg([1, 2, 3, 3, 4, 5]) = [3]
3.4.4 Type of Rules
Type 1. Distance rule
Property name | Description | Required | Value constraint |
---|---|---|---|
name | Name of rule | Yes | A string of length 32. The value range is a~z, A~Z, 0~9, _ .Unique in the same ruleset |
type | Type of rule | Yes | fixed "distanceRule" |
description | Description of rule | No | String of length 256 |
measurements | Measured value, used to compare distance with referenceValue | Yes | The calculation result is an expression of List\<number> or List\<List\<number>> Support multiple number expressions with accuracy of 2 decimal places |
referenceValue | Comparison value | Yes | Number or an expression whose result is a number with accuracy of two decimal places; can be used as an expansion target |
minDistance | Minimum distance difference | Fill in at least one of minDistance or maxDistance | The minimum difference of comparing each number calculated in measurements with referenceValue. The value range is a number between 0-99999 with accuracy of two decimal places. Can be used as an expansion target |
maxDistance | Maximum distance difference | Fill in at least one of minDistance or maxDistance | The maximum difference of comparing each number calculated in the measurements with referenceValue, . The value range is a number between 0-99999 with accuracy of two decimal places. Can be used as an expansion target |
partyAggregation | How to sort multi-player requests | No | Valid options include each(each), minimum(min), maximum(max), average(avg) or any one(any) for a request's player. Default is each |
// Meaning:
// The distance between average value of the "skill" of each team player and the average value of the "skill" of all players is not less than 5, not more than 10
"rules": [
{
"name": "distanceRuleExample",
"type": "distanceRule",
"measurements": [ "avg(teams[*].players.skill)" ],
"referenceValue": "avg(flatten(teams[*].players.playerAttributes[skill]))",
"minDistance":5,
"maxDistance":10
}]
Type 2. Comparison rule
Property name | Description | Required | Value constraint |
---|---|---|---|
name | Name of rule | Yes | A string of length 32. The value range is a~z, A~Z, 0~9, _ unique in the same ruleset |
description | Description of rule | No | string of length 256 |
type | Type of rule | Yes | fixed "comparisonRule" |
measurements | Expressions of rules | Yes | An expression whose calculation result is of List<?>, which can be List\<number> or List\<string>. Only 1 expression is supported temporarily. |
referenceValue | Value to compare with rule expressions | No | If referenceValue is defined, then compare each element in List\<?> with referenceValue; |
If referenceValue is not defined, then compare each element in List\<?>. In this case, operation can only take the value "=" or "!=" | |||
An expression whose calculation result is the same as the element type of the measurement, such as a number or string. Can be used as an extended target | |||
operation | Comparison symbol | Yes | Valid values: “=”, “!=”, “<”, “<=”, “>”, “>=” (These symbols can be used to compare numbers or strings). Can be used as an expansion target |
// Meaning:
// Players must all choose "solo" mode
"rules": [ {
"name": "modeRule",
"type": "comparisonRule",
"measurements": [
"flatten(teams[*].players.playerAttributes[mode])"
],
"referenceValue": "solo",
"operation": "="
}]
// Meaning:
// Players of team red must all be "attacker" side
"rules": [ {
"name": "side_binding_red",
"type": "comparisonRule",
"measurements": [
"flatten(teams[red].players.playerAttributes[side])"
],
"referenceValue": "attacker",
"operation": "="
}]
// Meaning:
// Players of each team must pick the same side
"rules": [ {
"name": "side_binding",
"type": "comparisonRule",
"measurements": [
"teams[*].players.playerAttributes[side]"
],
"operation": "="
} ]
Type 3. Latency rule
Property name | Description | Required | Value constraint |
---|---|---|---|
name | Name of rule | Yes | A string of length 32. The value range is a~z, A~Z, 0~9, _ and is unique in the same ruleset |
description | Description of rules | No | String of length 256 |
type | Type of rule | Yes | fixed "latencyRule" |
maxLatency | Maximum latency | Yes | Unit is ms; The value range is 0~999999; can be used as an expansion target |
partyAggregation | Processing mode of multiplayer request | No | Valid options include each(each), minimum(min), maximum(max), average(avg) or any one(any) for a request's player. Default is each |
// Meaning:
// The maximum delay from the player to the region does not exceed 150ms
"rules": [{
"name": "laytency_rule_example",
"type": "latencyRule",
"maxLatency": 150
}]
Type 4. Collection rule
Property name | Description | Required | Value constraint |
---|---|---|---|
name | Name of rule | Yes | A string of length 32. The value range is a~z, A~Z, 0~9, _ and is unique in the same ruleset |
description | Description of rules | No | String of length 256 |
type | Type of rule | Yes | fixed "collectionRule" |
measurements | Expressions of rules | Yes | An expression whose calculation result is List, which can be List of number or List of string. Only 1 expression is supported temporarily for a collection rule. |
referenceValue | Value to compare with rule expressions | No | The corresponding operation must be set to "contains" if referenceValue is defined. The collection rule treats the measurements result as a one-dimensional array (if the original result is two-dimensional, it will be automatically flattened into one dimension). The rule then counts elements matching the referenceValue within the flattened array. |
If referenceValue is not defined, then the corresponding operation must be set to "intersection" . The collection rule treats the measurements result as a two-dimensional array and attempts to compute the intersection across all sub-arrays. | |||
For collection rules, the referenceValue parameter must be assigned a constant value (literal) and cannot be an expression. | |||
operation | Operation of collection rule | Yes | Valid values: “intersection”, “contains” Intersection: Logical AND operation across sub-arrays (e.g., [[1,2], [2,3]] → intersection [2] ).Contains: Boolean check for value existence, aggregated as a count metric. |
minCount | Minimum count of elements in result array | Yes | Specifies the minimum allowed number of elements in the collection rule's result set. Set this to 0 to disable the minimum constraint. |
maxCount | Maximum count of elements in result array | Yes | Specifies the maximum allowed number of elements in the collection rule's result set. Set this to 0 to disable the maximum constraint. |
The calculation logic and effects of collecction rule is more abstract when comparing with other types of rules. We recommend using the Debug Rules feature in the ruleset editor to evaluate the behavior of set-based rules.
3.5 Rules Expansion
The matchmaking rules support the expansion of constraints of some fields to implement "time decay" matchmaking.
3.5.1 Structure of expansion item
Property name | Description | Required | Value constraint |
---|---|---|---|
target | The target property to relax | Yes | can be the following forms: teams[red].minPlayers teams[*].maxPlayers rules[mySkillRule].maxDistance rules[myLatencyRule].maxLatency rules[myCollectionRule].referenceValue |
steps | All steps by passed time | Yes | The count of steps should not exceed 10 |
steps/waitTimeSeconds | The absolute time passed since one player started matchmaking | Yes | The maximum waitTimeSeconds should not exceed the timeout limit defined in match config |
steps/value | The value of the target | Yes | The minPlayers of a team cannot be less than one. The maxDistance of distance rule cannot be less than minDistance. |
e.g
// Meaning:
// By time elapsed 5s without result, modify the value of minDistance to 4
// By time elapsed 5s without results, modify the value of maxDistance to 15
// By time elapsed 10s without results, modify the value of maxDistance to 20
"rules": [
{
"name": "distanceRuleExample",
"type": "distanceRule",
"measurements": [
"avg(teams[*].players.playerAttributes[skill])"
],
"referenceValue": "avg(flatten(teams[*].players.playerAttributes[skill]))",
"minDistance":5,
"maxDistance":10,
}
]
"expansions": [
{
"target": "rules[distanceRuleExample].minDistance",
"steps": [{
"waitTimeSeconds": 5,
"value": 4
}]
},
{
"target": "rules[distanceRuleExample].maxDistance",
"steps": [{
"waitTimeSeconds": 5,
"value": 15
},{
"waitTimeSeconds": 10,
"value": 20
}
]
// Meaning:
// By time elapsed 5s without result, modify the value of referenceValue to 2
// By time elapsed 10s without results, modify the value of referenceValue to 3
"rules": [
{
"name": "natRuleExample",
"type": "collectionRule",
"description": "",
"measurements": [
"flatten(teams[*].players.playerAttributes[natType])"
],
"referenceValue": "1",
"operation": "contains",
"maxCount": 10,
"minCount": 1
}
]
"expansions": [
{
"target": "rules[natRuleExample].referenceValue",
"steps": [
{
"waitTimeSeconds": 5,
"value": "2"
},
{
"waitTimeSeconds": 10,
"value": "3"
}
]
}
]