Files
orzo/docs/gyms/cross-timeline-interactions
..
2026-06-16 23:51:31 +02:00

Cross-Timeline Interactions Gym

Overview

Dialogic 2 provides seven distinct mechanisms for multiple timelines to interact with each other. This gym catalogs and demonstrates every mechanism, so you can choose the right one for your narrative structure.


The Seven Mechanisms

# Mechanism Timeline Syntax GDScript Required? Preserves Jump Stack?
1 Jump to another timeline jump TimelineName/Label No Yes (push)
2 Jump + Return (subroutine) jump → … → return No Yes (push/pop)
3 Choice → different timeline - Optionjump TimelineB/ No Depends
4 Condition → different timeline if {var}:jump TimelineB/ No Depends
5 Variable set → condition in next timeline set {x} = 1if {x}: in other timeline No (but needs trigger) N/A
6 Signal event → code → start_timeline() [signal arg=...] Yes No (manual)
7 do Call event → autoload method → start_timeline() do MyAutoload.method() Yes No (manual)
8 GDScript timeline_ended signal → chain None Yes (external) N/A
9 Text input → variable → subsequent timeline [text_input ...]if {name}: No N/A
10 Dynamic label jump (jump {variable}) jump {day_phase} No Yes

Mechanism 1: Direct Jump Between Timelines

The simplest form of cross-timeline interaction. A jump event in one timeline immediately transitions to another timeline (at a specific label or the beginning).

Timeline Text

# timeline_a.dtl
join Miko center
Miko (neutral): I need to go somewhere else now.
jump timeline_b/arrival_label

# timeline_b.dtl
label arrival_label
join Advisor left
Advisor (pl5): Welcome! You came from timeline_a.

What Happens

  • The current timeline + event index is pushed onto the jump stack
  • Dialogic loads timeline_b and starts at arrival_label (or the beginning if no label)
  • Dialogic.Jump.switched_timeline and Dialogic.Jump.jumped_to_label signals fire
  • The old timeline is NOT destroyed — it lives on the jump stack

Key Property

Jump to label in another timeline: jump TimelineName/LabelIdentifier
Jump to beginning of another timeline: jump TimelineName/


Mechanism 2: Jump + Return (Subroutine Pattern)

A timeline acts as a reusable "subroutine" — it does some work (sets variables, plays a scene, shows a choice), then return sends execution back to where the jump occurred.

Timeline Text

# main_story.dtl
join Miko center
Miko (joy): Let's check our inventory first.
jump inventory_check/        # Push current position onto stack
Miko (smile): Back from inventory. Let's continue.

# inventory_check.dtl
Advisor (pl5): Opening inventory...
set {coins} += 50
Advisor (pl5): You found 50 coins!
return                        # Pop stack → resume main_story right after the jump

Stack Behavior

main_story           stack: []
  jump inventory     stack: [(main_story, idx_2)]
  ── inventory_check runs ──
  return             stack: []  (popped)
  resumes at main_story idx_3

Key Property

  • If the stack is empty when return fires, the timeline ends
  • You can jump to one sub-timeline which jumps to another, creating a deep call stack
  • This is ideal for: inventory screens, crafting menus, mini-dialogues, flashbacks

Mechanism 3: Choice → Different Timeline

A dialogue choice doesn't just set a variable — it directly jumps to an entirely different timeline for each option.

Timeline Text

# crossroad.dtl (hub)
join Miko center
Advisor (doubt): Where should we go, Miko?
- The village market
	Miko (joy): Let's go shopping!
	jump market_scene/start

- The dark forest
	Miko (shock): Into the woods...
	jump forest_scene/start

- Stay here and rest
	Miko (smile): Good idea. Let's camp.
    jump camp_scene/start

What Happens

  • Player chooses "The village market"
  • Events inside that branch execute (text, variables, signals)
  • The jump marketplace_scene/start fires
  • Execution moves to market_scene.dtl at the start label

Key Property

Each choice branch can have multiple events before the jump — set variables, show reactions, play sounds — making it a full pre-transition sequence.


Mechanism 4: Condition → Different Timeline

Instead of a player choice, automatic branching based on variable values can route to different timelines.

Timeline Text

# auto_router.dtl
if {coins} >= 100:
	Miko (joy): I'm rich! Let's go to the VIP area.
    jump vip_timeline/
elif {reputation} <= -10:
    Miko (anger): They hate me here. I should leave town.
    jump exile_timeline/
else:
    Miko (neutral): Just an ordinary day.
    jump normal_day/

What Happens

  • Variables coins and reputation were set by previous timelines
  • When auto_router starts, Dialogic evaluates each condition
  • The matching branch executes and jumps to the appropriate timeline
  • If no condition matches, else runs (or nothing happens if no else)

Mechanism 5: Variable Set in One Timeline, Checked in Another

The most loosely-coupled pattern. Timeline A sets a variable. Later, Timeline B checks it with a Condition event. They don't jump to each other directly but something external (code, another timeline, signal) starts Timeline B.

Timeline Text

# quest_start.dtl
Miko (surprise): A dragon?! I accept the quest!
set {dragon_quest_accepted} = true
set {quest_difficulty} = "hard"
[end_timeline]

# town_square.dtl (started later by code)
if {dragon_quest_accepted}:
	Merchant (happy): Heard you're hunting a dragon. Need supplies?
    set {coins} -= 50
	Merchant (happy): Here's a fireproof shield!
else:
	Merchant (neutral): Just browsing today?

Key Property

  • Zero timeline coupling — town_square.dtl doesn't know or care which timeline set {dragon_quest_accepted}
  • Variables act as a shared "world state" between all timelines
  • Variable changes persist across jumps, returns, and start_timeline() calls
  • To reset: Dialogic.VAR.reset() or explicitly set {quest_accepted} = false

Mechanism 6: Signal Event → GDScript → start_timeline()

Timeline A emits a signal. GDScript code listens for it and starts Timeline B. This is the most flexible pattern — the code can do anything before starting the next timeline (load resources, change scenes, animate the world, etc.).

Timeline Text

# scene_cutscene.dtl
join Raptor (walk) center
Raptor (walk): Watch this transformation!
[signal arg=start_transformation]
[wait time="2.0"]
Raptor (roar): ROAR!
[end_timeline]

GDScript

# cross_timeline_gym.gd
extends Node2D

func _ready():
    Dialogic.signal_event.connect(_on_dialogic_signal)

func _on_dialogic_signal(argument: String):
    if argument == "start_transformation":
        # Do anything here: animate the world, change scenes, play music
        await get_tree().create_timer(0.5).timeout
        Dialogic.start_timeline("transformation_sequence")

Signal Data Options

  • String signal: [signal arg="activate_portal"]
  • Dictionary signal: [signal arg_type="Dictionary" arg={"target": "forest", "time": "night"}]

Key Property

The signal event does not end the current timeline — both timelines can run if you don't stop the first one. Use Dialogic.end_timeline() to explicitly end before starting a new one.


Mechanism 7: do Call Event → Autoload Method

Similar to signal but with typed arguments and method return values. The timeline calls a method on any autoload singleton, which can start new timelines.

Timeline Text

# trigger.dtl
Miko (neutral): Let me check something...
do GameManager.start_side_quest("forest_rescue", "hard")

Autoload (GameManager.gd)

extends Node

func start_side_quest(quest_name: String, difficulty: String):
	print("Starting quest: ", quest_name, " at ", difficulty)
	Dialogic.start_timeline(quest_name)

Key Difference from Signal

  • do calls a specific method with typed arguments (int, string, bool, array, expression)
  • [signal] emits a signal with a single untyped argument (string or dictionary)
  • do is better for well-defined APIs; [signal] is better for loose coupling

Mechanism 8: GDScript timeline_ended Chaining

Completely external to the timeline itself. Code listens for when Timeline A finishes, then immediately starts Timeline B.

GDScript

extends Node2D

@export var timeline_sequence: Array[DialogicTimeline] = []
var current_timeline_index := 0

func _ready():
	Dialogic.timeline_ended.connect(_on_timeline_ended)
	Dialogic.start(timeline_sequence[0])

func _on_timeline_ended():
	current_timeline_index += 1
	if current_timeline_index < timeline_sequence.size():
		await get_tree().create_timer(1.0).timeout  # optional pause
		Dialogic.start(timeline_sequence[current_timeline_index])

Key Property

  • The timelines themselves don't know about each other
  • You can insert any logic between timelines (fades, scene loads, world changes)
  • Order is determined by the exported array, editable in the Godot inspector

Mechanism 9: Text Input → Variable → Next Timeline

The player types text that gets stored in a variable. A subsequent timeline (or condition block) uses that variable to route or personalize the dialog.

Timeline Text

# name_entry.dtl
Miko (smile): Welcome, traveler!
Advisor (pl5): Before we begin... what should I call you?
[text_input text="Your name?" var="player_name" placeholder="Enter name..." default="Hero" allow_empty="false"]
Miko (joy): Nice to meet you, {player_name}!
jump personalized_greeting/

# personalized_greeting.dtl
if {player_name} == "Miko":
	Miko (surprise): Hey, that's MY name!
elif {player_name} == "Dragon":
	Miko (shock): Um... should I be worried?
else:
	Miko (smile): {player_name}, what a wonderful name!

Mechanism 10: Dynamic Label Jump (jump {variable})

Use a variable's value as the jump target. This is powerful for state-machine-like narrative flow.

Timeline Text

# daily_cycle.dtl
# day_phase is set elsewhere: "morning", "afternoon", "evening", "night"
jump {day_phase}

label morning
Miko (joy): Good morning! The sun is rising.
    jump end_of_day

label afternoon
Miko (neutral): It's a warm afternoon.
	jump end_of_day

label evening
Miko (shock): The sun is setting. We should hurry!
	jump end_of_day

label night
Miko (doubt): It's dark... be careful out there.
    jump end_of_day

label end_of_day
[end_timeline]

Key Property

  • The variable must resolve to a label identifier that exists in the current timeline
  • Can also reference timeline: jump {target_timeline}/{target_label}

Combining Patterns: A Practical Example

Real games combine multiple mechanisms. Here's a complex example:

# hub_world.dtl (entry point)
label hub
Miko (neutral): What should I do?
- Visit the shop          → jump shop_dialogue/
- Check quest board       → jump quest_board/
- Talk to NPCs            → if {met_elder}: jump elder_chat/
						   else: jump elder_intro/
- Save and quit           → [save slot="auto"]
							[end_timeline]
# quest_board.dtl
label start
set {viewed_board} = true
QuestGiver (happy): Here are today's quests!
- Hunt 5 wolves (reward: 50g)
    set {active_quest} = "wolf_hunt"
    [signal arg=quest_accepted]
    return   # ← back to hub

- Gather 10 herbs (reward: 30g)
    set {active_quest} = "herb_gathering"
    [signal arg=quest_accepted]
    return

- Maybe later
    return

The [signal arg=quest_accepted] is caught by GDScript which updates the world (spawns wolves, shows quest marker) and then call jump hub_world/hub to return the player to the hub.


Decision Guide: Which Mechanism to Use?

Use case Best mechanism
Narrative branches that diverge forever Jump to another timeline (1)
Reusable mini-scenes (inventory, crafting, shop) Jump + Return (2)
Player choice determines next scene Choice → Jump to timeline (3)
Automatic scene routing based on game state Condition → Jump (4) or Dynamic label jump (10)
World state affects dialog in another scene Variable set in one, checked in another (5)
Cutscene triggers world/engine changes Signal → GDScript (6) or do Call (7)
Linear sequence of cutscenes timeline_ended chaining (8)
Player-customized content Text input → variable (9)
Time-of-day / game phase routing Dynamic label jump (10)

Project Files in This Gym

File Purpose
gym_main.dtl Hub timeline — presents a menu of all patterns to explore
gym_pattern1_jump.dtl Mechanism 1: Simple cross-timeline jump
gym_pattern1_target.dtl Target timeline for pattern 1
gym_pattern2_subroutine.dtl Mechanism 2: Jump → work → return
gym_pattern2_caller.dtl Calls the subroutine
gym_pattern3_variable_chain_A.dtl Mechanism 5: Sets a variable
gym_pattern3_variable_chain_B.dtl Mechanism 5: Checks the variable
gym_pattern4_signal_chain.dtl Mechanism 6: Emits a signal
gym_pattern4_signal_target.dtl Mechanism 6: Started by signal handler
gym_pattern5_choice_jump.dtl Mechanism 3: Choice routes to different timelines
gym_pattern6_condition_router.dtl Mechanism 4: Variable conditions route timelines
gym_pattern7_text_input.dtl Mechanism 9: Player input drives next timeline
gym_pattern8_dynamic_label.dtl Mechanism 10: Variable-driven label jump
gym_pattern8_morning.dtl Day phase: morning
gym_pattern8_night.dtl Day phase: night
gym_controller.gd GDScript controller with signal handlers

Setup Instructions

1. Create the scene in Godot editor

The .dtl and .gd files are already in place. You just need to create the scene:

  1. Open the Godot editor for this project
  2. Create a new scene: Scene → New Scene
  3. Set the root node to Node2D and name it CrossTimelineGym
  4. Attach the script: click the script icon, choose res://docs/gyms/cross-timeline-interactions/gym_controller.gd
  5. In the Inspector, assign start_timeline to res://docs/gyms/cross-timeline-interactions/gym_main.dtl
  6. Save as res://docs/gyms/cross-timeline-interactions/gym_controller.tscn

2. Verify project settings

The project.godot file has already been updated with:

  • All 14 gym timelines registered in directories/dtl_directory
  • New variables: Gym.demo_flag, Gym.sub_value, Gym.player_name, Gym.time_of_day, Museum.coins, Museum.met_merchant, Museum.player_has_key

3. Run the scene

Press F6 (Run Current Scene) with gym_controller.tscn open to start the gym.

Running individual patterns

To test a specific pattern without the hub, call from any GDScript:

Dialogic.start("gym_pattern1_jump")          # Direct jump demo
Dialogic.start("gym_pattern2_caller", "start") # Subroutine demo
Dialogic.start("gym_pattern5_choice_jump", "start") # Choice routing demo
Dialogic.start("gym_pattern8_dynamic_label", "start") # Dynamic label demo

Limitations & Caveats

  1. Only one timeline runs at a time. You cannot have two parallel timelines executing simultaneously. Dialogic is single-timeline.

  2. Timeline references must be preregistered. The target timeline must have its .dtl file listed in project.godotdialogic/directories/dtl_directory before jump TimelineName/ will work.

  3. Jump stack is hidden state. If you jump without return, the stack grows without bound. Use Dialogic.Jump.is_jump_stack_empty() to check.

  4. start_timeline() bypasses the jump stack. Unlike jump, calling Dialogic.start_timeline() replaces the current timeline without pushing to the stack. Use jump in timelines, start_timeline() in code.

  5. Dialogic.start() vs Dialogic.start_timeline(). The former also loads a layout scene (use for the first dialog in a scene). The latter only starts the timeline (use when a layout is already active).