Attempted: Building a general-purpose QBN system
The term quality-based narrative (QBN) refers to a way of building interactive fiction most familiar from Failbetter Games’ Fallen London, as well as other games built on their now-defunct StoryNexus platform. Voyageur is also built on this model. Earlier this year, I worked on trying to build a general-purpose QBN tool, working off of the Voyageur codebase, but I didn’t get very far; this is basically a list of issues I encountered, as a sort of caution to people thinking of implementing those kinds of systems.
To recap, in a quality-based narrative, pieces of content (called storylets) are essentially free-floating, and players can access any one that they qualify for. The core structure in those games, then, isn’t explicit links from one piece of content to another but qualities that the player accumulates, which unlock content. A quality is essentially just a statistic, as should be familiar from stat-based hypertext like Choice of the Deathless, but QBNs make use of dozens or hundreds of them; everything about the player’s progression and state has to be explicitly tracked using qualities.
This approach is very good for building games which are worlds more than they are self-contained stories. It’s also very good for ongoing projects such as Fallen London, because new content can be slotted into the existing content fairly easily. Finally, it plays very well with procedural generation (as in Voyageur) and hybridization (as in Sunless Sea), because there are tight and direct connections from the game’s mechanical state to its narrative.
Ever since Failbetter’s cancellation of StoryNexus, there has been no general-purpose tool for writing quality-based narratives. This is a shame, because StoryNexus was home to a lot of valuable experimentation, and the presence of relatively accessible tools has been very powerful in driving the evolution of different forms in IF. However, building a general-purpose system invariably means making some choices and compromises about how that system works.
Scope: Narrative Engine, Text Engine, UI Engine
A system for writing IF has three overlapping components with separate responsibilities, and not all systems include all three of these; Inform 7 and Twine are essentially “full-package” solutions that include all three of those, but not every system has to work that way.
The narrative engine is in charge of consuming user input and responding with content. In the case of QBN, it decides what storylets are available at a given time, outputs the relevant content. Ink, for example, has some text generation affordances, but is almost purely a narrative engine.
The text engine generates text from its component parts. I’m using this as an umbrella term for a bunch of different tasks: Adapting text (for example, making sure verbs and pronouns match their subject), procedurally generating text, assembling text (such as generating a list of visible items in a room desscription in Inform). Tracery is an example of a pure text generation engine. Almost every IF system has some affordances for doing this, but how much varies, from the very limited (ChoiceScript) to the very sophisticated (Inform 7). How much of this is required also varies widely from one game to another.
The UI engine supplies an end-user-facing user interface, at least up to a point. ChoiceScript contains a UI engine; Ink does not. Finished games need to have a user interface of some kind, so this is mostly an author-facing question. Systems that let you click a button and output a playable game, like Twine, are generally going to be more widely used and are better for experimentation; systems that only provide an API so you can hook them up to an interface you built yourself are naturally more daunting. But not making UI coupled to the narrative system makes the system more useful in a broader variety of situations. Twine can only really be used on its own (though there are hacks for embedding it), while Ink can be incorporated into games as a partial thing; for example, you could use Ink to drive dialogue and scripted interactions in a game where the bulk of gameplay is doing something else. Twine has been used for this purpose, but usually by using external tools to export Twine files into a format used by a separate engine altogether.
Building without a UI is simpler, but carries with it its own challenge: defining an API that is actually useful and flexible enough to let users build their own interfaces for their games. Invariably, this means that the system has to be built alongside a reference implementation of a user interface anyway.
Programming: General-purpose, DSL, or none
It’s desirable to include some kind of scripting functionality in a narrative authoring system, especially a general-purpose one. You don’t fully know the scope of what others are going to do with it or what their needs are going to be. And particularly in a system that’s meant to embed well into a broader game, such as Ink, you want API hooks that let it communicate with the game state and make decisions based on that state.
On the other hand, a system that lets content be written and defined using only GUI tools will be much more approachable for non-programmers, and have a much shallower learning curve for everyone. If you do include scripting, how are you doing that?
You could build your engine as a library for a general-purpose programming language; in games, this would most likely be JavaScript (for the web) or C# (for Unity). This is a very straightforward way of building a system, but it’s not particularly friendly even for authors who are programmers, and it can lead to boilerplate. Voyageur’s underlying engine is built so that storylets are defined in javascript code. This makes it easy to define storylets with complex logic attached to them and means I didn’t have to plan ahead what kinds of logic I would need in storylet definitions; for example, late in the process, I wanted some storylets to show up purely at random. The way this is done, to ensure that a given planet always has the same set of storylets available on it, is that the planet’s random numeric ID is compared with a bitmask specific to the storylet. This was easy to figure out and implement as an ad-hoc thing without having to mess with the underlying engine. But at the same time, writing storylets as javascript object definitions has enough boilerplate to it that I eventually also built a code generator based on a simple DSL to output those definitions.
Building a DSL (domain-specific language) specifically for the engine itself is a very useful approach, but it adds complexity and learning curve; new authors can’t benefit from preexisting programming knowledge, while new authors who aren’t programmers still have to learn a programming language. DSLs also threaten to balloon into general-purpose programming languages. This was a major stumbling block for me; I’m not a classically-trained programmer, meaning that I don’t really know how a compiler works. To build a system around a DSL, I would have to develop skills that are very orthogonal to what I have.
Twine ends up including all three approaches: You can write javascript, you can write “twinescript” (really, one of the different variations of scripting and text markup included in the different Twine formats), or you can use very minimal scripting and simply make use of the graphical interface to build a branching story. This is useful, and it’s part of what makes Twine so popular and accessible, but it also makes Twine a bit of a mess.
Storylets: Fixed or dynamic
Is a storylet a fixed blob of data (requirements to enter it, content presented to the player, effects) or is it really a function to generate that data based on the game state? This is a really important choice. As is often the case, there is a simple alternative that makes it easier for almost everyone (fixed storylets) but which also dramatically limits what the system can do. A big challenge I encountered trying to plan how a general-purpose QBN system would work is a ratio I observed in Voyageur: 90% of everything about storylets is static, but the 10% of variable content and scripting is very important to making the whole thing work.
Miscellaneous Considerations
What other affordances are there for encountering storylets? Is there a “card deck” mechanic built in, as in StoryNexus?
How do storylets relate to one another, if at all? Can they “chain” into one another? Can storylets contain choices, allowing for segments of branching narrative built into the greater story?
Do we want to supply a dedicated app, IDE, or environment for authoring? Do we require one? Is the workflow when using a plain text editor passable?
There is no right answer
What eventually stopped me from building a system was not being able to come up with a set of requirements I thought was broad and satisfying enough. Including a UI system would have made it much easier to experiment with, but more complex overall and less useful as an engine for hybrid games. Building a DSL felt necessary, but beyond what I could do at the time.
Ultimately I came to the conclusion that I would have to build a new engine from the ground up; generalizing Voyageur’s internals to be a general-purpose engine would have been too difficult. But the fact that Voyageur’s engine is so tightly integrated with its specific mechanics gave me pause, and made me question how useful a general-purpose QBN engine would be. StoryNexus seems to imply there was at least some experimental interest in such a thing, but there are still too many unsolved problems for me; I hope that by going over those problems, I might help someone else reason around building an engine for their game, or even a general-purpose engine.
This post is brought to you thanks to my Patreon supporters. Special thanks to Emily Short, Kevin Snow, Liza Daly, and Doug Orleans.