Reference for the Channel and Scene types in a OctoMY™ preset — the data model that lets a single robot run multiple behaviour streams (locomotion, arm, turret, lights) in parallel and apply coordinated multi-channel snapshots with one click.
One-sentence summary
A channel is a declared group of stanzas with exactly one active at a time; a scene names a coordinated
(channel → stanza)map that activates atomically. The Studio's Scenes tab inside the preset editor is where scenes are authored.
For the conceptual model and motivation, see Channels, scenes, and coexistence. This page is the precise field-by-field reference.
Channel typestruct Channel {
QString id; // stable identifier, e.g. "locomotion"
QString name; // display name
QString defaultActiveStanzaId; // activates on boot
QString idleStanzaId; // synthesised if empty
QVector<QString> upgradeLadder; // optional auto-promotion targets
};
| Field | Type | Required | Description |
|---|---|---|---|
id |
string | yes | Stable identifier used as the wire-format key (e.g. in stanza.channelId, in scene assignments, in Runtime::activate(channelId, …)). Stanza ids may not collide with channel ids. |
name |
string | no | Human-readable display name. Falls back to id if empty. |
defaultActiveStanzaId |
string | no | The stanza that activates when the preset loads. Empty → the channel boots into idleStanzaId. |
idleStanzaId |
string | no | The "rest" stanza the channel reverts to when no other stanza is active or when conflict resolution force-demotes it. Empty triggers synthesis of a no-op idle stanza named <channelId>.idle (lobe type idle, no actuator claims, no input ports). |
upgradeLadder |
array<string> | no | Stanza ids the negotiate-policy walks when a higher-priority channel needs to free actuators. See Auto-promotion. |
{
"id": "locomotion",
"name": "Locomotion",
"defaultActiveStanzaId": "walk-6",
"idleStanzaId": "locomotion.idle",
"upgradeLadder": ["stand", "walk-4", "walk-6"]
}
idleStanzaId (declared or synthesised) covers the rest case.Stanza.channelId names exactly one channel; a stanza cannot float between channels.Runtime::activate tries to run them together.Scene typestruct Scene {
QString id;
QString name;
QHash<QString /*channelId*/,
QString /*stanzaId*/> assignments;
};
| Field | Type | Required | Description |
|---|---|---|---|
id |
string | yes | Stable wire-format key. The Remote-side scene picker indexes by id; renaming via the UI changes name but never id. |
name |
string | no | Display name shown on the Remote scene-picker button. Falls back to id. |
assignments |
object<channelId, stanzaId> | yes | Per-channel stanza picks. Omitting a channel from assignments means fall back to that channel's idle stanza on apply — explicit, no hidden default. |
{
"id": "cruise",
"name": "Cruise",
"assignments": {
"locomotion": "walk-6",
"arm": "hold",
"lights": "running-lights"
}
}
A preset that fails any of these is rejected at load time (Preset::fromJson populates the errors list):
channelId in assignments names an existing channel.stanzaId in assignments names an existing stanza.channelId matches the assignment key (no cross-channel borrowing).Live validation in the Studio Scenes tab surfaces these errors as the operator picks dropdowns — they never get to save a preset whose scenes wouldn't load.
Scenes live inside the preset, so the Studio's preset editor tab strip owns the editing UI. Open a preset from the Preset Manager and click the Scenes tab — it sits alongside Actuators / Sensors / Lobes / Stanzas.
The tab is a three-column layout:
Edits autosave through the same path as every other preset edit — there is no save button on the tab. Use the OS-level Back button when you're done.
For the full step-by-step walkthrough see Author scenes.
The agent-side runtime exposes one entry point:
ActivateResult Runtime::applyScene(const QString &sceneId, ConflictPolicy policy);
The implementation is equivalent to calling Runtime::activate(channelId, stanzaId, 0, policy) for every assignment in scene.assignments, in claim-decreasing order — channels claiming more actuators are activated first so the negotiate-policy has the most freedom to demote conflicting channels along their upgrade ladders. Any channel not named in assignments falls back to its idleStanzaId.
The Remote-side ScenePickerWidget calls applyScene via the fixo-runtime SyncSection's activeSceneId field. See fixo-runtime for the full ABI.
The upgradeLadder on a channel names stanzas the runtime is allowed to demote toward when conflict resolution under ConflictPolicy::Negotiate runs.
Example — a hexapod's locomotion channel:
{
"id": "locomotion",
"upgradeLadder": ["stand", "walk-4", "walk-6"]
}
walk-6 claims 18 leg actuators; walk-4 claims 12; stand claims 0. If the arm channel tries to activate a stanza that needs 4 of the leg actuators, the negotiator walks locomotion's ladder from its current position toward index 0 until the claim shrinks enough to fit. A channel without a ladder doesn't auto-promote — it requires explicit user-driven downgrade.
The ladder is purely advisory data. The runtime never auto-upgrades (walks toward higher indices on its own); promotion always happens under operator command or scene apply.
Both Runtime::activate and Runtime::applyScene take a ConflictPolicy:
| Policy | Behaviour |
|---|---|
Reject |
Return ok=false with the conflict list if any actuators overlap. The current activation is unchanged. |
Force |
Move every conflicting channel's active stanza to its idle stanza, then proceed. |
Negotiate |
Walk each conflicting channel's upgradeLadder until conflict resolves; if no ladder entry resolves it, return ok=false. |
Operator-driven activations from the Remote default to Reject — surfacing conflicts is the whole point of the channel model. Scene applies typically default to Force so a "cruise" scene reliably reaches its declared state. Autonomous services pick per-use-case.
Before Phase 14, what is now a channel was called a stanza set and was computed by running a union-find over actuator overlap across all stanzas. Channels are now declared by the preset author via Stanza.channelId; overlap detection still happens but at runtime, not as a static partition.
The data-model migration is one-way — the JSON IO no longer reads the old stanzaSets field. The migration tool that ships with Phase 14 fixtures is the canonical upgrade path for existing presets.
FixoRuntime — the runtime that holds the active stanza per channel and serves applyScene.