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 a world resolver (JavaScript)
Section titled “Bind a world resolver (JavaScript)”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.
Read-only properties
Section titled “Read-only properties”Four things, and they are the same four in Storylet Studio, in the same order, so a game running both engines sees one rule:
- Read-only is the story’s promise, declared on the property (
writable: falsein 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. - The runtime keeps the promise too. If a bundle somehow carries such a write, the engine
refuses it with
'alarm' is read-onlyand nothing changes. Your resolver’ssetis never called for a read-only property. - Your game is never bound by it. The value is yours:
setPropertywrites a read-only@worldproperty 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.) - A resolver with no
setmakes the whole of@worldread-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.
If you don’t bind a resolver
Section titled “If you don’t bind a resolver”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.
The native ports
Section titled “The native ports”Every runtime takes the same host scopes, in its own idiom, and self-backs @world when you bind
none:
- Unity takes
EngineOptions.HostScopes, anIHostScopeper token, and the Unity page has the detail. - Unreal binds a
UPatterWorldatUPatterEngine::Create(Bundle, World), covered on the Unreal page. - Godot uses the
host_scopesoption, aget/setpair 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 Ian Thomas.