Confusing Flexmatch Ruleset Behavior

HI!

We have a ruleset that’s using the below ruleset to keep clan members on the same team and also prevent clan remembers from also being placed on another team.

        {
            "name": "SameClan_team_1",
            "description": "Only match players with the same clan for team 1",
            "type": "comparison",
            "measurements": [
                "teams[team_1].players.attributes[clanId]"
            ],
            "operation": "="
        },
        {
            "name": "SameClan_team_2",
            "description": "Only match players with the same clan for team 1",
            "type": "comparison",
            "measurements": [
                "teams[team_2].players.attributes[clanId]"
            ],
            "operation": "="
        },
        {
          "name": "DifferentClanIdPerTeam",
          "description": "Keep clan mates on same team",
          "type": "comparison",
          "measurements": [
            "count(set_intersection(teams[*].players.attributes[clanId]))"
          ],
          "referenceValue": 0,
          "operation": "="
        }

This was working just fine if 4 players queued individually. eg:
Ticket 1
Player A, Clan A
Ticket 2
Player B, Clan A
Ticket 3
Player C, Clan B
Ticket 4
Player D, Clan B

We’ve recently introduced a feature for clan members to queue up together. When this happens, we create a single ticket for the two clanmates. eg:
Ticket 1
Player A, Clan A
Player B, Clan A
Ticket 2
Player C, Clan B
Player D, Clan B

Unfortunately, this new form of matchmaking isn’t passing the final ruleset DifferentClanIdPerTeam (I cloned the ruleset and started with an empty rule list and added each rule in the original ruleset 1 by 1 to confirm. sigh Anyways, I’m not sure why this is happening. I’m assuming there is something I don’t understand about how tickets with a player list larger than 1 are handled in Flexmatch. Any help would be greatly appreciated!

  • Austin

Hi @Nggieber - tagging you as you’d suggested

Hi @AustinK

I don’t think DifferentClanIdPerTeam is doing what it is intended to do.

set_intersection : “Get a list of strings that are found in all string lists in a collection.”

DifferentClanIdPerTeam has…

"measurements": [
  "count(set_intersection(teams[*].players.attributes[clanId]))"
],
"referenceValue": 0,
"operation": "="

Which can be roughly translated to: “grab all common clanIds in the tickets, count the common clanIds, and make sure the # of common clanIds is 0.” Or more literally, “make sure no players in the match have the same clanId”. Because players in a ticket are inseparable AND have the same clan id, your matchmaking ruleset would never find a match.

Here is the references on set_intersection: FlexMatch rules language - Amazon GameLift

Instead, I think you can do something similar to FlexMatch Example 5:

"measurement": [
  "flatten(teams[*].players.attributes[clanId])"
],
"referenceValue": "set_intersection(flatten(teams[*].players.attributes[clanId]))", # Make sure you include `flatten` here
"operation": "reference_intersection_count"
"minCount":0

This translates to: “collect all clan ids in a team, make sure the clan ids don’t appear in other team’s common clan ids” which should be what you intended to do.

I’ll give this a shot! Thank you :slight_smile:

I do have a question regarding the quote below:

The brought translation and literal translation mean two different things in my brain. The rough translation makes sense as to why if the players all queued individually the rules would pass, and they would be placed into the game session together with their clanmates on the same team. The literal translation wouldn’t allow individual queueing to pass the tests either.

Hi @JamesM_Aws

Do you have an update to the above question:

why if the players all queued individually the rules would pass, and they would be placed into the game session together with their clanmates on the same team

Also, the suggested example rule does not validate/work in our scenario. Could you help to make it work?

Do you have an update to the above question

Sorry, I don’t have all the information to answer the question. @AustinK only provided a partial ruleset. Could you provide the full ruleset as well as some sample StartMatch requests for both scenarios? If you don’t feel comfortable providing that here, you can start a secured messaging channel with the help of AWS technical support (AWS Support and Customer Service Contact Info | Amazon Web Services).

Also, the suggested example rule does not validate/work in our scenario. Could you help to make it work?

Could you please provide some more details on what doesn’t work? What’s the validation error you saw? (I free typed the ruleset so it’s possible that some syntactical errors were made, but the idea should hopefully be conveyed to get you unblocked.) If you meant that you fixed the validation error but the placement didn’t work expectedly, what’s the expected/actual outcome? What’s the ruleset that you tried? Could you provide some ticket ids?

@JamesM_Aws here is the full original rule:

{

    "name": "cdm_2t_2p_max4_v3",

    "ruleLanguageVersion": "1.0",

    "playerAttributes": [

        {

            "name": "mmr",

            "type": "number",

            "default": 10

        },

        {

            "name": "infamy",

            "type": "number",

            "default": 0

        },

        {

            "name": "level",

            "type": "number",

            "default": 10

        },

        {

            "name": "missionId",

            "type": "string",

            "default": "none"

        },

        {

            "name": "missionStateId",

            "type": "string",

            "default": "none"

        },

        {

            "name": "clanId",

            "type": "string",

            "default": "none"

        },

        {

            "name": "devFlagId",

            "type": "string",

            "default": "none"

        }

    ],

    "teams": [

        {

            "name": "team_1",

            "maxPlayers": 2,

            "minPlayers": 2

        },

        {

            "name": "team_2",

            "maxPlayers": 2,

            "minPlayers": 2

        }

    ],

    "rules": [

        {

            "name": "FairTeamMMR",

            "description": "The average MMR of players in each team is within 10 points from the average MMR of all players in the match",

            "type": "distance",

            // get mmr values for players in each team and average separately to produce list of two numbers

            "measurements": [

                "avg(teams[*].players.attributes[mmr])"

            ],

            // get mmr values for players in each team, flatten into a single list, and average to produce an overall average

            "referenceValue": "avg(flatten(teams[*].players.attributes[mmr]))",

            "maxDistance": 10 // minDistance would achieve the opposite result

        },

        {

            "name": "SameMissionState",

            "description": "Only match players looking for the same mission state",

            "type": "comparison",

            "measurements": [

                "flatten(teams[*].players.attributes[missionStateId])"

            ],

            "operation": "="

        },

        {

            "name": "SameDevFlagId",

            "description": "Only match players with the same devFlagId",

            "type": "comparison",

            "measurements": [

                "flatten(teams[*].players.attributes[devFlagId])"

            ],

            "operation": "="

        },

        {

            "name": "SameClan_team_1",

            "description": "Only match players with the same clan for team 1",

            "type": "comparison",

            "measurements": [

                "teams[team_1].players.attributes[clanId]"

            ],

            "operation": "="

        },

        {

            "name": "SameClan_team_2",

            "description": "Only match players with the same clan for team 1",

            "type": "comparison",

            "measurements": [

                "teams[team_2].players.attributes[clanId]"

            ],

            "operation": "="

        },

        {

          "name": "DifferentClanIdPerTeam",

          "description": "Keep clan mates on same team",

          "type": "comparison",

          "measurements": [

            "count(set_intersection(teams[*].players.attributes[clanId]))"

          ],

          "referenceValue": 0,

          "operation": "="

        }

    ],

    "expansions": [

        {

            "target": "rules[FairTeamMMR].maxDistance",

            "steps": [

                {

                    "waitTimeSeconds": 1,

                    "value": 100

                },

                {

                    "waitTimeSeconds": 2,

                    "value": 1000

                },

                {

                    "waitTimeSeconds": 3,

                    "value": 100000000

                }

            ]

        },

        {

            "target": "teams[team_1].minPlayers",

            "steps": [

                {

                    "waitTimeSeconds": 2,

                    "value": 1

                }

            ]

        },

        {

            "target": "teams[team_2].minPlayers",

            "steps": [

                {

                    "waitTimeSeconds": 4,

                    "value": 1

                }

            ]

        }

    ]

}

I replaced the DifferentClanIdPerTeam rule with the suggestion from the previous comment with some modifications:

  1. The example provided is for string_list properties, but in our case, it is string type.
  2. To pass the rule validation I removed flatten from measurements and referenceValue

This is the new replacement rule for DifferentClanIdPerTeam:

	{
        "name": "DifferentClanIdPerTeam",
        "type": "collection",
        "operation": "reference_intersection_count",
        "measurements": ["teams[*].players.attributes[clanId]"],
        "referenceValue": "set_intersection(teams[*].players.attributes[clanId])",
        "minCount":0
    }

This rule does not work at all - there is no player match for any of the examples described in the first post above.

See attached input examples:
player1_clan1_individual_join_input.txt (1.9 KB)
player1_clan1andPlayer2_clan1_team_join_input.txt (3.6 KB)
player2_clan2_individual_join_input.txt (1.9 KB)

Thanks for the details. I have sent this for the GameLift team to take a look.

P55317086

Hi, I don’t think we used set_intersection as intended. It apples at team level when unflattened. Then the final result is a list of common clanId found in each team.

I think what you are looking for is

{
    "name": "DifferentClanIdPerTeam",
    "type": "collection",
    "operation": "intersection",
    "measurements": ["teams[*].players.attributes[clanId]"],
    "maxCount":0
}

Hi @LumingAWS

I tried the suggestion, but it does not work - it does not match.
I tried with Player 1 (Clan A) and Player 2 (Clan B).

Ticket 1:

 "ticket_id": "de3b1e543c724ed8be3a0037538256ab"

Ticket 2:

 "ticket_id": "686815db133a4f6683694f61f50bc0e1"	

Hi @paulius

Apologies for all the back and forth here,
I dug into this issue a bit more today and have some findings on why the rule-sets above didn’t work (and another suggestion).

RuleSet #1 (with the comparison rule) fails to work as expected when grouping multiple players in the same ticket because of how FlexMatch handles party aggregation.
ComparisonRule only supports “min, max, avg” for partyAggregation, and in this case the attribute we are comparing is a string, and these values are not defined. (see FlexMatch rules language - Amazon GameLift )

RuleSet #2/#3 (using collectionRule) run into a similar issue where the attribute that is being compared is expected to be a collection and not a single string, so when collection operations are performed on the attribute (e.g. union) it fails to produce results that make sense.
Note how the examples (here: FlexMatch rule set examples - Amazon GameLift) use a stringList type for attributes that collectionRules are performed on.

With that in mind, I tested the following rule-set which seems to work and satisfy your requirements:


  "name": "testRuleSet",
  "ruleLanguageVersion": "1.0",
  "playerAttributes": [
    {
      "name": "mmr",
      "type": "number",
      "default": 10
    },
    {
      "name": "clanId",
      "type": "string_list",
      "default": ["none"]
    }
  ],
  "teams": [
    {
      "name": "team_1",
      "maxPlayers": 2,
      "minPlayers": 2
    },
    {
      "name": "team_2",
      "maxPlayers": 2,
      "minPlayers": 2
    }
  ],
  "rules": [
    {
      "name": "FairTeamMMR",
      "description": "The average MMR of players in each team is within 10 points from the average MMR of all players in the match",
      "type": "distance",
      "measurements": [
        "avg(teams[*].players.attributes[mmr])"
      ],
      "referenceValue": "avg(flatten(teams[*].players.attributes[mmr]))",
      "maxDistance": 10
    },
    {
      "name": "SameClan_team_1",
      "description": "Only match players with the same clan for team 1",
      "type": "comparison",
      "measurements": [
        "flatten(teams[team_1].players.attributes[clanId])"
      ],
      "operation": "="
    },

    {
      "name": "SameClan_team_2",
      "description": "Only match players with the same clan for team 1",
      "type": "comparison",
      "measurements": [
        "flatten(teams[team_2].players.attributes[clanId])"
      ],
      "operation": "="
    },
    {
      "name": "DifferentClanIdPerTeam",
      "type": "collection",
      "operation": "intersection",
      "measurements": ["flatten(teams[*].players.attributes[clanId])"],
      "maxCount":0
    }
  ]
}

Hi @Nathan,

The example works and it is good enough. But the caveat is that the player attribute clanId needs to be changed to the list (instead of string) in the backend and other rules. It would be great if that could work for the string type attribute.

Thanks for your help!