FX Spec
The design file format — one JSON file that describes a visual, its lifecycle states, bindings and performance rules, and renders identically on every platform.
An FX Spec is a small JSON file that describes a visual completely: the object and pattern, colour, materials, lifecycle states, data bindings and low-power rules. Every platform resolves it with the same engine, so one file renders the same on the web, iOS, Android and React Native. Designers can hand it over, and apps can ship it, load it from a server, or build it in code.
{
"fxSpec": "1.8",
"name": "Voice orb",
"description": "One shape for the whole conversation: the pattern stays `glowing` and the agent's state changes how it behaves -- speed, density, glow, particles, `ink` and which voice drives it.",
"object": "orb",
"pattern": "glowing",
"size": 64,
"ink": 1,
"color": {
"value": "#6E56CF",
"mix": 0.92
},
"params": {
"hueSpread": 30,
"hueSpeed": 0,
"surfaceScale": 1.2,
"surfaceSpeed": 0.1,
"nodeCount": 220,
"nodeSize": 1.15,
"saturation": 0.5
},
"materials": {
"glow": {
"strength": 0.25,
"radius": 3
}
},
"bindings": {
"muted": {
"input": "micMuted"
}
},
"states": {
"initializing": {
"speed": 0.8,
"params": {
"nodeCount": 160,
"nodeSize": 1,
"surfaceSpeed": 0.6,
"saturation": 0.4
},
"ink": 0.7
},
"idle": {
"speed": 0.6,
"materials": {
"pulse": {
"strength": 0.6,
"period": 4.5,
"scale": 0.04,
"opacity": 0.25
}
},
"ink": 0.8
},
"listening": {
"speed": 0.9,
"params": {
"nodeCount": 260,
"nodeSize": 1.3,
"surfaceSpeed": 0.28,
"saturation": 0.6
},
"materials": {
"glow": {
"strength": 0.3
},
"particles": {
"strength": 1,
"style": "attract",
"count": 36,
"size": 1.4,
"spread": 0.4,
"life": 1.8,
"audio": 1
}
},
"bindings": {
"audioLevel": {
"input": "micLevel",
"curve": "easeOut"
}
}
},
"thinking": {
"speed": 1.05,
"params": {
"nodeCount": 340,
"nodeSize": 1.05,
"surfaceScale": 3,
"surfaceSpeed": 0.55,
"saturation": 0.8,
"hueSpeed": 0
},
"materials": {
"glow": {
"strength": 0.28
},
"pulse": {
"strength": 0.6,
"period": 1.8,
"scale": 0.015,
"opacity": 0.45
}
}
},
"speaking": {
"speed": 1.15,
"params": {
"nodeCount": 260,
"nodeSize": 1.5,
"surfaceScale": 1.5,
"surfaceSpeed": 0.5,
"saturation": 0.62
},
"materials": {
"glow": {
"strength": 0.45
},
"particles": {
"strength": 1,
"style": "drift",
"count": 32,
"life": 1.4,
"audio": 1
}
},
"bindings": {
"audioLevel": {
"input": "agentVolume",
"inputRange": [
0,
0.8
]
}
}
}
}
}Structure
fxSpec(required): the version,"1.8".object(required) andpattern(required): what to draw. See the catalog.size(20 · 32 · 64) andspeed: see Speed & size.ink(1.8): how present the visual is,0to1(default1). It fades everything, halos included, and a state can patch it — an assistant rests below full ink when idle and comes to full ink when it listens.color,gradient: see Colour & theme.materials:glow,noise,pulse,liquid,particles,holographic. See Materials.params: the pattern's own props (the catalog's style props), e.g."ringCount": 3.bindings: map your app's inputs onto values. See Bindings.states: a patch per lifecycle state. See States vs patterns.performance:maxFps, and what to shed under low power.name,description,$schema: for people and editors.
Point $schema at fx-spec-1.schema.json for completion and validation in your editor.
Versioning
fxSpec is "1.8", written as major.minor. A file declares the version it was written for, and a runtime reads every file of its major version: keys it doesn't know become warnings, and a different major is refused. Declaring a version older than the keys you use is an error, so write "1.8" and use whatever this page documents.
Resolving a spec yourself
Views resolve specs for you. To inspect what the engine will draw, for tooling or tests, resolve one directly:
import { resolveFxSpec } from "@sinua/core";
import spec from "../spec/voice-orb.fxspec.json";
// Resolve once to see what the engine will draw for a lifecycle state.
const r = resolveFxSpec(JSON.stringify(spec), {
state: "listening",
inputs: { micLevel: 0.4, micMuted: 0 },
});
if (!r.ok) console.error(r.diagnostics);
console.log(r.state, r.overrides); // "listening" pattern, with audioLevel bound to micLevel
Every key
Top level
| Key | Type | Description |
|---|---|---|
$schema | string | |
fxSpec | string | "major.minor", 1.8 or later (the runtime's floor; 1.0-1.7 are rejected). A newer 1.x file resolves, with its unknown keys reported as warnings. |
name | string | |
description | string | |
object | ||
pattern | string | The visual pattern, e.g. working, metering, tracking, scanning, generating. Must belong to object's family. |
size | ||
speed | number | Multiplier on the preset's tuned speed; frameFromFxSpec applies elapsed * presetSpeed * speed. |
ink | number | FX Spec 1.8: the whole visual's opacity, 0 to 1. 1 draws it as the pattern defines it; lower fades everything, halos included. A state patches it (a voice assistant's idle typically sits below 1). |
color | A color (shorthand for { value, mix: 1 }) or the full section. | |
gradient | object | see gradient |
materials | object | |
params | object | Per-state engine opts (docs/parameters.md). Runtime inputs (pointer*, audio*, history*, interrupt*, muted*, voiceStateCode, peakN) and section-owned keys (color*, gradient*, glow*, noise*, pulse*) are rejected here. FX Spec 1.7: progress / segment may be arrays. |
bindings | object | see bindings |
states | object | Lifecycle key -> design entry, merged over the top-level design (RFC 7396: objects merge, null deletes, arrays replace). Keys are free-form; LiveKit's AgentState names (initializing, idle, listening, thinking, speaking) are the convention. A state not listed renders the top-level design. |
performance | object | see performance |
A states entry
| Key | Type | Description |
|---|---|---|
pattern | string | The pattern for this lifecycle key; defaults to the top-level pattern. |
speed | number | |
ink | number | FX Spec 1.8: this state's opacity; inherits the top-level ink when absent. |
color | object | null | |
gradient | object | null | |
materials | object | null | |
params | object | null | |
bindings | object | null |
A binding
| Key | Type | Description |
|---|---|---|
input | string | The app's input name (e.g. micLevel, steps, heartRate); the engine never interprets it. Missing at runtime = binding inactive. |
inputRange | array | |
outputRange | array | Default: the target's range, except the per-ring progress[0]…progress[3], which default to [0, 1] (the goal = one lap); for extra laps (up to 3) pass a multi-stop outputRange, e.g. [0, 1, 3]. |
curve |
Bindable targets: progress, quality, accuracy, audioLevel, muted, progress[0], progress[1], progress[2], progress[3], glow.strength, noise.strength, gradient.strength, pulse.strength, color.mix.
Deprecated aliases (FX Spec 1.6 and earlier; still accepted, with a warning in a 1.7 file): state → pattern, progress0 → progress[0], progress1 → progress[1], progress2 → progress[2], progress3 → progress[3], glowStrength → glow.strength, noiseStrength → noise.strength, gradientStrength → gradient.strength, pulseStrength → pulse.strength, colorMix → color.mix. See the rename map.
color
| Key | Type | Range | Description |
|---|---|---|---|
value | object | see color | |
mix | number | 0–1 | |
lightness | number | -1–1 | |
mode | enum | ink · fixed |
gradient
| Key | Type | Range | Description |
|---|---|---|---|
stops | array | ||
angle | number | -360–720 | |
strength | number | 0–1 | |
saturation | number | 0–1 | |
mid | number | 0.05–0.95 | |
path | enum | short · long |
materials.glow
| Key | Type | Range | Description |
|---|---|---|---|
strength | number | 0–1 | |
radius | number | 1–8 | |
layers | number | 1–8 | |
tint | number | 0–1 | |
hue | number | 0–360 | |
mode | enum | stacked · blur | stacked = concentric copies (every renderer); blur = one Gaussian-blurred halo per element (paint-contract blur). Low power with disable: ["blur"] falls back to stacked. |
blend | enum | normal · additive | How blur-mode halos composite: additive = Canvas lighter / SwiftUI plusLighter / Compose BlendMode.Plus. |
materials.noise
| Key | Type | Range | Description |
|---|---|---|---|
strength | number | 0–1 | |
amplitude | number | 0–1 | |
scale | number | 0–64 | |
speed | number | 0–16 | |
seed | number |
materials.pulse
| Key | Type | Range | Description |
|---|---|---|---|
strength | number | 0–1 | |
period | number | 0.05–60 | |
opacity | number | 0–1 | |
scale | number | 0–1 | |
phase | number |
materials.liquid
| Key | Type | Range | Description |
|---|---|---|---|
strength | number | 0–1 | 0 = off; also the liquid ink's alpha multiplier. |
reach | number | 1–12 | Influence radius x max(dot radius, half the median nearest-neighbour distance). |
threshold | number | 0.05–4 | |
cells | number | 8–96 | Field grid resolution across the frame. |
style | enum | outline · dots · fill | |
spacing | number | 0.5–12 | dots style: spacing x median dot diameter. |
width | number | 0.05–8 | outline style: stroke x median dot radius. |
keep | boolean | Keep the source dots on top. | |
blur | number | 0–32 | fill style: Gaussian sigma for a soft edge (paint effect). |
materials.particles
| Key | Type | Range | Description |
|---|---|---|---|
strength | number | 0–1 | 0 = off; alpha master. |
count | number | 0–200 | |
size | number | 0.05–4 | x the state's dot size (0.6 = the base radius, clamped to 0.8-1.8% of the frame; default 0.8). |
spread | number | 0–1 | Travel distance as a fraction of the frame. |
life | number | 0.1–30 | Real (wall-clock) seconds per life: the engine divides the state's preset speed back out; a spec's own speed still scales it. |
style | enum | drift · attract · orbit · rise | |
seed | number | ||
sync | number | 0–1 | 1.6: 0 = staggered births; 1 = every particle born together (a burst each life). |
audio | number | 0–1 | 1.6: couples brightness to the host's audioLevel when present (alpha x lerp(1, 0.25 + 0.75 level, audio)); positions never depend on it. |
materials.holographic
| Key | Type | Range | Description |
|---|---|---|---|
strength | number | 0–1 | 0 = off; how far saturation/hue move to the sweep. |
hue | number | 0–360 | Base hue, degrees. |
span | number | 0–720 | Degrees of the wheel the sweep covers. |
saturation | number | 0–1 | |
depth | number | 0–1 | Weight of the z (depth) term; dots only. |
facing | number | 0–1 | Weight of the Fresnel-like distance-from-view-point term. |
speed | number | -4–4 | Turns per second of engine time: hue drift and the view point's orbit. |
performance
| Key | Type | Range | Description |
|---|---|---|---|
maxFps | number | 1–120 | Frame-rate cap the host should honour. Absent = the host's default. |
lowPower | object |