Rulesets and Constraints
A ruleset decides who may be matched with whom. It is created once, named,
and attached to any number of pools via ruleset_id. Two rule lists with
different jobs:
- Hard constraints must all evaluate true for a candidate group, or the group is discarded.
- Soft preferences are scored and weighted; among the groups that pass the hard constraints, the highest-scoring one wins.
The rule shape
Each rule is { "rule_id", "expression", "description"?, "weight"? }
(weight defaults to 1.0 and only matters for soft preferences). The
expression is a tagged tree - every node is
{ "type": "<Operator>", "value": ... }, not a flat
attribute/operator object:
{
"type": "Equal",
"value": {
"left": { "type": "TicketAttribute", "value": { "ticket_index": 0, "path": "region" } },
"right": { "type": "TicketAttribute", "value": { "ticket_index": 1, "path": "region" } }
}
}TicketAttribute reads a field from a candidate ticket's attributes by
position in the group (ticket_index 0 is one ticket, 1 the next). The
operator set:
| Group | Operators |
|---|---|
| Comparison | Equal, NotEqual, GreaterThan, GreaterThanOrEqual, LessThan, LessThanOrEqual |
| Logic | And, Or, Not |
| Arithmetic | Add, Subtract, Multiply, Divide, Abs |
| Aggregates | Sum, Count, Average, Max, Min |
| Range and sets | InRange (value/min/max), Contains (collection/item) |
| Literals | Number, String, Boolean, Null |
A worked example
A hard constraint requiring both tickets in the same region, plus a soft
preference for skills within 200 points (Not(Abs(skill0 - skill1) > 200)):
snug matchmaking ruleset create -r docs4-region-lock -n "Docs4 Region Lock" \
--hard-constraints '[{"rule_id":"same-region","description":"Both tickets in the same region","expression":{"type":"Equal","value":{"left":{"type":"TicketAttribute","value":{"ticket_index":0,"path":"region"}},"right":{"type":"TicketAttribute","value":{"ticket_index":1,"path":"region"}}}}}]' \
--soft-preferences '[{"rule_id":"close-skill","weight":1.0,"expression":{"type":"Not","value":{"type":"GreaterThan","value":{"left":{"type":"Abs","value":{"type":"Subtract","value":{"left":{"type":"TicketAttribute","value":{"ticket_index":0,"path":"skill"}},"right":{"type":"TicketAttribute","value":{"ticket_index":1,"path":"skill"}}}}},"right":{"type":"Number","value":200}}}}}]'
snug matchmaking pool create -p docs4-ranked -n "Docs4 Ranked" -m streaming -r docs4-region-lockAgainst that pool:
snug matchmaking ticket submit -p docs4-ranked -a '{"skill":1500,"region":"us-west"}'
# second user:
snug matchmaking ticket submit -p docs4-ranked -a '{"skill":1510,"region":"eu-central"}'
# both tickets: "status": "waiting" - the hard constraint blocks the pair
snug matchmaking ticket submit -p docs4-ranked -a '{"skill":1620,"region":"us-west"}'
# the two us-west tickets: "status": "matched"; eu-central keeps waitingInspect and delete
snug matchmaking ruleset get -r docs4-region-lock
snug matchmaking ruleset delete -r docs4-region-lockRulesets are versioned (version in the response). Note a CLI wrinkle: in
--output json mode, ruleset create prints the ruleset fields at the top
level while ruleset get nests them under "ruleset".
Validation gotchas
- The CLI parses
--hard-constraints/--soft-preferenceslocally; a rule list that is not valid JSON in the tagged-tree shape fails client-side ("Invalid JSON for hard constraints") before any request is sent. - Raw HTTP is stricter than the envelope suggests: a malformed expression
(for example the flat
{"attribute": ..., "operator": ...}shape) is rejected during body deserialization with a plain-text422("missing fieldtype"), not an envelopedinvalid_rule_expressionerror. - A missing ruleset referenced by
pool create -ris not caught at pool creation. Worse, a submit to that streaming pool returns404 ruleset_not_foundafter creating the ticket - it sits in the waiting queue consuming one of the user's ticket slots even though the call reported failure (verified). Create the ruleset first.
The exact request and response schemas are in the Matchmaking API reference.