Skip to content

Top-level fields

The complete top-level shape of a Ruleset. Only a handful of fields are required; the rest are opt-in features.

interface Ruleset {
name: string;
version: number;
catalog?: { abstract: string; players?: string; thumbnail?: string };
teams: Team[];
minPlayers?: number;
maxPlayers?: number;
map: MapDefinition;
rules: Rule[];
playerJoinStates?: Record<string, unknown>[];
onStart?: Initializer[];
theme?: GameTheme;
teamAssignment?: { humanTeamId: string; botTeamId?: string };
bots?: BotClass[];
itemTypes?: ItemType[];
items?: ItemSpawnSpec[];
interactions?: InteractionSpec[];
countdownSeconds?: number;
objectives?: ObjectiveRule[];
visibility?: VisibilityRule[];
}

string — the display name shown in the catalog and on clients.

number — the ruleset version. Bump it when you change a published game.

Team[] — the teams. Use [] for a free-for-all game (no teams).

interface Team { id: string; name: string; color?: string }

Declaring teams enables auto-balancing (joiners spread across teams) and the { byTeam: id } selector. color is a hex string used for markers and UI.

MapDefinition — the zones, points, and annotations. See Map & zones. Always required (even a single play-area zone).

Rule[] — the game logic. May be empty ([]) for a pure sandbox, but that’s rare.

interface Rule { id: string; when: Condition; do: Action[] }

Each rule has a unique id, a when condition, and an ordered do list of actions.

Optional listing metadata.

interface GameCatalogMetadata {
abstract: string; // one-line pitch
players?: string; // human-readable, e.g. "4+ players, 2 teams"
thumbnail?: string; // filename inside the bundle
}

number (optional) — human-player count constraints enforced at game start. The pre-flight checklist blocks starting outside this range. (Bots are counted separately, by bot-class minimums.)

Record<string, unknown>[] (optional) — initial state objects applied to players in join order. If more players join than there are entries, the last entry is reused for everyone after.

playerJoinStates:
- { it: true } # 1st joiner
- { it: false } # 2nd joiner — and everyone after (last entry repeats)

A single-entry list means “everyone starts identical.”

number (optional) — fixed game duration in seconds. When set, the engine records a deadline at start and ends the game when it expires. Pause/resume rolls the deadline forward so visible time-remaining doesn’t jump during a pause.

FieldTypePage
onStartInitializer[]onStart & initializers
themeGameThemeTheme, objectives & visibility
objectivesObjectiveRule[]Theme, objectives & visibility
visibilityVisibilityRule[]Theme, objectives & visibility
teamAssignment{ humanTeamId; botTeamId? }Bots & behavior trees
botsBotClass[]Bots & behavior trees
itemTypesItemType[]Items & interactions
itemsItemSpawnSpec[]Items & interactions
interactionsInteractionSpec[]Items & interactions

{ humanTeamId: string; botTeamId?: string } (optional) — pins WebSocket joiners to a specific team instead of auto-balancing. Required for asymmetric games like Bots vs Humans. See Bots.