Sinua
Reference

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.

voice-orb.fxspec.json
{
  "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) and pattern (required): what to draw. See the catalog.
  • size (20 · 32 · 64) and speed: see Speed & size.
  • ink (1.8): how present the visual is, 0 to 1 (default 1). 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:

mount-spec.ts
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

KeyTypeDescription
$schemastring
fxSpecstring"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.
namestring
descriptionstring
object
patternstringThe visual pattern, e.g. working, metering, tracking, scanning, generating. Must belong to object's family.
size
speednumberMultiplier on the preset's tuned speed; frameFromFxSpec applies elapsed * presetSpeed * speed.
inknumberFX 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).
colorA color (shorthand for { value, mix: 1 }) or the full section.
gradientobjectsee gradient
materialsobject
paramsobjectPer-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.
bindingsobjectsee bindings
statesobjectLifecycle 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.
performanceobjectsee performance

A states entry

KeyTypeDescription
patternstringThe pattern for this lifecycle key; defaults to the top-level pattern.
speednumber
inknumberFX Spec 1.8: this state's opacity; inherits the top-level ink when absent.
colorobject | null
gradientobject | null
materialsobject | null
paramsobject | null
bindingsobject | null

A binding

KeyTypeDescription
inputstringThe app's input name (e.g. micLevel, steps, heartRate); the engine never interprets it. Missing at runtime = binding inactive.
inputRangearray
outputRangearrayDefault: 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

KeyTypeRangeDescription
valueobjectsee color
mixnumber0–1
lightnessnumber-1–1
modeenumink · fixed

gradient

KeyTypeRangeDescription
stopsarray
anglenumber-360–720
strengthnumber0–1
saturationnumber0–1
midnumber0.05–0.95
pathenumshort · long

materials.glow

KeyTypeRangeDescription
strengthnumber0–1
radiusnumber1–8
layersnumber1–8
tintnumber0–1
huenumber0–360
modeenumstacked · blurstacked = concentric copies (every renderer); blur = one Gaussian-blurred halo per element (paint-contract blur). Low power with disable: ["blur"] falls back to stacked.
blendenumnormal · additiveHow blur-mode halos composite: additive = Canvas lighter / SwiftUI plusLighter / Compose BlendMode.Plus.

materials.noise

KeyTypeRangeDescription
strengthnumber0–1
amplitudenumber0–1
scalenumber0–64
speednumber0–16
seednumber

materials.pulse

KeyTypeRangeDescription
strengthnumber0–1
periodnumber0.05–60
opacitynumber0–1
scalenumber0–1
phasenumber

materials.liquid

KeyTypeRangeDescription
strengthnumber0–10 = off; also the liquid ink's alpha multiplier.
reachnumber1–12Influence radius x max(dot radius, half the median nearest-neighbour distance).
thresholdnumber0.05–4
cellsnumber8–96Field grid resolution across the frame.
styleenumoutline · dots · fill
spacingnumber0.5–12dots style: spacing x median dot diameter.
widthnumber0.05–8outline style: stroke x median dot radius.
keepbooleanKeep the source dots on top.
blurnumber0–32fill style: Gaussian sigma for a soft edge (paint effect).

materials.particles

KeyTypeRangeDescription
strengthnumber0–10 = off; alpha master.
countnumber0–200
sizenumber0.05–4x the state's dot size (0.6 = the base radius, clamped to 0.8-1.8% of the frame; default 0.8).
spreadnumber0–1Travel distance as a fraction of the frame.
lifenumber0.1–30Real (wall-clock) seconds per life: the engine divides the state's preset speed back out; a spec's own speed still scales it.
styleenumdrift · attract · orbit · rise
seednumber
syncnumber0–11.6: 0 = staggered births; 1 = every particle born together (a burst each life).
audionumber0–11.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

KeyTypeRangeDescription
strengthnumber0–10 = off; how far saturation/hue move to the sweep.
huenumber0–360Base hue, degrees.
spannumber0–720Degrees of the wheel the sweep covers.
saturationnumber0–1
depthnumber0–1Weight of the z (depth) term; dots only.
facingnumber0–1Weight of the Fresnel-like distance-from-view-point term.
speednumber-4–4Turns per second of engine time: hue drift and the view point's orbit.

performance

KeyTypeRangeDescription
maxFpsnumber1–120Frame-rate cap the host should honour. Absent = the host's default.
lowPowerobject

On this page