Files
settled-reach/client/ui/meta/screens/loading/loading_screen.gd
T
jpmschweitzerandClaude Opus 4.6 2104348000 feat(client): Option A pre-game flow + ESC priority chain (Workstream 4)
main_menu now connects SimBridge before character_creation opens, gating
the transition on first ObserverSnapshot carrying a bookmark_catalog.
Loading screen is shown during the connect; on cancel the SimBridge
subprocess is torn down and the player returns to main_menu. Catalog is
read straight from GameState.bookmark_catalog in W6.

Flow (Option A):
1. New Game → SessionManager.new_game() creates save dir
2. main_menu pushes loading_screen via MetaStack with "Connecting to
   simulation..." message
3. SimBridge.connect_to_sim() spawned; main_menu listens on
   connection_state_changed, then on snapshot_received for the catalog
4. On catalog arrival: loading_screen closed, scene-transition to
   character_creation
5. character_creation Cancel → SimBridge.disconnect_from_sim() + scene
   transition back to main_menu (Tyre's recommendation: clean state per
   session over warm-start savings)
6. character_creation Start → ConfirmBookmark sent (stubbed for W4 with
   first catalog entry; real bookmark + location from W6's UI)

ESC priority chain in main.gd OPEN_MENU handler:
- MetaStack.handle_escape() first — closes the topmost meta overlay
- HudGroups.is_implant_active() / close_app() — closes active implant
- Fallback: toggle settings_dialog (existing W2 behavior)

Files:
- sim_bridge.gd: send_named_action(action_name, action_data) helper.
  Bridges named tag-enum PlayerActions (RequestBookmarkCatalog,
  ConfirmBookmark) into the existing outbound buffer, parallel to
  send_input's InputMapper.Action handling.
- loading_screen.gd: set_message(text) for the connecting/loading label.
- main_menu.gd: full Option A flow rewrite. Tracks _waiting_for_catalog
  so re-clicking New Game during connect is a no-op.
- character_creation.gd: _on_back disconnect path + _on_start
  ConfirmBookmark stub. MAIN_MENU_SCENE / GAME_SCENE constants.
- main.gd: connect_to_sim guard (don't reconnect when Option A leaves
  it CONNECTED). ESC chain wiring.

Verification:
- gdlint clean
- godot --headless --path client --quit — no SCRIPT ERROR
- test_protocol 62/62, test_implant_nav_stack 52/52, test_client_p3
  24/24, test_ui_framework_sprint15 54/54

Pre-existing failing suites unchanged: test_sprint2_proof,
test_dialogue_sprint18, test_client_p2 (camera-smoothing assertions
that pre-date W4 — main.gd has disabled position_smoothing_enabled
since #117 / #501 / #117 manual-lerp; tests were stale).

Workstream 5 (3-tab restructure of character_creation: Bookmark /
Appearance with sub-nav / Skills / Debug) lands next.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-20 11:30:15 +02:00

93 lines
3.2 KiB
GDScript

extends MetaScreen
## #257: Loading screen overlay — blocks input during save/load round-trip.
## Shown when LOAD_GAME fires; hidden when save_result arrives (success or failure).
## Full-screen, dark overlay with centered status text.
## #724: Shows client version (from project.yaml) and protocol version below the status text.
const BG_COLOR := Color(0.0, 0.0, 0.0, 0.75)
const TEXT_COLOR := Color("#c8d0e0")
const VERSION_COLOR := Color("#667788")
const FONT_SIZE := 18
const VERSION_FONT_SIZE := 11
var _label: Label = null
var _version_label: Label = null
func _ready() -> void:
closable_by_escape = false
visible = false
mouse_filter = Control.MOUSE_FILTER_IGNORE
set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
_build_ui()
func _build_ui() -> void:
var bg := ColorRect.new()
bg.color = BG_COLOR
bg.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
bg.mouse_filter = Control.MOUSE_FILTER_IGNORE
add_child(bg)
_label = Label.new()
_label.text = UIStrings.get_text("notifications.loading")
_label.add_theme_font_size_override("font_size", FONT_SIZE)
_label.add_theme_color_override("font_color", TEXT_COLOR)
_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
_label.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
_label.mouse_filter = Control.MOUSE_FILTER_IGNORE
add_child(_label)
# client_ver and proto_ver are independent — project.yaml version is the client release,
# Protocol.PROTOCOL_VERSION is the wire protocol. Mismatches between builds are visible
# only to the observer reading the loading-screen label; a future ticket will surface them.
var client_ver := _read_client_version()
var proto_ver: int = Protocol.PROTOCOL_VERSION
_version_label = Label.new()
_version_label.text = "v%s · protocol %d" % [client_ver, proto_ver]
_version_label.add_theme_font_size_override("font_size", VERSION_FONT_SIZE)
_version_label.add_theme_color_override("font_color", VERSION_COLOR)
_version_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
_version_label.anchor_left = 0.0
_version_label.anchor_right = 1.0
_version_label.anchor_top = 1.0
_version_label.anchor_bottom = 1.0
_version_label.offset_top = -32.0
_version_label.offset_bottom = -8.0
_version_label.mouse_filter = Control.MOUSE_FILTER_IGNORE
add_child(_version_label)
func _read_client_version() -> String:
var yaml_path := ProjectSettings.globalize_path("res://") + "/../project.yaml"
if not FileAccess.file_exists(yaml_path):
return "?.?.?"
var file := FileAccess.open(yaml_path, FileAccess.READ)
if file == null:
return "?.?.?"
var content := file.get_as_text()
file.close()
for line: String in content.split("\n"):
if line.begins_with("version:"):
var parts := line.split(":", false, 1)
if parts.size() >= 2:
return parts[1].strip_edges()
return "?.?.?"
## Update the status text shown while loading. Call before show_loading() or after.
func set_message(text: String) -> void:
if _label != null:
_label.text = text
func show_loading() -> void:
MetaStack.push(self)
open()
## Hide the loading overlay. success=false is reserved for future failure-state UI.
func hide_loading(_success: bool = true) -> void:
close()