Storylet Studio

Multiplayer storyworlds

Most storyworlds are single-player: one playthrough, one set of state, one person (or one group huddled around one screen) moving through the story. That's the default, and if it's all you need you can ignore this whole page - nothing here changes how single-player works.

A multiplayer storyworld is different: many people play the same live storyworld at once, each through their own device, and some of the story's state is shared between them while the rest stays personal to each player. This is the model for a venue installation, a guided group experience, a city-wide street game, or any "we're all in this together" deployment.

This page explains what changes when you switch a storyworld to multiplayer, and the two extra kinds of property it unlocks. It's written to make sense whether you're building a solo game or a shared installation - the same authoring tool covers both.

NOTE: Running multiplayer storyworlds only applies to the StoryletEngine Server, not to Unreal/Unity/JS runtimes.

Single-player vs multiplayer: the one idea

In single-player, everything is shared because there's only one player - there's no distinction to make. Every property is just "the state of the game".

In multiplayer, you have to answer one extra question for each piece of state: is this shared by everyone, or personal to each player?

  • Shared state has one value the whole session sees. If one player changes it, every other player sees the new value. Use it for the things that are true of the world everyone is in: a gate that's been opened, a boss that's been defeated, a tally everyone contributes to.
  • Personal state has its own value for each player. One player changing it doesn't affect anyone else. Use it for things that belong to that person: their own score, their own inventory, the choices they personally made.

That one shared-vs-personal choice is the heart of multiplayer authoring. Everything below is about where you make it and the two new scopes that make "remembered across sessions" possible.

A "player" is one device, not necessarily one person. A solo player and a small group sharing one screen look identical to the storyworld - both are one player. There's no separate "party" concept to set up. If you want several people to each have their own personal state, each needs their own device.

In a shared session, several players read and write one shared state while each also keeps personal state. Durable @player values persist per player across sessions; installation-wide @system state persists too and is the operator and external-system control surface.

Turning a storyworld multiplayer

The switch lives in Project Settings, as the storyworld's runtime model: Single-player (the default) or Multiplayer.

This is a big, rarely-reversed decision, so it's guarded:

  • Single-player → multiplayer. Switching changes what every property's defaults mean (see "Shared or personal" below). The tool shows you each property with the shared-or-personal default it's about to take, and lets you confirm or override before committing. Existing single-player saved states can't be resumed as multiplayer - the shape of the saved state is different - so any live or saved single-player session starts fresh.
  • Multiplayer → single-player. This is lossy: per-player and durable state collapse away. The tool makes you explicitly confirm you're discarding per-player progress.

Treat the switch like a one-time setup decision for the storyworld, not a toggle you flip back and forth.

While a storyworld is single-player, none of the multiplayer chrome appears - no shared/personal controls, no Player or System tabs. You author just as before.

Shared or personal: the layer choice

In a multiplayer storyworld, each property's editor gains a Multiplayer layer choice:

  • Shared - one value, seen by every player.
  • Personal - each player has their own value.
  • Default - leave it on the scope's natural default (see the table).

Each scope has a sensible default, so you rarely have to think about it:

Property scope Default layer Why
World Shared World state is usually "the state of the world everyone is in".
Deck / Act / Zone / Site Personal These usually track an individual's progress through some content.

You can override any property's default either way. The control is hidden entirely in single-player storyworlds.

A practical example, in a co-op dungeon:

  • @world.boss_defeated (shared) - once anyone beats the boss, it's beaten for the whole party.
  • @site.searched on a treasure room, left personal - each player searches it for themselves.
  • Override @site.lever_pulled to shared - a lever in the world stays pulled for everyone once one player pulls it.

The two new scopes: @player and @system

Multiplayer also unlocks two brand-new property scopes, each with its own tab on the Properties page. What makes them special is that they're durable - their values persist between sessions, not just within one playthrough. The six ordinary scopes (world, deck, act, zone, site, story) all reset to their defaults when a session starts; these two don't.

@player - durable, per-player memory

A Player Property is personal to each player and remembered across sessions. When the same player comes back another day, their @player values are still there.

  • Define them on the Player Properties tab. Reference them in conditions and outcomes as @player.name - they autocomplete and validate in every condition editor like any other property.
  • They're always both personal and durable, so unlike the other scopes there's no shared/personal choice to make.
  • Use them for a returning player's long-term identity: a loyalty level, "which endings this person has unlocked", "have they met the blacksmith before".

In a single-player game, "remember this person between visits" is rarely the point, so this scope is multiplayer-only. The tab is hidden in single-player.

@system - durable, installation-wide state

A System Property is shared by every player and durable across sessions. It's the long-term memory of the whole installation - the venue, the cloud backend, the street-game run - not of any one session or player.

  • Define them on the System Properties tab. Reference them as @system.name.
  • Examples: "trolls defeated since we opened", "total visitors this week", "which show phase the venue is currently in".

@system properties double as the installation's control surface. As well as story outcomes changing them, an operator (a person running the venue) or an external system (a show scheduler, a building control system, an IoT trigger) can read and set them from outside the story. To keep a control knob out of the hands of the story itself, each System Property has a write capability:

  • Anyone (the default) - story outcomes can change it too. This is the right choice for the common case: a counter that outcomes bump.
  • Operator & external only - story outcomes can't change it; only the operator or an external system can. Use this for a true control knob, like "the venue is now in its evening phase", that the story should react to but never set itself.

Like @player, this scope only makes sense where there's a persistent installation, so it's multiplayer-only and the tab is hidden in single-player.

Designing with external state

Because @system (and, in single-player, host-set @world) can be driven from outside the story, you can write content that only becomes reachable once an operator or external system sets a value. So this content doesn't look like a dead branch when you're testing, declare those possible external values as external inputs - see External inputs. Both Coverage and Simulate then exercise that content for you.

Testing a multiplayer storyworld

You don't need a live deployment to test multiplayer. Both testing tools run it right inside the authoring tool:

  • Simulate starts a shared session with a roster of players. Pick the active player to see the map, hands, properties, and log from their point of view, watch a shared write by one player appear for the others, act as the operator to set a @system value, and step turns per-player or all at once. The activity log has an All players toggle that merges every player's entries into one turn-ordered stream tagged by player - the quickest way to see how one player's action set up another's.
  • Coverage Testing gains a Players knob and simulates that many players sharing one session, so content that only unlocks once another player has changed shared, @player, or @system state gets reached and counted instead of looking like dead content.

When it goes live

Running a multiplayer storyworld as a real shared installation (players joining over a network, durable per-player and installation state stored centrally, the operator control surface exposed to real external systems) is the StoryletEngine Server's job. The authoring, testing, and content concepts on this page are exactly the same; the Server is what hosts the live shared session for real players. For the operator side of a live installation - returning players, the @system control surface, watching a shared session, and stepping in to unstick a player - see Publishing to the StoryletEngine Server and Monitoring the StoryletEngine Server.