Storylets and Saliency

When you do things right, people won’t be sure you’ve done anything at all. - Futurama

Introduction

Storylets and saliency build on earlier interactive storytelling techniques. This section shows how narrative approaches have developed over time.

A lot of the terminology used here, and in Yarn Spinner, is based on the work by Emily Short (2019), and Kreminski and Wardrip-Fruin (2018), and these are great resources on the topic, go read them.

The Evolution of Interactive Storytelling

Linear Narratives

The earliest approach to storytelling was linear. Stories followed a single, author-defined path with each moment crafted to lead directly to the next.

A linear story
A linear story, with one path from start to end.

Branching Narratives

The next evolution introduced branching, allowing stories to have multiple paths for players to explore.

A branching story
A branching story. The path splits in two and joins up again before the end.

Branching narratives are flexible. Some branches might be extensive with significant impact, while others could be minor, adding subtle flavour. Some paths might terminate, while others reconnect with the main storyline. Despite this flexibility, each step remains author-crafted, with branches fixed in the writer’s intended order.

If you are ever after a great breakdown of some of the most common shapes and patterns in branching narrative check out Sam Kabo Ashwell (2015).

Storylet-Based Narratives

Storylets represent the next step in this evolution. They break the rigid connections between narrative elements. Theoretically, a story could move from any storylet to any other storylet, creating a potential connection from every block to every other block.

A freeform story
A storylet-based story. The pieces have no fixed connections between them.

Understanding Saliency

If narrative chunks are disconnected, something has to make them form a coherent experience. Saliency is the mechanism that links story pieces together.

To be a bit more precise about this, saliency is the way Yarn Spinner does this. There are other approaches, the simplest being “present everything and let the player select the next piece”.

Saliency determines which storylet is most relevant (or “salient”) at any given moment. Instead of an author-defined sequence, saliency selects the ordering dynamically. For example, if you have two storylets (one about entering any room in an apartment, and another specifically about entering the bedroom), when the player is in the bedroom, the more specific storylet would be more salient.

Linear and branching narratives can be viewed as salient stories composed of storylets, where the most salient storylet is defined by the author’s explicit connections. This shows a strength of storylets: they can incorporate and blend previous structural approaches.

Combining linear, branching, and storylet storytelling
Short linear and branching pieces, kept apart so each can be used as a storylet.
You might be going “hang on, this sounds a lot like what [Classic Game] did, a game from ages ago I really enjoyed!?” and you’d be right, it is very similar. These ideas aren’t new, nor was the evolution of these concepts as discrete as we’ve implied here.

Storylet Granularity

The fundamental particles, including the Storylet. Modified image from Wikipedia
The Standard Model of particle physics, with the Storylet in place of the Higgs boson.

The storylet is the fundamental unit of this narrative system. How large or small a storylet should be depends on your narrative needs.

Storylets typically exist at three levels of granularity:

  1. Collection Level: The largest unit: a vignette, conversation, or paragraph. At this level, your story assembles by connecting self-contained moments. These moments generally function independently, so their precise order may be flexible, or they might be designed to flow naturally with a subset of other moments.
  2. Line Level: Here, a storylet represents a single line of dialogue. Conversations build by assembling individual lines, which then form larger narrative moments. This approach works well for NPC barks in games.
  3. Sub-Line Level: The smallest unit, where storylets are fragments within a single line. These pieces combine to form complete lines of dialogue, which then assemble into conversations.

Many games blend these approaches, often mixing storylet techniques with traditional linear or branching content. Rather than focusing exclusively on storylet size, consider what kind of narrative you want to create, and which level of storylet granularity suits it.

Types of Saliency

How you choose which storylet is “best” to present next depends on your narrative goals.

Quality-Based Saliency

The most common approach uses conditions on each storylet: boolean expressions like “are we in the bedroom?” or “does the player have more than 25 gold coins?” These conditions filter available storylets and help select among the remaining options. When multiple storylets are valid, you select the one with the highest “quality”, typically the one that satisfies the most conditions.

Tag-Based Saliency

Another approach tags storylets with attributes. When selecting the next storylet, you filter based on relevant tags. The filtering criteria might come directly from player choices or from systems guiding the narrative.

Directed Saliency

Some approaches aim to guide the narrative toward specific outcomes:

  • Pathfinding: Storylets exist on a graph, and the system attempts to progress from the current storylet toward a specific destination, influenced by player choices along the way.
  • Drama Management: A system with narrative goals selects storylets to work toward those goals. For example, Façade selects storylets to maximise conversational drama. Pathfinding can be viewed as drama management where the goal is reaching a specific point in the narrative graph.

Simple Saliency Approaches

Two simpler approaches are:

  • Player Selection: Present all available storylets and let the player choose.
  • Random Selection: Choose randomly from available storylets.

These methods often incorporate knockout or deprioritisation systems to prevent repetition. They’re also effective for resolving ambiguity when multiple storylets have equal saliency.

Storylets in Practice

Storylets have several uses in games:

NPC Barks

Perhaps the most common use is creating contextual NPC comments. Skyrim’s infamous “arrow to the knee” line is an example. It’s triggered when the player has cleared a dungeon, a condition most players eventually satisfy.

Environmental Flavor

Storylets create specific moments that add life to the game world without connecting directly to the main plot. Mass Effect’s elevator conversations, filtered based on your current squad members, demonstrate this approach.

Contextual Reactions

Storylets can create specific dialogue responding to gameplay moments, making NPCs seem more aware and reactive. Left 4 Dead’s combat and downtime barks do this, as do Ellie’s contextual reactions in The Last of Us.

Emergent Narrative

In games without predetermined stories, storylets create moments and side-narratives. Civilization VII uses storylets for historical and alternative historical events, triggered by factors like civilization type, leaders, and current player actions.

Explorable Narratives

Database-driven games use storylets to let players explore narrative space freely. Her Story is an example, with self-contained story chunks that both stand alone and contribute to an overall narrative.

Complete Narrative Systems

Some games build entire narratives from storylets. Mask of a Rose and Wildermyth assemble stories entirely from storylets based on player actions, allowing players to create their own narratives.

Many of these games haven’t talked about their approaches as using storylets, but it’s much easier to talk about it using the terminology already in this guide rather than use the terms the devs themselves said which are different for each game.

Storylets in Yarn Spinner

Yarn Spinner supports two primary levels of storylet granularity: line-based and node-based. Both work alongside traditional branching and linear Yarn scripts.

Line Groups

Line Groups implement line-level storylets. They visually resemble option groups but use => instead of ->. Each line within a group represents a potential storylet, but only one will be selected.

Yarn
title: Start
---
Barry: Oh is that so?

=> Alice: Yep.
=> Alice: Of course it is!
=> Alice: Why would you think otherwise?
===
A line group of replies from Alice.

The dialogue system receives the Barry line, then one selected line from Alice. The player might see:

Barry: Oh is that so?
Alice: Of course it is!
One way the conversation could play out.

The system doesn’t indicate that the Alice line came from a storylet selection. It’s presented as the next line of dialogue.

We can add conditions to modify selection:

Yarn
title: Start
---
Barry: Oh is that so?

=> Alice: Yep.
=> Alice: Of course it is! <<if $barry_suspicion > 3>>
=> Alice: Why would you think otherwise? <<if $barry_suspicion > 5>>
=> Alice: I am not talking without my lawyer <<if $barry_suspicion > 5 && $knows_barry_is_cop>>
===
A line group with conditions.

Assuming high Barry suspicion and knowledge that he’s a cop, the dialogue might present:

Barry: Oh is that so?
Alice: I am not talking without my lawyer
What the player sees when Barry is very suspicious and Alice knows he’s a cop.

The final line is selected as the most salient option, even though multiple options satisfy their conditions.

Like option groups, line groups support nested content that only appears if that specific line is chosen:

Yarn
title: Start
---
Barry: Oh is that so?

=> Alice: Yep.
    Barry: lol lmao, thanks nerd.
=> Alice: Of course it is! <<if $barry_suspicion > 3>>
=> Alice: Why would you think otherwise? <<if $barry_suspicion > 5>>
=> Alice: I am not talking without my lawyer <<if $barry_suspicion > 5 && $knows_barry_is_cop>>
    Barry: Why would you need a lawyer?
    Alice: *cool silence*
===
Lines that belong to items in a line group.

Any Yarn dialogue or commands can be nested, including other storylets, jumps, and detours.

Node Groups

Node groups allow larger storylet chunks to be associated. They look like standard nodes but require a when header:

Yarn
title: Barry
---
Barry: Oh is that so?
<<jump Alice>>
===

title: Alice
when: always
---
Alice: Yep.
Barry: lol lmao, thanks nerd.
===

title: Alice
when: $barry_suspicion > 3
---
Alice: Of course it is!
===

title: Alice
when: $barry_suspicion > 5
---
Alice: Why would you think otherwise?
===

title: Alice
when: $barry_suspicion > 5
when: $knows_barry_is_cop
---
Alice: I am not talking without my lawyer
Barry: Why would you need a lawyer?
Alice: *cool silence*
===
A node group of storylets.

When <<jump Alice>> executes, it jumps to the most salient node in the “Alice” group. If Barry is highly suspicious and Alice knows he’s a cop, the system selects the last node.

For this simple example, node groups require more writing than line groups for the same effect. However, for larger conversations with significant variability, node groups are more useful.

You might notice that you can have as many when headers as you want on a node storylet. You might also notice the when: always header on one of the storylets, this is required with node groups to say “this one is always available”.

Like line groups, node groups can contain any valid Yarn content, including jumps, detours, and line groups.

Saliency Strategies

The dialogue runner consults a saliency strategy when presenting a storylet, whether from a line group or node group. While you can create custom strategies to implement any approach, Yarn Spinner provides several built-in quality-based strategies:

  • Best (select the most salient)
  • Best Least Recently Viewed (prioritise salient content that hasn’t been seen recently)
  • Random Best Least Recently Viewed (like above, with some randomization)
  • Random (completely random selection)

All built-in strategies first filter out storylets with failing conditions, though custom strategies can include failing conditions if desired.

The recommended default is Random Best Least Recently Viewed, which balances selection of the highest-saliency content while preventing repetition.