feat(client): atlas roads/settlements overlays + legend + server-API data delivery (T-960, T-949)
T-960: gen_l2_roads (MaintenanceAuthority-colored polylines, rail styling, junction markers) + gen_l3_settlements (size-scaled markers, capital shape, name labels) overlays — cities render on generated bodies for the first time; left-side generation legend panel (D-226 item 3, data-driven per-overlay spec, implant component library); protocol.gd decodes road_graph/settlements + the two new response types. T-949: system_index/atlas_app/overview_screen migrated off the direct star_map_data.json read to StarMapRequest over the bridge (loading state + replay-on-connect, no silent file fallback); atlas_viewer _load_markers requests CityNamesResponse for non-Sol bodies; Sol keeps the legacy authored markers.json geometry read (D-236/T-1073, load-bearing guard). Lead fix: _send_star_map_request now carries the same guard as request_star_map — the autoload's _star_map_wanted leaked across gdUnit suites and the unguarded replay-on-CONNECTED crashed 8 pre-existing flow tests on a Nil bridge; reset_test_state clears the flag. Fixtures regenerated via gen_fixtures (road/settlement samples). Full suite 2946/2946. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,31 +1,69 @@
|
||||
class_name SystemIndex
|
||||
## Shared system data loader for implant apps (#844).
|
||||
## Static helper — call as SystemIndex.get_sorted_systems().
|
||||
## Shared, cached system data loader for implant apps (#844, T-949).
|
||||
##
|
||||
## T-949: replaces the direct star_map_data.json FileAccess read with a
|
||||
## StarMapRequest over the bridge (D-010 — the client never reads game data
|
||||
## files directly). A static cache so every caller (AtlasApp's Reach screen,
|
||||
## the economics OverviewScreen) shares one fetch instead of each re-asking.
|
||||
##
|
||||
## Usage per caller:
|
||||
## 1. Call request_refresh() once (e.g. in _ready()/on_install()) — idempotent,
|
||||
## no-op once loaded or already in flight.
|
||||
## 2. Connect to SimBridge.star_map_received and, in the handler, re-pull
|
||||
## get_sorted_systems() to refresh with the now-populated list.
|
||||
## The Atlas can open before the bridge finishes its handshake —
|
||||
## SimBridge.request_star_map() remembers the request and fires it
|
||||
## automatically the instant the connection reaches CONNECTED, so callers
|
||||
## never need to poll or retry themselves.
|
||||
|
||||
const DATA_PATH := "res://data/star_map_data.json"
|
||||
static var _cache: Array = []
|
||||
static var _loaded: bool = false
|
||||
static var _requested: bool = false
|
||||
|
||||
|
||||
## Sorted node list (by proper_name, falling back to system_id), or [] if the
|
||||
## star map has not arrived yet. Callers should re-pull this after
|
||||
## SimBridge.star_map_received fires.
|
||||
static func get_sorted_systems() -> Array:
|
||||
if not FileAccess.file_exists(DATA_PATH):
|
||||
push_warning("SystemIndex: %s not found" % DATA_PATH)
|
||||
return []
|
||||
var file := FileAccess.open(DATA_PATH, FileAccess.READ)
|
||||
if file == null:
|
||||
push_warning("SystemIndex: could not open %s" % DATA_PATH)
|
||||
return []
|
||||
var parsed: Variant = JSON.parse_string(file.get_as_text())
|
||||
file.close()
|
||||
if not (parsed is Dictionary):
|
||||
return []
|
||||
var nodes: Array = []
|
||||
for node: Dictionary in parsed.get("nodes", []):
|
||||
return _cache
|
||||
|
||||
|
||||
static func is_loaded() -> bool:
|
||||
return _loaded
|
||||
|
||||
|
||||
## Ask the bridge for the star map if it hasn't been fetched yet. Safe to call
|
||||
## from every screen's _ready()/on_install() — idempotent once loaded or a
|
||||
## request is already in flight.
|
||||
static func request_refresh() -> void:
|
||||
if _loaded or _requested:
|
||||
return
|
||||
_requested = true
|
||||
SimBridge.request_star_map()
|
||||
|
||||
|
||||
## Feed a decoded StarMapResponse (from a SimBridge.star_map_received handler)
|
||||
## into the cache. Sorts once here so every caller gets the same order for free.
|
||||
## Only a "Ready" status is trusted — an Error response (protocol.gd's
|
||||
## star_map_response_from_raw already reduces it to status/error/nodes=[])
|
||||
## must NOT mark the cache _loaded, and must clear _requested so a later
|
||||
## request_refresh() retries instead of treating the failed fetch as
|
||||
## permanently done.
|
||||
static func ingest(response: Dictionary) -> void:
|
||||
if str(response.get("status", "")) != "Ready":
|
||||
_requested = false
|
||||
return
|
||||
var nodes: Array = response.get("nodes", [])
|
||||
var sorted: Array = []
|
||||
for node: Dictionary in nodes:
|
||||
var sid: String = node.get("system_id", "")
|
||||
if not sid.is_empty():
|
||||
nodes.append(node)
|
||||
nodes.sort_custom(
|
||||
sorted.append(node)
|
||||
sorted.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
|
||||
)
|
||||
return nodes
|
||||
_cache = sorted
|
||||
_loaded = true
|
||||
|
||||
Reference in New Issue
Block a user