Appearance
Author's Guide
Version 0.0.2 — 2026-06-19
This guide describes how to build dialogue and narrative content in Tarinoi. It is written for two audiences: authors who design and write game content, and developers who implement the game systems that content depends on. The two groups work closely together, and this guide is intended to give both a shared vocabulary and a clear picture of where each side's responsibilities begin and end.
The guide covers the authoring model and its building blocks, how to design well-formed flows, and the conventions and coordination practices that keep large projects manageable.
1. Project structure
A Tarinoi project is the top-level container for all content belonging to a single game. Within a project, content is organised into boards — the main working surfaces where cards and connections live — and collections, which are named groups of related documents (cards, entities, variables, lists, and functions).
How you partition content across boards and collections is up to you, and the right structure will depend on the scope and nature of your project. It is worth spending time on this early: a well-organised project is much easier to navigate as it grows.
One important property: a board's identity is stable. Once created, a board has a permanent ID that is independent of its name and location. You can rename a board, move it to a different folder, and reorganise your project structure at any time without breaking any connections or jumps that point to cards on that board.
2. Cards and card bases
Cards are the fundamental building blocks of dialogue flows. Every card has a base that determines its role in the flow. Tarinoi ships with three built-in card types and four extensible bases. You build your actual card vocabulary on top of them using templates.
Jumps
A jump is a link to another card anywhere in the project. Jumps can target cards with or without input pins. They are useful for returning to a shared starting point from deep within a flow, for connecting large flows that span multiple boards, and for referencing shared content without duplicating it.
See Jump patterns for guidance on when and how to use them.
Backdrops
A backdrop is a zone with a title at the back of the graph. Use them to mark out areas in your flows to make them easier to navigate. Backdrops have a title that scales with zoom so they'll always be legible. You can also colour-code them to your needs.
Line cards
The line base is the primary vehicle for dialogue content. A line card has a line property that holds the text to be shown to the player, and an entity slot for the character who speaks or performs the line. Line cards are what appears in dialogue; non-line cards are traversed silently (see Flow control).
Annotations
Annotations are organisational tools. They do not participate in the flow and produce no game output. Use them the same way you would use comments in code: to document intent, flag areas that need attention, or group related cards visually on the board.
Media cards
Media cards are for displaying pictures in the graph. Use them to create mood boards and to illustrate your flows.
Media cards are not for in-game content. They're there to support authors in their creative work. Tarinoi resizes and compresses the images for optimal use on boards. Use your original assets in the actual game.
Blanks
The blank base provides an empty starting point for cards that do not directly produce dialogue. A blank card is traversed silently and is the right base for structural cards such as hubs, routing cards, and any card type whose purpose is to control flow rather than produce output.
Custom card templates
The built-in bases are the foundation, not the limit. You are expected to define your own card types using templates. A template can:
- Set a base (
lineorblank, most commonly) - Define additional properties beyond the default line text
- Add output pins (a card with one pin is a simple chain; a card with multiple pins forks the flow)
- Set default colours and icons for visual organisation on the board
Examples:
- A SkillCheck card (line-based) presents a line and triggers a skill check. It might have
skillandthresholdproperties and two output pins:successandfailure. - A Fork card (blank-based) silently evaluates a condition and routes flow through one of several output pins without producing any output.
- A SetFlag card (blank-based) fires a function to set a game flag and passes flow through a single output.
- A TriggerAnimation card (blank-based) fires a function to trigger an animation in the scene, then continues through a single output.
Template changes require coordination with programming. Every card template is part of the game's data contract. Adding a property, renaming a pin, or changing a base has gameplay implications — and may break existing content — so changes must be agreed on with the development team before being made. Do not change an existing template unilaterally in a live project.
3. Entities
An entity represents any named thing in the game world: a character, an inventory item, an in-world object, a journal entry, a quest, or anything else that has an identity and potentially game-facing text. At the data level, an entity is a typed container for properties.
Entities marked as dialog_capable can be associated with cards. A dialog_capable entity on a line card is the character speaking or performing that line.
If an entity is marked as a player character, any line card associated with it is treated as a player choice (see Flow control).
Like card templates, entity types are defined using templates. An entity template specifies what properties the entity has and what those properties mean in the game. You can define as many entity types as you need: characters with voice and portrait, item types with descriptions, quest templates with objectives, and so on.
Entity lifecycle and team ownership. The programming team is responsible for implementing the game-side behaviour of each entity type. Before creating a new entity template — or changing the properties of an existing one — coordinate with programming. The structure of an entity is part of the game's data contract.
4. Variables, lists, and functions
These three constructs are how Tarinoi content connects to game state.
Variables hold values that change during gameplay — flags, counters, stats, inventory quantities. In Tarinoi, a variable is a declaration: you give it a name and a type. The actual storage and update logic lives in the game engine.
Functions are callable operations: check a skill, get a character name, log an event. In Tarinoi, a function is a signature: its name and parameters. The implementation is in the plugin.
Lists are enumerations of named values — for example, a list of skill names, or a list of relationship states. Lists are fully defined in Tarinoi and the runtime plugin resolves references to them by name. They are the one construct where Tarinoi holds the authoritative definition.
Coordinating the game API with the development team. The set of variables, functions, and their signatures is the authoring API. Designing it well requires input from both sides: authors need to know what data is available and how to invoke game behaviour; developers need to know what the narrative requires before building the supporting systems. How savegames work, how variables are scoped, and what side effects functions have are all decisions with deep gameplay implications. Start these conversations early and revisit them as the design evolves.
5. The plugin boundary
Tarinoi handles the structure and content of dialogue: what cards exist, how they connect, what text they contain, what conditions gate them, and what functions they invoke. Everything that happens as a result — evaluating those conditions, executing those functions, advancing the narrative in the game world — is handled by the runtime plugin.
This boundary matters because it sets clear expectations:
- If you want to check whether a variable has a certain value, Tarinoi provides the condition; the plugin evaluates it at runtime.
- If you want to set a flag when a player selects a card, Tarinoi holds the function call; the plugin executes it.
- If you want to route flow based on a die roll, Tarinoi holds an output selector that calls a function; the plugin does the math and returns a result.
Tarinoi is not a programming language. Do not try to implement logic in Tarinoi — express it as function calls and condition references instead, and implement the actual behaviour in the plugin. Complex logic embedded in Tarinoi is hard to debug, hard to maintain, and will drift out of sync with the game.
6. Flow control
Output selectors
A card with multiple output pins must have an output selector: a function that returns a pin name. The runtime plugin calls the selector when that card is reached, and flow continues through the returned pin. The selector is where routing logic lives — skill check outcomes, condition evaluations, random branches, and so on.
You can use any function that returns a string as an output selector. It's up to you to implement it on the engine side so that the string it returns matches your output pin names.
Input pin conditions
An input pin can have a condition that determines whether the option it represents is available to the player. If the condition evaluates as false at runtime, the pin — and the card it leads to — is not shown.
An empty condition always evaluates as true. A pin with no condition is always available.
Show-once lines
A line card can be marked show only once. A card with this flag is presented to the player the first time it is traversed — a player line selected, or an NPC line passed through — and never again, for the rest of that playthrough. It is available only on line-based cards.
The flag is a gate on availability, exactly like an input pin condition, and it stacks with one: a card that has both is shown only when its condition passes and it has not been traversed before.
Typical uses are optional detail the player can ask about once, a greeting that should not repeat on a return visit to a hub, and a sequence of lines that plays through in order and then falls silent.
A show-once card is not guaranteed to be available, so a set made up entirely of show-once and conditional cards can leave the player with nowhere to go. See No condition-less fall-through in a set of cards.
PC lines, NPC lines, and line_mode
When a line card's entity is marked as a player character, the card is presented to the player as a choice to select. When the entity is an NPC, or when there is no entity, the card is shown as an NPC line with no player action required.
This behaviour can be overridden per card using the line_mode field:
| Value | Behaviour |
|---|---|
inherit | Default. Determined by the card's entity. |
pc | Always presented as a player choice. |
npc | Always presented as an NPC line. |
It may be helpful to define separate PC line and NPC line templates, with this flag pre-set, and with different colours. They make the dialogue easier to visualise.
Silent traversal of non-line cards
Non-line cards — blank-based cards, forks, hubs — are traversed silently. The runtime moves through them without showing anything to the player, following the output selector until it reaches line cards. This is useful for logic and routing, but see What makes a well-formed flow for the recommended pattern.
7. What makes a well-formed flow
The hub pattern
When a flow has both line cards and non-line routing cards, mixing them at the same level quickly becomes visually confusing and structurally fragile. The standard pattern is to use a hub: a blank card that acts as a collection point, with the lines fanning out from it as children. Routing logic that needs to happen before the lines fan out goes in the hub's output selector.
Visible function triggers
Cards that connect to game mechanics should be visually distinct. If you frequently trigger functions from regular lines, you should display the function you trigger in one of the slots that makes it visible on the board. Another option is to create templates specifically for different types of function triggers. Whichever way you go, it's a good idea to keep them visible rather than burying them in properties only accessible when you drill down.
No mixed PC and NPC lines at the same level
A card's children — the set of cards reachable through its output pins — should be either all PC lines or all NPC lines, never a mix. A set that contains both will only show the type the runtime selects; the others will be unreachable.
NPC lines require unique conditions
If a card has multiple NPC line children, each needs a condition that identifies when it should be shown. Two NPC lines with the same condition, or no condition at all, means one of them can never be reached. Since an empty condition is always true, if all NPC children have empty conditions only the first one reached will ever be shown.
Dead-end cards
A card that ends a flow should have no outward connections. Any connections from a dead-end card will be ignored by the runtime, but they create visual noise and suggest a continuation that does not exist.
No empty pins
An output pin with no connection represents an unfinished flow. Every pin in a finished flow should connect somewhere. An unconnected pin will cause the flow to end unexpectedly when the runtime reaches it.
8. Antipatterns
There are certain traps it's easy to fall into if you're not careful. Sometimes the easiest way to do something now will create a lot of pain later.
No condition-less fall-through in a set of cards
If every child line of a card has a condition, it is possible for all of those conditions to be false at runtime — leaving the player unable to proceed. The same applies to show-once cards: once traversed, they are gone, so a set of them can empty out just as completely. Where possible, designate one child line as the fall-through by giving it no condition and no show-once flag. If a fall-through is not appropriate for the design, manually verify that at least one condition is guaranteed to be true for all possible game states.
The structure health check flags any line set in which every card is gated this way, as No fall-through card.
A common exception to this rule is when you have two different options based on whether a flag is set or not. If you have
CheckFlag( some_flag )one one and!CheckFlag( some_flag )on another, one of these will always betrue. But that's on you.
Tarinoi is not a visual programming language
Tarinoi's condition builder and function call system are for invoking game logic, not implementing it. If you find yourself constructing elaborate condition chains to encode business rules, those rules belong in the plugin. Write a function, agree on its behaviour with the development team, and call it from Tarinoi. Just because you could do a lot with conditions, forks, and functions on cards doesn't mean you should — that will easily turn into a QA nightmare.
Tarinoi is not a media repository
Tarinoi uses images sparingly: as character portraits, project cover images, and for media uploads. They are always resized and compressed to work optimally in Tarinoi. That means they are unsuitable for use as in-game assets. In-game assets need to be optimised for your game, and these production pipelines are out of scope for Tarinoi.
Too many flows on a board
It is possible to put multiple independent flows on the same board. This almost always creates confusion: flows overlap visually, jumps may accidentally target cards in the wrong flow, and a busy board is slow to navigate. Keep one flow per board.
Flow is just too big
Tarinoi supports up to 10,000 cards per board. That doesn't mean it's a good idea to try to reach that ceiling. In practice, flows above around 1,000 cards become difficult to navigate, review, and maintain. A flow that big, even with a lot of reactivity and mutually-exclusive branches, is asking your player to stay in a dialogue for a pretty long time.
Ask yourself if that's the gameplay experience you want to have. If a flow is growing large, consider splitting it up at natural boundaries and connecting these to gameplay activities..
9. Triggering game events
Function calls can be placed directly in card content. When a player selects a card — either by choosing a PC line or by the flow reaching an NPC line — the card's function calls are triggered in the order they appear.
Use this for side effects that should happen as a direct result of a player choice or narrative moment: setting flags, logging events, triggering animations, awarding items, advancing quests. The function call is authored in Tarinoi; the implementation runs in the plugin.
10. Jump patterns
Same-board jumps: returning to a hub
The most common use of a jump within a single board is returning to a hub from somewhere deep in a flow. Rather than drawing a long wire back across the board — which is hard to follow and easy to accidentally disconnect — use a jump that targets the hub card. The flow reads as a clean return; the long-distance connection stays out of the way.
Cross-board jumps: use sparingly
A jump can also target a card on a different board. Cross-board jumps are harder to trace: source and destination are not visible at the same time, and following the flow requires switching boards. Reserve them for cases where content genuinely needs to be shared across multiple flows — for example, a common epilogue sequence that several independent flows converge on.
Avoid cross-board jumps as a purely organisational shortcut. If a flow is too large for one board, splitting it is the right answer; but if the two halves connect, a well-placed jump between them is preferable to an architectural tangle.
11. Naming conventions
Consistent naming is one of the most effective tools for keeping a large project navigable. A project without a naming convention will accumulate cards labelled "Node 47", collections called "test copy 2", and entity types that are indistinguishable at a glance.
Define a convention before you start and apply it everywhere: card labels, board names, collection names, entity type names, variable names, function names, pin names. Tarinoi does not enforce any particular style. Teams with a GDScript background often prefer snake_case_names; teams with a Unity background often prefer PascalCaseNames. Either works. The important thing is consistency.
Write the convention down somewhere accessible to the whole team, and apply it from day one.
12. Playback and testing
In-app playback
Tarinoi includes an in-app playback mode that lets you walk through a flow as it is authored, without a running game build. Playback is the fastest way to catch structural problems: dead ends, unreachable branches, missing connections, and flow logic that looks right in the graph but does not read well as dialogue.
What playback can tell you:
- Whether the flow structure is complete (no empty pins, no broken connections)
- Whether the authored content reads well in sequence
- Whether branching logic is present and plausible
What playback cannot tell you:
- Whether conditions evaluate correctly at runtime — condition evaluation requires the game plugin
- Whether function calls have the correct parameters or produce the intended side effects
- Whether the flow behaves correctly with real game state
- How the flow plays once show-once cards have been used up — playback shows every card each time it reaches it, regardless of the flag
Playback is a structural and content review tool, not a substitute for testing with a running game build. Use it early and often during authoring; test with the plugin before shipping.
In-engine playback
The Tarinoi plugin syncs game content with the game engine in real time. When you need to iterate on dialogues to see how they play in actual in-game scenes or debug conditions, game mechanics, branching, or side effects, enable real-time content sync and play away — the engine's debug console will tell the full story.
Dialogue content is loaded dynamically. You won't even need to reload your scene to see the changes — although you may want to implement your own debug tools to reset game state so you can replay the same dialogues.
13. Collaboration and versioning
Version history
All changes to a Tarinoi project are versioned. Every time you commit, a snapshot of the project is saved. Projects linked to a Git remote (see the Git Remotes guide) maintain a full commit history in the remote repository that is readable by standard Git tooling.
Working with other authors
Real-time collaborative editing is a core Tarinoi feature. You can work on boards together with your colleagues and see what all of you are doing in real time. However, if you're working at cross purposes or without coordination, you can break things. Plan collaborative sessions ahead of time, decide what the objectives are, and talk to each other while you're working. Otherwise, try not to step on each other's toes.
Tarinoi does not currently enforce exclusive access to boards or cards. When not engaged in planned collaboration sessions, coordinate by convention: agree on who owns which boards, avoid editing the same board at the same time, and sync regularly to avoid diverging states. Commit frequently — that's what creates the authoritative change log.
Future versions of Tarinoi will introduce collaboration features in Tier 2 — claims, locks, and other tools for coordinating concurrent work — that will make multi-author projects easier to manage. Until then, or if you choose to stay on Tier 1, communication and discipline substitute for enforcement.
14. Refactoring and project-wide changes
Entities, functions, variables, and lists are referenced from cards. Templates define the structures of cards and entities. If you change these, the references, entities, and cards will need to be updated. Tarinoi keeps track of this drift and offers you tools to make these updates immediately, upon request, and when you commit. However, while Tarinoi guarantees document and collection-level consistency, semantic project integrity isn't guaranteed, and if you don't pay attention to it, you can break things.
To avoid these problems, plan changes to functions and templates ahead of time, avoid compatibility-breaking changes, and coordinate with the game programming team to make sure nothing breaks between the Tarinoi project and the implementation.