diff --git a/client/scripts/autoloads/sim_bridge.gd b/client/scripts/autoloads/sim_bridge.gd index d3dca48f5..7095e919a 100644 --- a/client/scripts/autoloads/sim_bridge.gd +++ b/client/scripts/autoloads/sim_bridge.gd @@ -6,6 +6,8 @@ enum ConnectionState { DISCONNECTED, CONNECTING, CONNECTED, ERROR } var state: ConnectionState = ConnectionState.DISCONNECTED var test_mode: bool = true # Enable test mode for development without Rust server var _test_tick: int = 0 +var _last_snapshot: Variant = null # Most recent decoded snapshot (consumed by poll_snapshot) +var _outbound_buffer: Array[PackedByteArray] = [] # Encoded inputs awaiting transport # Signals signal connection_state_changed(old_state: ConnectionState, new_state: ConnectionState) @@ -35,14 +37,22 @@ func connect_to_sim() -> void: func disconnect_from_sim() -> void: _set_state(ConnectionState.DISCONNECTED) -# Send input to simulation (real implementation comes later) +# Send input to simulation server. +# player_input: Dictionary with "action" (int from InputMapper.Action enum) and "timestamp_msec". +# In test mode, inputs are silently dropped. Transport (ticket #79) will send the bytes. func send_input(player_input: Dictionary) -> void: if state != ConnectionState.CONNECTED: return - # TODO: Serialize and send via MessagePack when IPC is implemented - pass + if test_mode: + return + var action_name := _action_enum_to_wire(player_input.get("action", -1)) + if action_name.is_empty(): + return + var tick: int = player_input.get("timestamp_msec", 0) + _outbound_buffer.append(Protocol.encode_player_input(tick, action_name)) -# Poll for snapshot from simulation +# Poll for snapshot from simulation. +# In test mode returns hardcoded data. In live mode, returns the last decoded snapshot (if any). func poll_snapshot() -> Variant: if state != ConnectionState.CONNECTED: return null @@ -52,9 +62,40 @@ func poll_snapshot() -> Variant: snapshot_received.emit(snapshot) return snapshot - # TODO: Actual polling logic when IPC/MessagePack is implemented + if _last_snapshot != null: + var snapshot = _last_snapshot + _last_snapshot = null + snapshot_received.emit(snapshot) + return snapshot + return null +# Called by transport layer (ticket #79) when raw bytes arrive from the server. +func receive_bytes(bytes: PackedByteArray) -> void: + var snapshot = Protocol.decode_snapshot(bytes) + if snapshot != null: + _last_snapshot = snapshot + +# Drain the outbound buffer. Called by transport layer (ticket #79) to get encoded messages. +func drain_outbound() -> Array[PackedByteArray]: + var messages = _outbound_buffer.duplicate() + _outbound_buffer.clear() + return messages + +# Map InputMapper.Action enum values to wire-format action names (matching Rust PlayerAction). +static func _action_enum_to_wire(action: int) -> String: + match action: + 0: return "MoveNorth" # InputMapper.Action.MOVE_NORTH + 1: return "MoveSouth" # InputMapper.Action.MOVE_SOUTH + 2: return "MoveEast" # InputMapper.Action.MOVE_EAST + 3: return "MoveWest" # InputMapper.Action.MOVE_WEST + 4: return "Interact" # InputMapper.Action.INTERACT + 5: return "UsePerceptionMode" # InputMapper.Action.USE_PERCEPTION_MODE + 7: return "Pause" # InputMapper.Action.PAUSE + _: + push_warning("SimBridge: unknown action enum %s" % action) + return "" + # Hardcoded test snapshot for development (deterministic per D-010 principle 4) func _test_snapshot() -> Dictionary: _test_tick += 1