Asobi includes a query-based matchmaker that runs as a periodic tick via
the asobi_matchmaker gen_server.
- Player submits a matchmaking ticket with properties and a query
- Matchmaker ticks periodically (default every 1 second)
- Each tick groups tickets by mode/region, finds mutually compatible pairs
- When enough compatible players are found, a match is spawned
- Players are notified via WebSocket (
matchmaker.matched)
curl -X POST http://localhost:8080/api/v1/matchmaker \
-H 'Authorization: Bearer <token>' \
-H 'Content-Type: application/json' \
-d '{
"mode": "arena",
"properties": {"skill": 1200, "region": "eu-west"},
"query": "+region:eu-west skill:>=1000 skill:<=1400"
}'{
"type": "matchmaker.add",
"payload": {
"mode": "arena",
"properties": {"skill": 1200, "region": "eu-west"},
"query": "+region:eu-west skill:>=1000 skill:<=1400"
}
}Tickets include a query that specifies what opponents the player will accept. Both players must match each other's query for a pairing to form.
+region:eu-west mode:ranked skill:>=800 skill:<=1200
key:value-- exact match+key:value-- required (must match)key:>=N/key:<=N-- numeric range- Multiple conditions are AND-ed
When a player waits too long, the matchmaker automatically widens the skill
window. Each tick increments the expansion_level for unfilled tickets,
relaxing numeric range constraints.
{asobi, [
{matchmaker, #{
tick_interval => 1000, %% ms between matchmaker ticks
max_wait_seconds => 60 %% max wait before timeout
}}
]}Players can queue as a party. All party members are placed in the same match:
{
"type": "matchmaker.add",
"payload": {
"mode": "arena",
"party": ["player_id_2", "player_id_3"],
"properties": {"skill": 1200},
"query": "skill:>=1000 skill:<=1400"
}
}{"type": "matchmaker.remove", "payload": {"ticket_id": "..."}}Or via REST:
curl -X DELETE http://localhost:8080/api/v1/matchmaker/<ticket_id> \
-H 'Authorization: Bearer <token>'