18. Selectors
EBNF
selector = selector_header ":" selector_body .selector_header = [ reserved_construct_marker ] selector_type identifier .selector_type = "action-selector" | "plan-selector" .A selector definition, or just selector, specifies a set of candidate constructs and a policy for choosing among them. There are two kinds: action selectors choose among actions, and plan selectors choose among plans. Selectors are named with an identifier, and each selector in a content bundle must have a unique name.
Selector header
Section titled “Selector header”EBNF
selector_header = [ "reserved" ] selector_type identifier .selector_type = "action-selector" | "plan-selector" .The selector header introduces a selector definition. It declares the selector type and its name. Action selectors may optionally include a reserved marker:
action-selector choose-greeting: ...
plan-selector choose-revenge-plan: ...
reserved action-selector special-response: ...When an action selector is marked reserved, it may only be targeted via a reaction or through another selector, just like a reserved action. Plan selectors cannot be marked reserved, since plans are always targeted via reactions.
Selector body
Section titled “Selector body”EBNF
selector_body = (* unordered; target group required, others optional *) [ selector_roles ] [ selector_conditions ] selector_target_group .selector_roles = "roles" ":" role+ .selector_conditions = "conditions" ":" statements .The selector body contains a required target group and two optional fields: roles and conditions.
EBNF
selector_roles = "roles" ":" role+ .The optional roles field specifies one or more role definitions that parameterize the selector:
action-selector choose-social: roles: @person: as: initiator @bystander: as: bystander ...For action selectors, an initiator role is always required—but it does not always need to be declared explicitly. See initiator pass-through below.
Initiator pass-through
Section titled “Initiator pass-through”Like actions, an action selector requires an initiator role. The most common use of an action selector is to select an action for a given initiator during general action targeting—for instance, choosing between greet, wave, and nod for whoever the initiator happens to be. In this common case, the selector itself has no roles beyond the initiator, and each candidate receives the same initiator.
To support this, an action selector MAY omit the roles field entirely. When it does, the compiler creates a virtual initiator role and automatically passes the initiator from the targeting context through to each candidate:
// The initiator is passed through to whichever candidate is selectedaction-selector choose-greeting: target randomly: greet; wave; nod;If a selector needs additional roles beyond the initiator—for example, a bystander that parameterizes the choice of action—the roles field MUST be present, and it MUST include an explicit initiator role alongside the additional roles:
action-selector choose-social: roles: @person: as: initiator @bystander: as: bystander target randomly: greet; wave;Omitting the roles field signals that the initiator is the only role and should be passed through implicitly; including it signals that all roles—including the initiator—are explicitly declared.
Plan selectors and roles
Section titled “Plan selectors and roles”Plans do not have initiators, so there is no initiator pass-through for plan selectors. A plan selector MAY still omit the roles field—but the meaning is different: a role-less plan selector is pure dispatch, and the selected plan’s roles are cast entirely from the world state at execution time. When a plan selector does define roles, those roles can be used to parameterize the selection or to pre-bind candidate plan roles via bindings:
// Pure dispatch: plan roles are cast at execution timeplan-selector choose-plan: target randomly: revenge-plan; forgiveness-plan;
// Parameterized: the selector pre-binds a role in each candidate planplan-selector choose-plan-for: roles: @hero: as: character target randomly: revenge-plan: with partial: @avenger: @hero forgiveness-plan: with partial: @forgiver: @heroConditions
Section titled “Conditions”EBNF
selector_conditions = "conditions" ":" statements .The optional conditions field specifies a block of statements that must evaluate to truthy values for the selector to proceed.
Target group
Section titled “Target group”EBNF
selector_target_group = "target" selector_policy ":" selector_candidates .selector_policy = "randomly" | "with" "weights" | "in" "order" .selector_candidates = selector_candidate+ .selector_candidate = [ selector_candidate_weight ] selector_candidate_name ";" | [ selector_candidate_weight ] selector_candidate_name ":" bindings .selector_candidate_name = [ "selector" ] identifier .selector_candidate_weight = "(" expression ")" .The target group specifies the set of candidate constructs and the policy for choosing among them. It is introduced by the target keyword, followed by a target policy and a colon, and then one or more candidate entries.
Target policies
Section titled “Target policies”The target policy determines how a candidate is selected from the group:
| Policy | Behavior |
|---|---|
randomly | A candidate is chosen uniformly at random. |
with weights | A candidate is chosen with probability proportional to its weight. |
in order | Candidates are tried in the order listed; the first one whose targeting succeeds is selected. |
action-selector random-greeting: target randomly: greet; wave; nod;Candidates
Section titled “Candidates”A candidate entry names a construct to consider. By default, the candidate names an action (for action selectors) or a plan (for plan selectors). If prefixed with the selector keyword, the candidate names another selector, enabling chaining:
action-selector master-selector: target in order: selector urgent-responses; selector social-actions; idle;A candidate entry terminates with ; if it has no bindings, or with : followed by a bindings block if it does:
action-selector choose-response: roles: @person: as: initiator target randomly: // No bindings wave; // With bindings greet: with partial: @greeter: @personFor action selectors, each candidate MUST precast the selector’s initiator role in the candidate’s corresponding initiator role. This ensures the initiator is consistent across the selector and all of its candidates.
Weighting
Section titled “Weighting”When the with weights policy is used, each candidate MAY have a weight expression enclosed in parentheses before the candidate name. The expression SHOULD evaluate to a numeric value:
action-selector weighted-social: roles: @person: as: initiator target with weights: (80) greet; (15) wave; (5) ignore;If a weight expression evaluates to zero or a negative number, the candidate is excluded from selection.