> ## Documentation Index
> Fetch the complete documentation index at: https://docs.summerengine.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Summer Router in single-player and multiplayer games

> Single-player games call Summer.client.router from the player's game. Multiplayer games call Summer.authority.router from the server, for one player's session, and send the result to the players.

| Game | Who calls | What counts |
| - | - | - |
| Single-player | The player's game: `Summer.client.router` | The player's caps and the game's caps |
| Multiplayer | Your server: `Summer.authority.router`, for one player's session | The game's caps and call rate |

## Single-player games

The game calls the router and uses the answer directly:

```gdscript theme={null}
var r: SummerRouterResult = await Summer.client.router.reply("guard", line, {
    "tools": {"open_gate": func(_args): $Gate.open(); return {"opened": true}},
})
$Subtitle.text = r.text if r.ok else "The guard ignores you."
```

## Multiplayer games

In a multiplayer game the server owns the shared world, so the server talks to the character. The player's game sends what the player typed to the server as a Command, the same way it asks for any other action (see [share the game world](https://docs.summerengine.com/build/shared-state.md)). The server answers the Command, asks the character for that player's session, applies what the character did, and publishes the line in shared State so every player sees it. Each call names the session of the player it is for:

```gdscript server.gd theme={null}
func _on_player_asked(session: SummerSession, line: String) -> void:
    var r: SummerRouterResult = await Summer.authority.router.reply(session, "guard", line)
    publish_guard_line(r.text if r.ok else r.message)  # write it to shared State: every player sees it
    if r.ok and r.tool_calls.any(func(call): return call.name == "open_gate"):
        open_gate()  # the server changes the world, and shared State carries it to the players
```

The server has the same calls as the client, each with the session first:

| Call | Result |
| - | - |
| `reply(session, character, text, options)` | `text`, `tool_calls`, `exchange_id`, `conversation_id`. Options as on the client, without `tools`: the server reads `tool_calls` and acts, or answers them with `tool_results` on the next call. |
| `speak(session, text, {"character": id})` | `data` and `mime_type`, and `audio` when the server can decode it. Audio is too big for shared State: to voice a line, each player's game calls `Summer.client.router.speak(line, {"character": id})` for the line it shows. |
| `generate(session, "image", {"prompt": ..., "size": ...})` | `data` and `mime_type`, plus `image` and `texture` |

On a refusal, `r.message` is a plain line for the players, such as "AI limit reached for this game today". Show it, and the game goes on.

Server calls count toward the game's caps, not the player's. Pace each player on the server (one line in flight per player, a short gap, a length cap on the text), so one player cannot use up the game's daily cap.

When the character calls a tool, the server runs it and sends the result back, so the character can answer with it:

```gdscript server.gd theme={null}
var r: SummerRouterResult = await Summer.authority.router.reply(session, "shopkeeper", line, {"conversation_id": conversation})
if r.ok and not r.tool_calls.is_empty():
    var results := []
    for call in r.tool_calls:
        # Check every argument: the server, not the model, decides what happens.
        results.append({"call_id": call.call_id, "output": Shop.sell_to(session, str(call.args.get("item", "")))})
    r = await Summer.authority.router.reply(session, "shopkeeper", "", {"conversation_id": conversation, "tool_results": results})
```

Server calls run on worlds hosted on Summer Games. In the local emulator every call answers `unavailable`, so keep the offline line.

## When a player's game talks to a character

A player's game can also talk to a character itself, with `Summer.client.router`, and tell the server what happened, for example "the guard agreed to open the gate". Before the server acts on it, it checks the exchange:

```gdscript server.gd theme={null}
func _on_guard_agreed(session: SummerSession, exchange_id: String) -> void:
    var r: SummerRouterResult = await Summer.authority.router.verify_exchange(session, exchange_id)
    if r.ok and r.tool_calls.any(func(call): return call.name == "open_gate"):
        $Gate.open()
```

`verify_exchange` returns the reply and the tool calls exactly as the player saw them. It answers only for an exchange of that session's player, in this game, from the last hour. Anything else answers `invalid_input` with reason `not_found`: do not act on it.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.