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 = false binds 0.0.0.0 and there is no TLS — the auth_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.


← All docs Edit this page