From 28d95c23a84b6e44c112f07352e0b1b789633af4 Mon Sep 17 00:00:00 2001 From: Jeroen Schweitzer Date: Fri, 10 Apr 2026 14:03:49 +0200 Subject: [PATCH] =?UTF-8?q?fix(ui):=20address=20PR=20#124=20review=20?= =?UTF-8?q?=E2=80=94=20navigation,=20error=20handling,=20cleanup?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Economics panel: replace dead _gui_input LEFT/RIGHT with public navigate() method, wire [ ] keys in main.gd (avoids movement key conflict, fixes focus_mode=NONE issue) - Debug console: add explicit effect guard in econ inject no-commodity branch so invalid effects don't fall to commodity-form error - Snapshot consumer: null-clear GameState.economy_snapshot after consuming (matches one-shot consumer invariant) - Star map: remove duplicate doc comment above set_insert_active() - Generate script: remove stale comment, dead _WORKTREE_PARENT var, dead field extraction in parse_wiki_index, add try/except around DB queries Co-Authored-By: Claude Opus 4.6 --- client/scripts/main.gd | 8 +++ client/scripts/snapshot_consumers.gd | 1 + client/ui/debug_console.gd | 6 ++ client/ui/implant/economics_panel.gd | 35 +++------- client/ui/star_map.gd | 1 - tooling/generate-star-map-data.py | 97 +++++++++++++--------------- 6 files changed, 69 insertions(+), 79 deletions(-) diff --git a/client/scripts/main.gd b/client/scripts/main.gd index 518786283..a75fc3e8f 100644 --- a/client/scripts/main.gd +++ b/client/scripts/main.gd @@ -176,6 +176,14 @@ func _unhandled_key_input(event: InputEvent) -> void: # #824: E — toggle Economics Monitor implant panel if economics_panel: economics_panel.toggle_visible() + elif event is InputEventKey and event.keycode == KEY_BRACKETLEFT: + # #824: [ — cycle economics panel system selector backward + if economics_panel and HudGroups.is_app_active("implant/economics"): + economics_panel.navigate(-1) + elif event is InputEventKey and event.keycode == KEY_BRACKETRIGHT: + # #824: ] — cycle economics panel system selector forward + if economics_panel and HudGroups.is_app_active("implant/economics"): + economics_panel.navigate(1) func _process(delta: float) -> void: diff --git a/client/scripts/snapshot_consumers.gd b/client/scripts/snapshot_consumers.gd index ca886eafb..f278c574e 100644 --- a/client/scripts/snapshot_consumers.gd +++ b/client/scripts/snapshot_consumers.gd @@ -183,6 +183,7 @@ func consume_economy_snapshot() -> void: return if economics_panel.has_method("receive_economy_data"): economics_panel.receive_economy_data(GameState.economy_snapshot) + GameState.economy_snapshot = null # #174: Consume examine result — show overlay when server sends character-filtered observation. diff --git a/client/ui/debug_console.gd b/client/ui/debug_console.gd index 39306e7e8..5df3d2dcd 100644 --- a/client/ui/debug_console.gd +++ b/client/ui/debug_console.gd @@ -287,6 +287,12 @@ func _econ_inject(parts: Array) -> void: magnitude_str = args[2] if args.size() >= 4: ticks_str = args[3] + elif args.size() == 3: + # 3 args but args[1] isn't shock/boost — bad effect keyword, not a commodity + _append_text( + "econ inject: effect must be 'shock' or 'boost', got '%s'" % args[1], ERROR_COLOR + ) + return else: # With commodity_id: econ inject [ticks] commodity_id = args[1] diff --git a/client/ui/implant/economics_panel.gd b/client/ui/implant/economics_panel.gd index ae157a19b..546264d59 100644 --- a/client/ui/implant/economics_panel.gd +++ b/client/ui/implant/economics_panel.gd @@ -167,10 +167,11 @@ func _load_system_list() -> void: var sid: String = node.get("system_id", "") if not sid.is_empty(): _systems.append(node) - _systems.sort_custom(func(a: Dictionary, b: Dictionary) -> bool: - var na: String = a.get("proper_name", a.get("system_id", "")) - var nb: String = b.get("proper_name", b.get("system_id", "")) - return na < nb + _systems.sort_custom( + func(a: Dictionary, b: Dictionary) -> bool: + var na: String = a.get("proper_name", a.get("system_id", "")) + var nb: String = b.get("proper_name", b.get("system_id", "")) + return na < nb ) if not _systems.is_empty(): selected_system = _systems[0].get("system_id", "") @@ -256,7 +257,7 @@ func _rebuild_panel() -> void: _panel.add_component(_placeholder_notice) _panel.add_component(ImplantSeparator.new()) - _panel.add_component(ImplantTextBlock.new("◄ ► select · E close")) + _panel.add_component(ImplantTextBlock.new("[ ] select system · E close")) func _current_node() -> Dictionary: @@ -304,27 +305,11 @@ func _on_economy_data_updated(system_id: String, data: Dictionary) -> void: _placeholder_notice.text = "LIVE DATA ACTIVE" -# ============================================================================= -# Input — LEFT/RIGHT arrow navigation between systems -# ============================================================================= - - -func _gui_input(event: InputEvent) -> void: - if not visible: - return - if event is InputEventKey and event.pressed and not event.is_echo(): - match event.keycode: - KEY_LEFT: - _cycle_system(-1) - get_viewport().set_input_as_handled() - KEY_RIGHT: - _cycle_system(1) - get_viewport().set_input_as_handled() - - -func _cycle_system(direction: int) -> void: +## Cycle the system selector by delta steps (+1 or -1). +## Called from main.gd _unhandled_key_input — [ and ] keys when panel is active. +func navigate(delta: int) -> void: if _systems.is_empty(): return - _selected_idx = wrapi(_selected_idx + direction, 0, _systems.size()) + _selected_idx = wrapi(_selected_idx + delta, 0, _systems.size()) selected_system = _systems[_selected_idx].get("system_id", "") _rebuild_panel() diff --git a/client/ui/star_map.gd b/client/ui/star_map.gd index 579122dd7..0ebb3b462 100644 --- a/client/ui/star_map.gd +++ b/client/ui/star_map.gd @@ -135,7 +135,6 @@ func _process(_delta: float) -> void: _dirty = false -## Called from main.gd when insert state changes. ## Called from main.gd when insert state changes. func set_insert_active(active: bool) -> void: _insert_active = active diff --git a/tooling/generate-star-map-data.py b/tooling/generate-star-map-data.py index b2ba0c100..ba00836f2 100755 --- a/tooling/generate-star-map-data.py +++ b/tooling/generate-star-map-data.py @@ -7,8 +7,8 @@ Run from any directory — paths are resolved relative to this script's location Sources: docs/design/star-map.json — graph topology (nodes + edges) - server/server/data/systems.db — proper names, geographic sectors - wiki/star-systems/ — star type, bodies, population, GTTR excerpt + server/data/systems.db — proper names, geographic sectors, bodies, GDP tier + wiki/star-systems/ — star type, GTTR excerpt (bodies/population from systems.db) Output: client/data/star_map_data.json — self-contained client data for the star map UI @@ -21,16 +21,11 @@ import sqlite3 import sys import tempfile -# Resolve project root from this script's location: tooling/ is one level below root. +# Resolve project root from this script's location. # Works regardless of cwd — no fragile relative path guessing. _SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) _PROJECT_ROOT = os.path.dirname(_SCRIPT_DIR) -# Worktree layout: settled-reach/{client,server,main}/ -# This script lives in client/tooling/, so _PROJECT_ROOT = client/. -# The parent of _PROJECT_ROOT is the worktree parent where sibling dirs live. -_WORKTREE_PARENT = os.path.dirname(_PROJECT_ROOT) - STAR_MAP_PATH = os.path.join(_PROJECT_ROOT, "docs", "design", "star-map.json") SYSTEMS_DB_PATH = os.path.join(_PROJECT_ROOT, "server", "data", "systems.db") WIKI_PATH = os.path.join(_PROJECT_ROOT, "wiki", "star-systems") @@ -43,13 +38,14 @@ def system_id_to_wiki_slug(system_id: str) -> str: def parse_wiki_index(system_id: str) -> dict: - """Extract star type, bodies summary, and population from index.md. + """Extract star type from index.md. - Returns dict with keys: star_type, bodies, population (all strings, may be empty). + Bodies and population are authoritative from systems.db — not read from wiki. + Returns dict with key: star_type (string, may be empty). """ slug = system_id_to_wiki_slug(system_id) path = os.path.join(WIKI_PATH, slug, "index.md") - result = {"star_type": "", "bodies": "", "population": ""} + result = {"star_type": ""} if not os.path.exists(path): return result with open(path, encoding="utf-8") as f: @@ -60,18 +56,7 @@ def parse_wiki_index(system_id: str) -> dict: if m: raw = m.group(1).strip() # Extract spectral class — everything before " ·" or end of string - star_type = raw.split("·")[0].strip() - result["star_type"] = star_type - - # Bodies row: | **Bodies** | 2 habitable · 3 inhabited | - m = re.search(r"\|\s*\*\*Bodies\*\*\s*\|\s*([^|]+?)\s*\|", content) - if m: - result["bodies"] = m.group(1).strip() - - # Population row: | **Population** | 1,200,000,000 | - m = re.search(r"\|\s*\*\*Population\*\*\s*\|\s*([^|]+?)\s*\|", content) - if m: - result["population"] = m.group(1).strip() + result["star_type"] = raw.split("·")[0].strip() return result @@ -173,39 +158,45 @@ def generate() -> dict: with open(STAR_MAP_PATH) as f: star_map = json.load(f) - conn = sqlite3.connect(SYSTEMS_DB_PATH) - conn.row_factory = sqlite3.Row - cur = conn.cursor() - cur.execute( - "SELECT system_id, proper_name, geographic_sector, geographic_band " - "FROM star_systems" - ) - db_lookup = {row["system_id"]: dict(row) for row in cur.fetchall()} + try: + conn = sqlite3.connect(SYSTEMS_DB_PATH) + conn.row_factory = sqlite3.Row + cur = conn.cursor() - # Aggregate body data per system from the bodies table - cur.execute(""" - SELECT system_id, - SUM(CASE WHEN atmosphere IN ('breathable','standard') AND body_type IN ('planet','moon') THEN 1 ELSE 0 END) AS habitable, - SUM(CASE WHEN inhabited = 1 THEN 1 ELSE 0 END) AS inhabited, - SUM(CASE WHEN inhabited = 1 THEN COALESCE(population, 0) ELSE 0 END) AS total_pop - FROM bodies - GROUP BY system_id - """) - body_stats = {row["system_id"]: dict(row) for row in cur.fetchall()} + cur.execute( + "SELECT system_id, proper_name, geographic_sector, geographic_band " + "FROM star_systems" + ) + db_lookup = {row["system_id"]: dict(row) for row in cur.fetchall()} - # Also sum station populations - cur.execute(""" - SELECT system_id, - SUM(COALESCE(population, 0)) AS station_pop - FROM stations - GROUP BY system_id - """) - station_stats = {row["system_id"]: dict(row) for row in cur.fetchall()} + # Aggregate body data per system from the bodies table + cur.execute(""" + SELECT system_id, + SUM(CASE WHEN atmosphere IN ('breathable','standard') AND body_type IN ('planet','moon') THEN 1 ELSE 0 END) AS habitable, + SUM(CASE WHEN inhabited = 1 THEN 1 ELSE 0 END) AS inhabited, + SUM(CASE WHEN inhabited = 1 THEN COALESCE(population, 0) ELSE 0 END) AS total_pop + FROM bodies + GROUP BY system_id + """) + body_stats = {row["system_id"]: dict(row) for row in cur.fetchall()} - # Economic tier for GDP calculation - cur.execute("SELECT system_id, economic_tier FROM system_economy") - econ_tiers = {row["system_id"]: row["economic_tier"] for row in cur.fetchall()} - conn.close() + # Also sum station populations + cur.execute(""" + SELECT system_id, + SUM(COALESCE(population, 0)) AS station_pop + FROM stations + GROUP BY system_id + """) + station_stats = {row["system_id"]: dict(row) for row in cur.fetchall()} + + # Economic tier for GDP calculation + cur.execute("SELECT system_id, economic_tier FROM system_economy") + econ_tiers = {row["system_id"]: row["economic_tier"] for row in cur.fetchall()} + except sqlite3.Error as e: + print(f"ERROR: systems.db query failed: {e}", file=sys.stderr) + sys.exit(1) + finally: + conn.close() adjacency = build_adjacency(star_map["edges"])