Skip to content

World Properties

World Properties are the values your game owns and the story reads, referenced as @world.* in conditions, effects, and interpolated text. They’re how you make dialogue reactive to live game state: a threat level, the player’s class, whether the alarm is ringing.

You declare them in Patterpad (Project Settings ▸ World Properties, see Properties & game data), giving each a name, type, default, and whether the story may write it. At runtime you bind a resolver so the story reads, and if you allow it, writes your live state.

Bind world in the engine’s hostScopes, a get (and optional set) over your own state:

const engine = new Engine(bundle, {
hostScopes: {
world: {
get: (name) => game.world[name], // the story reads your live game state...
set: (name, value) => { game.world[name] = value; }, // ...and can write it back
},
},
});

Now a condition on @world.alarm reads your live game.world.alarm, and an effect that sets @world.reputation writes straight into your system, so the next line reacts and your game sees the change. Everything under @world goes through this one resolver. hostScopes is keyed by scope because every runtime takes the same option, but a Patter project declares the one scope, world.

Earlier releases took the resolver as a world option of its own. That still works, and goes in a later release.

Four things, and they are the same four in Storylet Studio, in the same order, so a game running both engines sees one rule:

  1. Read-only is the story’s promise, declared on the property (writable: false in the project, the Read-only switch in Patterpad). A condition can still read @world.alarm; an effect that sets it is a validation error, so a scene cannot move the game’s state by mistake.
  2. The runtime keeps the promise too. If a bundle somehow carries such a write, the engine refuses it with 'alarm' is read-only and nothing changes. Your resolver’s set is never called for a read-only property.
  3. Your game is never bound by it. The value is yours: setProperty writes a read-only @world property from your code, your tooling and Patterpad’s coverage drivers alike, whether you bound a resolver or left the property self-backed. The flag says what the STORY may do, not what you may do. (Before September 2026 the runtime refused every caller, so a game could not advance its own clock through the engine.)
  4. A resolver with no set makes the whole of @world read-only to everyone, whatever the declarations say, because there’s nowhere for a write to land. That is the game’s own doing rather than the story’s promise.

There is no write-only: a declared property can always be read by the story. If the game holds a value the story should not see, do not declare it.

A third kind of read-only belongs to your own container, not to Patter: a game that keeps its own @world object (Unreal’s UPatterWorld and its kin) can refuse a name to the story there too. Three rules, and each one says whose it is.

Binding is optional. Leave world unbound and the runtime self-backs @world from the declared defaults: a live in-memory value per property, seeded from its default, that the story reads and writes for the length of the run. That’s what lets a story using @world play standalone, in the Play window, a playable HTML export, or a quick test, with no host wiring.

A self-backed @world is a property like any other, so it is saved with the game. Only values your game keeps and lends through a resolver stay out of the save, because your game persists those itself.

In a game that runs more than one engine, or that hands Patter a registry of its own, register @world once in that registry instead, and give no engine a resolver. Register it owned when the registry should store and save it, or foreign, with a resolver, when your game keeps the values. One registry per game has the pattern.

Every runtime takes the same host scopes, in its own idiom, and self-backs @world when you bind none:

  • Unity takes EngineOptions.HostScopes, an IHostScope per token, and the Unity page has the detail.
  • Unreal binds a UPatterWorld at UPatterEngine::Create(Bundle, World), covered on the Unreal page.
  • Godot uses the host_scopes option, a get / set pair per token, covered on the Godot page.

The rules are the same everywhere: a bound @world is never in a save, a self-backed one is; a writable: false declaration refuses the STORY’s write with the same sentence ('@world.x' is read-only), bound or self-backed, while the game’s own setProperty writes it; and a per-name policy your game keeps on its own container is the container’s to refuse.

Open source under the MIT licence, made by .