Basic Concepts

Understanding the fundamental building blocks of interactive storytelling in TellsTree.

📚

Basic Concepts

Verstehen Sie die Grundlagen interaktiven Storytellings

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

Wichtige Konzepte

✓
Edge-Semantik
3 Typen: contains, flow, link
✓
Validierung
Nur flow-Edges sind testbar
✓
Hierarchie
contains für strukturelle Zugehörigkeit
✓
Referenzen
link für lose, nicht-testbare Verbindungen
💡

Warum das wichtig ist

→
Klare Semantik
Jeder Edge-Typ hat eine eindeutige Bedeutung
→
Bessere Validierung
Automatische Tests prüfen flow-Completeness
→
Intuitive UI
Unterschiedliche Darstellung je nach Typ
→
Saubere Architektur
Trennung von Struktur, Ablauf und Referenzen

🔢 Variables & Query DSL

TellsTree verwendet eine eigene Domain-Specific Language (DSL) für den Zugriff auf Daten in Ihrer Geschichte. Mit dieser Query-Syntax können Sie auf Nodes, Edges, Project-Informationen und Variables zugreifen.

💡 Wichtig: Die DSL ermöglicht dynamische Inhalte, bedingte Verzweigungen und variable Dialoge. Sie ist das Herzstück der interaktiven Funktionen in TellsTree.

📝 Query Syntax

Grundformat:

<entity>[<selector>].<field>[<key>]
  • entity — Der Entitätstyp: node, edge, self, project
  • selector — ID oder self (optional)
  • field — Das Datenfeld: content, variables, label, type, etc.
  • key — Array-Schlüssel für verschachtelte Daten (optional)

✅ Gültige Queries:

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

💡 Erläuterungen:

  • node[21] — Zugriff auf Node mit ID 21
  • self — Aktueller Node oder Edge im Kontext
  • content[name] — Feld "name" aus content-Objekt
  • variables[level] — Variable "level"
  • project.title — Projekttitel (ohne Selektor)

🎯 Verfügbare Entities

🔵 node

Zugriff auf Story-Knoten (Charaktere, Dialoge, Szenen, etc.)

Verfügbare Fields:
  • content — Content-Daten (Array)
  • variables — Gespeicherte Variablen
  • media — Media-URLs
  • label — Anzeigename
  • type — Node-Typ
  • node_id / id — Eindeutige ID
Beispiele:
node[15].content[dialog_text]
node[self].variables[health]
node[42].type

🔗 edge

Zugriff auf Verbindungen zwischen Nodes

Verfügbare 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
Beispiele:
edge[8].condition[operator]
edge[self].type
edge[5].from_node_id

⭐ self

Referenz auf die aktuelle Entity im Kontext (Node oder Edge)

Verwendung:
  • Zugriff auf eigene Daten
  • Relative Referenzen
  • Kontext-abhängige Queries
Beispiele:
self.content[name]
self.variables[score]
self.label

📁 project

Zugriff auf Projekt-Metadaten

Verfügbare Fields:
  • project_id / id — Projekt-ID
  • title — Projekttitel
  • description — Beschreibung
  • is_public — Öffentlich?
Beispiele:
project.title
project.is_public

🎨 Template Resolution

Queries können in Templates mit doppelten geschweiften Klammern eingebettet werden.

Syntax:

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

→ Wird zu: Hello, Alice! (wenn node 15 name="Alice" hat)

🔄 Verschachtelte Queries:

TellsTree unterstützt verschachtelte Query-Resolution. Innerste Ausdrücke in Klammern () werden zuerst aufgelöst.

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

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

Beispiele:

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

→ Your level: 5

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

→ Hello, John

Anwendungsfälle:

  • ✓ Dynamische Dialog-Texte
  • ✓ Variable Edge-Labels
  • ✓ Bedingte Anzeige-Texte
  • ✓ Player-spezifische Inhalte
  • ✓ Kontext-abhängige UI

⚖️ Bedingungen (Conditions)

Queries können in Bedingungen verwendet werden, um Edges oder UI-Elemente konditional anzuzeigen.

Struktur:

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

→ Bedingung ist wahr wenn level ≥ 5

Verfügbare Operatoren:

== / equals
Gleich
=== / strict_equals
Strikt gleich
!= / not_equals
Ungleich
> / greater_than
Größer als
>= / greater_or_equal
Größer gleich
< / less_than
Kleiner als
<= / less_or_equal
Kleiner gleich
contains
Enthält Substring
in
In Array

Beispiel-Bedingungen:

self.variables[has_key] == true
Spieler hat Schlüssel
node[42].content[health] > 0
Charakter lebt noch
self.variables[level] >= 10
Level-Requirement erfüllt

🎯 Verwendung in Edges:

Flow-Edges können Bedingungen haben. Nur Edges mit erfüllter Bedingung werden im Gameplay aktiviert. Dies ermöglicht branching narratives basierend auf Spieler-Entscheidungen und Status.

🔒 Sicherheit & Permissions

Automatische Permission-Checks:

  • ✓ Alle Queries werden gegen Projekt-Zugriff validiert
  • ✓ Nur Nodes/Edges im aktuellen Projekt sind zugreifbar
  • ✓ User-Permissions werden automatisch geprüft
  • ✓ Öffentliche Projekte sind für alle lesbar
  • ✓ Private Projekte nur für Besitzer/Mitglieder

💡 Hinweis: Ungültige Queries (z.B. fehlende Permissions, nicht existierende IDs) geben null zurück und werden geloggt. Das System ist fail-safe designed.

💎 Best Practices

✅ Do:

  • • Verwende self für relative Referenzen
  • • Nutze sprechende Variablen-Namen
  • • Teste Bedingungen vor Production
  • • Verwende Templates für dynamische Inhalte
  • • Halte Queries einfach und lesbar

❌ Don't:

  • • Vermeide zu tief verschachtelte Queries
  • • Keine Queries auf nicht-existierende IDs
  • • Keine zirkulären Referenzen
  • • Keine sensiblen Daten in Variables
  • • Nicht mehr als 10 Verschachtelungs-Level

📘 Query DSL (Domain-Specific Language)

TellsTree verwendet eine eigene Domain-Specific Language für den Zugriff auf Daten in Ihren Projekten. Die DSL ermöglicht dynamische Inhalte, bedingte Verzweigungen und interaktive Dialoge.

💡 Neu in Version 2.0: Die DSL verwendet jetzt Dot-Notation statt Bracket-Notation für sauberere, lesbarere Ausdrücke.

📝 Syntax-Übersicht

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]

✅ Gültige Ausdrücke:

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

Greift auf den aktuellen Node oder Edge zu, der den Ausdruck auswertet.

self.data.name
self.variables.level
self.type
Verfügbare Felder: id, type, label, data, variables, created_at, updated_at

🔹 Project-Referenz

Greift auf das aktuelle Projekt zu.

project.name
project.variables.questComplete
project.owner_id
Verfügbare Felder: id, name, owner_id, variables, created_at, updated_at

🔹 Entity-Referenz (Node/Edge)

Greift auf einen spezifischen Node oder Edge über seine ULID zu.

node[id=01HQRST0123456789ABCDEFG00].data.name
edge[id=01HQRST0123456789ABCDEFG10].data.distance
node[id=$targetNodeId].variables.health
💡 Hinweis: IDs müssen im ULID-Format vorliegen (26 Zeichen, Base32). Sie können auch Variablen verwenden: $variableName

🔹 Variable-Referenz

Greift auf Variablen aus dem Kontext zu.

$heroLevel
$questComplete
$targetNodeId
Suchpfad: Kontext-Variablen → Node/Edge-Variablen → Project-Variablen

🔑 Feld-Zugriff

Nach der Referenz können Sie mit Dot-Notation auf verschachtelte Felder zugreifen:

Verschachtelte Pfade:

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

⚠️ Wichtig: Wenn ein Feld nicht existiert, gibt die DSL im lenient-Modus null zurück. Im strict-Modus wird eine Exception geworfen.

⚠️ Fehlerbehandlung

Lenient-Modus (Standard):

Fehlende Felder oder Entitäten geben null zurück, keine Fehler werden geworfen.

self.data.nonexistent → null

Strict-Modus:

Fehler werden als Exceptions geworfen. Nützlich für Debugging.

self.data.nonexistent → Exception: UNDEFINED_FIELD

📚 Beispiele

Beispiel 1: Character-Information

self.data.character.name

Gibt den Namen des Charakters aus dem aktuellen Node zurück.

Beispiel 2: Quest-Fortschritt

project.variables.questsCompleted

Gibt die Anzahl der abgeschlossenen Quests aus den Projekt-Variablen zurück.

Beispiel 3: Referenzierter NPC

node[id=$mentorId].data.name

Greift auf den Namen eines NPCs zu, dessen ID in der Variable $mentorId gespeichert ist.

Beispiel 4: Verschachtelte Daten

self.variables.stats.health.current

Greift auf die aktuelle Gesundheit aus verschachtelten Variablen zu.

✨ Best Practices

  • ✓ Verwenden Sie self für Zugriff auf den aktuellen Node/Edge
  • ✓ Verwenden Sie project für globale Projekt-Daten
  • ✓ Verwenden Sie Variablen ($varName) für dynamische ID-Referenzen
  • ✓ Vermeiden Sie tiefe Verschachtelung (max. 5-7 Ebenen)
  • ✗ Verwenden Sie nicht die alte Bracket-Notation (veraltet)
  • ✗ Verwenden Sie nicht numerische IDs (nur ULIDs werden unterstützt)

🎯 Conditions System

Das Conditions-System ermöglicht bedingte Logik in TellsTree. Mit Predicates und Conjunctions können Sie komplexe Bedingungen erstellen, die bestimmen, ob ein Edge sichtbar ist oder welche Aktionen verfügbar sind.

💡 Wichtig: Conditions verwenden die DSL-Syntax für den Zugriff auf Daten und unterstützen AND/OR-Verknüpfungen beliebiger Tiefe.

🏗️ Struktur

Eine Condition besteht aus zwei Haupttypen:

🔹 Predicate (Vergleich)

Ein einzelner Vergleich zwischen zwei Werten.

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

🔹 Conjunction (Verknüpfung)

Verbindet mehrere Conditions mit AND oder OR.

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

⚙️ Operatoren

== (Gleich)

Strenger Gleichheitsvergleich (keine Typ-Konvertierung).

{ "operator": "==", "left": "self.data.name", "right": "\"Arthur\"" }
✓ Gibt true zurück, wenn beide Werte identisch sind.

!= (Ungleich)

Strenger Ungleichheitsvergleich.

{ "operator": "!=", "left": "self.data.class", "right": "\"Mage\"" }
✓ Gibt true zurück, wenn Werte unterschiedlich sind.

in (Enthalten in)

Prüft, ob ein Element in einem Array oder String enthalten ist.

{ "operator": "in", "left": "\"Sword\"", "right": "self.data.inventory" }
✓ Funktioniert mit Arrays und Strings (substring-Prüfung).

contains (Enthält)

Prüft, ob ein Container ein Element enthält (symmetrisch zu "in").

{ "operator": "contains", "left": "self.data.inventory", "right": "\"Shield\"" }
✓ Funktioniert mit Arrays und Strings.

🔗 Conjunctions (Verknüpfungen)

AND (Alle müssen erfüllt sein)

Gibt true zurück, wenn alle Bedingungen erfüllt sind.

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

✓ Beide Bedingungen müssen true sein.

OR (Mindestens eine muss erfüllt sein)

Gibt true zurück, wenn mindestens eine Bedingung erfüllt ist.

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

✓ Mindestens eine Bedingung muss true sein.

Verschachtelte Conjunctions

Conjunctions können beliebig tief verschachtelt werden.

{ "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)

📚 Beispiele

Beispiel 1: Einfache Level-Prüfung

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

Prüft, ob der Spieler mindestens Level 5 erreicht hat.

Beispiel 2: Klassen-Prüfung

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

Prüft, ob der Character ein Mage oder Sorcerer ist.

Beispiel 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\"" } ] }

Prüft mehrere Bedingungen für Quest-Zugang: Level, vorherige Quest und Item.

Beispiel 4: Cross-Node-Referenz

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

Prüft das Trust-Level eines anderen NPCs (Mentor).

🔀 Edge Conditions

Edges können Conditions haben, die bestimmen, ob sie sichtbar oder klickbar sind.

Edge-Struktur:

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

💡 Wichtig: Bei Edge-Conditions bezieht sich self auf den Source-Node, nicht auf den Edge selbst.

✨ Best Practices

  • ✓ Verwenden Sie explizite Conjunctions für Klarheit
  • ✓ Halten Sie Conditions flach (max. 3-4 Ebenen Tiefe)
  • ✓ Verwenden Sie Variablen für dynamische Werte
  • ✓ Testen Sie Conditions im strict-Modus während der Entwicklung
  • ✗ Vermeiden Sie Typ-Vermischung (Zahlen vs. Strings)
  • ✗ Vermeiden Sie zirkuläre Referenzen
  • ✗ Verwenden Sie nicht mehr als 10 Bedingungen pro Conjunction

📝 Template System

Das Template-System ermöglicht dynamische Inhalte in TellsTree. Mit der {{...}}-Syntax können Sie DSL-Ausdrücke in Texte einbetten, die zur Laufzeit aufgelöst werden.

💡 Wichtig: Templates verwenden dieselbe DSL-Syntax wie Conditions, aber mit spezieller Template-Markierung {{...}}.

🔤 Template-Syntax

Grundformat:

"Text {{expression}} mehr Text"

Alles zwischen {{...}} wird als DSL-Ausdruck behandelt und zur Laufzeit aufgelöst.

✅ Gültige 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"

🔢 Mehrere Ausdrücke

Sie können beliebig viele Template-Ausdrücke in einem Text verwenden:

Template:

"{{self.data.name}} ist ein Level {{self.variables.level}} {{self.data.class}} im Projekt {{project.name}}"

Ergebnis:

"Arthur ist ein Level 10 Warrior im Projekt Heldenreise"

🎨 Datentypen

Template-Ausdrücke werden automatisch in Strings konvertiert:

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 können andere Templates auflösen (max. 5 Ebenen Tiefe):

Beispiel:

Variable "template" enthält:
"Hello {{self.data.name}}"
Äußeres Template:
"Message: {{$template}}"
Ergebnis:
"Message: Hello Arthur"

⚠️ Wichtig: Zirkuläre Template-Referenzen werden erkannt und führen zu einem Fehler oder geben das Template unverändert zurück (je nach Modus).

🎯 Anwendungsfälle

1. Dialog-Text

Personalisierte NPC-Dialoge mit Spieler-Namen und Status:

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

2. Quest-Beschreibungen

Dynamische Quest-Texte mit Zielen und 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

Texte, die basierend auf Conditions angezeigt werden:

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

⚠️ Fehlerbehandlung

Lenient-Modus (Standard):

Fehlgeschlagene Ausdrücke werden durch leere Strings ersetzt:

"Value: {{self.data.missing}}"
→ "Value: "

Strict-Modus:

Fehler werden als Exceptions geworfen (nützlich für Debugging):

Exception: UNDEFINED_FIELD - Field 'missing' not found

📚 Vollständige Beispiele

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

  • ✓ Verwenden Sie kurze, lesbare Ausdrücke in Templates
  • ✓ Speichern Sie komplexe Werte in Variablen
  • ✓ Verwenden Sie Fallback-Werte für optionale Felder
  • ✓ Testen Sie Templates im strict-Modus während der Entwicklung
  • ✗ Vermeiden Sie tiefe Verschachtelung (> 3 Ebenen)
  • ✗ Vermeiden Sie komplexe Ausdrücke direkt in Templates
  • ✗ Verwenden Sie keine Arrays direkt in Texten (formatieren Sie sie zuerst)

🔗 Edge Types

In TellsTree unterscheiden wir drei grundlegende Edge-Typen, die jeweils unterschiedliche semantische Bedeutungen haben und sich in ihrer Darstellung und Validierung unterscheiden.

💡 Wichtig: Die richtige Wahl des Edge-Typs ist entscheidend für die Validierung und Darstellung Ihrer interaktiven Geschichten. Nur flow-Edges können getestet werden und bilden den logischen Ablauf.

📦 1️⃣ contains — Strukturelle Zugehörigkeit

Beschreibt hierarchische Beziehungen und Besitz. Dieser Edge-Typ definiert, dass ein Element Teil eines anderen ist.

Eigenschaften:

  • Beschreibt Besitz, Teilmenge, Einbettung
  • Keine zeitliche Komponente
  • Keine Richtung im Sinne von Ablauf
  • Keine Bedingungen möglich
  • Nicht test-relevant

Semantische Aliase:

located_in appears_in owns wears has_trait

Beispiele:

Chapter → Scene
Scene → Story-Segment
Dialog → Dialog-Entry
Inventory → Item
Character → Trait
Stage → Gameplay-Segment

Alle diese Beziehungen bedeuten: "A gehört zu B"

➡️ UI-Darstellung:

  • ✗ Kein Pfeil
  • ✓ Sidelane / Grouping / Umrandung
  • ✓ Rein organisatorisch

🌊 2️⃣ flow — Gerichtete, bedingte Abfolge

Dies ist der einzige Edge-Typ, der zeitlich, testbar und logisch relevant ist. Flow-Edges definieren den tatsächlichen Ablauf Ihrer Geschichte.

Eigenschaften:

  • Immer gerichtet
  • Optional: Bedingungen (conditions)
  • Optional: Effekte / Events
  • Relevant für Validierung
  • Kann "offene Enden" erzeugen

Semantische Varianten:

follows branches_to unlocks requires blocks completes fails talks_to

✅ Kritische Regel:

Alle flow-Verbindungen müssen von Start bis Ende gehen. Nur flow-Edges bilden Graphen, die man testen kann.

Beispiel-Ablauf:

Start → Scene 1
follows
Scene 1 → Choice A
Scene 1 → Choice B
branches_to (mit Bedingungen)
Choice A → End

➡️ UI-Darstellung:

  • ✓ Gerichteter Pfeil
  • ✓ Bedingungen anzeigen
  • ✓ Highlighting bei Validierung
  • ✓ "Offene Enden" rot markieren

🔗 3️⃣ link — Lose, nicht-sequenzielle Verbindung

Ein Edge-Typ, den viele Systeme nicht sauber trennen. Links sind rein referenziell und haben keine logische oder zeitliche Bedeutung.

Eigenschaften:

  • Keine zeitliche Komponente
  • Keine Reihenfolge
  • Keine Validierungslogik
  • Rein semantisch / referenziell
  • Optional ein-/ausblendbar

Semantische Varianten:

mentions connects_to references related_to

Beispiele:

Character A ⋯→ Character B

mentions (in dialogue)

Location A ⋯→ Location B

connects_to (map reference)

Scene 5 ⋯→ Scene 12

references (callback)

⚠️ Wichtig:

Link-Edges sind niemals testrelevant. Sie dienen nur der Dokumentation und semantischen Verbindung, nicht dem logischen Ablauf.

➡️ UI-Darstellung:

  • ✓ Gestrichelte Linie oder Pfeil
  • ✓ Label / Beschreibung anzeigen
  • ✓ Optional ein-/ausblendbar
  • ✗ Niemals in Validierung einbeziehen

📊 Vergleichstabelle

Eigenschaft contains flow link
Zeitlich ❌ ✅ ❌
Gerichtet ❌ ✅ ➖ (optional)
Bedingungen möglich ❌ ✅ ❌
Testrelevant ❌ ✅ ❌
UI-Darstellung Grouping Pfeil → Gestrichelt ⋯→
Validierung Organisatorisch Vollständig Keine

📦 Nodes

Coming soon...

🕸️ Graph Structure

Coming soon...