Games are written in YAML, and this is the shape of every file. The details about triggers, conditions, chance and actions are covered once, in Core mechanics.
Overview
Every file in your game says in its name whose it is and what it holds, so you can tell any file apart at a
glance. A name has three parts before .yml (or
.yaml): who it belongs to, what kind of thing that is, and what the file holds.
ferryman.game.config.yml the game's settingsferryman.game.vocabulary.yml the game's wordstavern.scene.config.yml the tavern itselftavern.scene.topics.yml what can be asked about in the tavernlantern.object.config.yml a lanternguard.character.config.yml the guard
Vocabulary
[id].game.vocabulary.yml
The most important file in your source, what your game understands - Verbs, Articles, Prepositions,
Conjunctions, Topics and Directions. It is named for your game, as all of the game's own files are.
The verbs a player can type which are recognised by the engine for your game. A verb whose target is a direction rather than an object, such as walk, sets navigational: true. A verb that asks a character something, such as ask, sets conversational: true. A verb that may name who something is for before naming the thing, such as give, sets recipient-first: true.
directions
- id: string synonyms: - string
The ways the engine understands the player wants to move somewhere, corresponds directly to a definition inside a scene file.
articles
- id: string synonyms: - string
Articles are what stands before an object or character, such as the, an and a.
prepositionsobject
There are two kinds of prepositions, filler and significant - the first is simply meaningless filler, the latter changes the meaning of a verb. For example, walk to the fire and walk the fire compile to the same command whereas significant prepositions help the engine understand the difference between look under the bed and look at the bed.
prepositions.filler
- id: string synonyms: - string
The prepositions considered to be filler, see above.
prepositions.significant
- id: string synonyms: - string
The prepositions considered to be significant, see above.
conjunctionsobject
Conjunctions allow commands to be chained by the player.
conjunctions.coordinatingstring[ ]
Coordinating conjunctions can be declared such as and, but, then etc.
conjunctions.subordinatingstring[ ]
There is no requirement for subordinating conjunctions, yet.
Game
[id].game.config.yml
The game's config gives the engine its basic information. Its name gives your game its id.
id: reading-roomstart: reading-roommetadata: title: The Reading Room version: 1.0.0store: description: A quiet room, a fast clock and a drawer that won't open. assets: tile: tile.png genres: [Mystery]export: bundle-id: com.example.reading-room platforms: macos: [arm64, x86_64] windows: [x86_64] icon: icon.pnggui: gui/
idstring
The unique identifier of your game.
startstring
The first scene which loads in your game.
metadataobject
What your game is, as far as the engine needs to know.
metadata.titlestring
The title of your game, a human readable name.
metadata.versionstring
The current version of your game. Semantic versioning is recommended, though other formats are fine.
metadata.copyrightstring
Who owns the game, in the words you'd put at the bottom of a box. Shown when the game starts, and on its page in the Stage app.
storeobject
What a shop shows about your game before anyone opens it. None of it changes how the game plays, though the tile does double as your game's icon if you don't give it one.
store.descriptionstring
What your game is about, in your own words.
store.assetsobject
The images your game shows for itself. Each one is a PNG, and each is carried inside your built game, so there's nothing extra to hand over.
store.assets.tilestring
Path to the square image shown where your game sits beside others, like a shelf. At least 800 by 800 pixels, and no more than 2 MB. If your game has no export.icon, this is its icon too.
store.assets.bannerstring
Path to the wide image shown at the top of your game's own page. At least 1200 by 600 pixels, and no more than 3 MB.
store.assets.screenshotsstring[ ]
Paths to up to six images of the game being played, shown in the order you list them. Each at least 1920 by 1080 pixels, and no more than 2 MB.
store.ratingstring
What you were told to say about who your game is for, word for word: PEGI 16, ESRB Teen, USK 12.
store.genresstring[ ]
What kind of game it is, in your own words and your own order. Shown, and searched by whatever holds a shelf of games.
store.releaseobject
When the game came out.
store.release.datestring
The day, as YYYY-MM-DD.
store.developer
name: stringwebsite: string
Whoever made the game. Their website, like the game's own, has to start with http:// or https://.
store.publisher
name: stringwebsite: string
Whoever put the game out, with the same rule for their website.
store.websitestring
The game's own place on the web, as against the people behind it. It has to start with http:// or https://.
exportobject
How your game becomes a program of its own, one somebody can double-click without installing anything. See handing it to someone.
export.bundle-idstring
The name a computer knows your game by, written backwards like a web address: com.example.my-game. Letters, digits, hyphens and dots only, and no dot at either end or two in a row. Leave it out and Stage makes one from your game's id.
export.platformsobject
Which computers your game can be made into a program for, and which kinds of each: macos: [arm64, x86_64], windows: [x86_64], linux: [x86_64, arm64]. Name only the ones you want. On its own, stage export makes a program for the computer you're on. To make every one you've named at once, it needs the published copies of Stage for those computers, which you point it at with --templates.
export.iconstring
Path to your game's icon: one square PNG, between 1024 and 4096 pixels across, and no more than 2 MB. Stage makes every icon each computer wants from it, and on a Mac it gives it the rounded shape every other app has, so draw it right to the edges and let Stage do the corners. Leave it out and your tile is used instead.
guistring
Path to a folder holding your game's own screen, if it has one - see
Your own screen.
Player
[id].game.player.yml
Who the player is, and what they carry, in a file of its own. Leave the file out and the
player is just "you", with nothing to carry a limit on and no words of their own. Leave out the name and the
words, and nothing in your game can name the player at all, which suits a game that never needs to.
name: Wrennoun: propersynonyms: [wren, me, myself]actions: - id: look triggers: - type: response data: text: Mud to the knees, and the coin still in your fist.carries: loadmeasures: - id: load min: 0 max: 3 start: 0
The file holds the player's fields directly, with no player: line above them. If you are moving
a game written before this file existed, cut the player: block out of your config, paste it here
and unindent it.
namestring
What the player is called, where the engine has to name them in a message of its own. Without one
they're just "you". Add noun: proper for a name like Wren, the same as you would for a
character, so nobody calls them "the Wren".
synonymsstring[]
The words the player can use for themselves, like me and myself, so
examine me means something. The story is still told to "you" whatever you put here.
nounstring
How the player's name is introduced, exactly as for objects and characters.
actionsAction[ ]
What happens when the player does something to themselves: look for what they see in
the mirror, eat for an unwise idea. Written like any thing's
actions, and like a character's they can be
ordered, for me, wait.
measures
- id: string min: number max: number start: number thresholds: - from: number triggers: - type: response data: text: string
The player's own measures, such as health or how much they're carrying - see
Measures.
carriesstring
Which of the player's measures says how much they may hold at once. Left out,
nothing is ever refused for weight.
Achievements
[id].game.achievements.yml
The things a player can earn in your game, listed in a file of their own. Each one is
unlocked by an achievement:unlock trigger wherever you decide it's been earned - see
Triggers.
- id: lit-the-lantern title: First Light description: Light the lantern. icon: assets/lantern.png- id: nine-rings title: Nine Rings description: Count the stump's rings all the way round. secret: true
The file holds the list directly, with no achievements: line above it.
idstring, required
What an achievement:unlock trigger calls it. No two achievements may share one.
titlestring, required
Its name, as a player sees it.
descriptionstring, required
What it was earned for.
iconstring
Path to a image for it, from the top of your game: a PNG, at least 256 by 256 pixels, and no more
than 4 MB.
secretboolean
Kept hidden until it's earned, so a list of what can be earned doesn't give the game away.
Sounds
[id].game.sounds.yml
The sounds your game carries, listed once and played by id. The file holds the list directly; every field
each sound takes is in Sound.
The images your game carries, listed once and shown by id. The file holds the list directly; every field
each image takes is in Images.
- id: reading-room file: images/reading-room.jpg alt: A long room of mismatched chairs, with a writing desk against the wall and a clock above it.
Scene
[id].scene.config.yml
Scene files are the building blocks of your game, they allow the player to move between your world.
They sit inside a scenes directory, and can be nested in scene-specific directories - see the
Ferryman demo game as an example.
id: gardenname: The Walled Gardenpresence: - triggers: - type: response data: text: A walled garden, four beds and a wet path, with the rain just off.objects: - id: path synonyms: [gravel] actions: - id: look triggers: - type: response data: text: Wet gravel, and one set of footprints going up it ahead of yours.navigation: - id: south synonyms: [door, inside] triggers: - type: scene:change data: { scene: reading-room }
idstring
The unique identifier of the scene.
namestring
What the scene is called - shown to the player and used to name it in the journal. Falls back to the scene's id when left out.
How the scene describes itself to the user upon entry. It may say things and play sounds, and nothing else, because it's shown again every time the player looks.
The ways out of a scene and into another scene - the same gates and triggers as an action.
tagsstring[ ]
A list of tags used to categorise the scene, which can be used in triggers later.
metadata
# any properties - never read by Stagedescription: string
Your own bookkeeping. Never read by Stage - any properties are accepted.
Objects
[id].object.config.yml
Objects can sit inline or within a file of their own and references by ID in the scene files.
id: keyname: brass keysynonyms: [little key]portable: truestart: reading-roomactions: - id: look triggers: - type: response data: text: Small and brass, and warm from wherever it has been.
idstring
The unique identifier of the object.
synonymsstring[ ]
Alternative words that the player may type to reference the same object.
namestring
The text a player is shown about an object.
metadata
# any properties - never read by Stagedescription: string
Your own bookkeeping. Never read by Stage - any properties are accepted.
noun"common" | "proper" | "plural" | "as-written"
Can be one of common (the default), proper (for a name), plural or as-written.
portableboolean
Whether the object can be held or added to an inventory.
containsboolean
Whether other objects can exist within this object, such as a sack or backpack.
What the scene says about an object that exists within it, and any sounds it plays while it's there.
sizestring
Which of this object's own measures says how heavy it is. Left out, it weighs
one; only read where the game names a carrying limit at all - see
Measures.
startstring
Objects can move between scenes and/or be carried by characters or the player - this decides
where it starts. Can be either a scene ID, offstage or in: (another object).
conditions
- type: string data: object negate: boolean
The conditions that must match for it to be present where it is defined - see Conditions.
The verbs that the object will answer to - see Actions.
measures
- id: string min: number max: number start: number thresholds: - from: number triggers: - type: response data: text: string
The numbers that are attributed to an object, such as quantity, fragility, power, whatever your game needs to keep track of.
affordancesboolean
Provides a way to exclude the object from appearing in the affordances list.
Characters
[id].character.config.yml
Characters can sit inline or within a file of their own and references by ID in the scene files.
id: librariansynonyms: [them, they]start: reading-roomactions: - id: look triggers: - type: response data: text: Halfway through a tray of cards, and glad of the interruption.knowledge: - topic: lock triggers: - type: response data: text: '"The key lives behind the clock," they say, without looking up.'
idstring
The unique identifier of the character.
synonymsstring[ ]
Alternative names that the player may type to reference the same character.
namestring
The text a player is shown about an character.
metadata
# any properties - never read by Stagedescription: string
Your own bookkeeping. Never read by Stage - any properties are accepted.
noun"common" | "proper" | "plural" | "as-written"
Can be one of common (the default), proper (for a name), plural or as-written.
conditions
- type: string data: object negate: boolean
The conditions that must match for the character to be present where it is defined - see Conditions.
The verbs that the character will answer to - see Actions.
Mark one ordered: true and it answers the character being told to do something instead,
with object for the thing the order is about - see Giving orders.
hearsanywhere | adjacent
Leave it out and the character only takes orders from someone in the same room. Set it to
adjacent and they also take them from any room an exit joins to theirs, either way and
locked or not, so you can shout through a door. Set it to anywhere and they take them from
any room at all, as long as they're somewhere in the game.
measures
- id: string min: number max: number start: number thresholds: - from: number triggers: - type: response data: text: string
The numbers that are attributed to a character, such as fragility, power, whatever your game needs to keep track of.
startstring
Characters can move between scenes and/or be carried by characters or the player - this decides where it starts. Can be either a scene ID or offstage.
holdsstring[ ]
Characters can hold objects, this list defines what they are.
What the character knows: the answers they give when they're asked or told about something. An answer can carry knowledge of its own, for what the conversation can lead on to.
What the scene says about a character that exists within it, and any sounds it plays while it's there.
affordancesboolean
Provides a way to exclude the character from appearing in the affordances list.
Giving orders
Sooner or later a player will try bossing your characters about, with librarian, open the door
or tell the librarian to open the door. An order is just another of the character's
actions, marked ordered: true: something the player does to them, by telling them
to do something. A character only answers the orders you've written. Everything else gets turned down,
and the player never ends up doing the job themselves. Either way it costs them a turn, so anything
you've set to happen every turn carries on happening.
id: geniesynonyms: [genie, demon]actions: - ordered: true conditions: - type: flag:is negate: true data: { flag: genie-paid } triggers: - type: response data: text: '"My fee is not paid!"' - id: give ordered: true object: wand indirect: player triggers: - type: object:move data: { object: wand, to: held:player } - type: response data: text: '"I hear and obey!"' - ordered: true triggers: - type: response data: text: The genie folds its arms.
Ordered actions are tried from the top. The first one that fits the order and whose conditions
hold is the answer. Leave out id, object, preposition or
indirect and that order fits anything there, so the last one above catches whatever's left.
A condition with its own failure answers with that when it fails; one without moves on to
the next. indirect: player is how genie, give the wand to me names the player, as
long as you've given the player words like me to answer to. The player can have ordered
actions of their own too, in the player block.
To have the character actually do what they were told, the way the player would, put a
character:obey in the triggers. At that point they walk through the room's real exits, take
and drop things with their own hands, or set off the actions on whatever they were told to touch. Anything
before it is said first, anything after it afterwards. Leave it out and the order is only answered, which
is how the genie above refuses and how a companion says "Lead on" below.
While a character is carrying out an order, they're the one doing it. here is where they're
standing, inventory is their own hands, and scene:change walks them off rather
than the player. So the robot above walks through an exit into a room the player can't fit into, and
pushes a button there that only works for it, using character:obeying. When you really do mean
the player, say so: held:player is always the player's hands, and character:here
with player asks whether the player's in the same room as them. Flags, measures and
achievements work exactly as they always do.
When a character does something, the usual messages are used, like get.done for taking a thing.
If yours reads oddly for anyone but the player, write a second one with -by-character on the
end, and <Character> to name them: get.done-by-character: <Character> picks up
<thing>. The player keeps using the first.
Normally you can only boss about someone who's in the room with you. Give a character
hears: adjacent and they'll hear you from next door too, through any exit between the rooms,
even a locked one. Give them hears: anywhere and they'll take orders wherever they are, as long
as they're somewhere in the game. That's how you shout instructions to the robot you've squeezed through a gap you can't fit through
yourself. When they're in another room, character:here with player doesn't hold,
so their ordered actions can tell you're not with them.
A companion who walks with you
There's no special setting for a companion. They're a character with an ordered action that sets a flag, and an
every-turn rule that brings them along while the flag's set. The rule's own conditions say where they
won't go.
# in the character's actions- id: follow ordered: true triggers: - type: flag:set data: { flag: following } - type: response data: text: '"Lead on."'# in your game's turns fileevery: - conditions: - type: flag:is data: { flag: following } - type: character:here negate: true data: { character: companion } - type: scene:is negate: true data: { tag: haunted } triggers: - type: object:move data: { object: companion, to: here } - type: response data: text: Your companion catches up with you.
There are four ways an order gets turned down: it was given to something that isn't a character, to somebody
who isn't in the room, about more than one thing at once, or the character simply won't. Each one is a
message, so you can put it in your game's own words. Alongside a message's usual <thing>
and <name>, <named> is the word the player used.
# in your game's messages fileorder.not-character: You can't give orders to <thing>.order.absent: There is nobody called <named> here to listen.order.many: One thing at a time, please.order.refused: <Thing> pretends not to hear you.order.crowd: Nobody is listening to "<named>" all at once.order.self: Talking to yourself again?
order.crowd is for an order given to more than one character at a time, like
tell the troll and the thief to go north. Nothing in the player's line happens, and it doesn't
cost them a turn. order.self is for the player bossing themselves about, if you've given them
words like me to answer to.
A comma only counts as giving an order when what comes before it is one of your characters. Anything else
keeps its usual meaning, so take the lamp, the key is still a list, and
the fire, touch it is still about the fire. Talking isn't an order either:
librarian, hello is just the player saying hello, as long as your greeting verb is marked
conversational.
Actions
[id].game.actions.yml
An actions file provides a way for the engine to react to a verb defined nowhere else,
such as wait or pray. If the same verb is defined in the current scene, that will
be used first, else it will fallback to this. Individual scenes can also define their
own [id].scene.actions.yml file to better organise their actions.
The file is the list of actions itself, so there's no actions: line at the top: the name of
the file already says what's in it.
- id: wait triggers: - type: response data: text: Time passes, and nothing comes of it.
The turns file gives you the ability to write rules that happen on each turn with optional conditions -
luck among them - and finally triggers. The game's own runs on every scene's turns; a scene's own,
[id].scene.turns.yml, runs only while the player is in that scene.
The rules go under every:, since they are checked every turn. That leaves room for more ways
of time passing beside it later.
What a character can be asked or told about, deliberately kept separate from the character itself so more than one
of them can share the same knowledge. Your game's own, [id].game.topics.yml, applies everywhere; a
scene's own, [id].scene.topics.yml, only applies there, and its topic IDs are qualified with the
scene's own.
The file is the list of topics itself. Answers that aren't about any one topic live in a
knowledge file of their own, so a topic and an answer never get mixed up.
The topics a player can ask or tell characters about.
Answers anybody gives
[id].game.knowledge.yml
Sometimes a reply isn't about any topic at all: what anybody says when they're asked something they know
nothing about, or when the player just says hello. Those answers live in a knowledge file, holding
the list directly. Your game's own is heard everywhere, and a scene's own, [id].scene.knowledge.yml,
only there.
- verb: ask triggers: - type: response data: text: '"No idea," comes the answer.'
An answer here can't name a topic. If it's about one, write it under that topic in
topics file instead.
The messages file allows you to override the engine's own built-in strings - the things the engine itself
renders, like a refused action or a blocked exit. A built .stg file never bundles Stage's defaults, only
what's written here, so leaving this file out means the engine speaks in it's own words unchanged.
Each message sits at the top of the file under its own key, with no messages: line above them.
get.done: "Taken: <name>."action.blocked: Not just yet.
[id].game.messages.ymlobject
A fixed set of named strings (action.blocked, get.done, journal.achievement and many
more) - write only the ones you want to change. Each key accepts only its own placeholders, and
an empty string silences that message entirely.