# Section 02: Godot Addon -- WebSocket Server

## Overview

This section implements `godotiq_server.gd`, the central WebSocket server hub that lives inside the Godot editor addon. It accepts WebSocket connections from the Python MCP server, dispatches incoming JSON requests to editor-side handlers or forwards them to the running game via the debugger bridge, and sends responses and push events back to connected Python clients.

This is a pure GDScript file. There are no Python tests for this section; validation is done manually in the Godot editor.

## Dependencies

- **section-01-godot-plugin** must be implemented first. The plugin (`godotiq_plugin.gd`) creates the server node as a child and calls into it during `_enter_tree()` / `_exit_tree()`.
- **section-03-godot-debugger-runtime** provides the debugger plugin that the server forwards game-side requests to, and the runtime autoload that executes those requests in the running game.

## File to Create

```
godot-addon/addons/godotiq/godotiq_server.gd
```

Full path: `/Users/salvatorefarruggio/Desktop/godotiq/godot-addon/addons/godotiq/godotiq_server.gd`

## Architecture Context

In Godot 4, `WebSocketServer` was removed (PR #66594). The replacement pattern uses `TCPServer` to accept raw TCP connections, then upgrades each one to a WebSocket via `WebSocketPeer.accept_stream()`. Polling must happen every frame in `_process()` because no signals are emitted.

## JSON Protocol

All communication uses JSON text frames over WebSocket.

**Request** (Python to Godot): `{"id": "req_001", "method": "screenshot", "params": {"scale": 0.5}}`
**Response** (Godot to Python): `{"id": "req_001", "status": "ok", "result": {"fps": 60}}`
**Error** (Godot to Python): `{"id": "req_001", "status": "error", "error": {"code": "GAME_NOT_RUNNING", "message": "..."}}`
**Event** (Godot push to Python): `{"event": "game_started", "data": {}}`

## Design Specification

### Script Header

`@tool` script extending `Node`. Added as a child node of the EditorPlugin by `godotiq_plugin.gd`.

### Core State Variables

- `_tcp_server: TCPServer` -- listens on configurable port (default 6007), bound to `127.0.0.1` only
- `_peers: Dictionary` -- maps `peer_id: int` to `WebSocketPeer`. Peer IDs assigned incrementally via `_next_peer_id`
- `_peer_tcp: Dictionary` -- maps `peer_id: int` to `StreamPeerTCP`. Keeps TCP stream references alive to prevent GC
- `_pending_game_requests: Dictionary` -- maps `request_id: String` to `{peer_id: int, method: String, timeout_at: int}`
- `_game_running: bool` -- tracks game state, updated by debugger events
- `_next_peer_id: int` -- incrementing counter for peer ID generation
- `debugger` -- untyped reference to `godotiq_debugger.gd` instance, set by plugin

### Port Configuration

Reads port from `.godotiq.json` at `server.addon_port` via a `_load_port_from_config()` helper:
1. Look for `res://.godotiq.json` using `FileAccess.file_exists()`
2. If found, parse JSON and read `server.addon_port`
3. Fall back to `6007` if missing or unparseable
4. Bind `_tcp_server` to `127.0.0.1` on the resolved port in `_ready()`

### `_process()` Loop (every frame)

1. **Accept new TCP connections**: While `_tcp_server.is_connection_available()`, take connection, create `WebSocketPeer`, call `accept_stream()`, store in `_peers` and `_peer_tcp` with incremented peer ID
2. **Poll all peers**: For each peer, call `peer.poll()`, check `get_ready_state()`:
   - `STATE_OPEN`: Read available packets. If `was_string_packet()`, decode UTF-8 and pass to `_handle_message()`
   - `STATE_CLOSING`: Wait for close to complete
   - `STATE_CLOSED`: Mark for removal
3. **Remove disconnected peers**: Erase from BOTH `_peers` AND `_peer_tcp` dictionaries
4. **Check timed-out game requests**: For requests where `Time.get_ticks_msec()` exceeds `timeout_at`, send timeout error and remove

### Message Handling (`_handle_message()`)

Parses JSON text with `JSON.parse_string()`. Extracts `id`, `method`, `params`. Validates that JSON is not null (sends PARSE_ERROR on failure). Calls `_dispatch()`.

### Request Dispatch (`_dispatch()`)

**Editor-side handlers** (handled directly):
- `"ping"` -- Returns editor version, game state, addon version "2.0.0"
- `"editor_context"` -- Returns open scenes, selected nodes, game state, project path via `EditorInterface`
- `"run"` -- Calls `EditorInterface.play_main_scene()` / `play_current_scene()` / `play_custom_scene()` based on `scene` param
- `"stop"` -- Calls `EditorInterface.stop_playing_scene()`

**Game-side handlers** (forwarded via debugger):
- `"screenshot"` -- Requires `_game_running`, forwards via `debugger.send_to_game("godotiq:screenshot", [params])`, timeout 30000ms
- `"perf_snapshot"` -- Requires `_game_running`, forwards via `debugger.send_to_game("godotiq:query_perf", [params])`, timeout 5000ms

**Unknown methods** return `UNKNOWN_METHOD` error.

### Game Response Handling

`handle_game_response(response_type: String, data: Array)` -- Called by debugger when game sends results back. Searches `_pending_game_requests` for matching method, resolves by sending response to the originating peer, removes pending entry.

Special handling for screenshot: if `data.size() >= 4`, builds result dict with `{image, format, width, height}` from the array elements.

### Game Lifecycle Callbacks

- `on_game_started()` -- Sets `_game_running = true`, broadcasts `"game_started"` event
- `on_game_stopped()` -- Sets `_game_running = false`, fails all pending requests with `GAME_STOPPED` error, broadcasts `"game_stopped"` event

### Transport Helpers

- `send_response(peer_id, id, result)` -- Builds ok response JSON, sends via `send_text()`
- `_send_error(peer_id, id, code, message)` -- Builds error response JSON, sends via `send_text()`
- `send_event(event_name, data)` -- Builds event JSON, broadcasts to all open peers
- `_send_text(peer_id, text)` -- Safe send to specific peer (checks existence and state)

### Cleanup (`_exit_tree()`)

Closes all WebSocket peers, clears dictionaries, stops TCP server.

## Tests

No Python tests. Manual validation:
1. Enable addon in Godot Project Settings
2. Check Output for "GodotIQ: WebSocket server listening on 127.0.0.1:6007"
3. Use WebSocket client to send ping: `{"id":"1","method":"ping","params":{}}`
4. Verify response contains `status: "ok"` with editor version

## Implementation Notes

- Use `JSON.stringify()` for encoding and `JSON.parse_string()` for decoding
- When iterating peers for cleanup, collect keys to remove in a separate array first
- The `_peer_tcp` dictionary prevents StreamPeerTCP garbage collection
- Timeout checking uses `Time.get_ticks_msec()` for millisecond precision
- The `get_port()` method exposes the configured port for the plugin's activation message

## Implementation Notes (Post-Build)

### Files Modified
- `godot-addon/addons/godotiq/godotiq_server.gd` — full implementation (replaced stub)
- `godot-addon/addons/godotiq/godotiq_plugin.gd` — added `_server.debugger = _debugger` wiring

### Deviations from Plan
1. **Packet read order fix**: `get_packet()` called before `was_string_packet()` — Godot API requires this order
2. **Cached port**: Added `_port` var; `get_port()` returns cached value instead of re-parsing config
3. **Cross-reference wiring**: Plugin now sets `_server.debugger = _debugger` (missing from section-01 spec)
4. **Peer-scoped request keys**: `_pending_game_requests` keyed by `"%d:%s" % [peer_id, id]` to prevent cross-peer ID collision
5. **EditorInterface call optimization**: `get_open_scenes()` cached to local variable
