Selectors & values
Two small types thread through every condition and action: selectors (which players) and values (what to compare or store). Understanding them — and the binding model that connects them — is the difference between rules that work and rules that do the right thing to the wrong player.
Selectors
Section titled “Selectors”type PlayerSelector = | "any" | "same" | "other" | { byId: string } | { byTeam: string };| Selector | Means |
|---|---|
any | Every player (used in conditions to iterate / match anyone) |
same | The primary bound player of the current match |
other | The partner bound by a two-party condition (or the item, in item interactions) |
{ byTeam: "blue" } | All players on that team |
{ byId: "abc123" } | One specific player by id |
Selectors appear in conditions (who, a, b) to decide who matches, and in
actions (target, to) to decide who’s affected.
- { kind: increment_state, target: { byTeam: blue }, key: score, by: 1 } # whole team- { kind: send_event, to: same, event: { type: toast, text: "Hi" } } # just this playerBindings: how same and other get set
Section titled “Bindings: how same and other get set”A binding is the set of players a condition match carries into the rule’s
actions and nested conditions. The rule: the condition decides who same and
other are; everything downstream refers to them.
| Condition | Binds same | Binds other |
|---|---|---|
entered_zone / left_zone | the crossing player | — |
state_equals / count_state / compare | each matching player | — |
proximity { a, b } | a player matching a | a player matching b |
interaction (player target) | the initiator | the target player |
interaction (item target) | the initiator | the matched item |
Inside an and, the first player-binding child establishes same/other;
later children (state_equals who: same, compare ... other.teamId) filter that
binding. This is why order matters in an and.
when: kind: and children: - { kind: proximity, a: any, b: any, meters: 5 } # same = a, other = b - { kind: state_equals, who: same, key: hasFlag, value: red } - { kind: compare, op: eq, left: { ref: other.teamId }, right: red }do: - { kind: set_state, target: same, key: hasFlag, value: null } # acts on the carrier - { kind: spawn_item, item: red_flag, at: same }Item as other
Section titled “Item as other”In a worldItem/heldItem interaction the item is bound as other. You can
read item fields by path and the custody actions default to it:
- id: pickup_coin when: { kind: interaction, verb: grab_coin } do: - { kind: set_state, target: same, key: hasCoin, value: true } - { kind: set_state, target: same, key: coinId, value: { ref: other.id } } # remember which coinSource: apps/wage-engine/src/games/pirates_booty/game.yaml.
Values
Section titled “Values”type Value = | string | number | boolean | null // a bare literal | { lit: unknown } // an explicit literal | { ref: string }; // a path into the current bindingA Value is what goes on either side of a compare, or into a set_state /
set_state_delayed.
- Bare primitive —
5,"red",true,null— used directly as a literal. { lit: ... }— an explicit literal. Use it when the literal is itself an object, or when its shape might be mistaken for a{ ref }/{ lit }.{ ref: "path" }— read a value from the current binding’s players.
Ref paths
Section titled “Ref paths”A ref path is (same|other).(id|teamId|name|state.KEY):
| Path | Reads |
|---|---|
same.id / other.id | the player’s id |
same.teamId / other.teamId | the player’s team |
same.name / other.name | the player’s display name |
same.state.score | a state key on that player |
other.state.targetId | a state key on the partner |
An unresolvable path yields undefined.
# Same-team check.- { kind: compare, op: eq, left: { ref: same.teamId }, right: { ref: other.teamId } }
# Threshold against live state.- { kind: compare, op: gte, left: { ref: same.state.score }, right: 30 }
# Identity check (is the partner my assigned target?).- { kind: compare, op: eq, left: { ref: other.id }, right: { ref: same.state.targetId } }
# Copy a value across players.- { kind: set_state, target: same, key: targetId, value: { ref: other.state.targetId } }These examples are drawn from king_of_the_hill, capture_the_flag, and
assassin under apps/wage-engine/src/games/.