# 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_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 `jump`s 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 ```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) ```gdscript 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 ```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: ```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.godot` → `dialogic/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).