fix(simulation): PR #176 review round — all 12 findings + 2 recommendations addressed
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>
This commit is contained in:
@@ -103,6 +103,12 @@ func reset_test_state() -> void:
|
||||
harness.reset()
|
||||
# T-949: don't leak a star-map request across tests (autoload state).
|
||||
_star_map_wanted = false
|
||||
# SystemIndex's static cache is the same cross-suite leak class (PR #176
|
||||
# review H2/T1) — reset it here so the dozens of suites already calling
|
||||
# SimBridge.reset_test_state() cover both. load() inline per the autoload
|
||||
# parse-order rule (CLAUDE.md).
|
||||
var SI := load("res://ui/implant/widgets/system_index.gd")
|
||||
SI.reset_test_state()
|
||||
|
||||
|
||||
func _test_snapshot() -> Dictionary:
|
||||
|
||||
@@ -774,9 +774,11 @@ static func decode_atlas_layer_response(bytes: PackedByteArray) -> Variant:
|
||||
## road_graph/settlements (T-960): passthrough fields for the L2 road/rail
|
||||
## graph and L3 settlement placements, mirroring the district_grid precedent
|
||||
## (T-1046) — raw decoded maps/arrays, no further client-side reshaping.
|
||||
## PLACEHOLDER key names ("road_graph"/"settlements") pending confirmation
|
||||
## against dudley-atlas-server's RoadGraphLayer/SettlementLayer field names —
|
||||
## this is the one spot to rename if the server picks different keys.
|
||||
## Key names "road_graph"/"settlements" are the CONFIRMED wire contract —
|
||||
## identical to server/src/atlas/layer_proxy.rs AtlasLayerResponse's field
|
||||
## names (pinned 2026-07-14; round-tripped by test_atlas_overlays.gd and the
|
||||
## server's msgpack round-trip tests). This remains the one client-side spot
|
||||
## to touch if the contract ever changes.
|
||||
static func atlas_response_from_raw(raw: Variant) -> Variant:
|
||||
if not raw is Dictionary or not raw.has("status"):
|
||||
return null
|
||||
|
||||
@@ -149,8 +149,11 @@ func test_city_names_received_error_status_leaves_markers_empty() -> void:
|
||||
var v := _make_viewer()
|
||||
v.show_body({"body_id": NON_SOL_BODY_ID}, {"system_id": NON_SOL_SYSTEM_ID})
|
||||
|
||||
# Normalized string form — production handlers only ever see the output of
|
||||
# Protocol.city_names_response_from_raw, which reduces {"Error": msg} to
|
||||
# "Error" (review H5: the raw wire shape only passed by str() coincidence).
|
||||
v._on_city_names_received(
|
||||
{"body_id": NON_SOL_BODY_ID, "status": {"Error": "db unavailable"}, "cities": []}
|
||||
{"body_id": NON_SOL_BODY_ID, "status": "Error", "cities": []}
|
||||
)
|
||||
|
||||
assert_that(v.get_markers()).override_failure_message(
|
||||
|
||||
@@ -228,9 +228,60 @@ func test_sim_bridge_still_routes_snapshots_after_starmap_additions() -> void:
|
||||
|
||||
func test_request_star_map_is_noop_in_test_mode() -> void:
|
||||
# SimBridge defaults to test_mode = true (SR_LIVE unset) — request_star_map
|
||||
# must not error even with no live bridge/server.
|
||||
# must not error even with no live bridge/server, but it MUST still record
|
||||
# the wish so replay-on-connect can fire later (review H3: this flag's
|
||||
# unreset leak was the cross-suite crash fixed in this same PR).
|
||||
SimBridge.reset_test_state()
|
||||
SimBridge.request_star_map()
|
||||
assert_bool(SimBridge._star_map_wanted).override_failure_message(
|
||||
"test-mode request_star_map must still set the replay flag"
|
||||
).is_true()
|
||||
SimBridge.reset_test_state()
|
||||
|
||||
|
||||
func test_request_city_names_is_noop_in_test_mode() -> void:
|
||||
# Must not error — and must NOT touch the star-map replay flag (guards a
|
||||
# future copy-paste wiring city-names into the wrong flag).
|
||||
SimBridge.reset_test_state()
|
||||
SimBridge.request_city_names("GJ903b")
|
||||
assert_bool(SimBridge._star_map_wanted).is_false()
|
||||
|
||||
|
||||
class _CaptureBridge:
|
||||
var sent: Array = []
|
||||
|
||||
func send_message(bytes: PackedByteArray) -> int:
|
||||
sent.append(bytes)
|
||||
return OK
|
||||
|
||||
|
||||
func test_replay_on_connect_sends_star_map_request_live() -> void:
|
||||
# Review H7: the replay-on-CONNECTED path (_set_state →
|
||||
# _send_star_map_request) is the exact mechanism behind the cross-suite
|
||||
# crash fixed in this PR, and every other test runs test_mode=true where
|
||||
# the guard short-circuits before touching _bridge — proving only
|
||||
# "doesn't crash", never "actually replays". Exercise the positive case
|
||||
# per the test_local_bridge.gd test-mode-flip precedent.
|
||||
var original_test_mode: bool = SimBridge.test_mode
|
||||
var original_state: SimBridge.ConnectionState = SimBridge.state
|
||||
var original_bridge = SimBridge._bridge
|
||||
SimBridge.reset_test_state()
|
||||
|
||||
var stub := _CaptureBridge.new()
|
||||
SimBridge.test_mode = false
|
||||
SimBridge._bridge = stub
|
||||
SimBridge.state = SimBridge.ConnectionState.CONNECTING
|
||||
SimBridge._star_map_wanted = true
|
||||
SimBridge._set_state(SimBridge.ConnectionState.CONNECTED)
|
||||
|
||||
assert_int(stub.sent.size()).override_failure_message(
|
||||
"reaching CONNECTED with _star_map_wanted set must replay the request"
|
||||
).is_equal(1)
|
||||
var decoded = Messagepack.decode(stub.sent[0])
|
||||
assert_that(decoded.value).is_equal({"star_map": true})
|
||||
|
||||
# Restore autoload state for later suites.
|
||||
SimBridge.test_mode = original_test_mode
|
||||
SimBridge._bridge = original_bridge
|
||||
SimBridge.state = original_state
|
||||
SimBridge.reset_test_state()
|
||||
|
||||
@@ -99,14 +99,23 @@ func test_ingest_ignores_non_ready_status() -> void:
|
||||
|
||||
|
||||
func test_request_refresh_retries_after_a_failed_ingest() -> void:
|
||||
# request_refresh() is a no-op in test mode either way (SimBridge has no
|
||||
# live connection), so this only proves the _requested/_loaded bookkeeping
|
||||
# doesn't get stuck: a failed ingest must clear _requested so a later
|
||||
# request_refresh() is willing to ask again (not just silently no-op
|
||||
# forever because a prior request was already "in flight").
|
||||
SystemIndex.ingest({"status": "Ready", "nodes": [{"system_id": "GJ 1"}]})
|
||||
assert_bool(SystemIndex.is_loaded()).is_true()
|
||||
SystemIndex.request_refresh() # no-op — already loaded
|
||||
# The retry semantic itself (review H4): a failed ingest must clear the
|
||||
# in-flight flag so a later request_refresh() actually asks again — not
|
||||
# silently no-op forever because a prior request was "in flight".
|
||||
SystemIndex.reset_test_state()
|
||||
SystemIndex.request_refresh()
|
||||
assert_bool(SystemIndex.has_pending_request()).is_true()
|
||||
assert_bool(SystemIndex.is_loaded()).is_false()
|
||||
# A failed fetch arrives: stays unloaded, and the in-flight flag clears...
|
||||
SystemIndex.ingest({"status": "Error", "error": "db unavailable", "nodes": []})
|
||||
assert_bool(SystemIndex.has_pending_request()).override_failure_message(
|
||||
"a failed ingest must clear _requested so a later refresh can retry"
|
||||
).is_false()
|
||||
assert_bool(SystemIndex.is_loaded()).is_false()
|
||||
# ...so a retry genuinely re-requests instead of no-opping.
|
||||
SystemIndex.request_refresh()
|
||||
assert_bool(SystemIndex.has_pending_request()).is_true()
|
||||
SystemIndex.reset_test_state()
|
||||
|
||||
|
||||
func test_request_refresh_does_not_crash_when_already_loaded() -> void:
|
||||
|
||||
@@ -32,6 +32,23 @@ 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.
|
||||
|
||||
Reference in New Issue
Block a user