Looking for feedback on new authoring syntax for Elm Narrative Engine

Hi! I’ve been working on the Elm Narrative Engine (“ENE”) for a while, and I am preparing to release a new version with some big changes that include a new salience and quality based matching system and unrestricted world model.

The engine is written in Elm, which is a language that compiles to and inter-opts with JavaScript. The engine is divorced from any view layer, and works as a way to add a narrative system as a component to a larger JS/Elm game.

The bulk of authoring work involves writing declarative rules which match against player interactions to change the state of the world and trigger side effects (somewhat similar to “rulebooks” in systems like Inform 7). These can be be tedious to write as code, so I am working on a custom syntax to make it easier to author stories, and provide some validation at authoring time.

I’d like to share what I have planned to get some feedback.


How it would work:

First you need to define your world. The world is just a collection of “entities”. Each entity has a unique id. Each entity can be further defined with various properties:

  • Tags - any string that has meaning in your story.
  • Stats - a pair of any string key and any numeric value.
  • Links - a pair of any string key and the id of any other entity.

Example world definition:

CAVE_ENTRANCE.location
CAVE.location.dark

GOBLIN.character.sleeping.location=CAVE
PLAYER
.character
.fear=0
.treasure_hunt_plot=1
.location=CAVE_ENTRANCE

LIGHTER.item.illumination=2.location=PLAYER
TORCH.item.illumination=7.location=CAVE_ENTRANCE
BAG_OF_GOLD.item.quest_item.location=CAVE.guarded_by=GOBLIN

The above would define seven entities with various properties.

For example, this defines an entity with an id of “GOBLIN”, which has a tag of “character” and “enemy”, a stat of key “strength” with value “3”, and a link with key “location” and value the id for the cave entity.

Note that entity ids don’t have to be caps, they can be any string, as long as they are all unique. Likewise, there is no expectation by the engine that you will have “locations,” “characters,” or “items.” You can define your tags and stats and links to be whatever makes sense for your story.

Given this set up, you can query the world in various ways:

// Get a list of all of the locations:
*.location

// Get all items in the player's inventory:
*.item.location=PLAYER

// Test if the player has any item with enough illumination:
*.item.location=PLAYER.illumination>5

These queries are used in two place: rendering the view, and defining rules.

Rules are the keystone of the Elm Narrative Engine. You can make a rule for any important action in a story, like an action that has a special narrative, or alters the state of the world.

Rules have a “trigger” and a list of additional “conditions.” Each time a player interacts with an entity, the engine runs through all of the defined rules, checking each one against what the player interacted with and the current state of the world, to find the best matching rule, to determine how to respond.

Rules are built up of queries. Here is an example rule:

// cave is too dark to enter
trigger: CAVE
narrative: The cave is pitch black.  You can't see enough to enter.

This rule would match if the player interacted with the cave. The world state would not change, and the supplied narrative would be shown.

If you wanted to make a generic rule about not being able to enter dark places you could use trigger: *.dark instead, and adjust the narrative accordingly. You could also make a rule for CAVE.dark if you wanted a rule that only triggered if the cave is dark.

Here is another rule:

trigger: CAVE.!explored
conditions: 
*.item.location=PLAYER.illumination>5
changes: 
PLAYER.location=CAVE.fear+2
CAVE.explored
narrative: You can see a short ways into the cave, and bravely enter.  You hear an awful snoring sound...

This rule also matches when the player interacts with the cave, as long the cave does not have the “explored” tag (indicated by the “!”), and as long as the player also has an item with an illumination greater than 5. This rule has more constraints, so if it matches, it will outrank the less specific rule above, and its changes and narrative would apply.

In this case, the changes would update the player’s link of “location” to the cave id, and increase the player’s “fear” stat by 2. It also adds the “explored” tag to the cave. Adding the “explored” tag and checking for it in the trigger makes sure this rule will only trigger once (to void increasing the fear stat more than once).

One more:

trigger: GOBLIN.sleeping
changes:
GOBLIN.-sleeping
PLAYER.fear=9
narrative: There's an old saying, "Let sleeping dogs lie."  That applies double when it comes to goblins.  Too late...

This triggers if the player interacts with the goblin while he is sleeping. It sets the player’s “fear” stat to 9, and it removes the “sleeping” tag from the goblin.

You can make generic rules too:

// moving around
trigger: *.location
changes: PLAYER.location=$

// picking stuff up
trigger: *.item.!location=PLAYER
changes: $.location=PLAYER
narrative: This might be useful.

In these cases, “$” means “the id of the entity that the player interacted with.” So the first rule sets the player’s location link to what ever the player clicked on. The second one sets the location of what ever item the player clicked on to the player (as long as it wasn’t already in the players inventory). Keep in mind that these rules would only apply if a more specific rule did not match.


The above covers most of the syntax I have in mind. Hopefully I explained everything clearly enough, and you can imagine how you could write other rules. My intention is to make it as easy as possible to write rules for a story, while being flexible enough to allow for any kind of story and complexity.

I imagine these rules would be entered into something like a spreadsheet, which could be imported and parsed by the engine into the code format it uses now. I have plans for making a visual editor that could assist with features such as auto-complete for entity ids and the keys, and could also validate each rule for correctness.

I have a few other ideas for more advanced syntax, such as nested queries, like “PLAYER.location=(*.dark)”, which would match if the player is anywhere that is “dark”. I also think being able to compare entity stats or links would be useful too.

Also, the “narrative” part of the rule is external to the engine. It is only an example of how to can attach side effects to matched rules, which you can use how ever you want. For example, you may have a way to parse the narrative based on repeat counts and the world state (like Ink), or you might change some css in your view, or you might play a sound.

Please let me know what you think of this proposed syntax, or the world/rules system in general. I appreciate your input, and hope to create a tool that others will want to use!

2 Likes