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 | - Option → jump TimelineB/ |
No | Depends |
| 4 | Condition → different timeline | if {var}: → jump TimelineB/ |
No | Depends |
| 5 | Variable set → condition in next timeline | set {x} = 1 → if {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_band starts atarrival_label(or the beginning if no label) Dialogic.Jump.switched_timelineandDialogic.Jump.jumped_to_labelsignals 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
returnfires, 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/startfires - Execution moves to
market_scene.dtlat thestartlabel
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
coinsandreputationwere set by previous timelines - When
auto_routerstarts, Dialogic evaluates each condition - The matching branch executes and jumps to the appropriate timeline
- If no condition matches,
elseruns (or nothing happens if noelse)
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.dtldoesn'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 explicitlyset {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
docalls a specific method with typed arguments (int, string, bool, array, expression)[signal]emits a signal with a single untyped argument (string or dictionary)dois 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:
- Open the Godot editor for this project
- Create a new scene: Scene → New Scene
- Set the root node to Node2D and name it
CrossTimelineGym - Attach the script: click the script icon, choose
res://docs/gyms/cross-timeline-interactions/gym_controller.gd - In the Inspector, assign
start_timelinetores://docs/gyms/cross-timeline-interactions/gym_main.dtl - 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
-
Only one timeline runs at a time. You cannot have two parallel timelines executing simultaneously. Dialogic is single-timeline.
-
Timeline references must be preregistered. The target timeline must have its
.dtlfile listed inproject.godot→dialogic/directories/dtl_directorybeforejump TimelineName/will work. -
Jump stack is hidden state. If you
jumpwithoutreturn, the stack grows without bound. UseDialogic.Jump.is_jump_stack_empty()to check. -
start_timeline()bypasses the jump stack. Unlikejump, callingDialogic.start_timeline()replaces the current timeline without pushing to the stack. Usejumpin timelines,start_timeline()in code. -
Dialogic.start()vsDialogic.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).