Players and turns
PlayerDef
abstract class PlayerDef<
PublicState extends Record<string, unknown> = {},
PrivateState extends Record<string, unknown> = {},
Static extends Record<string, unknown> = {},
> {
readonly static?: Static;
publicState: PublicState;
privateState: PrivateState;
protected publicDefaults(): Partial<PublicState>;
protected privateDefaults(): Partial<PrivateState>;
constructor(params?: PlayerDefConstructorParams<PublicState, PrivateState>);
}
Player
class Player<MetaData = Record<string, unknown>> {
get baseData(): BasePlayerWithId;
get playerId(): number;
get color(): string;
get username(): string;
get avatarUrl(): string | null;
get notification(): Notification | undefined;
set notification(value: Notification | undefined);
get publicState(): Record<string, unknown>;
set publicState(value: Record<string, unknown>);
get privateState(): Record<string, unknown> | null;
set privateState(value: Record<string, unknown> | null);
get def(): PlayerDef | undefined;
get static(): Record<string, unknown>;
get isPrivate(): boolean;
// Scoped lookups (same signatures as GameState but limited to this player's spaces / pieces)
piece(...), ensurePiece(...), pieces(...);
space(...), ensureSpace(...), spaces(...);
spaceOfKind(...), ensureSpaceOfKind(...), spacesOfKind(...);
addSpace(spaceId: string, def: SpaceDef): Space<MetaData>;
}
type Notification = {
title: string;
intent: "success" | "warning" | "danger" | "info";
description?: string;
duration?: number;
};
Top-level state
| Property | Type | Mutable? |
|---|---|---|
currentPlayer |
Player<MetaData> |
no (derived from session) |
activePlayerIds |
number[] |
yes : controls whose turn it is |
metaData |
MetaData (defaults to Record<string, unknown>) |
yes |
scoreLabels |
string[] | undefined |
yes |
defRegistry |
DefRegistry |
no (framework-built) |
scoreLabels names each entry of the points arrays your getPlayerScores returns, in order. The labels head the columns of the final scores table that players see at the end of a match and in their Recently played history, and of the score table on the studio Matches page. Studio Stats also uses them for "Points by category", which compares how many points winners and everyone else score in each label. Points are matched to labels by name, so keep a label's name stable across versions to compare it over time. An entry with no label is shown as "Score 1", "Score 2", and so on.

Player-scoped lookups
Player has the same piece / pieces / space / spaces / *OfKind methods, scoped to that player's owned spaces and the pieces inside them.
const me = state.currentPlayer;
me.pieces(); // pieces in spaces I own, sorted by order
me.pieces("card", (c) => c.isSelected);
me.spaceOfKind("hand"); // first space I own with kind "hand"
me.ensureSpaceOfKind("hand"); // throws if missing
me.addSpace("scratch", new GridCell({ x: 0, y: 0 })); // adds + sets playerId
Per-player spaces use synthesized ids like ${playerId}/space/${configKey}, so player.space("hand") (matching spaceId) usually won't hit. Use player.spaceOfKind("hand") instead.