Python reference
Every function a script may call is a host function: bound under the name an author types, present without an import, and answered by the app or by the runtime playing a campaign. There is no import ripple, no class to instantiate and no object syntax. A component or an entity crosses as a dict, because the interpreter has no class.
Functions
Section titled “Functions”Twenty-one names, bound on every surface, in five groups. Signatures are the ones the app registers; the page for each group lists what every function answers and refuses.
Components and entities
Section titled “Components and entities”Detail: Components and entities.
| Function | Does |
|---|---|
component_names() |
Every component the project holds. |
component_get(name) |
One component as its file describes it, as a dict of labels, types and starting values, or None. A type you do not recognise is one a later version wrote. |
entity_names(of=None) |
What the story has made, all of it or only what came from one component. |
entity_get(name) |
A snapshot of one entity’s attributes, or None. Changing the dict changes nothing. |
entity_of(name) |
Which component an entity was made from, or None. |
entity_set(name, attribute, value) |
Writes one attribute. |
entity_create(name, of=None) |
Makes an entity from a component and answers its attributes. Refuses a name that is taken. |
entity_delete(name) |
Unmakes one. The board’s nodes still name it and make a fresh one if they run again. |
entity_rename(name, to) |
Renames one, keeping what it is and what it holds. A node still naming the old one makes a fresh one if it runs again. |
component(name) |
The attributes of a component or entity, as a dict, or None. Superseded by component_get for the definition and entity_get for what the story has changed. |
components() |
Every name you can ask for. Superseded by component_names and entity_names, which do not run the two together. |
set_attribute(name, attribute, value) |
Writes to an entity; True when it landed. Superseded by entity_set, which says why when it cannot. |
Console variables
Section titled “Console variables”Detail: Console variables. A value is coerced to the declared type when that loses nothing, and refused otherwise.
| Function | Does |
|---|---|
cvar_names() |
Every console variable, in declaration order. |
cvar_type(name) |
Its type as the file spells it: one of the twenty-four, such as int, markdown, json, list<string> or map<float>. |
cvar_get(name) |
What it holds now. A list or map is a copy. |
cvar_set(name, value) |
Writes it, coercing to the declared type when nothing is lost and refusing otherwise. |
cvar_reset(name) |
Puts it back to its default. |
Detail: Sounds.
| Function | Does |
|---|---|
play_sound(path, volume=1.0, speed=1.0) |
Plays the file at path (sfx/bell.ogg, as the project spells it) once and returns at once. volume runs from 0 to 1, speed from a little above 0. |
A soundscape is set on a passage, not from Python.
The world outside the story
Section titled “The world outside the story”Detail: The world outside the story. Read only when the reader allows it; a read they did not allow raises PermissionError, unless they gave a stand-in.
| Function | Does |
|---|---|
location_get() |
Where the reader is, as a dict of label, latitude, longitude, accuracy and when, any of which may be None; or None when there is no position yet. |
location_distance(latitude, longitude) |
How far the reader is from there, in metres, or None when the reading has no coordinates. |
The clock is the standard library’s own: datetime.now() and date.today(), read the same way.
The reader’s language
Section titled “The reader’s language”Nothing to allow: a story is played in a language by choosing it.
| Function | Does |
|---|---|
locale_get(locale=None) |
The reader’s language, or the one named — it or it-IT — as a dict of id, language, region, name, englishName, system (metric, us or uk), currency and isDefault, the same facts the Get locale card publishes. None for a language the app does not know, and in the design-time consoles, where nobody is reading. |
Surfaces
Section titled “Surfaces”Who is asking decides what may happen. Every function is bound on every surface; what differs is what a call does.
| Surface | Where | Reads | Writes |
|---|---|---|---|
script |
A Script node on a board | Yes | Yes |
passage |
{{ … }} and ```ripple in prose, and in a Localized string’s words |
Yes | Refused |
console |
The editor’s Python console, at design time | Definitions and defaults | Refused |
agent |
python_eval over MCP |
Definitions and defaults | Refused |
playthrough |
The campaign window’s console, during play | Yes | Yes |
- A passage, and a Localized string’s words, refuse writes because of where the call runs: a state change belongs in a node you can point at on the canvas.
- A passage refuses a sound because its snippets run again on every redraw.
- The console and an agent refuse writes because nothing is running at design time: a project with no playthrough has no entities, and a console variable holds its default.
- Reads work at design time.
component_getandcomponent_namesanswer from the files.entity_names()is[]andentity_get(name)isNone. These are answers, not failures. - The playthrough surface may do everything a Script node may. Play from here starts a story with nothing made, and this console stands in for the nodes the story did not run.
The reasons are in Python in a story.
Calling
Section titled “Calling”Required arguments are positional. The optional of is keyword-only. play_sound and locale_get are the exceptions: play_sound’s volume and speed, and locale_get’s locale, may be given by position or by name. No keyword other than those listed is accepted.
entity_names(of="Goblin") # rightentity_names("Goblin") # raises: too many positional argumentsentity_create("Goblin-7", frm="Goblin") # raises: unknown keyword, names the ones it knows| Function | Positional | Keywords |
|---|---|---|
entity_names |
0 | of |
entity_create |
1 | of |
play_sound |
1 to 3 | volume, speed |
locale_get |
0 to 1 | locale |
Other functions take exactly the positional arguments in their signature and no keywords. A wrong call raises rather than answering. component, components and set_attribute are exempt: what they do with a mistake is as frozen as their names.
Host functions cannot be called inside map(), filter() or sorted(key=…). The interpreter raises. Inside a def or a comprehension they work:
sorted(entity_names(), key=lambda n: entity_get(n)["HP"]) # raises
pairs = [(entity_get(n)["HP"], n) for n in entity_names()] # worksweakest = sorted(pairs)[0][1]Reads are copies
Section titled “Reads are copies”A value crosses from the app to Python through JSON, so what entity_get, component_get and cvar_get answer is a snapshot. Changing it is legal, silent and has no effect:
e = entity_get("Mira")e["HP"] = e["HP"] - 3 # runs, changes nothingentity_set and cvar_set are how a script writes.
Refusals
Section titled “Refusals”Every refusal reaches Python as a RuntimeError carrying one sentence and nothing else.
try: entity_create("Goblin-1", of="Goblin")except RuntimeError: pass # it already existedThe sentences a surface refuses with are fixed, because a script may already have branched on which it got.
| Refused | Sentence |
|---|---|
| A passage, any write | a passage describes the world; only a Script node changes it |
| Console or agent, an entity write | no campaign is running, so there are no entities to change — press play, or put this in a Script node |
| Console or agent, a variable write | no campaign is running, so a console variable holds its default — press play, or put this in a Script node |
A passage, play_sound |
a passage cannot play a sound — its snippets run again every time it is redrawn, so put this on a Play sound node or in a Script node |
Console or agent, play_sound |
no campaign is running, so there is nothing to play this through — press play, or put this on a Play sound node |
Two refusals are not a RuntimeError. A read of the world the reader did not allow is Python’s own PermissionError, so except PermissionError: catches it and nothing else. A clock read that was allowed, when the host has no time to give, is an OSError: this host has not said what time it is. See The world outside the story.
A refusal inside a passage does not end the run. It renders in place as a ⚠ RuntimeError: … chip and the story carries on, which is why every message is one line. A name quoted in a refusal is cut to about forty characters.
Interpreter
Section titled “Interpreter”The interpreter runs a sandboxed Python subset, not CPython: no class, no random, no import requests, no filesystem, no real standard library. What is available is what the sandbox report behind the console’s i button states on the day you ask, and python_capabilities returns the same text to an agent. Where this page and the report disagree, the report is right.
A script that has not finished after two seconds is stopped. See The sandbox and its limits.
Versioning
Section titled “Versioning”These names are a contract with every passage and script written against them from 1.3 onward (play_sound from 1.4, location_get, location_distance and locale_get from 1.6), and are added to rather than repurposed. The shape component_get answers is shared with component_read over MCP.
Related
Section titled “Related”- Python in a story: why Python is a host surface and what each surface is for.
- How to run Python in a story
- How to build a campaign with no Python
- MCP tools:
python_eval,python_capabilitiesandcomponent_read.