Backburner Month 04: Rakonteto

This is an entry from a list of projects I hope to some day finish. Return to the backburnered projects index.

What is it? A very simple library and engine for writing storylet-based narratives.

“Storylets” are an approach to writing procedural, interactive narratives. There are a lot of different ways you can approach storylets, but the core idea is that you've got some kind of world state, and based on that world state, you choose a piece of “content”—in Rakonteto's case, a fragment of prose—that you can present to the player. The player can then make choices which change the world state, and that in turn allows the game to present another piece of content to the user.

Probably the simplest way to contrast this to other interactive narrative systems is by comparing it to a Choose-Your-Own-Adventure-like branching narrative system. Imagine that you want to write a story in which the player must go to two different places (A and B) in either order, and only after that can they return to a third, final place (Z). In a pure CYOA-style game, you effectively must have two branches, one corresponding to the sequence A → B → Z, the other B → A → Z, which implies that you must duplicate the content for A and B even if there are otherwise no changes to the visit to A and the visit to B. (And that's just for two places: if we have three places to visit in any order, we'd have to duplicate the content for each of the six possible visit orders!)

In contrast, a storylet-based system would let you represent A and B separately, and each one would end with modifying the world state with information indicating that the location had been visited. Finally, Z would include as precondition that A and B had both been visited. Many storylet systems allow for selecting the most salient next fragment, as well: that means that A and B can also include special content which will be surfaced in the specific situation that they're being visited first or second, but without having to duplicate all the content associated with A and B.

My implementation—Rakonteto—is designed as a textual language for producing interactive prose, loosely inspired by Ink. A given fragment of a story would look sort of like this:

=== [var: expr, var: expr, ...] ===

A fragment of text, to be shown to the player.

+ A choice. -> [var: new_expr, ...]
+ A different choice. -> [...]

The stuff in square brackets at the top is a set of “variables”—properties of the world, so to speak—with their current values. When the underlying story engine needs to choose a new story fragment to show to the user, it looks at each fragment to find whether the declared variables match the current state of the world, and then (using a handful of heuristics) choose a matching fragment to show to the player.

The bits at the bottom introduced with + are choices the use can make, and the stuff in brackets afterwards describes how to change the world-state so that a different fragment can be chosen next. A more “realistic” example might look like this:

=== [has_sword: false, in_location: forge] ===

As you walk into the forge, smoke stings your eyes. A
towering, muscular man with a singed beard is staring
pensively at the embers: as you approach, he is roused
from his thoughts and nods. Without a word, he turns,
walks to the back of the forge, and returns with a
beautiful and freshly polished sword, its surface only
marred by the man's sooty fingerprints.

+ Take the sword. -> [refused_sword: false, has_sword: true]
+ Decline the sword. -> [refused_sword: true]

The world state can include far more variables than are listed, and only the variables mentioned in the choices get updated: the others persist. One thing I hadn't figured out was the correct heuristics for handling selection among multiple different fragments that match to the same degree of “salience”. Another was how to provide tools to make certain common patterns more convenient: for example, I planned to have syntax in which you could include or swap out individual phrases or sentences based on parts of the world state, so that certain bits of customization didn't need an entirely new fragment. I also planned to have some choices “gated” by aspects of the world state, and also syntactic sugar for small branching scenes (so that you don't need to introduce new variables for situations like singular yes/no decisions that only matter within a single fragment.)

The original version was constrained entirely to finite sets of “world state” variables which could range over atomic strings, but I planned to also experiment with constrained integer arithmetic and eventually maybe even composite values like lists or sets.

Why write it? Mostly for experimentation! There were a few specific features I wanted to experiment with.

One such feature was that I wanted to have an engine for storylets which could be quickly and easily turned into something which can be played, sort of like the interfaces provided by Twine or Ink. My goal was that you could write a game and then “compile” it to a standalone HTML document that includes all the JavaScript necessary to run an interactive version of the game. For early playtesting, I also created a tiny command-line driver, which allowed you to choose from a list in order to move forward.

Another feature was an embeddable API. I wanted to be able to take this and link it in to other programs which could interact with it: for example, exposing it as a library so I could integrate it into a Unity or Unreal game (although I'd likely have started by writing bindings in something much simpler, like Löve.) The previous compile-to-HTML feature would let you get stories in front of people quickly, but I wanted to design the system such that you could use it as the “story backend” to a different kind of game, as well.

Finally, I wanted to experiment with debugging and modeling tools. With the state space constrained sufficiently, you could start throwing various solver techniques at a narrative. One reason you might want to do this is reachability: “I have these variables which have different states, and can change in this way. Can I actually ever get to this specific state?” Another is as a kind of automated play-testing. When your story is complicated enough, it might be possible for a player to “sequence break”: that is, to experience the story in a way that you didn't anticipate. I imagined addressing this with a solver as well: you could query your story model and ask questions like, “Is it possible that the player could experience fragment X before fragment Y?” and the tool could either confirm that it's impossible, or show that it is and give you an example of such a play-through.

Why the name? The word rakonteto is Esperanto: it's a diminutive form of rakonto “story”, so it means something like “little tale”.

#backburner