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 SDK surface follows a small set of rules so it stays predictable and hard to misuse. Knowing them makes the whole API guessable.

1. Events vs. callbacks

  • Lifecycle / multi-fire → an emitter. client.on("ready" | "refused" | "error" | "refresh", fn) returns an unsubscribe function. Use it for things that happen repeatedly or that multiple listeners care about.
  • One-shot wiring / per-item telemetry → an option callback. e.g. useLipsyncStream({ onFrame }). Use it to configure one thing at construction.
There is never a second mechanism for the same concern (no onReady prop when client.on("ready") exists).

2. Options object, not positional

New hooks and functions take a single options object — e.g. useLoadRive({ stateMachineName, ... }). No public function takes multiple positional arguments where an options object would do.

3. Async is honest

If a value is computed off-thread (worker, network), the method is async and stays asyncclient.diagnostics(), session.pushWindow(). Nothing hides a Promise behind a sync-looking API, so you always know where to await.
const d = await client.diagnostics();          // off-thread → async
const frame = await session.pushWindow(window); // inference → async

4. Error taxonomy

Five classes — LipsyncError (base) plus License / Network / Engine / RefusedError — mapped to failure domains. You branch on .code, not the subclass. A new failure that is not a new domain reuses LipsyncError with a new .code (e.g. bad_timeline) rather than adding a subclass per code. Every code is registered in Error codes.

5. Serialized-format versioning

Anything you can persist and feed back later carries an explicit version and a validating parser that rejects mismatches loudly. VisemeTimeline has version: VISEME_TIMELINE_VERSION plus frameMs, and parseTimeline throws LipsyncError("bad_timeline", …) on any incompatibility. The version is bumped on any breaking shape or semantics change; an old shape is never silently accepted. Always load persisted data through the validating parser, never JSON.parse alone. See the timeline model.

6. Module boundaries

Import the narrowest entry point for the job:
EntryContainsExcludes
@mascotbot/coreEngine, VisemeTimeline + helpers, createPCMStreamPlayerNo Rive, no React
@mascotbot/core/riveMascotPlayback, getRiveInputs, hasRiveInputNo React; @rive-app/webgl2 is an optional peer
@mascotbot/reactMascotProvider, useMascot, useProcessAudioNo Rive
@mascotbot/react/riveThe React Rive layer
A Rive type never appears on the core root entry, and core never depends on React. The split keeps bundles minimal and the dependency graph honest.

7. The SDK stays out of your Rive instance

The SDK writes exactly three input families — mouth visemes, is_speaking, stress — and nothing else. Every other Rive capability is reached directly on the raw instance. This is a hard contract, documented in Rive co-existence: the SDK never wraps, gates, proxies, or constrains Rive.

Next

Error codes

The full code matrix.

Rive co-existence

The Rive ownership contract.

Migration

The 0.2.x symbol map.