The entry-point coordinator read as an internal name ("GameFlow" buried in
scenes/flow/), so it wasn't obvious THIS is the scene that boots first — the
title screen looked like the entry point. Renamed to the Godot-conventional
Main (scenes/Main.tscn + scripts/main.gd) so the boot scene is self-evident.
TitleScreen is unchanged and still shown BY Main as its first screen.
Also drops the hand-written gd_scene uid (now matches sibling scenes) —
closes the Minor review finding. Full suite 327/327 green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
94 lines
3.2 KiB
GDScript
94 lines
3.2 KiB
GDScript
class_name Main
|
|
extends Node
|
|
## The single home for the game's screen sequence: Title → Creation → Shell.
|
|
## Script-first by design — a pure coordinator with no Control layout, so ADR 0001's
|
|
## editor-first rule does not apply (that rule governs layout scenes). It owns one
|
|
## ContentDB + one DmService and swaps screens via instantiate → inject → add_child,
|
|
## the injection seam change_scene_to_file cannot provide.
|
|
##
|
|
## §2: the flow carries only plain data; NewGame.construct re-rolls the character
|
|
## from the creation seed itself. ⛨ saga: _show_creation and _start_campaign are
|
|
## separate, so a fourth entry path is an added arm, not surgery on existing edges.
|
|
|
|
const HttpTransport = preload("res://scripts/net/http_transport.gd")
|
|
|
|
const TITLE := preload("res://scenes/title/TitleScreen.tscn")
|
|
const CREATION := preload("res://scenes/creation/CharacterCreation.tscn")
|
|
const SHELL := preload("res://scenes/shell/MainWindowShell.tscn")
|
|
|
|
# Injection seams — set BETWEEN instantiate() and add_child(), as the shell/creation
|
|
# do for their own deps. _ready builds the real ones when nothing was supplied.
|
|
var world: ContentDB = null
|
|
var service: DmService = null
|
|
var origin: Dictionary = {}
|
|
|
|
var _current: Node = null
|
|
var _http: HTTPRequest
|
|
|
|
|
|
func _ready() -> void:
|
|
if world == null:
|
|
world = ContentDB.new()
|
|
world.load_from(ContentDB.default_content_root())
|
|
if origin.is_empty():
|
|
origin = ContentDB.load_json(ContentDB.origin_path("deserter"))
|
|
if service == null:
|
|
_http = HTTPRequest.new()
|
|
add_child(_http)
|
|
service = DmService.new(HttpTransport.new(_http), FallbackLibrary.new())
|
|
_show_title()
|
|
|
|
|
|
func _show(next: PackedScene, inject: Callable) -> void:
|
|
## The one swap primitive. inject runs after instantiate() (which does NOT run
|
|
## _ready) and before add_child (which does) — the injection window. The outgoing
|
|
## screen is queue_free'd (deferred): a signal handler must never free the node
|
|
## still executing up its own call stack.
|
|
var old := _current
|
|
var screen := next.instantiate()
|
|
inject.call(screen)
|
|
_current = screen
|
|
add_child(screen)
|
|
if old != null:
|
|
old.queue_free()
|
|
|
|
|
|
func _show_title() -> void:
|
|
_show(TITLE, func(s): s.menu_activated.connect(_on_menu))
|
|
|
|
|
|
func _show_creation() -> void:
|
|
_show(CREATION, func(s):
|
|
s.world = world
|
|
s.origin = origin
|
|
s.creation_confirmed.connect(_start_campaign))
|
|
|
|
|
|
func _show_shell(log: CanonLog, state: GameState) -> void:
|
|
_show(SHELL, func(s):
|
|
s.service = service
|
|
s.injected_log = log
|
|
s.injected_state = state)
|
|
|
|
|
|
func _on_menu(id: StringName) -> void:
|
|
match id:
|
|
&"new_game":
|
|
_show_creation()
|
|
&"quit":
|
|
get_tree().quit()
|
|
_:
|
|
pass # continue / load land in later milestones (M9); inert for now
|
|
|
|
|
|
func _start_campaign(creation: Dictionary) -> void:
|
|
## Takes a plain Dictionary — does NOT assume it came from the creation screen, so
|
|
## a saga can synthesize one and call this directly (skipping creation).
|
|
var result := NewGame.construct(origin, world, creation)
|
|
if not result.get("ok", false):
|
|
# Unreachable via the UI (the CTA gates on validate), but §2 forbids trusting
|
|
# the caller and §13 forbids surfacing an error. Log and stay put.
|
|
push_error("Main: construct rejected a creation: %s" % str(result.get("errors", [])))
|
|
return
|
|
_show_shell(result["log"], result["state"])
|