Skip to content

Attribute Filtering

Problem

You only want to pair clients that share a common attribute — the same game mode, the same language, or a compatible skill bracket — rather than matching anyone who happens to be waiting.

Solution

When entering an auto pool, pass member metadata describing yourself (meta in TypeScript, attributes in Python) and a filter that constrains who you'll accept. The server pairs two members only when the filters on both sides are satisfied. A literal filter value matches exactly; the special value "@self" matches members whose attribute equals your own.

This recipe shows the ad-hoc form. When you provision a matchmaking policy, the filter lives on the policy server-side and clients just supply their metadata — see Matchmaking as a Policy.

Code

typescript
import { StarfishClient } from "@starfish/client";

const client = new StarfishClient({ server: "ws://localhost:4000" });
await client.connect();

client.pool.matched$.subscribe(async ({ session }) => {
  await client.join(session);
});

// Only match other "intermediate" players.
await client.pool.enter("ranked", {
  groupSize: 2,
  mode: "auto",
  create: true,
  meta: { skill: "intermediate" },
  filter: { skill: "@self" },
});
python
from starfish import StarfishClient, StarfishClientOptions, PoolEnterOptions

client = StarfishClient(StarfishClientOptions(server="ws://localhost:4000"))
await client.connect()

async def on_match(result):
    await client.join(result.session)

client.pool_matched.subscribe(on_match)

# Only match other "intermediate" players.
await client.pool_enter(PoolEnterOptions(
    pool="ranked",
    group_size=2,
    mode="auto",
    attributes={"skill": "intermediate"},
    filter={"skill": "@self"},
))

Explanation

  • Literal values match exactly: filter: { language: "en" } pairs you only with members whose language attribute is "en".
  • "@self" matches members whose attribute equals your own value: filter: { skill: "@self" } pairs an "intermediate" player only with other "intermediate" players.
  • Both sides must be satisfied. A match requires that each member's filter is satisfied by the other's attributes. If one side has no filter, it accepts anyone — but the other side's filter still constrains the pair.
  • If a filter references an attribute the other member doesn't have, the filter fails and the pair is skipped.
  • Filters apply to auto mode only. In claim-based and delegated modes, matching decisions are made by clients or the matchmaker, not the server.

Variations

Multiple filter keys (combined with AND)

All keys must be satisfied for a match to fire.

typescript
await client.pool.enter("ranked", {
  groupSize: 2,
  mode: "auto",
  create: true,
  meta: { language: "en", region: "europe" },
  filter: { language: "@self", region: "@self" },
});
python
await client.pool_enter(PoolEnterOptions(
    pool="ranked",
    group_size=2,
    mode="auto",
    attributes={"language": "en", "region": "europe"},
    filter={"language": "@self", "region": "@self"},
))

One-directional filter

Only one side needs to filter to constrain the pair. Here beginners are matched only with other beginners, while everyone else accepts anyone.

typescript
// Beginner client — restrict to fellow beginners.
await client.pool.enter("ranked", {
  groupSize: 2,
  mode: "auto",
  create: true,
  meta: { skill: "beginner" },
  filter: { skill: "@self" },
});
python
# Beginner client — restrict to fellow beginners.
await client.pool_enter(PoolEnterOptions(
    pool="ranked",
    group_size=2,
    mode="auto",
    attributes={"skill": "beginner"},
    filter={"skill": "@self"},
))