by Pivec Labs
Manual Full reference

NetCrafter Relay — Manual

Full reference for every tab, field, and feature of the NetCrafter Relay editor window.

Full reference for the NetCrafter Relay editor window. Open it from Tools → Pivec Labs → NetCrafter Relay.

The window is divided into four top-level sections, each with a sidebar of sub-tabs: Definition, Code, Scenes, and Settings.

01

Definition section#

Schema bar

The schema bar runs across the top of every section. It shows a dropdown of all discovered definitions (schemas found anywhere under Assets/PivecLabs/NetCrafterRelay/Definitions/). Selecting a definition switches the entire window to that definition's assets.

Click Create Definition when no definition exists or to add a new one.

Messages tab

Displays all GameplayMessageDefinition assets found in Definitions/{Name}/Messages/.

To create a message: click Create Gameplay Message.

Message fields

Field Description
Message Name The base class name. A ClientToHost message generates {Name}Request.cs. A HostToClient message generates {Name}Event.cs.
Direction ClientToHost — client sends to whichever peer is hosting (becomes a Request). HostToClient — host pushes to a client (becomes an Event). ClientToHostBroadcast — client sends to host; host re-broadcasts to all (also becomes a Request).
Transport Reliable — app-level ACK + retry wrapper. Unreliable — fire-and-forget, used for continuous transform sync.
Host Handler Name The name that must match a Host Action's Handler Name. Validation will fail if no matching Host Action exists.
Fields The data payload. Each field has a Name and a Type (see Field Types below).

Field types

Schema type C# type Wire format Notes
int int WriteInt32 / ReadInt32
float float WriteFloat / ReadFloat
bool bool WriteBool / ReadBool
string string WriteString / ReadString
Vector2 Vector2 WriteVector2 / ReadVector2
Vector3 Vector3 WriteVector3 / ReadVector3
Quaternion Quaternion WriteQuaternion / ReadQuaternion Edit asset YAML directly to use
PlayerId int WriteInt32 / ReadInt32 Semantic alias for player IDs
ItemId string WriteString / ReadString Semantic alias for item identifiers
ModelName ModelName WriteTo(RelayWriter) / ReadFrom(RelayReader) Edit asset YAML: set type: ModelName

Arrays: any field type can be made an array. Edit the asset YAML directly and set isArray: 1. The generated C# field becomes List<T>, written as a count prefix followed by each element in order.

The editor type dropdown exposes int, float, string, bool, Vector2, Vector3, PlayerId, and ItemId. To use Quaternion, nested model types, or array fields, open the .asset file in a text editor and edit the YAML directly.

There are no per-field keys on the wire — fields are written in schema declaration order, since client and host logic are generated together from one schema in one pass.

Data Definitions tab

Displays all DataDefinition assets in Definitions/{Name}/DataDefinitions/.

A Data Definition defines a reusable data model. The generator produces a {Name}.cs class with serialisation helpers (WriteTo(RelayWriter) and ReadFrom(RelayReader)).

To create a model: click Create Data Definition. Set a Model Name and add fields exactly as you would for a message.

Host Actions tab

Displays all HostActionDefinition assets in Definitions/{Name}/HostActions/.

Host Actions serve two purposes:

  1. They satisfy the validation check — every message's Host Handler Name must match at least one Host Action's Handler Name.
  2. They drive HostActionRouter.cs generation — the logic that runs only on whichever peer is currently hosting.
Field Description
Handler Name Must match the Host Handler Name on one or more messages or events.
Trigger Message Optional reference to the message that triggers this action.
Response Event The HostToClient event sent back to peers after this action runs.
Logic Type BroadcastAll (host relays to every peer), SenderOnly (echo back to the sender), TargetUser (relay to one specific peer — requires an int targetId field on the trigger message), or Custom.
Custom Host Code Freeform C# for the Custom logic type, spliced directly into the generated handler method.

There is no server to deploy: HostActionRouter.cs compiles straight into the same client build as everyone else.

Sync Rules tab

Displays all SyncRuleDefinition assets in Definitions/{Name}/SyncRules/.

Click Create Sync Rule to add a new rule to the list. Select a rule in the left panel to edit its details on the right.

Field Description
Rule Name Identifier for this rule. Also used as the network command suffix: __nc_sync_{RuleName}.
Sync Target The component property to synchronise. See the table below.
Sync Mode Always, OnChange, or Interval. See below.
Interval Seconds between syncs when Sync Mode is Interval. Default: 0.05 (20 Hz).

Sync Targets

Target Component Property
Transform_Position Transform position (world space)
Transform_Rotation Transform rotation (world space quaternion)
Transform_Scale Transform localScale
Rigidbody_Velocity Rigidbody linearVelocity
Rigidbody_AngularVelocity Rigidbody angularVelocity

Sync Modes

Mode Behaviour
Always Sends every FixedUpdate. Use for values that change every frame (e.g. position of a moving character).
OnChange Sends only when the value has changed by more than an epsilon since the last send (0.001 units for Vector3, 0.1 degrees for rotation). Lower bandwidth cost; adds a single-frame detection lag.
Interval Sends every N seconds (interval field). Use for infrequently-changing values or where exact timing matters more than immediate response.

Sync rules are stored in the schema and exported for validation. The runtime system is implemented via NetCrafterSyncManager and NetCrafterSyncBinding — see the Sync Rule Runtime section below.

02

Code section#

Build Code tab

Control Action
Validate Definition Runs the validator and shows a success dialog or opens the error window. Does not generate any files.
Preview JSON Opens a read-only window showing the raw schema JSON that will be passed to the generator.
Generate All Files Validates, then generates client code, host code, and config.
Client Only Validates, then generates client C# files only.
Host Only Validates, then generates HostActionRouter.cs only.
Open Output Folder Reveals the schema's Generated/ folder in Explorer.

All generation paths run schema validation first. If validation fails the error window opens and generation is aborted.

Generated files land in:

text
Definitions/{Name}/Generated/Client/
Definitions/{Name}/Generated/Host/
Definitions/{Name}/Generated/Config/

Schema validation

The validator (NC_ValidateSchema) performs four checks in order:

  1. Duplicate names — no two messages, events, or models may share a name; no message and model may share a name.
  2. Invalid field types — all field types must be a supported primitive or a declared model name.
  3. Missing handler references — every message and event must name a handler that exists in the Host Actions list.
  4. Circular model references — model fields must not form a cycle (e.g. A references B which references A).

Errors are shown in a scrollable Validation Error Window. Fix all errors before generating.

Code Preview tab

Displays the generated files for the selected definition, split into three groups: Client, Host, Config.

Group tab Shows
Client (n) All .cs files in Generated/Client/
Host (n) HostActionRouter.cs in Generated/Host/
Config (n) RelayConfig.json in Generated/Config/

Selecting a file displays it in the right panel with syntax highlighting (VS Code–style dark theme or a darker light theme, adapting to your editor skin).

Normal / Diff toggle: after you generate at least once, the toggle becomes active. Diff view compares the on-disk file against a snapshot captured immediately before the last generation run. Added lines have a green background; removed lines have a red background. The toggle is greyed out with a tooltip if no snapshot exists yet.

Action buttons (top of the file panel):

Button Action
Copy Copies the file content to the clipboard
Open ↗ Opens the file in the system default application
Normal / Diff Toggles between syntax-highlighted view and diff view

Config Generator tab

Generates RelayConfig.json from the current schema's Relay Settings asset — relay host/port, rendezvous URL, and credential mode. There is no zone/room/AOI concept here; every peer talks to the same fixed relay endpoint.

Button Action
Refresh Preview Re-derives the JSON from the current Relay Settings without writing to disk. The preview can go stale if you edit Settings after opening the tab — click this to update it.
Generate RelayConfig.json Writes the config to Definitions/{Name}/Generated/Config/RelayConfig.json.
Open Config Folder Reveals Generated/Config/ in Explorer without writing.
03

Settings section#

Relay Settings tab

Settings are stored in Definitions/{Name}/RelaySettings.asset — one asset per definition.

Relay

Field Default Description
Relay Host (empty — required) Your self-hosted coturn TURN/STUN endpoint. No default; NetCrafter Relay does not ship with or point at any Pivec Labs-hosted relay. See Server Deployment Guide.
Relay Port 3478 TURN default port
Rendezvous URL (empty — required) Your self-hosted join-code → host relayed-address lookup service. No default; see Server Deployment Guide.
Credential Mode StaticSecret StaticSecret or RestApi — how TURN credentials are obtained
Shared Secret (empty) Used when Credential Mode is StaticSecret
Auto-Reconnect true Reserved for future use
Debug Logging true Enables relay client-side verbose logging

Login

Field Default Description
Login Mode Guest Guest: assigns an auto-generated guest name. UsernamePassword and Custom are reserved for future use.
Guest Name Prefix Player_ Prefix prepended to the auto-assigned guest name

Transport

Field Default Description
Send Queue Size 4096 Bytes
Max Message Size 32768 Bytes — a soft cap on the send queue, not a guarantee of delivery. See Message size limits below: payloads much above ~1200-1400 bytes are not reliably delivered over the relay.

Click Save Settings to persist changes. Click Reset to Defaults to restore the values shown above.

Project Settings tab

Settings are stored in Definitions/{Name}/ProjectSettings.asset.

Output Folders

Read-only display of the three output paths, all derived from the schema's definition folder. Cannot be changed manually.

Template Settings

Field Description
Use Custom Templates When enabled, the generator looks for override templates in Definitions/{Name}/Templates/ before falling back to the built-in templates.

When Use Custom Templates is on:

  • The Override Folder path is shown (read-only, auto-computed).
  • Create Override Folder copies all nine built-in templates into the override folder and prepends a header comment identifying them as overrides. The button is disabled once the folder exists.
  • The Template Files grid shows each template with a green [✓] if an override exists or grey [ ] if the global fallback will be used.
  • Reset next to each template deletes the override file, reverting that template to the global fallback.

See the Template Guide for full details.

Naming Conventions

Field Default Description
Namespace PivecLabs.Generated C# namespace used in all generated files (client and host code share this namespace)
Class Prefix (empty) Prepended to every generated class name
Class Suffix (empty) Appended to every generated class name

Generation Behavior

Field Default Description
Overwrite Existing Files true When false, the generator skips files that already exist on disk
Show Summary After Build true Reserved — generation summary logging is always written to the Unity console

Logging

Field Default Description
Verbose Logging false Passes the flag to the generator; currently only affects console log output

Experimental Features

Field Description
Enable Partial Class Support Reserved — not yet wired to the generator
Enable Advanced Sync Rules Reserved — not yet wired to the generator
04

Scenes section#

Prefab Generator tab

The Prefab Generator creates Unity prefabs for use in your multiplayer scene.

ConnectionManager Prefab

The Create ConnectionManager Prefab button packages whichever generated type implements INetCrafterConnection (normally RelayConnectionManager.cs) into a reusable prefab saved to:

text
Definitions/{Name}/Prefabs/ConnectionManager.prefab

Deliberately a sibling of Generated/, not inside it — this is a hand-configured runtime asset (your actual relay host, rendezvous URL, and TURN credentials once you set them in the Inspector), not codegen output, so it's never touched by Build Code's regeneration.

Requirements: client code must have been generated first (Build Code tab). If no INetCrafterConnection type is found in Generated/Client/, an error dialog explains what to do.

The panel shows the expected output path and whether the prefab already exists. The Scene Assistant tab reads this same path and prefers the prefab when setting up scenes. If the prefab is absent, the scene buttons fall back to creating a raw GameObject via reflection.

Coming Soon — Prefab Generator

The following sections are visible in the UI but greyed out.

Section What it will do
Player Prefab Generate a player prefab from a base prefab, optionally adding NetworkIdentity, MovementSync, RotationSync, AnimationSync, and HealthSync components
Chat UI Generate a UI prefab in Minimal, Modern, or MMO-Style layouts
NPC Prefab Generate an NPC prefab with configurable AI and synchronisation options

Scene Assistant tab

The Scene Assistant scaffolds a Unity scene with all required multiplayer GameObjects.

Settings are stored in Definitions/{Name}/SceneAssistantSettings.asset — one asset per definition, created automatically when you open the tab.

ConnectionManager Prefab Status

A banner at the top of the tab shows whether a ConnectionManager.prefab exists. If it is missing, a warning prompts you to create it in the Prefab Generator tab first. Scene buttons work either way — the prefab is preferred, but a raw GameObject is added via reflection if the prefab does not yet exist.

Scene Setup

Control Description
Scene Name Filename for the generated scene. Default: GameScene. The scene is saved as Definitions/{Name}/{SceneName}.unity.
Create Multiplayer Scene Creates a new empty scene containing: a Main Camera (with Camera and AudioListener), a ConnectionManager GameObject, an EventDispatcher GameObject (if the toggle is enabled), and a NetworkManager empty GameObject reserved for future use. Prompts you to save any unsaved changes first. Leaves the new scene open in the editor.
Add to Existing Scene Adds only the ConnectionManager (and EventDispatcher if toggled) to the currently open scene. If a ConnectionManager is already present, a confirmation dialog asks before adding a second one. Does not add a camera or NetworkManager.

Event Dispatcher

The Add Event Dispatcher toggle controls whether a NetCrafterEventDispatcher GameObject is included when a scene is created or patched. See the Event Dispatcher section below for full details on the API.

Player Spawning

Field Description
Spawn Mode Auto or Manual — stored for future runtime use
Player Prefab Project reference to your player prefab asset
Spawn Points A list of Vector3 world positions

Spawn point list controls:

  • [i] (x, y, z) Remove — one row per point. Remove deletes that entry and immediately renames any SpawnPoint_N children in the active scene to stay in sequence.
  • + Add Spawn Point — appends a new point at (0, 0, 0).

Place Spawn Points in Scene — creates or updates a SpawnPoints parent GameObject in the active scene. Each child is named SpawnPoint_0, SpawnPoint_1, … and carries a NetCrafterSpawnPointGizmo that draws a green wire sphere with a Scene view label. Button is disabled with a tooltip when no spawn points are defined.

Place Player Prefab — instantiates the assigned player prefab at spawnPoints[0] (or Vector3.zero if no points are defined), registers the action with undo, and selects the new GameObject. Disabled when no prefab is assigned. A HelpBox below the button notes that spawn point 0 is always used and suggests using multiple points for team starts or respawn logic.

When you click Create Multiplayer Scene and spawn points are already defined, the SpawnPoints hierarchy is placed in the new scene automatically — no extra button click needed.

Additive Scenes

Each entry in the list has:

  • A SceneAsset picker — the chosen scene's path is stored as a string in SceneAssistantSettings.asset so it survives builds.
  • An Async toggle — when on, the scene is loaded with SceneManager.LoadSceneAsync; when off, with SceneManager.LoadScene (synchronous, blocking).
  • A Remove button.

Generate Scene Loader — writes NetCrafterSceneLoader.cs to Generated/Client/ alongside the RelayConnectionManager. The file contains an IEnumerator Start() that loads each scene in list order. Re-generate any time the list changes; the file is overwritten.

All additive scenes must be added to File → Build Settings before building. Missing scenes cause runtime errors. A HelpBox warning appears in the tab whenever at least one scene is defined.

At these session sizes (2–4 players typical, up to 8–10) there's no proximity/area-of-interest management to configure — every peer sees the whole session, so there's no MMO bounds section here (unlike NetCrafter's SFS3-backed MMO mode).

Scene Events tab

Binds HostToClient network events to scene-level UnityEvent hooks without writing code.

Setup

  1. The tab discovers all HostToClient GameplayMessageDefinition assets in the current schema and displays one binding entry per event.
  2. Click Add Router to Scene to create a SceneEventRouter GameObject in the active scene. The tab header immediately changes to a green [✓] SceneEventRouter status with a Select button. A second click is disabled — only one router per scene.
  3. Wire each event's On Received UnityEvent in the Inspector-style UI. Bindings are serialized into the scene and survive save/reopen.
  4. The enabled toggle on each entry silences that binding at runtime without removing it.

How it works at runtime

NetCrafterSceneEventRouter subscribes to NetCrafterEventDispatcher in Start(). When an event fires, the router stores the payload in an internal dictionary and calls onReceived.Invoke(). To read the event payload in your handler:

csharp
var ev = (ChatBroadcastEvent)
    NetCrafterSceneEventRouter.Instance.GetLastPayload("ChatBroadcast");

A HelpBox below each event's UnityEvent shows the exact cast pattern for that event type.

Orphaned bindings

If an event is renamed or removed from the schema, its binding moves to an Orphaned Bindings section at the bottom of the tab with a warning and a Remove button.

05

Event Dispatcher#

NetCrafterEventDispatcher is a runtime MonoBehaviour that ships with NetCrafter Relay (Scripts/NetCrafterEventDispatcher.cs). It is not generated — it is a permanent part of the package available to any script in your project.

Why it exists

The generated RelayConnectionManager routes all incoming relay messages through a private dispatch switch. Traditionally, to intercept events you would either subclass the generated file or edit it directly — both approaches break on the next Generate run. NetCrafterEventDispatcher provides an alternative: a string-keyed publish–subscribe bus that any MonoBehaviour can subscribe to without touching the generated code.

Adding it to a scene

Enable Add Event Dispatcher in Scenes → Scene Assistant, then use Create Multiplayer Scene or Add to Existing Scene. The dispatcher is added as a separate EventDispatcher GameObject with DontDestroyOnLoad.

To add it manually: create a GameObject and attach NetCrafterEventDispatcher to it.

API

csharp
// Register a callback for a host event
NetCrafterEventDispatcher.Instance.Subscribe("ChatBroadcast", OnChatBroadcastReceived);

// Deregister — call this in OnDisable or OnDestroy
NetCrafterEventDispatcher.Instance.Unsubscribe("ChatBroadcast", OnChatBroadcastReceived);

The callback receives the already-deserialised typed event object as object. Cast it to the generated event class:

csharp
private void OnChatBroadcastReceived(object payload)
{
    var ev = (ChatBroadcastEvent)payload;
    chatWindow.AddMessage(ev.senderName, ev.message);
}

Command names match your schema event names exactly — schema event ChatBroadcast → command string "ChatBroadcast".

How the dispatcher integrates with generated code

Every switch case in the generated RelayConnectionManager.DispatchLocal calls Dispatch before the stub handler. Both always fire — the dispatcher fires first so subscribers receive the event, then the On{Event} stub runs so any direct override in a subclass also executes.

csharp
case "ChatBroadcast":
{
    var ev = ChatBroadcastEvent.Deserialize(payload);
    NetCrafterEventDispatcher.Instance?.Dispatch("ChatBroadcast", ev);
    OnChatBroadcast(ev);  // stub — override in a subclass if preferred
    break;
}

Exception isolation

Each subscriber callback is wrapped in an individual try/catch. One failing subscriber cannot block other subscribers from receiving the same event. The error is logged to the Unity console with the command name and full stack trace.

Lifetime and cleanup

NetCrafterEventDispatcher is a singleton with DontDestroyOnLoad. Always call Unsubscribe in OnDisable or OnDestroy — failing to do so leaves a dangling reference and the callback will continue to fire for the lifetime of the dispatcher.

06

Sync Rule Runtime#

The Sync Rule Runtime automatically synchronises component values between peers according to the rules defined in the Sync Rules tab. It ships as three permanent runtime MonoBehaviours in Scripts/ — nothing is generated per-schema.

Components

Script Role
NetCrafterSyncManager Singleton. Owns the object registry, routes incoming sync payloads to the correct binding, and sends outgoing data via INetCrafterConnection.
NetCrafterSyncBinding Placed on each networked GameObject. Reads a SyncRuleDefinition from the Inspector, drives the tick loop, builds and sends payloads (if hasAuthority), or applies incoming payloads (if not).
INetCrafterConnection Interface implemented by the generated RelayConnectionManager. Decouples the sync runtime from the generated namespace.

Setting up sync in a scene

  1. Add NetCrafterSyncManager to a persistent GameObject in the scene (or enable Add Event Dispatcher in Scene Assistant and add it manually). It auto-detects the RelayConnectionManager at Start. If no connection is found, it logs a clear error and gates all send calls.
  2. Add NetCrafterSyncBinding to each networked GameObject.
  3. In the Inspector, assign:
    • Rule — drag a SyncRuleDefinition asset from Definitions/{Name}/SyncRules/.
    • Object Id — a unique integer identifying this object in the session. Assign from game logic (e.g. RelayConnectionManager.Instance.LocalUserId on connect for the local player).
    • Has Authority — check this for the local player. Leave unchecked for remote players.

Authority and object ID

Each networked GameObject has an objectId and an authority flag:

  • hasAuthority = true (local player): The binding sends data unreliably every tick according to the sync mode. The host relays this to other peers via BroadcastAll.
  • hasAuthority = false (remote players): The binding subscribes to incoming sync events via NetCrafterEventDispatcher and applies received values to its component.

objectId must be unique per session and agreed between sender and receiver. NetCrafter Relay does not assign or manage IDs — this is game-logic responsibility. RelayConnectionManager.Instance.LocalUserId is a natural choice for player objects.

Data flow

Outbound (local player → host):

text
NetCrafterSyncBinding.FixedUpdate  →  BuildPayload (RelayWriter → byte[])
  →  NetCrafterSyncManager.TrySendSync
    →  INetCrafterConnection.Send(cmd, payload, reliable: false)
      →  relay client sends the raw UDP envelope

Inbound (host → remote peer binding):

text
Relay envelope "__nc_sync_{RuleName}" arrives
  →  RelayConnectionManager default case
    →  NetCrafterEventDispatcher.Dispatch(cmd, (object)new RelayReader(payload))
      →  NetCrafterSyncManager callback (RelayReader payload)
        →  bindings[data.ReadInt32()].ApplyIncoming(data)
          →  transform.position = data.ReadVector3()

Wire format

All sync commands use the prefix __nc_sync_ followed by the rule name. The payload is a positional binary blob (no per-field keys):

Order Type Description
1st int objectId of the sending binding
2nd Vector3 or Quaternion Position, scale, or velocity (Vector3); rotation (Quaternion)

Payload convention note

Sync callbacks receive a RelayReader as the object payload — not a typed event class — with the "id" field already consumed by NetCrafterSyncManager before the binding's ApplyIncoming is called. This is different from normal event callbacks (which receive a deserialised event object). See the comment in NetCrafterSyncManager.SubscribeToSyncRule and the default case in the generated RelayConnectionManager.

What is NOT handled

  • Interpolation / smoothing: values are applied directly. Use Vector3.Lerp or a similar approach in game code if smoothing is needed.
  • Ownership transfer: hasAuthority is set in the Inspector and does not change at runtime.
  • Bandwidth budgeting: tick rate is controlled by Unity's FixedUpdate rate and the interval field.
  • Duplicate suppression on sync messages: sync traffic is sent unreliably by design (no retry/dedupe) — only Reliable messages get the ACK+retry treatment.
07

Known Limitations#

Message size limits

Keep individual message payloads under roughly 1200-1400 bytes. Larger payloads are silently dropped, not truncated or fragmented — confirmed against a live coturn server: 100/500/1200/1400-byte payloads round-tripped correctly, a 4000-byte payload never arrived at all.

This isn't a relay bug — it's an inherent limit of sending raw UDP through TURN with no application-level fragmentation. Payloads that push the underlying UDP datagram past the typical ~1500-byte Ethernet MTU get IP-fragmented, and fragmented UDP is unreliable across the public internet (many routers, NAT devices, and firewalls drop fragments). The Max Message Size setting (default 32768 bytes) only limits what's queued for send — it does not mean payloads that large will actually arrive.

If a message needs to carry more data than that (e.g. a large snapshot or asset chunk), split it into multiple smaller messages at the application level and reassemble on the receiving side. NetCrafter Relay does not do this chunking automatically today.

Disconnect detection

OnPeerDisconnected is basic silence-based detection, not a full connection-quality or reconnection system. There's no distinction between "brief lag spike" and "peer actually gone" — it's purely: no traffic for ~15 seconds, fire once.

RelayConnectionManager sends a tiny liveness heartbeat to the connected peer every 5 seconds (piggybacked on the same timer that refreshes the TURN allocation/permission — see API Reference). If no traffic at all — heartbeat or any real message — has been received from a peer for about 15 seconds (3x the heartbeat interval), OnPeerDisconnected(peerId) fires exactly once for that peer. Confirmed live: after a force-killed peer, the surviving side fired the event in ~15 seconds, versus no signal at all previously.

What it does not do: reconnect automatically, retry the underlying TURN allocation on the disconnected peer's behalf, or fire more than once per peer. Treat it as "this peer is gone, handle it in your own game logic" (show a disconnect screen, end the session, etc.) — not as a building block for resilience against flaky connections.

08

Coming Soon#

The following features are visible in the UI but not yet fully implemented.

Feature Status
Prefab Generator — Player / Chat UI / NPC Coming. Sections visible in the UI but greyed out.
Partial classes / Advanced sync Coming.
Rendezvous REST-API credential mode Placeholder — RelayConnectionManager's TURN credential wiring currently only stubs StaticSecret.
coturn channel binding (RFC 5766 §11) Not yet implemented — the relay client uses Send/Data indications only, which have higher per-packet overhead than a bound channel.