fix(ui): address PR #124 review — navigation, error handling, cleanup
- 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 <noreply@anthropic.com>
This commit is contained in:
@@ -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:
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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 <system> <commodity> <effect> <magnitude> [ticks]
|
||||
commodity_id = args[1]
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"])
|
||||
|
||||
|
||||
Reference in New Issue
Block a user