extends Node2D ## Bootstrap. Mounts the world view + UI overlay. ## ## Once we add menus / continue-game / new-game flows this will branch ## on game state. For Phase 1 it just instances the World and TopBar, ## which are children placed in main.tscn. ## ## Phase 15 — StorytellerBanner and StorytellerModal are runtime-instantiated ## here (same pattern as world.gd's BeautySystem / DirtinessSystem). Both are ## CanvasLayer nodes so they draw above the world regardless of scene-tree order. ## ## Phase 16 — LoadMenu (layer 25) and ResumeToast (layer 22) are runtime- ## instantiated here. LoadMenu ref is injected into TopBar so the Load button ## can call open() without a get_node("/root/…") call. const STORYTELLER_BANNER_SCRIPT: Script = preload("res://scenes/ui/storyteller_banner.gd") const STORYTELLER_MODAL_SCRIPT: Script = preload("res://scenes/ui/storyteller_modal.gd") const LOAD_MENU_SCRIPT: Script = preload("res://scenes/ui/load_menu.gd") const RESUME_TOAST_SCRIPT: Script = preload("res://scenes/ui/resume_toast.gd") # Phase 17 — PawnDetailPanel (layer 18) and SettingsMenu (layer 26). const PAWN_DETAIL_PANEL_SCRIPT: Script = preload("res://scenes/ui/pawn_detail_panel.gd") const WORKBENCH_PANEL_SCRIPT: Script = preload("res://scenes/ui/workbench_panel.gd") const STOCKPILE_PANEL_SCRIPT: Script = preload("res://scenes/ui/stockpile_panel.gd") const MEDIEVAL_THEME_SCRIPT: Script = preload("res://scenes/ui/medieval_theme.gd") # Built once in _ready and re-applied to any CanvasLayer-rooted Control because # CanvasLayer doesn't propagate the root-Window theme cascade. var _app_theme: Theme = null const SETTINGS_MENU_SCRIPT: Script = preload("res://scenes/ui/settings_menu.gd") # Phase 17 (Agent B) — BuildDrawer bottom-sheet (layer 16). const BUILD_DRAWER_SCRIPT: Script = preload("res://scenes/ui/build_drawer.gd") # Phase 17 (Agent C) — WorkPriorityMatrix (layer 17) and AlertsLog (layer 19). const WORK_PRIORITY_MATRIX_SCRIPT: Script = preload("res://scenes/ui/work_priority_matrix.gd") const ALERTS_LOG_SCRIPT: Script = preload("res://scenes/ui/alerts_log.gd") # Phase 17 — DaySummaryCard end-of-day recap modal (layer 19). const DAY_SUMMARY_CARD_SCRIPT: Script = preload("res://scenes/ui/day_summary_card.gd") # Phase 19 — HintOverlay top-center banner (layer 22). const HINT_OVERLAY_SCRIPT: Script = preload("res://scenes/ui/hint_overlay.gd") # Phase 19 — HelpModal static reference (layer 20). const HELP_MODAL_SCRIPT: Script = preload("res://scenes/ui/help_modal.gd") func _ready() -> void: Audit.log("main", "Phase 3 — world + AI (Decision + JobRunner + RestProvider) online.") # Autoloads — keep these asserts; cheap and catch a renamed-autoload # regression instantly. assert(World != null, "World autoload missing") assert(Sim != null, "Sim autoload missing") assert(GameState != null, "GameState autoload missing") assert(EventBus != null, "EventBus autoload missing") assert(Strings != null, "Strings autoload missing") assert(SaveSystem != null, "SaveSystem autoload missing") assert(Autosave != null, "Autosave autoload missing") # Medieval-warm Theme — assigned on the root Window first. Cascade alone # doesn't reach Controls inside CanvasLayers (CanvasLayer has no theme # property), so we also walk the tree post-mount and apply to every Control # encountered. (2026-05-16 polish pass.) _app_theme = MEDIEVAL_THEME_SCRIPT.build() get_tree().root.theme = _app_theme # Phase 15 — Storyteller UI layers. Runtime-instantiated so no .tscn edit is # needed. CanvasLayer ensures correct draw order above World/TopBar regardless # of parent-node position. var banner := CanvasLayer.new() banner.set_script(STORYTELLER_BANNER_SCRIPT) banner.name = "StorytellerBanner" add_child(banner) var modal := CanvasLayer.new() modal.set_script(STORYTELLER_MODAL_SCRIPT) modal.name = "StorytellerModal" add_child(modal) Audit.log("main", "Phase 15 — StorytellerBanner + StorytellerModal mounted.") # Phase 16 — Save/Load UI layers. var resume_toast := CanvasLayer.new() resume_toast.set_script(RESUME_TOAST_SCRIPT) resume_toast.name = "ResumeToast" add_child(resume_toast) var load_menu := CanvasLayer.new() load_menu.set_script(LOAD_MENU_SCRIPT) load_menu.name = "LoadMenu" add_child(load_menu) # Inject LoadMenu ref into TopBar so the Load button can call open() # without reaching into the scene tree by path. var top_bar = get_node_or_null("TopBar") if top_bar != null: top_bar.load_menu = load_menu Audit.log("main", "Phase 16 — LoadMenu + ResumeToast mounted.") # Phase 17 — PawnDetailPanel (layer 18) and SettingsMenu (layer 26). var pawn_detail_panel := CanvasLayer.new() pawn_detail_panel.set_script(PAWN_DETAIL_PANEL_SCRIPT) pawn_detail_panel.name = "PawnDetailPanel" add_child(pawn_detail_panel) # Bill-editor bottom-sheet for workbenches. Same shape as PawnDetailPanel # (right-anchored 360 px, layer 18); mutually exclusive with it via Selection. var workbench_panel := CanvasLayer.new() workbench_panel.set_script(WORKBENCH_PANEL_SCRIPT) workbench_panel.name = "WorkbenchPanel" add_child(workbench_panel) # Stockpile filter + priority editor. Right-anchored 360 px, layer 18; # mutually exclusive with PawnDetailPanel and WorkbenchPanel via Selection. var stockpile_panel := CanvasLayer.new() stockpile_panel.set_script(STOCKPILE_PANEL_SCRIPT) stockpile_panel.name = "StockpilePanel" add_child(stockpile_panel) var settings_menu := CanvasLayer.new() settings_menu.set_script(SETTINGS_MENU_SCRIPT) settings_menu.name = "SettingsMenu" add_child(settings_menu) # Inject SettingsMenu ref into TopBar so the Settings button can call open() # without reaching into the scene tree by path. if top_bar != null: top_bar.settings_menu = settings_menu if top_bar.has_method("_add_settings_btn"): top_bar._add_settings_btn() Audit.log("main", "Phase 17 — PawnDetailPanel + WorkbenchPanel + SettingsMenu mounted.") # Phase 17 (Agent B) — BuildDrawer bottom-sheet (layer 16). # Must mount AFTER the World node is ready (World._ready seeds designation_ctl). # Resolve the Designation controller from the World child so BuildDrawer can # call set_active_tool() without a get_node("/root/…") call. var build_drawer := CanvasLayer.new() build_drawer.set_script(BUILD_DRAWER_SCRIPT) build_drawer.name = "BuildDrawer" add_child(build_drawer) # Inject Designation ref (World child node) into the drawer. var world_node = get_node_or_null("World") if world_node != null: var desig = world_node.get_node_or_null("DesignationCtl") if desig != null: build_drawer.designation = desig else: Audit.log("main", "BuildDrawer: DesignationCtl not found on World — tool paint disabled") else: Audit.log("main", "BuildDrawer: World node not found — tool paint disabled") # Inject BuildDrawer ref into TopBar and add the Build button. if top_bar != null: top_bar.build_drawer = build_drawer if top_bar.has_method("_add_build_btn"): top_bar._add_build_btn() Audit.log("main", "Phase 17 (Agent B) — BuildDrawer mounted.") # Phase 17 (Agent C) — WorkPriorityMatrix (layer 17) and AlertsLog (layer 19). var work_matrix := CanvasLayer.new() work_matrix.set_script(WORK_PRIORITY_MATRIX_SCRIPT) work_matrix.name = "WorkPriorityMatrix" add_child(work_matrix) var alerts_log := CanvasLayer.new() alerts_log.set_script(ALERTS_LOG_SCRIPT) alerts_log.name = "AlertsLog" add_child(alerts_log) # Inspect tooltip — hover any tile to see what's there (pawn / wall / bed # / crate contents / tree progress / item stack / stockpile filter). # Layer 50: above world, below modals (which sit at 100+). var inspect := CanvasLayer.new() inspect.set_script(preload("res://scenes/ui/inspect_tooltip.gd")) inspect.name = "InspectTooltip" add_child(inspect) # Inject refs into TopBar and add Work + Log buttons to ButtonRow. if top_bar != null: top_bar.work_priority_matrix = work_matrix top_bar.alerts_log_panel = alerts_log if top_bar.has_method("_add_work_log_btns"): top_bar._add_work_log_btns() Audit.log("main", "Phase 17 (Agent C) — WorkPriorityMatrix + AlertsLog mounted.") # Phase 17 — DaySummaryCard (layer 19) — auto-opens on day_ended signal. var day_summary_card := CanvasLayer.new() day_summary_card.set_script(DAY_SUMMARY_CARD_SCRIPT) day_summary_card.name = "DaySummaryCard" add_child(day_summary_card) Audit.log("main", "Phase 17 — DaySummaryCard mounted.") # Phase 19 — HintOverlay top-center banner (layer 22). # HintOverlay._ready() registers itself with HintSystem autoload so the # autoload never has to find_child() the tree at call time. var hint_overlay := CanvasLayer.new() hint_overlay.set_script(HINT_OVERLAY_SCRIPT) hint_overlay.name = "HintOverlay" add_child(hint_overlay) # Phase 19 — HelpModal static reference panel (layer 20). # Subscribes to EventBus.help_requested in its own _ready; no injection needed. var help_modal := CanvasLayer.new() help_modal.set_script(HELP_MODAL_SCRIPT) help_modal.name = "HelpModal" add_child(help_modal) Audit.log("main", "Phase 19 — HintOverlay + HelpModal mounted.") # Apply the medieval theme to every Control under each CanvasLayer. # CanvasLayers interrupt the root-Window theme cascade so we have to seed # each one explicitly. Defer one frame so panels that build their UI in # _ready (PawnDetailPanel, WorkbenchPanel, BuildDrawer) finish first. call_deferred("_apply_theme_to_canvas_layers") ## Walks the scene tree and assigns _app_theme to every Control directly under ## a CanvasLayer (the topmost Control in each layer's branch). From there the ## standard Control-to-Control cascade carries the theme to all descendants. ## Also catches popups and modals that mount later via child_entered_tree. func _apply_theme_to_canvas_layers() -> void: for c in get_children(): if c is CanvasLayer: _seed_layer_theme(c) # Watch for late additions (popup menus, modals). if not c.child_entered_tree.is_connected(_on_layer_child_added): c.child_entered_tree.connect(_on_layer_child_added) func _seed_layer_theme(layer: CanvasLayer) -> void: for c in layer.get_children(): if c is Control and c.theme == null: c.theme = _app_theme func _on_layer_child_added(node: Node) -> void: if node is Control and node.theme == null: node.theme = _app_theme