Skip to content

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.

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
...
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 @betrayal

The 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.

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
end

A 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 group
loop @setup* as _@s:
_@s preceded @climax
end

The 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.

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 @betrayal

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-999

Here, @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.