10. Actions
EBNF
action = action_header ":" action_body | action_header ";" .An action definition, or just action, is made up of a header and a body. The body is optional for actions that inherit from a parent action, as explained in the section below on inheritance.
Action header
Section titled “Action header”EBNF
action_header = [ reserved_construct_marker | template_action_marker ] "action" identifier [ parent_action_declaration ] .reserved_construct_marker = "reserved" .template_action_marker = "template" .parent_action_declaration = "from" identifier .An action header introduces an action definition. It declares whether the action is reserved or a template, its name, and its parent definition, if any. Here are some examples:
action insult: ...
action ridicule from insult;
action specific-insult from insult: ...
reserved action plot-revenge: ...
reserved action plot-arson from plot-revenge: ...
template action social-template: ...An action header terminating with : MUST be followed by an action body:
// Illegalaction bad:// Legalaction good: ...When an action header terminates with ;, the definition terminates too, but this is only legal when a parent action is specified:
action parent: ...
// Legalaction child from parent;
// Illegalaction head-only;Action name
Section titled “Action name”An action name is an identifier. Each action in a content bundle must have a unique name. As such, uniqueness is enforced across includes. For instance, if a file F1 defines an action foo, and a file F2 defines an action foo, the compiler SHALL throw an error if F1 is (recursively) included in F2 (or vice versa). Such cases are resolved by renaming one of the actions.
Reserved marker
Section titled “Reserved marker”The reserved marker reserved is used to introduce the definition for a reserved action—one that may only be targeted via a reaction or through selector targeting:
// This is a reserved actionreserved action foo: ...If neither the reserved nor template marker is present, the definition specifies a general action—one that may be targeted anytime:
// This is a general actionaction foo: ...An action MUST be marked reserved if it has any non-initiator precast roles, since there is no way to cast a precast role via general action targeting. (The initiator role is automatically precast by the compiler, so its presence alone does not require the reserved marker.)
Template marker
Section titled “Template marker”The template marker template marks the action as a template action—one that is intended solely for inheritance and will not appear in the content bundle as a standalone action:
// This action exists only to be inherited fromtemplate action base-social: ...
action greet from base-social: ...Inheritance declaration
Section titled “Inheritance declaration”Using the from keyword, an author may specify a single parent action from which the definition at hand will inherit:
// This action, 'foo', is a child of 'bar'action foo from bar: ...Inheritance
Section titled “Inheritance”Viv supports inheritance between action definitions, where the definition of a child action incorporates material from the definition of a parent action.
Policy
Section titled “Policy”A child action duplicates the parent action, with the following modifications:
-
The child action uses its own action name.
-
Regardless of whether the parent action is marked reserved, the child action is reserved only if it is itself marked reserved.
-
If a field is not present in the child action, the child duplicates the parent field.
-
If a field is present in the child action and is not introduced by the
joinkeyword, the child overrides the parent field. -
If a field is present in the child action and is introduced by the
joinkeyword, the fields will be joined according to a field-specific policy. These policies are explained below in the respective field sections.
Here is an illustrative example:
action parent: roles: @foo: as: initiator conditions: @foo.baz effects: @foo.mood += 10
// This action is named 'child', and it is reservedreserved action child from parent: // Roles will be joined join roles: @bar: as: recipient // This will be the sole condition of 'child' conditions: @foo.mood > 10 // 'child' will duplicate all other fieldsField joinability
Section titled “Field joinability”Only joinable fields may be joined during inheritance:
-
Joinable: tags, roles, conditions, scratch, effects, reactions, embargoes.
-
Non-joinable: gloss, report, importance, saliences, associations.
Non-joinable fields MUST NOT be introduced by the join keyword:
action parent: ...
action child from parent: // Legal for this field join roles: ... // Legal regardless of field gloss: ... // Illegal for this field join report: ...Inheritance chaining
Section titled “Inheritance chaining”Inheritance chaining is supported:
action grandparent: ...
action parent from grandparent: ...
action child from parent: ...Cycles are not permitted in inheritance chains, meaning the following example will result in an error:
action foo from bar: ...
action bar from foo: ...Multiple inheritance
Section titled “Multiple inheritance”Multiple inheritance is not supported. A child action MUST have a single parent action.
Action body
Section titled “Action body”EBNF
(* Each field MUST NOT appear more than once in an action body. *)action_body = action_field { action_field } .action_field = gloss_field | report_field | importance_field | saliences_field | associations_field | tags_field | roles_field | conditions_field | scratch_field | effects_field | reactions_field | embargoes_field .An action body combines one or more action fields: gloss, report, importance, tags, roles, conditions, scratch, effects, reactions, saliences, associations, embargoes.
Unless the body is part of a child action—meaning one that inherits from a parent action—the roles field MUST be present. All other fields are optional. A given field MUST NOT appear multiple times within an action definition.
The fields may appear in any order. Only joinable fields may be introduced by the join keyword.
EBNF
action_gloss = "gloss" ":" string .The gloss field is introduced by the gloss keyword, and specifies how to briefly describe an instance of this action. The field accepts either a string or template string:
action foo: // String literals are permitted gloss: "Something happens" ...
action bar: // Template strings are also permitted gloss: "@person did something" roles: @person: as: initiatorThe gloss field is optional and is not joinable.
Report
Section titled “Report”EBNF
action_report = "report" ":" string .The report field is introduced by the report keyword, and specifies (at more length than a gloss) how to describe an instance of this action. Like the gloss field, the report field accepts either a string or template string.
The report field is optional and is not joinable.
Importance
Section titled “Importance”EBNF
action_importance = "importance" ":" ( enum | number ) .The importance field is introduced by the importance keyword, and specifies a numeric score indicating the significance of the action for purposes of story sifting. The value may be a number or an enum:
action gossip: importance: 3 ...
action murder: importance: #LIFE_CHANGING ...The importance field is optional and is not joinable.
EBNF
action_tags = [ "join" ] "tags" ":" tags .tags = tag { "," tag } .tag = identifier .The tags field is introduced by the tags keyword, and specifies a set of one or more annotations that will be attached to all instances of this action. Tags are comma-separated identifiers (not strings):
// Illegal: tags must be identifiers, not stringsaction foo: tags: "fun" ...// Legalaction foo: tags: fun, cool ...While their function can be extended on the host application, tags are intended to facilitate search over the chronicle for purposes of story sifting.
The tags field is optional. It is also joinable, with a simple concatenation policy: the child and parent tags are concatenated, with deduplication.
EBNF
action_roles = [ "join" ] "roles" ":" role+ .The roles field is introduced by the roles keyword, and specifies the cast of entities and symbols that may be involved in an instance of the action at hand. There MUST be a single initiator role, but otherwise there are no constraints on the total number of roles or their respective labels.
For full details on role definitions, see the Roles chapter.
The roles field is required. It is also joinable: when joined, the child’s role definitions are merged with the parent’s, as explained in Role joining.
Conditions
Section titled “Conditions”EBNF
action_conditions = [ "join" ] "conditions" ":" statements .The conditions field is introduced by the conditions keyword, and specifies a block of statements that must all evaluate to truthy values in order for the action to be performed with a given prospective cast. Conditions are tested during action targeting.
action befriend: conditions: @person.sociability > 50 @target.openness > 30 !(@person == @target) roles: @person: as: initiator @target: as: recipientThe conditions field is optional. It is also joinable: when joined, the child’s conditions are appended to the parent’s.
Scratch
Section titled “Scratch”EBNF
action_scratch = [ "join" ] "scratch" ":" statements .The scratch field is introduced by the scratch keyword, and specifies a block of statements that prepare temporary variables for use elsewhere in the action definition. Scratch variables are prefixed with the scratch sigil $:
action negotiate: scratch: $@mediator = ~findMediator(@buyer, @seller) $&price = (@buyer.budget + @seller.asking) / 2 conditions: $@mediator != null effects: @buyer.gold -= $&price @seller.gold += $&price roles: @buyer: as: initiator @seller: as: recipientThe scratch field is optional. It is also joinable: when joined, the child’s scratch statements are appended to the parent’s.
Effects
Section titled “Effects”EBNF
action_effects = [ "join" ] "effects" ":" statements .The effects field is introduced by the effects keyword, and specifies a block of statements that are executed when an instance of the action is performed. Effects typically mutate host-application state via assignment expressions and custom function calls:
action insult: effects: @target.mood -= 10 @target.opinion[@insulter] -= 5 roles: @insulter: as: initiator @target: as: recipientThe effects field is optional. It is also joinable: when joined, the child’s effects are appended to the parent’s.
Reactions
Section titled “Reactions”EBNF
action_reactions = [ "join" ] "reactions" ":" ( conditional | loop | reaction )+ .The reactions field is introduced by the reactions keyword, and specifies a block of reaction declarations that may cause other actions, selectors, or plans to be queued in response to an instance of this action. Reactions may be wrapped in conditionals and loops.
For full details on reaction declarations, see the Reactions chapter.
The reactions field is optional. It is also joinable: when joined, the child’s reactions are appended to the parent’s.
Saliences
Section titled “Saliences”EBNF
action_saliences = "saliences" ":" saliences_body .saliences_body = (* unordered, each field optional *) [ saliences_default ] [ saliences_roles ] [ saliences_custom_field ] .saliences_default = "default" ":" ( enum | number ) .saliences_roles = "roles" ":" saliences_roles_entry+ .saliences_roles_entry = "@" identifier [ group_role_decorator ] ":" ( enum | number ) .saliences_custom_field = "for" local_variable ":" statement+ "end" .The saliences field is introduced by the saliences keyword, and specifies how to determine the numeric salience score for this action as experienced by a given character. Salience represents how memorable or significant the action is from that character’s perspective.
The saliences body has three optional subfields:
-
default: A fallback salience value (a number or enum) used for any character for which no more specific rule applies. -
roles: A mapping from entity role names to salience values. For a character bound in a given role, the corresponding value determines their salience. -
for: A custom computation block. A local variable is bound to the character at hand, and the block’s statements are evaluated in turn. The first numeric result becomes the character’s salience. This field is only consulted for characters to whom norolesentry applies.
The resolution order is: roles, then for, then default.
action betray: saliences: default: #MODERATE roles: @betrayer: #HIGH @victim: #EXTREME @witnesses*: #LOW for _@witness: if _@witness.perceptiveness > 80: #HIGH end ...The saliences field is optional and is not joinable.
Associations
Section titled “Associations”EBNF
action_associations = "associations" ":" associations_body .associations_body = (* unordered, each field optional *) [ associations_default ] [ associations_roles ] [ associations_custom_field ] .associations_default = "default" ":" tags .associations_roles = "roles" ":" associations_roles_entry+ .associations_roles_entry = "@" identifier [ group_role_decorator ] ":" tags .associations_custom_field = "for" local_variable ":" associations_statement+ "end" .The associations field is introduced by the associations keyword, and specifies the subjective associations—tag-like annotations—that a character will attach to their memory of this action. Like saliences, associations are per-character.
The associations body has three optional subfields, mirroring the structure of saliences:
-
default: A fallback set of association tags. -
roles: A mapping from entity role names to association tags. -
for: A custom computation block, structured like the saliences custom field but yielding tag lists instead of numbers.
action betray: associations: default: social, betrayal roles: @betrayer: guilt, scheming @victim: trauma, betrayal @witnesses*: gossip ...The associations field is optional and is not joinable.
Embargoes
Section titled “Embargoes”EBNF
action_embargoes = [ "join" ] "embargoes" ":" embargo+ .embargo = "embargo" ":" embargo_body .embargo_body = (* unordered, each field optional *) [ embargo_location ] [ embargo_time_period ] [ embargo_roles ] .embargo_location = "location" ":" ( "here" | "anywhere" ) .embargo_time_period = "time" ":" ( "forever" | time_period ) .embargo_roles = "roles" ":" role_reference { "," role_reference } .The embargoes field is introduced by the embargoes keyword, and specifies one or more embargo declarations that constrain the subsequent performance of this action. An embargo prevents the action from being targeted again (for the specified roles, location, and time period) after an instance has been performed.
Each embargo declaration is introduced by the embargo keyword and has three optional subfields:
-
location: Eitherhere(the embargo holds only at the location where the action was performed) oranywhere(the embargo holds everywhere). -
time: Eitherforever(the embargo is permanent) or a time period specifying the duration. -
roles: A comma-separated list of role references specifying which role bindings the embargo tracks. For instance, if only the initiator is specified, the embargo applies to any future instance with the same initiator, regardless of other bindings.
action confess: embargoes: embargo: location: here time: 2 weeks roles: @confessor
embargo: location: anywhere time: forever roles: @confessor, @confidant ...The embargoes field is optional. It is also joinable: when joined, the child’s embargo declarations are appended to the parent’s.