H1 demux: ShapeProbe defensive multi-shape rejection (union frames now Err, not first-match; +2 tests) and doc claim made honest. H2/T1 SystemIndex.reset_test_state() folded into SimBridge.reset_test_state() (load() inline per autoload rule) + has_pending_request() accessor. H3 no-op tests now assert the replay flag both directions. H4 retry test actually ingests a failure and asserts the retry semantic. H5 error fixture uses the normalized status string. H6 bridge_tcp e2e sends all five frame shapes over real TCP (star-map + city-names buffers asserted). H7 positive replay-on-CONNECTED test via the test_local_bridge test-mode-flip precedent (stub bridge captures + decodes the request bytes). H8/T2 stale PLACEHOLDER doc replaced with the confirmed contract. H9 is_capital doc matches the COALESCE reality. T-r1 demux ceiling written down (next shape = tagged envelope). T-r2 AtlasLayerResponse governance ceiling comment. Lead item: the four cargo-fmt-formatted files from the gate round are now committed (layer_proxy/plugin/bridge-mod/main). H10 note for the record: the 13 snapshot_*.msgpack fixtures in commit 845737617 were regenerated because they were stale against their own generator (pre-existing version-key removal) — verified harmless, no client reads that key.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
87 lines
3.2 KiB
GDScript
87 lines
3.2 KiB
GDScript
class_name SystemIndex
|
|
## 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.
|
|
|
|
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:
|
|
return _cache
|
|
|
|
|
|
static func is_loaded() -> bool:
|
|
return _loaded
|
|
|
|
|
|
## True while a StarMapRequest is considered in flight — the test-observable
|
|
## counterpart of the retry bookkeeping (see ingest()'s Error path).
|
|
static func has_pending_request() -> bool:
|
|
return _requested
|
|
|
|
|
|
## Test-only: clear the process-global static cache. Statics leak across
|
|
## gdUnit suites in one process — the same class as the
|
|
## SimBridge._star_map_wanted leak fixed in this PR (review H2/T1).
|
|
## SimBridge.reset_test_state() calls this, so every suite already using it
|
|
## gets both resets.
|
|
static func reset_test_state() -> void:
|
|
_cache = []
|
|
_loaded = false
|
|
_requested = false
|
|
|
|
|
|
## 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():
|
|
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
|
|
)
|
|
_cache = sorted
|
|
_loaded = true
|