Rules: actions
A rule’s do is an ordered list of actions that run top-to-bottom when the
condition matches. This page documents every
action kind in the Action union from packages/wage-protocol/src/rules.ts.
Actions act on players named by a selector
(same/other/any/{ byTeam }/{ byId }) bound by the condition.
State actions
Section titled “State actions”set_state
Section titled “set_state”{ kind: "set_state"; target: PlayerSelector; key: string; value: unknown }Set target’s state[key] to value. value accepts a
Value: a bare primitive is a literal;
{ ref } / { lit } resolve through the binding (so you can copy
other.state.X into same.state.Y).
- { kind: set_state, target: other, key: it, value: true }- { kind: set_state, target: same, key: targetId, value: { ref: other.state.targetId } }set_state_delayed
Section titled “set_state_delayed”{ kind: "set_state_delayed"; target; key; value; delayMs: number }Like set_state, but applied after delayMs milliseconds. Useful for timed
unlocks (lift a locked flag after a countdown).
- { kind: set_state_delayed, target: same, key: locked, value: false, delayMs: 5000 }increment_state
Section titled “increment_state”{ kind: "increment_state"; target: PlayerSelector; key: string; by: number }Add by to a numeric state[key] (missing/non-number treated as 0). by may be
negative. Targeting { byTeam } increments every member.
- { kind: increment_state, target: { byTeam: blue }, key: score, by: 1 }- { kind: increment_state, target: other, key: health, by: -10 }assign_state_random
Section titled “assign_state_random”{ kind: "assign_state_random"; assigns: { key: string; value: unknown }[]; filters?: { key: string; value: unknown }[]; exclude?: "same" | "other"; notify?: Record<string, unknown>;}Pick one random player from the pool — optionally narrowed by filters
(keys are teamId or state.<name>) and with exclude removing the current
same/other — and apply every assigns to them. notify sends a game event
directly to the chosen player.
# Bomb Tag: hand the bomb to a random living non-it player who isn't the tagger.- kind: assign_state_random assigns: - { key: hasBomb, value: true } - { key: bombTick, value: 0 } filters: - { key: alive, value: true } - { key: it, value: false } exclude: same notify: { type: toast, text: "You have the bomb!" }Source: apps/wage-engine/src/games/bomb_tag/game.json.
Control flow
Section titled “Control flow”random_action
Section titled “random_action”{ kind: "random_action"; branches: { weight?: number; do: Action[] }[] }Pick one branch at random (weighted; default weight 1) and run its actions with the current binding. Express a stochastic outcome without splitting into mutually-exclusive rules.
# Coins drip in ~1-in-6 ticks: weight 1 spawns, weight 5 does nothing.- kind: random_action branches: - weight: 1 do: [{ kind: spawn_item, item: coin, inZone: treasure_island }] - weight: 5 do: []Source: apps/wage-engine/src/games/pirates_booty/game.yaml.
# Bots vs Humans: a 50/50 battle applying opposite health swings.- kind: random_action branches: - do: - { kind: increment_state, target: same, key: health, by: -10 } - { kind: increment_state, target: other, key: health, by: 10 } - do: - { kind: increment_state, target: same, key: health, by: 10 } - { kind: increment_state, target: other, key: health, by: -10 }Source: apps/wage-engine/src/games/bots_vs_humans/game.json.
Item actions
Section titled “Item actions”See Items & interactions for the item
model. In item interactions, the matched item is bound as other, and the
custody actions default to it when item is omitted.
spawn_item
Section titled “spawn_item”{ kind: "spawn_item"; item: string; at: PlayerSelector } // at a player's position{ kind: "spawn_item"; item: string; inZone: string } // random point in a zone{ kind: "spawn_item"; item: string; atPoint: string } // a named map pointCreate a new item of kind item. Provide exactly one of at, inZone,
atPoint. Each is silently skipped if its anchor isn’t available yet (player has
no position, zone/point not placed). inZone honors exclusions.
- { kind: spawn_item, item: red_flag, atPoint: red_flag } # respawn captured flag home- { kind: spawn_item, item: red_flag, at: same } # drop where the carrier stood- { kind: spawn_item, item: coin, inZone: treasure_island } # random driphold_item
Section titled “hold_item”{ kind: "hold_item"; target: PlayerSelector; item?: Value }Give the item to target (sets its holder, clears its world position). item
defaults to the item bound as other.
drop_item
Section titled “drop_item”{ kind: "drop_item"; item?: Value; at: PlayerSelector } // at a player{ kind: "drop_item"; item?: Value; inZone: string } // random point in a zone{ kind: "drop_item"; item?: Value; atPoint: string } // a named pointReturn a held item to the world. Exactly one of at/inZone/atPoint.
destroy_item
Section titled “destroy_item”{ kind: "destroy_item"; item?: Value }Remove an item entirely (consume it). item defaults to the item bound as
other — so in a pickup rule, destroy_item with no args consumes the thing
just grabbed.
- id: pickup_chest when: { kind: interaction, verb: grab_chest } do: - { kind: set_state, target: same, key: carrying, value: true } - { kind: destroy_item }Communication
Section titled “Communication”send_event
Section titled “send_event”{ kind: "send_event"; to: PlayerSelector; event: Record<string, unknown> }Send a game event to the players in to. The event is an arbitrary object the
client interprets by its type. Common types: toast (a transient message) and
countdown.
- { kind: send_event, to: same, event: { type: toast, text: "You're it!" } }- { kind: send_event, to: { byTeam: red }, event: { type: toast, text: "Your flag was stolen!" } }{ kind: "log"; message: string }Write a line to the session event log (visible to the GM). {player}
interpolates same’s name and {other} interpolates other’s. Your primary
debugging tool while authoring.
- { kind: log, message: "{player} tagged {other}" }Ending the game
Section titled “Ending the game”end_game
Section titled “end_game”{ kind: "end_game" }End the game immediately: sets status to ended and broadcasts a game-over
(reason rules). Use it in the do of a win rule. (Games can also end via
countdownSeconds expiry.)
do: - { kind: set_state, target: same, key: won, value: true } - { kind: send_event, to: any, event: { type: toast, text: "Game over!" } } - { kind: end_game }