16. Sifting patterns
EBNF
sifting_pattern = sifting_pattern_header ":" sifting_pattern_body .sifting_pattern_header = "pattern" identifier .sifting_pattern_body = (* unordered; actions required, others optional *) [ sifting_pattern_roles ] sifting_pattern_actions [ sifting_pattern_conditions ] .A sifting-pattern definition, or just sifting pattern, describes a causally related sequence of actions that constitutes a storyline. Sifting patterns are the primary mechanism for story sifting—detecting emergent narratives in the chronicle of simulated events. They are named with an identifier, and each sifting pattern in a content bundle must have a unique name.
Sifting patterns are invoked at runtime via sifting expressions.
Sifting-pattern header
Section titled “Sifting-pattern header”EBNF
sifting_pattern_header = "pattern" identifier .The sifting-pattern header is introduced by the pattern keyword, followed by the pattern’s name:
pattern betrayal-arc: ...EBNF
sifting_pattern_roles = "roles" ":" role+ .The optional roles field specifies one or more role definitions that represent the recurring characters (or other entities) across the actions in the pattern:
pattern betrayal-arc: roles: @betrayer: as: character @victim: as: character ...Actions
Section titled “Actions”EBNF
sifting_pattern_actions = "actions" ":" sifting_pattern_action+ .sifting_pattern_action = entity_sigil identifier [ group_role_decorator ] ":" sifting_pattern_action_body .sifting_pattern_action_body = [ role_slots ] ( role_casting_pool_is | role_casting_pool_from ) .The actions field is introduced by the actions keyword, and specifies the action instances that make up the pattern. Each action entry names a variable (prefixed with @) and provides a casting-pool directive (is or from) that binds it to a specific action instance or a collection of candidate instances. Casting-pool expressions typically use action searches to find candidates from the inherited search domain or from a character’s memories:
query acts-of-trust: tags: any: trust, friendship
query acts-of-betrayal: tags: any: betrayal, treachery
pattern betrayal-arc: roles: @betrayer: as: character @victim: as: character actions: @trust: from: search query acts-of-trust: over: inherit @betrayal: from: search query acts-of-betrayal: over: inherit conditions: @trust caused @betrayalThe action entries define the action variables that can be referenced in conditions and tested with relational operators like preceded, caused, and triggered (see Expressions).
Unlike roles, action entries are exempt from the entity uniqueness constraint—the same action instance may appear in multiple action roles. This is essential for pattern composition, where two sub-patterns naturally share actions at their boundary.
Group action roles
Section titled “Group action roles”An action entry may use the group-role decorator * and a slots specification to match multiple action instances under a single role. This is useful when a storyline involves a variable number of actions filling the same narrative function—for example, several “setup” actions or many “scheme” actions:
pattern revenge: roles: @avenger: as: character actions: @setup*: from: search: over: inherit n: 1-50 @climax: from: search query climactic-acts: over: inherit conditions: loop @setup* as _@s: _@s caused @climax endA group action role MUST use from, not is, and MUST have a max of at least 2. As with group roles in other constructs, the * decorator must appear in the declaration and in every reference.
Action-relation operators (preceded, caused, triggered) require single-action operands. To test a relation involving a group action role, use a loop:
// Error: group action role as action-relation operand@setup* preceded @climax
// Correct: iterate over the grouploop @setup* as _@s: _@s preceded @climaxendThe same computational considerations that apply to group roles in any construct apply here. Large slot ranges over large candidate pools increase casting time; authors SHOULD use the narrowest casting pool and the smallest slot range that captures the intended pattern.
Conditions
Section titled “Conditions”EBNF
sifting_pattern_conditions = "conditions" ":" statements .The optional conditions field specifies a block of statements that must evaluate to truthy values for the pattern to match. Conditions typically reference the pattern’s roles and action variables to establish causal and temporal relationships:
pattern betrayal-arc: roles: @betrayer: as: character @victim: as: character actions: @trust: from: search query acts-of-trust: over: inherit @betrayal: from: search query acts-of-betrayal: over: inherit conditions: @trust preceded @betrayal @trust caused @betrayalPattern composition
Section titled “Pattern composition”Sifting patterns can be composed by using sifting expressions in action casting pools. A sift pattern expression in a from directive returns the matched actions as a flat collection of action instances, which the enclosing pattern can then bind to a group action role.
To constrain how two sub-patterns relate to each other, introduce a shared role in the enclosing pattern and precast it into both sub-patterns. The shared role acts as a hinge—an action that must satisfy both patterns simultaneously:
pattern eye-for-an-eye: roles: @first-avenger: as: character @second-avenger: as: character @crux: as: action from: search: over: inherit actions: @first-revenge*: from: sift pattern revenge: over: inherit with partial: @avenger: @first-avenger @climax: @crux n: 1-999 @second-revenge*: from: sift pattern revenge: over: inherit with partial: @avenger: @second-avenger @victim: @first-avenger @offense: @crux n: 1-999Here, @crux is precast as the @climax of the first revenge story and the @offense of the second. The sifting engine finds an action that serves as both—the act of revenge that itself provokes counter-revenge. This composition is type-safe: the compiler validates that every precast role name exists on the target pattern, so renaming a role in the sub-pattern produces a compile-time error rather than a silent failure.