Posted On: 2019-06-10
Non-linear narrative has some unique challenges compared with a traditional linear narrative. In particular, supporting player interaction introduces a lot of complexity, and a Branching Narrative approach (the most common form of non-linear narrative) is prone to generating extra complexity under certain circumstances. To make my branching narrative system more maintainable, I've augmented it with some patterns drawn from another non-linear narrative approach: "Salience-based narrative." While I expect I am not the first to do so, I wasn't able to find many resources about it online, so I thought I should contribute what I learned in case others are looking to do the same.
Writing A Notebook Prototype, I relied heavily on Branching Narrative to represent and manage the player's flowing through a conversation. This approach made it quite simple to manage individual conversations, as the entire web of possible player choices mapped neatly to a tree (or a hub-and-spoke, depending on the particular kind of conversation.) Unfortunately branching narrative proved to be a poor fit for determining which conversation to start: as characters (and the player) grew over the course of the game, the topics they discussed and the phraseology they used changed, often times leading to multiple parallel conversation trees. This lead to what I refer to as the "hub-of-hubs" problem (not sure if there is another term for this, but I haven't found anything myself.)
The "hub-of-hubs" problem can be best understood by walking through how it occurs. Initially, the player is presented with a set of choices - let's say, characters to talk to - and the writer represents this as a hub: one node in the branching dialogue that connects to many other, unrelated branches.
In time, changes to those conversations necessitate a change to the structure - perhaps a character that is stand-offish becomes more friendly. To accommodate this, the writer adds in a node to automatically route the conversation to the correct node.
Again, over time, the conversation structure changes - perhaps there is a different set of characters the player can visit. To accommodate this, the writer adds in another node to route the player to the correct set of characters, based on the setting.
This same pattern, representing complexity by adding yet another "hub" to handle routing to other nodes, can keep recurring for as long as the scope of the project increases. Unfortunately, the same is true if you start with an outline: outlining all the places where the narrative may diverge will result in many hubs layered on top of one another.* Importantly, all of the complexity described is not modeling actual flow of conversations - it merely models which conversation should start given the current context.
While the "hub-of-hubs" problem is surmountable, and perhaps considered normal or inevitable, I found that spending time fiddling with the routing would often distract me from the task at hand (usually writing or testing.) Reflecting on the issue, I was able to identify an underlying problem: series of if-else that jump to different branches is not an accurate model for the way that I was conceptualizing the conversations. For me, the routing is more accurately represented as a set of affordances associated with a conversation (for example, a character is friendly and in the same location as player.) Writing and maintaining hubs required converting the affordances into a set of conditionals, and the process became increasingly involved as the complexity of the context increased.
*Note: a hub is any node that connects to a wide variety of unrelated nodes, simply for routing purposes. This means that automatic routing (such as which conversation to start based on reputation) and player-driven routing (such as choosing which character to talk to) are both considered "hubs".
While investigating this issue I accidentally arrived upon a potential solution. While I didn't find much on the topic of architecting a branching narrative to avoid the hub-of-hubs problem, I did encounter an excellent article by Emily Short describing alternatives to branching narratives. In it, she describes what she calls a "salience-based narrative" which is aimed at solving the problem of playing the correct narrative content given the current circumstances. In the context of the article, Short focuses on using salience as the primary form of narrative interaction, but the approach seemed like it should work even if it's used as a supporting system to solve just the hub-of-hubs problem.
As a part of creating the Magic Training Prototype I worked through the implementation of what I call the "Salience Engine" (which is basically a simplified Rules Engine focused on just dealing with narrative branches.) It is designed to take in a series of possible starting points for a conversation and a set of rules describing how to evaluate the current state of the world (usually just looking up Yarn variables) along with a relative level of importance for each rule. Given that information, it determines which one is most salient (or picks a semi-random one in the event of a tie.) The output is then fed to a Yarn Command, which is capable of forcing the engine to jump to an arbitrary node. (This required changing Yarn itself to have a "back door" that can be used to change the state machine, but it was relatively simple.)
At this point, I am quite pleased with how the whole thing has turned out. Although the narrative of the Magic Training Prototype is much simpler than that of the Notebook, I still consider it a valuable test of the system. In particular, I found that certain patterns that ordinarily added little value but a lot of complexity (like rare/hidden mini-conversations) had virtually no impact to the complexity of the salience-based routing. There are, of course, more things that I can do with this system and more polish that is needed around the experience of assigning salience to the nodes (right now it's a bunch of text files in a folder) but the current implementation serves as an excellent demonstration of just how well it can solve the hub-of-hubs problem.
Adding a Salience Engine to the dialogue system I am using has served to solve a problem of creeping complexity, allowing me to focus on writing without getting bogged down in the details of routing the player so that they can reach a particular piece of content. I am very happy with how this has enhanced my project, and hopefully this write-up about the topic will be useful for others who are looking to avoid the "hub-of-hubs" problem. If you'd like to hear more about this topic in general, or would like to know about some of the actual implementation details for the engine, please let me know.