Debug Protocol
The Remote Debug Protocol is a lightweight JSON-RPC 2.0 server embedded in every Turian game build that links the debug module. It exposes the Runtime Introspection Layer over TCP so external tools — the CLI, the MCP server, and custom scripts — can query and (optionally) mutate live engine state.
Wire format
One JSON object per line (\n-terminated). No Content-Length headers.
Request:
{"jsonrpc":"2.0","id":1,"method":"entity.inspect","params":{"name":"Player"}}
Success response:
{"jsonrpc":"2.0","id":1,"result":{...}}
Error response:
{"jsonrpc":"2.0","id":1,"error":{"code":-32000,"message":"Entity not found"}}
Methods
Scene
| Method | Params | Description |
|---|---|---|
scene.list |
— | All loaded scenes |
scene.inspect |
name? |
Entities in a scene (active scene if omitted) |
Entity
| Method | Params | Description |
|---|---|---|
entity.find |
name?, component? |
Filter entities by name or component type |
entity.inspect |
name or index |
Transform + component detail |
Component
| Method | Params | Description |
|---|---|---|
component.get |
entity, component |
Component field values |
Assets
| Method | Params | Description |
|---|---|---|
asset.list |
— | All project assets (guid, path, type) |
asset.inspect |
guid |
One asset's guid / path / type |
Diagnostics
| Method | Params | Description |
|---|---|---|
snapshot |
— | Full scene snapshot (all entities + metrics) |
schema |
— | Built-in component type catalog |
metrics |
— | Runtime performance counters |
profiler.capture |
— | Latest profiler frame (counters + zones) |
memory |
— | memory_bytes + allocation_count |
errors |
— | Recent std.log warn/err entries (newest first) |
ping |
— | Health check; returns "pong" |
auth |
token |
Authenticate (required when server has a token set) |
Mutations (read-write mode only)
Mutating methods return { "ok": bool, "message": string }. They require the
server started in read-write mode (allow_write / CLI --rw); otherwise they
return READONLY. In the Studio every mutation routes through the editor's undo
stack, so AI/CLI edits are undoable and consistent with the UI.
| Method | Params | Description |
|---|---|---|
component.set |
entity, component, field, value |
Set a component field |
transform.set |
entity, channel, value ([x,y,z]) |
Set position/rotation/scale |
entity.spawn |
name |
Create a new empty entity |
entity.destroy |
entity |
Remove an entity |
asset.reload |
guid |
Hot-reload an asset |
Sessions & events
| Method | Params | Description |
|---|---|---|
session.readonly |
— | Drop this connection's write rights (cannot re-grant) |
subscribe |
event (or "*") |
Subscribe to runtime events |
unsubscribe |
event (or "*") |
Stop receiving an event |
Subscribed clients receive JSON-RPC notifications (no id) as events fire:
| Event | When |
|---|---|
entity.created |
An entity was spawned |
entity.destroyed |
An entity was removed |
scene.loaded |
A scene became active / finished loading |
scene.unloaded |
A scene was unloaded |
resource.reloaded |
An asset was hot-reloaded |
fps.changed |
The integer FPS bucket changed |
In Turian Studio, scene.loaded / scene.unloaded fire as you open, switch, or
close scene tabs, and fps.changed is driven from the editor's own frame timing —
so it ticks while you edit, not only in Play mode.
Error codes
| Code | Name | Meaning |
|---|---|---|
-32700 |
PARSE_ERROR |
Invalid JSON |
-32600 |
INVALID_REQUEST |
Missing required fields |
-32601 |
METHOD_NOT_FOUND |
Unknown method |
-32602 |
INVALID_PARAMS |
Missing or wrong params |
-32603 |
INTERNAL_ERROR |
Serialisation or internal failure |
-32000 |
NOT_FOUND |
Entity/scene/component not found |
-32001 |
READONLY |
Mutation attempted on a read-only session |
-32002 |
RATE_LIMITED |
Per-connection rate limit exceeded |
Starting the debug server
const debug = @import("debug");
var srv = debug.Server.init(allocator, .{
.port = 7777,
.localhost_only = true,
.auth_token = "", // empty = no auth
.allow_write = false, // true to permit mutations
.max_clients = 8,
.rate_limit_per_sec = 0, // 0 = unlimited
.max_outbound_queue = 2048, // per-client event backlog cap
.max_outbound_bytes = 16 << 20,
.max_inbound_queue = 4096, // pending requests across all clients
});
defer srv.deinit(io);
try srv.start(io); // spawns the accept loop only
// Once per frame, on the main thread:
srv.pump(world, applier); // executes queued reads/writes against live state
The accept loop runs on a background thread, but every request is executed on
the main thread inside pump — so reads are race-free and writes can touch
live engine state with no locking. Multiple clients are supported simultaneously.
Mutations are applied through a host-supplied MutationApplier; events are
emitted with srv.emit(...). Call srv.stop(io) to shut down cleanly.
Large responses & backpressure
A full snapshot of a large scene serialises well past 64 KiB. Messages are
newline-framed but not capped at 64 KiB — both client and server grow the
read buffer on the heap up to a 16 MiB hard ceiling, so big responses arrive
intact rather than failing.
Each connection's outbound event queue is bounded (max_outbound_queue /
max_outbound_bytes). A subscriber that stops reading has its oldest events
dropped (events are lossy by nature) instead of growing server memory without
limit; a client too far behind to take a pending response is disconnected. The
shared inbound queue is bounded by max_inbound_queue; once full, new requests
are rejected with a clear "server busy" error.
Network exposure. Setting
localhost_only = falsebinds0.0.0.0and there is no TLS — theauth_token(and all traffic) is sent in the clear and is the only gate. Use this only on a trusted LAN behind a firewall/VPN; never expose the debug server to the public internet.
Using from turian-cli
turian-cli debug connect
turian-cli debug scenes
turian-cli debug inspect Player
turian-cli debug snapshot
turian-cli debug schema
turian-cli debug metrics
turian-cli debug assets
turian-cli debug errors
# mutations (server must be read-write):
turian-cli debug set Player Light intensity 2.5
turian-cli debug spawn Crate
turian-cli debug destroy Crate
# events & sessions:
turian-cli debug watch entity.created fps.changed
turian-cli debug record session.jsonl
turian-cli debug replay session.jsonl
See turian-cli debug --help for the full subcommand list.
Default port
7777. Override with --port on both server and client side.