Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.mascot.bot/llms.txt

Use this file to discover all available pages before exploring further.

The contract

The SDK writes exactly three input families: mouth visemes (100..118), is_speaking, and stress. Everything else on the Rive instance — other state-machine inputs, data binding / ViewModels, events, listeners — is owned by the consumer and accessed directly on the raw rive object. The SDK never wraps, gates, proxies, or constrains that. rive is always fully exposed.
This is a hard design rule, not a guideline.

Why

Avatars do far more than lip sync: gender / skin / outfit inputs, gesture triggers, click events, data-bound ViewModels, scene state. If the SDK owned the Rive instance, every one of those would have to be re-exposed through SDK API forever, and the SDK would become a bottleneck on the Rive runtime’s own evolution. Keeping the SDK to a three-input writer keeps integration “bring your own Rive, we animate the mouth” — composable, and future-proof against Rive API changes.

Get the raw instance

import { useMascotRive } from "@mascotbot/react/rive";

function Costume() {
  const { rive } = useMascotRive(); // inside <Mascot>
  // rive is the unmodified @rive-app/* instance.
  // Set any input, fire any trigger, attach EventType.RiveEvent listeners,
  // bind a ViewModel — none of it involves the SDK.
}
Framework-agnostic, the same is true of getRiveInputs(rive) from @mascotbot/core/rive — it reads inputs off a Rive instance you constructed and own.

Presence checks — use has(), not raw introspection

A missing input handle resolves to a silent no-op shim (DEFAULT_SM_INPUT) so the SDK’s own mouth writes never throw on an artboard that lacks a viseme. That shim is structurally identical to a real input — you cannot tell them apart by inspection. To know whether an input actually exists, ask:
import { useMascotInputs } from "@mascotbot/react/rive";

function Wave() {
  const { has, custom } = useMascotInputs<"wave">();
  if (has("wave")) custom.wave.fire(); // consumer-owned, SDK-untouched
  return null;
}
has(name) is the authoritative check. The framework-agnostic equivalents are getRiveInputs(rive).has(name) and hasRiveInput(rive, name) in @mascotbot/core/rive. custom from useMascotInputs is never undefined, which removes the optional-chaining tax from consumer code. Drive the input itself however you like — that part is entirely yours.

Rive file requirements

ElementRequirement
ArtboardCharacter
State machinemascotStateMachine (the SDK’s input lookup also accepts the alternate InLesson name)
Mouth inputsNumber inputs 100118 (viseme ids)
Optionalis_speaking, eyes_smile, stress, plus any consumer inputs (e.g. gesture)
Pass only mascotStateMachine in the stateMachines array to new Rive(...) (or rely on STATE_MACHINE_NAMES[0]). Rive 2.37+ throws on any unknown state-machine name in that array; the throw propagates through initStateMachines, fires LoadError, suppresses Load, and leaves the canvas blank.

Next

React hooks

useMascotRive, useMascotInputs.

React SDK

Provider and client components.

Migration

The 0.2.x symbol map.