Concepts

A Novella game is data, and one runtime plays it. This page describes that data and that runtime: the words the rest of the documentation uses, and the rules that decide what a player sees.

The game definition

Everything about a game is in a game definition:

Part What it holds
Configuration How the game behaves: title, resolution, entry labels, rollback and save settings, preference defaults. Set with define config.key = value.
Theme How it looks: fonts, colours, the dialogue window, choices, menus. Set with define theme.key = value.
Story The labels and everything they refer to.
Assets Where the files come from: folders on disk, files in memory, a Divooka document's library.

A script, a C# program and a Divooka graph all fill in the same definition, which is why they can be mixed: a program can load scripts, change one of their settings and add labels written with the C# story builder, and a graph can build a story from nodes and add a script file to it. Theming lists every configuration and theme key.

Asset paths are relative, use forward slashes and are matched without regard to case, so a game moves between a folder and a document library unchanged. When several sources hold the same path, the one added last wins, which is how a patch folder overrides a document's files.

Stories and labels

A story is an ordered set of labels, each a named block of statements, together with its definitions: characters, images, layered images, transforms, transitions, audio aliases and channels, variable defaults and constants, the gallery, the music room, achievements and translations.

Several scripts merge into one story. Labels must not clash (a label defined twice is an error that names both places), and definitions from a later script replace earlier ones of the same name. Scripts loaded from a folder load in path order, so a file named options.novella or script_01_definitions.novella can come before the chapters that use its definitions.

A label is an entry point. The game starts at start; jump continues at another label; call runs one and comes back, binding arguments to the label's parameters (label chapter_title(number, title):) and receiving a return value in _return. Reaching the end of a label falls through to the next label written in the same file, as in Ren'Py; falling off the last label of a file ends the game, or returns to the caller if the label was called. A return with no caller ends the game. Labels built in C# or by nodes come from no file, so they never fall through: reaching the end of one ends the game, or returns to its caller. The lint warns when a label built in code can run off its end and the label after it is built in code too, since that is where a script's label would have fallen through.

The story also has entry labels the engine looks for: splashscreen runs once at launch before the main menu, main_menu replaces the built-in main menu (when it returns, a new game starts), and after_load is called after a save is loaded. Each is optional and can be renamed in the configuration.

Scenes

A scene is a way to write a story without writing control flow. It is the configuration model Novella One built games from: a scene has a name, a background (brought in with a transition, fade unless another is given), music, its lines, and an ending that says where the story goes next:

Ending What happens
Go to a scene The story continues at another scene.
Choose a scene The player is offered choices, each leading to a scene; a choice can be offered only when a condition holds, and can change variables when taken.
Branch to a scene A condition decides which of two scenes comes next.
End The game ends (or, in a called scene, returns).

A game is the set of its scenes, and the one named start begins it. A scene is not a new kind of thing to the runtime: it becomes an ordinary label of its name, holding the background, the music, the lines and then a jump, a menu of jumps or a branch. So a scene saves, rolls back and validates like any other label, other labels can jump to it, and scenes, labels and scripts mix in one story. Scenes are written with the Scenes nodes in Divooka or with SceneDefinition in C#.

Statements and line ids

Statements are Ren'Py's: dialogue (say), narration, extend, show, hide, scene, with, pause, jump, call, return, menu, if/elif/else, while, assignments ($), play/queue/stop, voice, movie, input, window, nvl clear, notify, achieve, screens (show screen, call screen), custom commands (run) and, in a game that opts into it, python: blocks. A screen is story data too, written in a screen language of boxes, conditions and loops that is laid out again every frame from the story's variables, or a C# class the game registers. The Script Language page has them all.

Every line of dialogue and every menu choice has a stable id: the label's name plus a hash of the speaker and the text, with a counter when the same line occurs twice in a label, or the id written with id name. Ids are content-derived rather than positional, so inserting a line above another keeps the other's id, and with it its seen flag and its translations. Rewriting a line changes its id, which is right: a rewritten line is unseen and needs translating again. Games that will be translated usually write explicit ids so that editing the English does not orphan the translations.

Characters

A character is who speaks and how their lines look: the id the script uses (q), the name shown ("Qinglan", which may interpolate variables, as in "[player_name]"), a name colour and text colour, a font and size, quotation marks to wrap lines in, an image tag, whether that image shows as a side image in the dialogue window, a typing sound, and a kind: adv (the dialogue window), nvl (a full-screen page that fills line by line) or centered (text in the middle of the screen with no window).

Three characters are built in: narrator, centered and nvl_narrator. A line with no speaker is the narrator's.

A character with an image tag can change its picture as it speaks: q smile "..." shows qinglan smile and keeps it, q @ smile "..." shows it only for that line.

Images

An image has a name made of words: the first word is its tag, the rest are attributes. qinglan smile has the tag qinglan and the attribute smile. Only one image per tag is on the stage at a time, so showing qinglan sad replaces qinglan smile.

A full-screen picture (a CG) shown like any other image

Images are defined three ways:

  • From file names, automatically. Every image file among the assets defines an image unless the story defines that name itself: the path without its extension, folders and file name joined by spaces, in lower case, with a top folder called images, sprites or characters dropped. bg/plum_garden.jpg is bg plum_garden; sprites/qinglan/smile.png is qinglan smile.
  • In a script, with image name = source, where the source is a file, a colour, a movie, a placeholder, a filter over another source, a composite or a condition switch.
  • As layered images, assembled from layers chosen by attributes, so show qinglan dress smile blush combines three layers without a file for that combination.

Two images are built in, black and white: solid colours that fill the screen, there for scene black without any definition. A story that defines either name uses its own.

When a script asks for attributes, the engine finds an image that has all of them, preferring one that keeps the attributes already shown: after show qinglan dress smile, show qinglan sad finds qinglan dress sad if it exists. When nothing matches, the image is drawn as a placeholder (a silhouette, or a gradient for a background) and the validator reports it, so a story can be written and played before its art exists.

Recognised image files are .png, .jpg, .jpeg, .webp, .bmp, .gif, .tga and .qoi.

The stage

The stage is the logical picture: which images are shown, on which layer, in what order, with which transforms, and which custom screens are up. The runner owns it and the presentation mirrors it, which is why a save restores the screen exactly and a test can check what is on it without drawing anything.

Three characters on the stage over a background

There are four built-in layers, and onlayer with any other name makes a layer of that name. Bottom to top:

Layer Drawn Typical use
master Under the dialogue window Backgrounds and characters: the default for show and scene.
Any other name Above master and below transient, several in name order Layers a story makes for itself, such as weather between the characters and the window.
transient Under the dialogue window Pictures for one moment: as in Ren'Py, it is cleared automatically after each line, menu, input or click-to-continue pause.
screens Over the dialogue window Pictures that must sit above the text.
overlay Over the dialogue window Frames, vignettes, weather.

Within a layer, images stack in the order they were shown, ordered by zorder and adjusted by behind; scene clears one layer.

Transforms

A transform places and animates an image. Positions written with a decimal point are fractions (of the screen for a position, of the image for an anchor) and integers are pixels, so xpos 0.5 is the middle and xpos 640 is 640 pixels from the left. The built-in positions are left, right, center, midleft, midright, truecenter, top, topleft, topright, offscreenleft, offscreenright and fullscreen; default (bottom centre) applies when no transform is given.

A transform can also be an animation timeline in Ren'Py's ATL style: property settings, interpolations with a warper, pauses, picture lines that change what the image shows, repeat, block, parallel, weighted choice, time, handlers for the image being shown, replaced or hidden, contains and function. An image can itself be a timeline, which is how frames are animated: a candle that flickers wherever it is shown. A timeline is evaluated as a pure function of the time since the image was shown, and each shown image carries a seed of its own, kept in saves, that its random choices are drawn from, so an animation is exact after a load or rollback. Its expressions can read the transform's own parameters and the built-in functions, but not the game's variables, because an animation must not depend on state a save would not restore. When an image already showing is given a new transform, the new timeline starts from where the image was, so an interpolation such as ease 1.0 xalign 0.0 glides from the old position rather than jumping back to a default first.

Transitions

A transition animates a change of picture. Written as with on a show, hide, scene or window statement, at the end of a line of dialogue (where it plays before the line appears), on show screen or hide screen (a called screen instead fades in over the transition's duration), or on its own line, it runs over everything that changed since the player last interacted, from the picture before to the picture after. Transitions are specified in the model (dissolve, fade, pixellate, move, slide, push, wipe, iris, image dissolve, zoom, punch, flash, blinds, squares) and drawn by the presentation. The player can turn transitions off in the preferences, and skipping passes over them.

The runner and interactions

The runner is the story's virtual machine. It is single-threaded and synchronous: it executes statements until one needs the player, and then waits on that interaction. There are seven kinds:

Interaction Made by Answered with
Say dialogue and narration a click
Menu menu the index of a choice
Input input the text typed
Pause pause a click, or the time running out
Transition with the transition finishing
Movie movie the movie ending, or a click if skippable
Screen call screen the value the screen returns, or none if the player closes it

show, hide, scene, audio and assignments update the state and run on without waiting. Nothing in the runner draws, plays sound or reads a clock, which is what lets the same runner sit under a window, a headless test and an automated playthrough. A story that runs a million statements without waiting stops with an error, which catches a while loop with no line or menu in it.

Variables

A story's variables live in the game state and are set with $, default, input and menu set lists. Names beginning persistent. live in the persistent store instead (below), and a called label's parameters are local to that call. A few variables are set by the engine:

Variable Set by
_return return value, a run command's result, a called screen's result
_choice, _choice_index the text and index of the menu choice just made

default name = value sets a variable when a new game starts. define name = value sets a constant at start and again after every load, so a changed constant takes effect in old saves.

A variable holds a number, text, a boolean, none or a list. A python: block, in a game that opts into Python, is given the story's variables and hands back what it set that is one of those; anything else Python holds as data, such as a dictionary, a set or an object of a class init python: defines, is pickled into one hidden story variable, _python, whenever a block changes it, so it saves, loads and rolls back with the story's own variables. Functions and classes are code, defined again whenever the game starts, so they belong in init python:. Python there has its standard library (random, os, json, pickle, datetime and the rest import), but for chance a story still uses its own random, chance and choice, because their draws come from the game state and a rollback replays them, where Python's random draws again.

Checkpoints and rollback

Every line, menu, input, click-to-continue pause and called screen is a checkpoint: the runner keeps a snapshot of the state before it, that is the variables, the stage, what each audio channel plays, the call stack, the history, the NVL page and the random generator's state. Rollback (the mouse wheel up, Page Up, or Back on the quick menu) restores the previous checkpoint and shows its interaction again; roll forward replays the answer the player gave before. A line that does not wait for a click (one ending in {nw}) would advance again the moment it was shown, so rolling back passes over it to the line before. The quick menu's Back is dimmed when there is nothing to roll back to: at the first line, when rollback is off, in a live script, or at a menu that rollback_past_choices = false protects.

The history screen

Because the random generator is part of the state, a random draw is replayed when a player rolls back and chooses again: rolling back is not a way to reroll a dice. config.rollback_limit sets how far back a player can go, config.allow_rollback turns rollback off, and config.rollback_past_choices = false stops rollback at a menu so a choice cannot be retaken.

Saves and persistent data

A save is the newest checkpoint plus what the load screen shows: the time, the label, the line or menu it was made at, the play time, the game version and a thumbnail. Loading a save restores the screen exactly, re-applies the script's constants, calls the after_load label if there is one, and resumes the music that was playing. Slots are named page-slot (1-3), auto-n and quick-1; each is a JSON file with a PNG thumbnail beside it, in %AppData%/Novella/<game id> on Windows. The game autosaves at menus (at most every thirty seconds) and when the player quits or returns to the main menu.

The save screen

Achievements, kept in persistent data (Simplified Chinese)

A save addresses its position as a label plus a path of statement indices, so a save made inside a label survives edits to other labels but may land on a different line if statements before it in the same label are added or removed; a save whose label no longer exists cannot be loaded, and the player is told so.

Persistent data is everything that survives across playthroughs and is never rolled back or replaced by loading: lines seen (for skipping seen text), images seen (for the gallery), audio heard (for the music room), labels reached, choices made in earlier playthroughs (shown dimmer), achievements, persistent. variables and the player's preferences. It is saved in persistent.json beside the saves. Use it for anything a player should keep after loading an old save, such as endings seen.

Stories written in code

A story written with the C# story builder, or built from nodes, is data in exactly the same way as a parsed script: labels of statements, with conditions as expressions and changes to variables as assignments. Nothing about it is a running procedure, so it saves, loads, rolls back, skips seen text and gets line ids like a scripted story.

A save does not contain the story. It holds a position (a label and a path of statement indices), the variables and the rest of the state, and loading it finds that position in the story the game has now. A script provides the same story every time because it is the same file; a program provides it because it runs the same code every time it starts. The rule that follows is to build the same story on every run: a story whose shape depends on the clock, a random number or a file that changes would load old saves at the wrong place.

A condition or effect can be a C# lambda over the story's variables instead of an expression; in a story built from nodes, a Function graph handed to a From Function node plays the same part, and what follows holds for it too. The lambda itself is never saved; the story built on the next start holds it again, and what it reads and writes are the story's variables in the game state, so its changes roll back and save with everything else. Two consequences:

  • A lambda should touch only the story's variables. Anything it changes outside the story (a field of the program, a file) does not roll back when the player rolls back, and is not restored by loading.
  • A lambda has no script form. A condition built from parts (a variable compared with a value, a label visited, conditions joined) is the same expression a script's if holds, so the story prints as script and reads back the same; a lambda prints only a description such as a C# condition, which does not read back.

Live scripts

A procedure can be the story instead of labels. A C# method or a Divooka procedural graph implements the game's Script callback (or is passed to UseLiveScript), and starting a game runs it on a thread of its own, where Say, Show, Scene, Menu, Input and the rest are blocking calls: each plays immediately through the runner, exactly as a script statement would, and returns when the player has answered. Menu returns the index of the choice, ready for a Branch or Switch. It is the most direct way to write a small game as nodes.

Its limits follow from what it is. A running procedure's position is not data, so a live script cannot be saved, loaded or rolled back: the save screen still opens, but choosing a slot, or quick saving, shows the notification The game cannot be saved here. and writes nothing, and autosaves are skipped. Call Label runs a story label from a live script and returns to it afterwards; the label behaves as written in every other way, but while a live script is underneath it, it cannot be saved or rolled back either. For a game that players save, write the story as labels or scenes (in script, with the C# story builder, or as nodes) and use code for commands.

How a session flows

When the game opens, it runs the splashscreen label if there is one, then shows the main menu (or runs the main_menu label). Start begins a new game at the start label (or runs the live script), Continue loads the most recent save, and the game menu (right click or Escape) holds History, Save, Load, Preferences and, when the story defines them, the Gallery, the Music Room and Achievements. When the story ends, the game returns to the main menu through config.end_game_transition.

Controls

Click, Space or Enter advances; the wheel up or Page Up rolls back, and the wheel down or Page Down rolls forward; right click or Escape opens the game menu; Ctrl skips while held, Tab toggles skipping, and A toggles auto-forward; H or a middle click hides the window; F5 and F9 quick save and quick load; S saves a screenshot; F, F11 or Alt+Enter toggles fullscreen; the number keys choose menu options. In developer mode, Shift+O opens the console and Shift+R reloads the scripts.

A gamepad plays the game as the keyboard does:

Pad Does
A Continues, and presses the selected button.
B Goes back, or opens the game menu.
Start Opens the game menu.
D-pad, left stick Moves between buttons, repeating while held.
Left and right bumpers Roll back and roll forward.
X Hides the window.
Y Toggles auto-forward.
Back Toggles skipping.

Every screen works with a pad, a game's own screens included, because the pad's buttons are the keys the screens already answer: nothing has to be written for it.