API

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 settings
ferryman.game.vocabulary.yml    the game's words
tavern.scene.config.yml         the tavern itself
tavern.scene.topics.yml         what can be asked about in the tavern
lantern.object.config.yml       a lantern
guard.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.

verbs:
  - id: walk
    synonyms: [go, head, enter]
    navigational: true

  - id: look
    synonyms: [examine, read]

  - id: ask
    synonyms: [question]
    conversational: true

  - id: wait
    synonyms: [z]

directions:
  - id: north
    synonyms: [n]

  - id: south
    synonyms: [s]

articles:
  - id: the
    synonyms: []

prepositions:
  filler:
    - id: to
      synonyms: [towards, at]

  significant:
    - id: with
      synonyms: [using]

conjunctions:
  coordinating: [and, then]
  subordinating: []

verbs
- id: string
  synonyms:
    - string
  affordances: boolean
  navigational: boolean
  conversational: boolean
  recipient-first: boolean

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.

prepositions object

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.

conjunctions object

Conjunctions allow commands to be chained by the player.

conjunctions.coordinating string[ ]

Coordinating conjunctions can be declared such as and, but, then etc.

conjunctions.subordinating string[ ]

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-room
start: reading-room

metadata:
  title: The Reading Room
  version: 1.0.0

store:
  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.png

gui: gui/

id string

The unique identifier of your game.

start string

The first scene which loads in your game.

metadata object

What your game is, as far as the engine needs to know.

metadata.title string

The title of your game, a human readable name.

metadata.version string

The current version of your game. Semantic versioning is recommended, though other formats are fine.

metadata.copyright string

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.

store object

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.description string

What your game is about, in your own words.

store.assets object

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.tile string

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.banner string

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.screenshots string[ ]

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.rating string

What you were told to say about who your game is for, word for word: PEGI 16, ESRB Teen, USK 12.

store.genres string[ ]

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.release object

When the game came out.

store.release.date string

The day, as YYYY-MM-DD.

store.developer
name: string
website: string

Whoever made the game. Their website, like the game's own, has to start with http:// or https://.

store.publisher
name: string
website: string

Whoever put the game out, with the same rule for their website.

store.website string

The game's own place on the web, as against the people behind it. It has to start with http:// or https://.

export object

How your game becomes a program of its own, one somebody can double-click without installing anything. See handing it to someone.

export.bundle-id string

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.platforms object

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.icon string

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.

gui string

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: Wren
noun: proper
synonyms: [wren, me, myself]

actions:
  - id: look
    triggers:
      - type: response
        data:
          text: Mud to the knees, and the coin still in your fist.

carries: load

measures:
  - 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.

name string

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".

synonyms string[]

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.

noun string

How the player's name is introduced, exactly as for objects and characters.

actions Action[ ]

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.

carries string

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.

id string, required

What an achievement:unlock trigger calls it. No two achievements may share one.

title string, required

Its name, as a player sees it.

description string, required

What it was earned for.

icon string

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.

secret boolean

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.

- id: drawer-lock
  file: sounds/drawer-lock.mp3

- id: clock-ticking
  file: sounds/clock-ticking.wav
  loop: true

Images

[id].game.images.yml

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: garden

name: The Walled Garden

presence:
  - 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 }

id string

The unique identifier of the scene.

name string

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.

presence
- conditions:
    - type: inventory:has
      data:
        object: string
  also: true
  triggers:
    - type: response
      data:
        text: string

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.

actions
- id: string
  conditions:
    - type: string
  triggers:
    - type: string

The verbs a player may type within the scene, and what happens when they do - see Actions.

objects
- string

- id: string
  name: string

The objects that exist in the scene.

characters
- string

- id: string
  name: string

The characters that exist in the scene.

navigation
- id: string
  synonyms:
    - string
  affordances: boolean
  conditions:
    - type: string
  once:
    failure:
      triggers:
        - type: string
  triggers:
    - type: scene:change
      data:
        scene: string

The ways out of a scene and into another scene - the same gates and triggers as an action.

tags string[ ]

A list of tags used to categorise the scene, which can be used in triggers later.

metadata
# any properties - never read by Stage
description: 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: key

name: brass key
synonyms: [little key]
portable: true
start: reading-room

actions:
  - id: look
    triggers:
      - type: response
        data:
          text: Small and brass, and warm from wherever it has been.

id string

The unique identifier of the object.

synonyms string[ ]

Alternative words that the player may type to reference the same object.

name string

The text a player is shown about an object.

metadata
# any properties - never read by Stage
description: 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.

portable boolean

Whether the object can be held or added to an inventory.

contains boolean

Whether other objects can exist within this object, such as a sack or backpack.

presence
- conditions:
    - type: inventory:has
      data:
        object: string
  also: true
  triggers:
    - type: response
      data:
        text: string

What the scene says about an object that exists within it, and any sounds it plays while it's there.

size string

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.

start string

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.

actions
- id: string
  conditions:
    - type: string
  triggers:
    - type: string

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.

affordances boolean

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: librarian

synonyms: [them, they]
start: reading-room

actions:
  - 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.'

id string

The unique identifier of the character.

synonyms string[ ]

Alternative names that the player may type to reference the same character.

name string

The text a player is shown about an character.

metadata
# any properties - never read by Stage
description: 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.

actions
- id: string
  conditions:
    - type: string
  triggers:
    - type: string

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.

hears anywhere | 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.

start string

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.

holds string[ ]

Characters can hold objects, this list defines what they are.

knowledge
- topic: string
  verb: string
  conditions:
    - type: inventory:has
      data:
        object: string
  triggers:
    - type: response
      data:
        text: string
  knowledge:
    - topic: string
      verb: string
      open: true
      triggers:
        - type: response
          data:
            text: string
  id: string

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.

presence
- conditions:
    - type: inventory:has
      data:
        object: string
  also: true
  triggers:
    - type: response
      data:
        text: string

What the scene says about a character that exists within it, and any sounds it plays while it's there.

affordances boolean

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: genie
synonyms: [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.

id: robot
actions:
  - id: walk
    ordered: true
    triggers:
      - type: response
        data:
          text: '"Whirr, buzz, click!"'
      - type: character:obey
  - id: push
    ordered: true
    triggers:
      - type: character:obey
  - ordered: true
    triggers:
      - type: response
        data:
          text: '"My programming is insufficient."'

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 file
every:
  - 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 file
order.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.

[id].game.actions.yml
- id: string
  conditions:
    - type: string
  triggers:
    - type: string

The verbs answered from anywhere - see Actions.

Every turn

[id].game.turns.yml

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.

every:
  - conditions:
      - type: scene:is
        data: { tag: outdoors }
      - type: chance
        data: { name: thunder, of: 6, index: 1 }
    triggers:
      - type: response
        data:
          text: Somewhere over the hills, thunder.

every
- conditions:
    - type: inventory:has
      data:
        object: string
    - type: chance
      data:
        name: string
        of: number
        index: number | number[ ]
  triggers:
    - type: response
      data:
        text: string

The rules checked at the end of every turn.

Topics

[id].game.topics.yml

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.

- id: lock

- id: garden
  synonyms: [flowers]

[id].game.topics.yml
- id: string
  synonyms:
    - string
  affordances: boolean
  knowledge:
    - verb: string
      conditions:
        - type: inventory:has
          data:
            object: string
      triggers:
        - type: response
          data:
            text: string

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.

[id].game.knowledge.yml
- verb: string
  conditions:
    - type: inventory:has
      data:
        object: string
  triggers:
    - type: response
      data:
        text: string

Answers not filed under any particular topic.

Messages

[id].game.messages.yml

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.yml object

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.