Basic Concepts

Understanding the fundamental building blocks of interactive storytelling in TellsTree.

πŸ“š

Basic Concepts

Understand the basics of interactive storytelling

1
Variables
2
Edge Types
3
Node Types
4
Graph Structure
🎯

Wichtige Konzepte

βœ“
Edge-Semantik
3 Typen: contains, flow, link
βœ“
Validierung
Only flow edges are testable
βœ“
Hierarchie
contains for structural belonging
βœ“
Referenzen
link for loose, untestable connections
πŸ’‘

Why this matters

β†’
Klare Semantik
Every edge type has one clear meaning
β†’
Bessere Validierung
Automatic tests check flow completeness
β†’
Intuitive UI
Unterschiedliche Darstellung je nach Typ
β†’
Saubere Architektur
Separation of structure, flow and references

πŸ”’ Variables & Query DSL

TellsTree uses its own domain-specific language (DSL) to reach the data in your story. With this query syntax you can reach nodes, edges, project information and variables.

πŸ’‘ Important: the DSL allows dynamic content, conditional branches and variable dialogues. It is the heart of the interactive features in TellsTree.

πŸ“ Query Syntax

Grundformat:

<entity>[<selector>].<field>[<key>]
  • entity β€” the entity type: node, edge, self, project
  • selector β€” ID oder self (optional)
  • field β€” Das Datenfeld: content, variables, label, type, etc.
  • key β€” array key for nested data (optional)

βœ… Valid queries:

node[21].content[name]
self.variables[level]
node[self].content[speaker]
edge[5].condition[type]
project.title
node[42].label

πŸ’‘ Explanations:

  • node[21] β€” reach the node with id 21
  • self β€” the current node or edge in the context
  • content[name] β€” the field "name" of the content object
  • variables[level] β€” Variable "level"
  • project.title β€” Projekttitel (ohne Selektor)

🎯 Available entities

πŸ”΅ node

access to story nodes (characters, dialogues, scenes, etc.)

Available fields:
  • content β€” Content-Daten (Array)
  • variables β€” Gespeicherte Variablen
  • media β€” Media-URLs
  • label β€” Anzeigename
  • type β€” Node-Typ
  • node_id / id β€” Eindeutige ID
Examples:
node[15].content[dialog_text]
node[self].variables[health]
node[42].type

πŸ”— edge

access to the connections between nodes

Available fields:
  • condition β€” Bedingungsdaten (Array)
  • type β€” Edge-Typ (flow/contains/link)
  • edge_id / id β€” Eindeutige ID
  • from_node_id β€” Start-Node
  • to_node_id β€” Ziel-Node
Examples:
edge[8].condition[operator]
edge[self].type
edge[5].from_node_id

⭐ self

a reference to the current entity in the context (node or edge)

Verwendung:
  • access to its own data
  • Relative Referenzen
  • context-dependent queries
Examples:
self.content[name]
self.variables[score]
self.label

πŸ“ project

access to project metadata

Available fields:
  • project_id / id β€” Projekt-ID
  • title β€” Projekttitel
  • description β€” Beschreibung
  • is_public β€” public?
Examples:
project.title
project.is_public

🎨 Template Resolution

Queries can be embedded in templates with double curly braces.

Syntax:

Hello, {{node[15].content[name]}}!

β†’ Wird zu: Hello, Alice! (when node 15 has name="Alice")

πŸ”„ Verschachtelte Queries:

TellsTree resolves nested queries. The innermost expressions in parentheses () are resolved first.

{{node[id=(self.data.speakerId)].data.name}}

1. (self.data.speakerId) β†’ 01HQRST...
2. node[id=01HQRST...].data.name β†’ Alice

Examples:

"Your level: {{self.variables[level]}}"

β†’ Your level: 5

"{{node[8].content[greeting]}}, {{self.content[player_name]}}"

β†’ Hello, John

Use cases:

  • βœ“ Dynamische Dialog-Texte
  • βœ“ Variable Edge-Labels
  • βœ“ Bedingte Anzeige-Texte
  • βœ“ Player-spezifische Inhalte
  • βœ“ context-dependent UI

βš–οΈ Bedingungen (Conditions)

Queries can be used in conditions to show edges or UI elements conditionally.

Struktur:

{
  "query": "self.variables[level]",
  "operator": ">=",
  "value": 5
}

β†’ the condition holds when level β‰₯ 5

Available operators:

== / equals
Gleich
=== / strict_equals
Strikt gleich
!= / not_equals
Ungleich
> / greater_than
Greater than
>= / greater_or_equal
Greater or equal
< / less_than
Kleiner als
<= / less_or_equal
Kleiner gleich
contains
Contains a substring
in
In Array

Example conditions:

self.variables[has_key] == true
the player has the key
node[42].content[health] > 0
Charakter lebt noch
self.variables[level] >= 10
level requirement met

🎯 Verwendung in Edges:

Flow edges can carry conditions. Only edges whose condition holds are active in the gameplay. This allows branching narratives based on the player's decisions and state.

πŸ”’ Sicherheit & Permissions

Automatische Permission-Checks:

  • βœ“ all queries are validated against project access
  • βœ“ only nodes and edges in the current project can be reached
  • βœ“ user permissions are checked automatically
  • βœ“ public projects are readable for everyone
  • βœ“ private projects only for the owner and the members

πŸ’‘ Note: invalid queries (for example missing permissions, ids that do not exist) return null and are logged. The system is designed to fail safe.

πŸ’Ž Best Practices

βœ… Do:

  • β€’ use self for relative references
  • β€’ use speaking variable names
  • β€’ test conditions before production
  • β€’ use templates for dynamic content
  • β€’ Halte Queries einfach und lesbar

❌ Don't:

  • β€’ Vermeide zu tief verschachtelte Queries
  • β€’ no queries on ids that do not exist
  • β€’ no circular references
  • β€’ no sensitive data in variables
  • β€’ no more than 10 levels of nesting

πŸ“˜ Query DSL (Domain-Specific Language)

TellsTree uses its own domain-specific language to reach the data in your projects. The DSL allows dynamic content, conditional branches and interactive dialogues.

πŸ’‘ Neu in Version 2.0: Die DSL verwendet jetzt Dot-Notation statt Bracket-Notation for cleaner, more readable expressions.

πŸ“ Syntax overview

Dot-Notation (Neu):

self.data.name
project.name
node[id=01HQRST...].data.class
$variableName

Bracket-Notation (Alt, veraltet):

self.content[name]
node[21].variables[level]

βœ… Valid expressions:

self.data.name
self.variables.level
project.name
node[id=01HQ...].data.class
$heroLevel
(self.data.inventory)

πŸ’‘ Bedeutung:

  • self β€” Aktueller Node oder Edge
  • project β€” Aktuelles Projekt
  • node[id=...] β€” Spezifischer Node (ULID)
  • $variable β€” Variable aus Kontext
  • (...) β€” Gruppierter Ausdruck
  • .field.nested β€” Verschachtelte Felder

🎯 Referenztypen

πŸ”Ή Self-Referenz

Reaches the node or edge that evaluates the expression.

self.data.name
self.variables.level
self.type
Available fields: id, type, label, data, variables, created_at, updated_at

πŸ”Ή Project-Referenz

Reaches the current project.

project.name
project.variables.questComplete
project.owner_id
Available fields: id, name, owner_id, variables, created_at, updated_at

πŸ”Ή Entity-Referenz (Node/Edge)

Reaches one specific node or edge by its ULID.

node[id=01HQRST0123456789ABCDEFG00].data.name
edge[id=01HQRST0123456789ABCDEFG10].data.distance
node[id=$targetNodeId].variables.health
πŸ’‘ Note: ids must be in the ULID shape (26 characters, base32). You can use variables as well: $variableName

πŸ”Ή Variable-Referenz

Reaches variables from the context.

$heroLevel
$questComplete
$targetNodeId
Suchpfad: Kontext-Variablen β†’ Node/Edge-Variablen β†’ Project-Variablen

πŸ”‘ Feld-Zugriff

After the reference you can reach nested fields with dot notation:

Verschachtelte Pfade:

self.data.character.name
self.data.stats.health.current
project.variables.settings.difficulty

⚠️ Important: when a field does not exist, the DSL in lenient mode null. In strict mode an exception is thrown.

⚠️ Fehlerbehandlung

Lenient-Modus (Standard):

Missing fields or entities return null; no errors are thrown.

self.data.nonexistent β†’ null

Strict-Modus:

Errors are thrown as exceptions. Useful for debugging.

self.data.nonexistent β†’ Exception: UNDEFINED_FIELD

πŸ“š Examples

Example 1: Character information

self.data.character.name

Returns the name of the character from the current node.

Example 2: Quest progress

project.variables.questsCompleted

Returns the number of finished quests from the project variables.

Example 3: A referenced NPC

node[id=$mentorId].data.name

Reaches the name of an NPC whose id is kept in the variable $mentorId.

Example 4: Nested data

self.variables.stats.health.current

Reaches the current health from nested variables.

✨ Best Practices

  • βœ“ Use self to reach the current node or edge
  • βœ“ Use project for the global project data
  • βœ“ Use variables ($varName) for dynamic id references
  • βœ“ Vermeiden Sie tiefe Verschachtelung (max. 5-7 Ebenen)
  • βœ— Do not use the old bracket notation (outdated)
  • βœ— Do not use numeric ids (only ULIDs are supported)

🎯 Conditions System

The conditions system allows conditional logic in TellsTree. With predicates and conjunctions you can build complex conditions that decide whether an edge is visible or which actions are available.

πŸ’‘ Important: conditions use the DSL syntax to reach data and support AND/OR joins of any depth.

πŸ—οΈ Struktur

Eine Condition besteht aus zwei Haupttypen:

πŸ”Ή Predicate (Vergleich)

Ein einzelner Vergleich zwischen zwei Werten.

{ "operator": "==", "left": "self.data.class", "right": "\"Warrior\"" }

πŸ”Ή Conjunction

Joins several conditions with AND or OR.

{ "type": "AND", "conditions": [ { "operator": "==", ... }, { "operator": ">=", ... } ] }

βš™οΈ Operatoren

== (Gleich)

Strenger Gleichheitsvergleich (keine Typ-Konvertierung).

{ "operator": "==", "left": "self.data.name", "right": "\"Arthur\"" }
βœ“ Returns true when both values are identical.

!= (Ungleich)

Strenger Ungleichheitsvergleich.

{ "operator": "!=", "left": "self.data.class", "right": "\"Mage\"" }
βœ“ Returns true when the values differ.

in (Enthalten in)

Checks whether an element is contained in an array or a string.

{ "operator": "in", "left": "\"Sword\"", "right": "self.data.inventory" }
βœ“ Works with arrays and strings (substring check).

contains

Checks whether a container holds an element (symmetric to "in").

{ "operator": "contains", "left": "self.data.inventory", "right": "\"Shield\"" }
βœ“ Works with arrays and strings.

πŸ”— Conjunctions

AND (all must hold)

Returns true when all conditions hold.

{ "type": "AND", "conditions": [ { "operator": "==", "left": "self.data.class", "right": "\"Warrior\"" }, { "operator": ">=", "left": "self.variables.level", "right": "5" } ] }

βœ“ Both conditions must be true.

OR (at least one must hold)

Returns true when at least one condition holds.

{ "type": "OR", "conditions": [ { "operator": "==", "left": "self.data.class", "right": "\"Mage\"" }, { "operator": "==", "left": "self.data.class", "right": "\"Warrior\"" } ] }

βœ“ At least one condition must be true.

Verschachtelte Conjunctions

Conjunctions can be nested to any depth.

{ "type": "AND", "conditions": [ { "operator": "==", "left": "self.type", "right": "\"character\"" }, { "type": "OR", "conditions": [ { "operator": ">=", "left": "self.variables.level", "right": "10" }, { "operator": "==", "left": "self.variables.hasKey", "right": "true" } ] } ] }

βœ“ Entspricht: character AND (level >= 10 OR hasKey)

πŸ“š Examples

Example 1: A simple level check

{ "operator": ">=", "left": "self.variables.level", "right": "5" }

Checks whether the player has reached at least level 5.

Example 2: A class check

{ "type": "OR", "conditions": [ { "operator": "==", "left": "self.data.class", "right": "\"Mage\"" }, { "operator": "==", "left": "self.data.class", "right": "\"Sorcerer\"" } ] }

Checks whether the character is a mage or a sorcerer.

Example 3: Quest gating

{ "type": "AND", "conditions": [ { "operator": ">=", "left": "self.variables.level", "right": "10" }, { "operator": "==", "left": "project.variables.quest1Complete", "right": "true" }, { "operator": "contains", "left": "self.data.inventory", "right": "\"Magic Stone\"" } ] }

Checks several conditions for quest access: level, previous quest and item.

Example 4: Cross-node reference

{ "operator": "==", "left": "node[id=$mentorId].variables.trustLevel", "right": "5" }

Checks the trust level of another NPC (a mentor).

πŸ”€ Edge Conditions

Edges can carry conditions that decide whether they are visible or clickable.

Edge-Struktur:

{ "id": "01HQRST...", "type": "flow", "source_id": "01HQRST...", "target_id": "01HQRST...", "condition": { "operator": ">=", "left": "self.variables.playerLevel", "right": "3" } }

πŸ’‘ Important: in edge conditions self refers to the source node, not to the edge itself.

✨ Best Practices

  • βœ“ Use explicit conjunctions for clarity
  • βœ“ Halten Sie Conditions flach (max. 3-4 Ebenen Tiefe)
  • βœ“ Use variables for dynamic values
  • βœ“ Test conditions in strict mode while building
  • βœ— Vermeiden Sie Typ-Vermischung (Zahlen vs. Strings)
  • βœ— Avoid circular references
  • βœ— Use no more than 10 conditions per conjunction

πŸ“ Template System

The template system allows dynamic content in TellsTree. With the {{...}} syntax you can embed DSL expressions in texts, which are resolved at run time.

πŸ’‘ Important: templates use the same DSL syntax as conditions, aber mit spezieller Template-Markierung {{...}}.

πŸ”€ Template-Syntax

Grundformat:

"Text {{expression}} mehr Text"

Alles zwischen {{...}} is treated as a DSL expression and resolved at run time.

βœ… Valid templates:

"Hello {{self.data.name}}"
"Welcome to {{project.name}}"
"Your level: {{self.variables.level}}"
"Quest by {{node[id=$questGiverId].data.name}}"

πŸ’‘ Ergebnisse:

"Hello Arthur"
"Welcome to My Story"
"Your level: 10"
"Quest by Merchant"

πŸ”’ Several expressions

You can use any number of template expressions in one text:

Template:

"{{self.data.name}} is a level {{self.variables.level}} {{self.data.class}} in the project {{project.name}}"

Ergebnis:

"Arthur is a level 10 Warrior in the project Hero's Journey"

🎨 Datentypen

Template expressions are converted to strings automatically:

Strings

{{self.data.name}}
β†’ "Arthur"

Zahlen

{{self.variables.level}}
β†’ "10"

Booleans

{{self.variables.questComplete}}
β†’ "true" / "false"

Arrays

{{self.data.inventory}}
β†’ JSON string

Null/Undefined

{{self.data.nonexistent}}
β†’ "" (leerer String)

πŸ”„ Verschachtelte Templates

Templates can resolve other templates (at most 5 levels deep):

Example:

The variable "template" holds:
"Hello {{self.data.name}}"
Outer template:
"Message: {{$template}}"
Ergebnis:
"Message: Hello Arthur"

⚠️ Important: circular template references are detected and lead to an error, or return the template unchanged (depending on the mode).

🎯 Use cases

1. Dialog-Text

Personalised NPC dialogues with the player's name and state:

"Greetings, {{self.data.name}}! I see you've reached level {{self.variables.level}}"

2. Quest-Beschreibungen

Dynamic quest texts with goals and items:

"Collect {{$requiredItems}} items and deliver them to {{node[id=$questGiverId].data.name}}"

3. Status-Anzeigen

HUD-Elemente mit aktuellen Werten:

"Health: {{self.variables.health.current}} / {{self.variables.health.max}}"

4. Bedingte Texte

Texts that are shown based on conditions:

"Welcome back, {{self.data.title}} {{self.data.name}}"

⚠️ Fehlerbehandlung

Lenient-Modus (Standard):

Failed expressions are replaced by empty strings:

"Value: {{self.data.missing}}"
β†’ "Value: "

Strict-Modus:

Errors are thrown as exceptions (useful for debugging):

Exception: UNDEFINED_FIELD - Field 'missing' not found

πŸ“š Complete examples

NPC-Dialog

Template:
"Welcome, {{self.data.name}}! I am {{node[id=$npcId].data.name}}. You seem to be a {{self.data.class}} of considerable skill (level {{self.variables.level}}). Perhaps you can help me with a quest in {{project.name}}"
Ergebnis:
"Welcome, Arthur! I am Merchant Aldric. You seem to be a Warrior of considerable skill (level 10). Perhaps you can help me with a quest in Heldenreise"

Quest-Log-Eintrag

Template:
"Quest: {{$questTitle}}\n Giver: {{node[id=$questGiverId].data.name}}\n Progress: {{self.variables.questProgress}} / {{$questGoal}}\n Reward: {{$questReward}} gold"
Ergebnis:
Quest: The Missing Artifact Giver: Merchant Aldric Progress: 3 / 5 Reward: 500 gold

✨ Best Practices

  • βœ“ Use short, readable expressions in templates
  • βœ“ Keep complex values in variables
  • βœ“ Use fallback values for optional fields
  • βœ“ Test templates in strict mode while building
  • βœ— Vermeiden Sie tiefe Verschachtelung (> 3 Ebenen)
  • βœ— Avoid complex expressions right in templates
  • βœ— Use no arrays right in texts (format them first)

πŸ”— Edge Types

In TellsTree unterscheiden wir drei grundlegende Edge-Typen, die jeweils unterschiedliche carry meaning of their own and differ in how they are shown and checked.

πŸ’‘ Important: the right choice of edge type decides Validierung und Darstellung Ihrer interaktiven Geschichten. Nur flow-Edges can be tested and form the logical flow.

πŸ“¦ 1️⃣ contains β€” structural belonging

Beschreibt hierarchische Beziehungen und Besitz. This edge type says that one element is part of another.

Eigenschaften:

  • Beschreibt Besitz, Teilmenge, Einbettung
  • No element of time
  • No direction in the sense of a flow
  • No conditions possible
  • Nicht test-relevant

Semantische Aliase:

located_in appears_in owns wears has_trait

Examples:

Chapter β†’ Scene
Scene β†’ Story-Segment
Dialog β†’ Dialog-Entry
Inventory β†’ Item
Character β†’ Trait
Stage β†’ Gameplay-Segment

All these relations mean: "A belongs to B"

➑️ UI-Darstellung:

  • βœ— Kein Pfeil
  • βœ“ Sidelane / Grouping / Umrandung
  • βœ“ Rein organisatorisch

🌊 2️⃣ flow β€” Gerichtete, bedingte Abfolge

This is the only edge type that is temporal, testable and logically relevant. Flow edges define the actual flow of your story.

Eigenschaften:

  • Immer gerichtet
  • Optional: Bedingungen (conditions)
  • Optional: Effekte / Events
  • Relevant for validation
  • Kann "offene Enden" erzeugen

Semantische Varianten:

follows branches_to unlocks requires blocks completes fails talks_to

βœ… Kritische Regel:

All flow connections must run from start to end. Only flow edges form graphs that can be tested.

Example flow:

Start β†’ Scene 1
follows
Scene 1 β†’ Choice A
Scene 1 β†’ Choice B
branches_to (with conditions)
Choice A β†’ End

➑️ UI-Darstellung:

  • βœ“ Gerichteter Pfeil
  • βœ“ Bedingungen anzeigen
  • βœ“ Highlighting bei Validierung
  • βœ“ "Offene Enden" rot markieren

πŸ”— 3️⃣ link β€” Lose, nicht-sequenzielle Verbindung

An edge type many systems do not keep apart. Links are purely referential and carry no logical or temporal meaning.

Eigenschaften:

  • No element of time
  • No order
  • No validation logic
  • Rein semantisch / referenziell
  • Optional ein-/ausblendbar

Semantische Varianten:

mentions connects_to references related_to

Examples:

Character A β‹―β†’ Character B

mentions (in dialogue)

Location A β‹―β†’ Location B

connects_to (map reference)

Scene 5 β‹―β†’ Scene 12

references (callback)

⚠️ Wichtig:

Link edges are never relevant for tests. They serve only documentation and semantic connection, not the logical flow.

➑️ UI-Darstellung:

  • βœ“ Gestrichelte Linie oder Pfeil
  • βœ“ Label / Beschreibung anzeigen
  • βœ“ Optional ein-/ausblendbar
  • βœ— Niemals in Validierung einbeziehen

πŸ“Š Vergleichstabelle

Eigenschaft contains flow link
Zeitlich ❌ βœ… ❌
Gerichtet ❌ βœ… βž– (optional)
Conditions possible ❌ βœ… ❌
Testrelevant ❌ βœ… ❌
UI-Darstellung Grouping Pfeil β†’ Gestrichelt β‹―β†’
Validierung Organisatorisch Complete Keine

πŸ“¦ Nodes

Coming soon...

πŸ•ΈοΈ Graph Structure

Coming soon...