Fluffer by MotorbunnyDevelopers

Web Launcher SDK

Manifest

Every field of a Fluffer app manifest.json: engine, entry, orientation, capabilities, auth scopes, validation rules and examples.

Every Fluffer app ships a manifest.json at the root of its package. It tells Fluffer what kind of app this is, how to start it, and which capabilities it may use. Fluffer reads it before loading your app and enforces the capabilities it lists.

#Example

{
  "manifestVersion": 1,
  "minSdkVersion": "0.1.0",
  "engine": "html5",
  "entry": "index.html",
  "orientation": "landscape",
  "capabilities": ["device.vibrate", "device.rotate"]
}

#Fields

FieldTypeRequiredDescription
manifestVersion1yesManifest format version. Always 1.
minSdkVersionstringyesThe minimum SDK version your app expects, as major.minor.patch (e.g. "0.1.0").
engine"html5", "threejs" or "unity"yesWhat your app is built with.
entrystringyesThe page that starts your app. In your package this is a path relative to the package root, usually "index.html". No spaces.
orientation"portrait", "landscape" or "auto"yesThe screen orientation your app needs. "auto" follows the device.
capabilitiesstring[]yesThe capabilities your app may use. No duplicates. Use [] if you need none.
auth{ "scopes": string[] }only with account.identityThe account scopes your app may ask for. Required when capabilities includes account.identity, and not allowed otherwise.
compatnullnoReserved for legacy Fluffer titles. Omit it or set it to null.

There are no defaults: every required field must be present.

interface AppManifest {
  manifestVersion: 1;
  minSdkVersion: string;
  engine: 'html5' | 'threejs' | 'unity';
  entry: string;
  orientation: 'portrait' | 'landscape' | 'auto';
  capabilities: Capability[];
  auth?: { scopes: AuthScope[] };
  compat?: null;
}

#Capabilities

Declare only what you use. When your app runs, Fluffer grants the overlap of what your manifest declares, what your app requests at handshake (the capabilities option of createGame()), and what the player allows. Calls outside that set fail with PERMISSION_DENIED.

CapabilityWhat it unlocks
device.vibrateMotor control: setAllMotors, setMotorIntensity, stopVibration, executeRoutine, shake, and every game-kit haptic. Almost every app needs it.
device.rotateThe rotation motor: setRotationSpeed, stopRotation, haptics.rotate()
device.batteryThe toy's battery level
user.profileuser.get(): the player's display name, after they consent
tips.fuelTip and fuel events and game.tips
media.photosSaving photos and video to the photo library (game.media)
account.identityConsent-first account identity and signed tokens (game.auth)

Starting and closing your app, setting the orientation and asking which toy is connected are always allowed, so they don't need a capability.

See Bridge protocol for the exact methods and events each capability covers.

Declaring user.profile or account.identity only means your app may ask. Nothing about the player's account (id, name, picture or token) reaches your app until the player allows it from a sheet that Fluffer shows. Your app must keep working when they say no. See Accounts.

#auth.scopes

An app that declares account.identity must list, up front, every account scope it might ever ask for. Fluffer shows the player only the scopes being asked for at that moment, in these words:

ScopeShown to the player as "Your app wants to ..."Notes
profile.readsee your username and picture, and whether you are age-verifiedRequired. Every app asks for this first.
content.reactlike, follow and report as you
content.postpost and share under your accountAsk when the player takes that action, not at launch.
moderation(never shown)Reserved for staff tools. Never prompted; granted only to accounts with a staff role.
{
  "manifestVersion": 1,
  "minSdkVersion": "0.1.0",
  "engine": "html5",
  "entry": "index.html",
  "orientation": "portrait",
  "capabilities": ["device.vibrate", "account.identity"],
  "auth": { "scopes": ["profile.read", "content.react", "content.post"] }
}

Asking for a scope at runtime that isn't in auth.scopes fails with INVALID_ARGUMENT.

#Validation rules

A manifest is rejected when:

npm run package runs this check before it builds your zip. You can also run it yourself with validateManifest() from @fluffer/launcher-sdk, which never throws and returns every issue at once:

import { validateManifest, assertValidManifest } from '@fluffer/launcher-sdk';
import manifest from './manifest.json';

const { ok, issues } = validateManifest(manifest);
if (!ok) {
  for (const i of issues) console.error(`${i.path || '<root>'}: ${i.message}`);
}

// Or throw with every issue listed:
assertValidManifest(manifest);

The SDK also exports CAPABILITIES (every valid capability token), AUTH_SCOPES (every valid scope), and the isCapability() and isAuthScope() helpers.