fix(protocol): drop PROTOCOL_VERSION lockstep — D-192 (#875)
Removes the version-mismatch guard from Protocol.decode_snapshot() and the PROTOCOL_VERSION constant from the client (server side done in #874). Core changes: - protocol.gd: remove const PROTOCOL_VERSION, remove version mismatch guard, remove "version" from return dict, add gauntlet_mode/room_id decode - sim_bridge.gd: remove handshake version check; relax handshake guard to require only a valid Dictionary (server no longer sends protocol_version); emit handshake_complete(0) for API compat - loading_screen.gd: drop "· protocol N" suffix from version label - test_harness.gd: replace Protocol.PROTOCOL_VERSION with literal 23 Test updates (21 files): replace "version": Protocol.PROTOCOL_VERSION with "version": 23 in all snapshot bytes dicts; remove snapshot.version == N assertions; remove version-rejection tests (test_rejects_version_6, test_decode_snapshot_rejects_missing_version, test_decode_snapshot_rejects_old_version, test_protocol_rejects_version_mismatch, test_sim_bridge_test_snapshot_uses_current_protocol_version). Also includes: #872 bookmark_catalog carry-forward regression test, and #873 merge-path flow tests (test_merge_path_flows_sprint37.gd). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -246,13 +246,10 @@ func _process(delta: float) -> void: # gdlint:disable=max-returns
|
||||
if msg.is_empty():
|
||||
return # Not ready yet, continue polling
|
||||
|
||||
# Decode HandshakeMessage: { "protocol_version": N }
|
||||
# Decode HandshakeMessage — D-192 (#875): protocol_version field dropped.
|
||||
# Server sends {} or a minimal dict; only structural validity is required.
|
||||
var decoded: Variant = Messagepack.decode(msg)
|
||||
if (
|
||||
decoded.status != null
|
||||
or not (decoded.value is Dictionary)
|
||||
or not decoded.value.has("protocol_version")
|
||||
):
|
||||
if decoded.status != null or not (decoded.value is Dictionary):
|
||||
var reason := "Handshake decode failed: malformed HandshakeMessage"
|
||||
push_error("SimBridge: %s" % reason)
|
||||
handshake_failed.emit(reason)
|
||||
@@ -260,18 +257,6 @@ func _process(delta: float) -> void: # gdlint:disable=max-returns
|
||||
_set_state(ConnectionState.ERROR)
|
||||
return
|
||||
|
||||
var server_version: int = decoded.value["protocol_version"]
|
||||
if server_version != Protocol.PROTOCOL_VERSION:
|
||||
var reason := (
|
||||
"Protocol version mismatch: server=%d, client=%d"
|
||||
% [server_version, Protocol.PROTOCOL_VERSION]
|
||||
)
|
||||
push_error("SimBridge: %s" % reason)
|
||||
handshake_failed.emit(reason)
|
||||
_bridge.disconnect_from_server()
|
||||
_set_state(ConnectionState.ERROR)
|
||||
return
|
||||
|
||||
# Send startup message with world_seed and character appearance (#175, D-010/D-029, #718).
|
||||
# Server blocks waiting for this before entering the tick loop.
|
||||
var startup_bytes := Protocol.encode_startup_message(
|
||||
@@ -296,7 +281,7 @@ func _process(delta: float) -> void: # gdlint:disable=max-returns
|
||||
_set_state(ConnectionState.ERROR)
|
||||
return
|
||||
|
||||
handshake_complete.emit(server_version)
|
||||
handshake_complete.emit(0) # D-192: protocol_version field dropped; signal kept for API compat
|
||||
_set_state(ConnectionState.CONNECTED)
|
||||
# #646: Request full settings dump on connect — hydrates GameState.ai_enhanced_dialogue_enabled
|
||||
# from server SQLite so the client reflects the authoritative persisted state (D-138).
|
||||
@@ -464,6 +449,15 @@ func receive_bytes(bytes: PackedByteArray) -> void:
|
||||
and _last_snapshot.get("settings_response") != null
|
||||
):
|
||||
snapshot["settings_response"] = _last_snapshot["settings_response"]
|
||||
# #872: Carry forward bookmark_catalog (one-shot, consumed by main_menu._on_snapshot_received_for_catalog).
|
||||
# Server sends catalog on tick 0 and after RequestBookmarkCatalog. If tick 0 and tick 1
|
||||
# arrive in the same TCP batch, the inner receive loop overwrites _last_snapshot and the
|
||||
# catalog is silently lost — this carry-forward prevents that race.
|
||||
if (
|
||||
snapshot.get("bookmark_catalog") == null
|
||||
and _last_snapshot.get("bookmark_catalog") != null
|
||||
):
|
||||
snapshot["bookmark_catalog"] = _last_snapshot["bookmark_catalog"]
|
||||
_last_snapshot = snapshot
|
||||
|
||||
|
||||
|
||||
@@ -9,13 +9,6 @@ extends Node
|
||||
## Unit enum variants (no data) → bare strings ("MoveNorth", "Npc")
|
||||
## Data enum variants → single-element maps ({"UsePerceptionMode": "thermal"})
|
||||
|
||||
## Protocol version — must match server PROTOCOL_VERSION in bridge/types.rs.
|
||||
## Reject snapshots where version != this value.
|
||||
## v20: adds settings_response field to ObserverSnapshot (#627, D-138).
|
||||
## v21: adds economy_snapshot field to ObserverSnapshot (#822, D-181).
|
||||
## v23: adds bookmark_catalog field to ObserverSnapshot (#614).
|
||||
const PROTOCOL_VERSION: int = 23
|
||||
|
||||
# -- Decode: bytes from server → GDScript types --------------------------------
|
||||
|
||||
|
||||
@@ -34,17 +27,6 @@ static func decode_snapshot(bytes: PackedByteArray) -> Variant:
|
||||
push_error("Protocol: snapshot missing required fields")
|
||||
return null
|
||||
|
||||
# Version check: reject snapshots from incompatible server
|
||||
var version: Variant = raw.get("version")
|
||||
if version != PROTOCOL_VERSION:
|
||||
push_error(
|
||||
(
|
||||
"Protocol: version mismatch (got %s, expected %s). Server and client are out of sync."
|
||||
% [version, PROTOCOL_VERSION]
|
||||
)
|
||||
)
|
||||
return null
|
||||
|
||||
var entities: Array[Dictionary] = []
|
||||
var raw_entities: Array = raw["entities"]
|
||||
var dropped := 0
|
||||
@@ -67,7 +49,7 @@ static func decode_snapshot(bytes: PackedByteArray) -> Variant:
|
||||
# in any realistic scenario (would require ~29 billion years at 10 ticks/game-minute per D-031).
|
||||
var tick: int = raw["tick"]
|
||||
|
||||
# version already checked above; game_time for HUD display
|
||||
# game_time for HUD display
|
||||
var game_time: Variant = raw.get("game_time")
|
||||
|
||||
# player_facing: FacingDirection is a unit enum → bare string in rmp_serde
|
||||
@@ -223,6 +205,18 @@ static func decode_snapshot(bytes: PackedByteArray) -> Variant:
|
||||
"speaker_entity_id": int(raw_dr.get("speaker_entity_id", -1)),
|
||||
}
|
||||
|
||||
# v8: gauntlet_mode and room_id (#496) — present only in Gauntlet sessions.
|
||||
# gauntlet_mode is a bool flag; room_id is a String room identifier or absent.
|
||||
# Snapshot handler (snapshot_handler.gd) reads these via snapshot.has() guards.
|
||||
var gauntlet_mode: bool = false
|
||||
var raw_gauntlet: Variant = raw.get("gauntlet_mode")
|
||||
if raw_gauntlet == true:
|
||||
gauntlet_mode = true
|
||||
var room_id: Variant = null
|
||||
var raw_room_id: Variant = raw.get("room_id")
|
||||
if raw_room_id is String:
|
||||
room_id = raw_room_id
|
||||
|
||||
# v9: conversation_events (#535, D-078) — overheard NPC-to-NPC dialogue lines.
|
||||
# Each event carries pre-occluded text plus speaker/target attribution.
|
||||
var conversation_events: Array = []
|
||||
@@ -484,7 +478,6 @@ static func decode_snapshot(bytes: PackedByteArray) -> Variant:
|
||||
"tick": tick,
|
||||
"entities": entities,
|
||||
"decode_errors": dropped,
|
||||
"version": version,
|
||||
"game_time": game_time,
|
||||
"player_facing": player_facing,
|
||||
"player_stance": player_stance,
|
||||
@@ -508,6 +501,8 @@ static func decode_snapshot(bytes: PackedByteArray) -> Variant:
|
||||
"current_ticker": current_ticker,
|
||||
"settings_response": settings_response,
|
||||
"bookmark_catalog": bookmark_catalog,
|
||||
"gauntlet_mode": gauntlet_mode,
|
||||
"room_id": room_id,
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -272,7 +272,7 @@ func snapshot() -> Dictionary:
|
||||
|
||||
return {
|
||||
"tick": tick,
|
||||
"version": Protocol.PROTOCOL_VERSION,
|
||||
"version": 23,
|
||||
"game_time":
|
||||
{
|
||||
"day": 0,
|
||||
|
||||
Reference in New Issue
Block a user