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