TalebenchReference
Package interface 1.2 (what interface in module.json names) · Talebench's module interface 3.5
Messages
Every message is JSON text. The engine sends a point; while answering, the add-on may make host calls and proposals; its answer is one JSON object.
| prepare | once per content key in the sandbox ( |
|---|---|
| on | a story moment, |
| take | an act of a kind it owns ( |
| ask | a question about something it knows ( |
| contribute | its part of shown text ( |
| formula | a formula function it answers ( |
| view | a screen of its own ( |
| migrate | its content and state of an earlier version ( |
| check | its own checks of the game's content (no game played): it answers |
| content | its own content (Talebench's Collections: |
|---|---|
| content_key | a key that changes with its content (read and parse it again only then) |
| story | the game's whole content (only in |
| state | its state slot, |
| meta | its meta slot, |
| value | a game value by its |
| eval | a condition in the story's context, without draws ( |
| draw | a number from the engine's seeded draws |
| set_state | its whole state slot ( |
|---|---|
| effects | the format's effects, applied as the core's ( |
| set_meta | a JSON merge patch on its meta slot ( |
| emit | an event of a kind it owns ( |
| warn | a warning ( |
| again |
|
|---|---|
| flow |
|
| error |
|
| took |
|
| done |
|
| answer |
|
| spans |
|
| text |
|
| value |
|
| parts |
|
| problems |
|
| stopped | given by the sandbox when a call stops (a package that answers it is taken as stopped): what the call proposed is dropped, |
module.json
| Field | What it holds | Checked |
|---|---|---|
|
| lowercase letters, digits and |
|
| both languages, not empty |
| semver | three numbers |
| the package interface it needs, | the major the engine carries, a minor not above the engine's |
| the Talebench format versions its content and state are written for, | the engine's format version within |
|
| known points and kind prefixes; |
|
| the file there, its size and hash the same, at most 2 MB, compiling in the sandbox |
|
| both files there; the story played twice on upload |
| its settings: | the schema subset; every field labelled in both languages |
| its part of a save: | the schema subset |
| its stand-ins in the book: | each text in both languages |
| its pictures: | each file there, a PNG, JPEG or WebP, its size and hash as named, at most 512 KB each and 2 MB together |
Points: on, take, ask, contribute, formula, view, migrate, check. Kinds an add-on may own: effect:<name>, debug:<name>, command:<name>, step:<name>, event:<name>, warning:<name>, check:<name>.
Settings schema
The schema a package describes its content with (L-197 i9; docs/architecture_spec.md, "A package's own content and its form"): a subset of JSON Schema that the engine checks and the editor draws a form from. object (properties, required), string (maxLength, enum), number and integer (minimum, maximum), boolean, array (items, maxItems); default, title and description on any.
Screen parts
text, heading, value, progress, row, column, list, picture, button, found, description, entries, frame
Codes
An add-on file is refused with:
package.asset, package.format, package.function, package.id, package.interface, package.logic, package.logic_changed, package.logic_size, package.manifest, package.migration, package.no_logic, package.no_manifest, package.no_test, package.not_a_zip, package.not_deterministic, package.not_registered, package.owns, package.owns_differ, package.page, package.point, package.schema, package.talebench, package.test_story, package.test_unfinished, package.test_unused, package.test_warned, package.texts, package.too_large, package.version
While a game plays, the engine says:
replay.packages:a replay naming packages run where no sandbox is, or its package file refused (L-197 i6)
engine.module_newer:content that records a module (
moduleVersions) at a newer major version than the engine carries, both versions named (Versions and dependencies)module.view:a module's screen asked for (
Engine::module_view) that it does not have, or a view with a part the player may not draw (unknown, too deep or wide, a picture not in the release) (A package's screens)save.module_newer:a save that names a module (
GameState.moduleVersions) at a newer major version than the engine carries, both versions namedsave.module_migrate:a save of an older version of a package whose migration fails (stopped, or answering neither content nor state): refused rather than misread (L-197 i10)
module.refused:a module package refused what it was asked to carry out; its own code and words in the message (The interface for packages, L-197)
module.stopped:a module package's call stopped (a trap, its step budget or its memory used up): what it proposed in that call is dropped and the game goes on as without it; the package, the point and why in the message — where the engine records warnings (its moments, acts and contributions; a stopped question or formula answers nothing) (The interface for packages)
package.content:A workspace's package's own content (
modules.<id>) does not fit the schema itsmodule.jsondescribes, or lacks a required field: at the field's pointer (A package's own content, L-197 i9)
Talebench's module interface
Talebench's own modules (battles, dungeons, collections …) use these hooks inside the engine; add-ons reach the same moments through package interface 1's messages above. INTERFACE =
3.5 since 2026-11-11 (3.5: by_hand, whether a person decides for a side, the party's or in a match both, L-205a; 3.4: migrate_state, a package's state brought forward as a save loads, L-197 i10; 3.3: view, a module's screen as data, L-197 v1; 3.1: the attack command's boost places, boost, P8-86; 3.2: the question BlowEstimate, what a set-up deals and takes against a foe for the VS screen, P8-95), interface 2 since 2026-11-05; before it, 1.1 added the story modules' hooks for Collections, 1.2 a step and a screen of a module's own for Shops, 1.3 a part of the view, a passage kept open, a way back, a battle's end and the test runs' tries for Camp, 1.4 a start, a passage entered and shown, time passing and the moment for Time, 1.5 an exit target of a module's own, a journey on, a route planned and the test runs' own options for World maps, 1.6 an option offered as several and one of them taken for Decks, L-180; 2.0 gathered every hook into the points below, L-200; 2.1 the services Checks use, 2.2 a page rendered for its reading and the wait over named for its kind for Documents, 2.3 XP won, after a battle and a character's values for Levels, 2.4 a passage being entered and an encounter due for Battles, 2.5 a battle started, ended and played on, a save loaded and a rest, 2.6 the services the battle code uses through the host (B1), L-180; 3.0 took the battle's doings out of BattleHost into the battles crate, which a module adding to a fight depends on (B3, L-180; no package was built against 2.x, so none is kept beside)
| Hook | The engine calls it when | The module answers | Without the module |
|---|---|---|---|
| — | its name, its registry row (version, needs, switch), the prefix of its own options' IDs | — |
| the game reaches a moment ( |
| nothing happens |
| something of a kind it declared is to be carried out ( | its result ( | the core's warning or |
| the game asks a question ( | an | the core's own answer |
| a save recording an older version of a package loads (L-197 i10: the package's | its state slot as this version has it, or why it cannot | the slot as saved |
| a surface is shown ( | its part, changing nothing | the core's part only |
| the player meets something ( | its part, and what meeting it changes (an entry revealed, a card drawn) | the core's part only |
| a formula calls a function no core part has | its value | none (the formula's error) |
| the player asks for a screen of the module (its page from the game's menu, a step's) | its view in the parts the player draws (L-197 v1) | none: nothing is drawn |
| a game's engine is made; a save meets content that changed | its working state; its own state made to fit (true: the position no longer fits) | none; nothing changed |
| an action's effects begin; a damaging blow on a target; a blow it decided landed; after a result on a target | its state for the action; the blow it decides; statuses to put on; effects to add | the damage formula decides; none |
| a test run sees an event; a test run ends | what it shows covered; what of its own no run came to | nothing |
Versions and the compatibility promise
An add-on names the package interface it needs (interface, major.minor); a version of it never changes once uploaded, and a newer one brings content and saves forward with migrate. Talebench keeps its own promise:
- During the prototype (internal testing), the format may change without a migration; project files, saves and releases from that time carry no guarantee.
- From the first version used outside the team (the compatibility baseline: format version 4, recorded on 2026-10-16,
BASELINE_FORMAT_VERSION), every later version (showing the demos to visitors who only play, as in P3-62, is not such a use: nobody outside the team keeps a project file or save from it): - imports every project file any earlier version exported, and plays every earlier release and save, through the migrations above; - reads oldermanifest.jsonfiles of project files: fields are only added, never renamed or removed, and unknown fields are ignored, so an older file never needs its creator's version. - Breaking changes after the baseline are made only as a new
formatVersionwith a migration; refusing an older file is a bug. - CI keeps one stored project file per past
formatVersion(from the baseline on) next to the stored manifest, save and replay, and checks that it imports, validates and plays. For the baseline they were written once from the demos (WRITE_BASELINE=1runs the writer inpackages/demos/test/demos.test.ts) and are never changed:engine/samples/v4/holds The Ember Road's content, its release manifest and a save at its first travel offer (cargo testloads the save and travels on, and parses the manifest also with a field it does not know),engine/tests/replays/baseline-v4-ember-road.jsonthe replay of the way there (it must reproduce its expected run; events may gain fields after the baseline, as the manifest does, so its run must hold every field of the expected one unchanged and may hold more), andengine/samples/v4/project/clockmaker/an unpacked project file, which the API's integration test imports, with and without an unknownmanifest.jsonfield, and makes a preview release of.
- What keeps the promise: the player's shell reads a release's content only after migrating it (
migrate_manifestin the player build; saves still name the content's hash as stored), as the engine does; a replay names the format version of its content and initial state, which are migrated before it runs (its hash is checked on the stored content), and the engine refuses content of another version than its own instead of reading it as current; the meta state, a save record and a.talebench-savefile carry a format version too, with their own migration steps (one stored without a version is the baseline's; one from a newer player is not shown and never overwritten); a draft restored from the history is migrated like any draft loaded; the API'sFORMAT_VERSIONis the engine's (format_versions()), checked by a test. A word that becomes reserved in formulas (a new keyword or function name) comes with a new format version whose migration renames keys that use it; each step's list of words is fixed in its migration, never shared with the language's current list. - The baseline's stored files are checked against a list of their SHA-256 sums (
engine/samples/v4/SHA256SUMS, every file of the baseline listed), so a run that rewrites expected results (UPDATE_REPLAYS) cannot change them. Besides the files above they hold, all written once withWRITE_BASELINE_SAVES=1: a save at each kind of wait (choices, continue, check, battle, read, ask, a stay reply, travel, ending) with the demo content it belongs to (saves/,demos/;engine/crates/talebench-core/tests/baseline.rsloads each), a meta state and a test state from the demos' walkthroughs, a.talebench-savefile and the player's stored record as testers' browsers hold them (player/, read by the player's tests), and a project file written by the real exporter (project-export/, imported by the API's integration test). - Saves from places that came after the baseline (still format 4) are stored the same way, once, in a dated set beside it that is never changed either:
engine/samples-later/2026-10-23/(P8-12) holds a save in a planned battle (The Sunken Choir), at a shop and at the camp (The Ember Road) and at a deck's offer (The Lantern Keeper), each with its demo's content and aSHA256SUMSlist, written withWRITE_LATER_SAVES=2026-10-23bypackages/demos/test/demos.test.ts; a second set,engine/samples-later/2026-10-23b/(P8-14), holds a save inside an open conversation level and one after a visit left from a level (returnLevels; Wintermere's talk with Oskar) and one while riding (the Ember Road's courier on Bramble), written withWRITE_LATER_SAVES=2026-10-23b. The writer refuses a set whoseSHA256SUMSexists, so no run rewrites one; a set written later the same day takes a letter. A battle kind that ships after a set gets saves in its middle in the next set: the third set,engine/samples-later/2026-11-07/(P8-22), holds dice battles with the standard's fighter fields — the throw for who starts still to come, the party's turn in round 2 and a foe's blow waiting for the defence (Dice Duel's first table) and a foe's defence laid face down (the classic tables' Masked Gambler), written withWRITE_LATER_SAVES=2026-11-07.baseline.rsloads each natively and the demos' tests load each in every engine build (playtest, release-full, and release-story where the build plays the content), all still waiting where they were saved. A later set gets a new dated directory (a second one on the same day a letter:2026-10-23b); the sets sit outsideengine/samples/, whose directories are one per format version.
The public API
A workspace's own tools read and change its projects with a key made in the workspace settings. Every route and tool is described in the API's OpenAPI file.