Overview
The VIVERSE Me SDK lets a third-party host site embed the Happy Path character flow so visitors can pick or create an avatar without calling account APIs directly from the host page.
A host page owns its own app experience and loads the SDK as an embed layer. In Happy Path mode,
the SDK owns the VIVERSE Me iframe, character picker, Avatar Creator launch, developer-owned
anonymous storage, and avatar asset API calls. The stable public entry is
https://viverse-me.com/sdk.js.
What the host page should do
- Load
https://viverse-me.com/sdk.js. - Pass a registered SDK Integration ID with
data-ac3-partner-idorpartnerId. - Set
data-ac3-mode="sdk-happy-path". - Listen for SDK lifecycle and avatar events.
- Use returned signed URLs for immediate preview only.
- Keep durable avatar state in a backend, not in browser-only storage.
/api/ac3/me, /api/ac3/files,
/api/ac3/download-url, /api/ac3/upload-vrm, or /api/ac3/claim-draft
directly from the host page. The embedded SDK and VIVERSE Me pages own those flows.
Quick Start
Add a target element and the SDK script, then listen for avatar selections.
<div id="viverse-me-button-slot"></div>
<div id="host-sign-in-button-slot"></div>
<script
src="https://viverse-me.com/sdk.js"
data-ac3-mode="sdk-happy-path"
data-ac3-target="#viverse-me-button-slot"
data-ac3-host-sign-in-target="#host-sign-in-button-slot"
data-ac3-partner-id="partner_..."
data-ac3-label="Start"
async
></script>
Then listen for avatar selections:
window.addEventListener('viverse-me:avatar-selected', (event) => {
const avatar = event.detail.avatar;
hostScene.loadAvatar(avatar.vrmUrl);
partnerApi.saveSelectedAvatarReference({
partnerId: avatar.partnerId,
userId: avatar.userId,
tenantId: avatar.tenantId,
key: avatar.key
});
});
vrmUrl and thumbnailUrl are short-lived signed URLs for immediate preview.
Store stable reference fields such as tenantId, key, and partnerId
for durable use.
Getting your snippet
This public guide shows a partner_... placeholder everywhere a real SDK Integration ID
is required. You never paste your ID from documentation — you copy a ready-made snippet.
- Sign in at VIVERSE Me and open Account Settings → My SDK.
- Add the website origins where the SDK may run under Allowed Websites.
- Use Copy in the SDK Code Snippet box. The copied snippet already contains your SDK Integration ID.
- Paste it into your host page and adjust the Happy Path attributes described in this guide.
partner_..., substitute the value from your own snippet.
Requirements
Before a production host can initialize the SDK:
- The signed-in VIVERSE Me account must have an SDK Integration ID.
- The host website must be saved in that account's Allowed Websites.
- The production snippet must include
data-ac3-partner-idor a manualpartnerId. - The host CSP must allow VIVERSE Me frames, for example
frame-src https://viverse-me.com. - To use the landing-matched Roboto button exactly, the host CSP must allow
style-src https://fonts.googleapis.comandfont-src https://fonts.gstatic.com; otherwise the browser uses its sans-serif fallback. - Google Sign-In requires the exact embed origin to be allowed in the Google OAuth client when using preview or custom origins.
- Apple Sign In requires the configured Services ID redirect URI to match the deployed VIVERSE Me callback URL.
Local development origins such as http://localhost:<port> are supported when added to Allowed Websites.
Website Authorization
The SDK verifies the current window.location.origin before creating its iframe UI.
Production snippets must include a valid partnerId, and that partner ID must allow the host
origin. Users manage Allowed Websites from Account settings. Each allowed website is an exact origin,
such as https://www.example.com. It must not include paths, query strings, hashes,
wildcards, usernames, or passwords.
If authorization fails
ViverseMeSDK.init()resolves tonull.- No SDK iframe UI is created.
- The SDK logs a warning.
viverse-me:authorization-deniedis dispatched.
SDK Happy Path
SDK Happy Path is the game-start embed mode. It defaults to a no-login anonymous flow, and can optionally let end users switch into their authenticated VIVERSE Me Library inside the same panel.
<div id="viverse-me-button-slot"></div>
<script
src="https://viverse-me.com/sdk.js"
data-ac3-mode="sdk-happy-path"
data-ac3-target="#viverse-me-button-slot"
data-ac3-partner-id="partner_..."
data-ac3-label="Start"
async
></script>
When the SDK launch button (or instance.open()) is triggered, a centered modal opens with a
character picker. By default the panel lists the public default avatars plus a Create entry.
If this browser previously created an avatar for the same SDK Integration ID, the panel also displays that
anonymous saved avatar:
- Pick a default → emits
viverse-me:avatar-selectedwith the public defaultvrmUrland closes the modal. - Create → opens Avatar Creator with a developer-owned anonymous session (no sign-in). On finish it saves the avatar in that developer's storage namespace, generates a thumbnail, emits
viverse-me:avatar-selectedwith short-lived signedvrmUrl/thumbnailUrl, and closes the modal. - Saved avatar → requests fresh signed asset URLs and restores the active avatar created previously in the same browser.
Use VIVERSE Me Library
The panel also shows a Use VIVERSE Me Library button by default. That button opens the same
email / Google / Apple sign-in flow used by the Quick Selector. After sign-in, Happy Path switches from
anonymous storage to the authenticated account library:
- The account avatars replace the anonymous saved avatars and public defaults.
- The button label changes to
Sign Out. - New avatars created from Happy Path are stored in the signed-in account.
- If the account is already at its avatar cap, Happy Path blocks creation and shows a library-full dialog.
localStorage. Clearing site data,
changing browser profiles, or changing devices loses the anonymous link unless the host adds an
authenticated recovery flow. On mobile, the embedded creator releases the Unity WebGL runtime and restores
a thumbnail-only viewer before generating the saved avatar thumbnail.
Host Sign In
If the host has its own account system, keep the default Start button as the anonymous flow and add a separate SDK-owned Host Sign In button.
Provide a button slot with data-ac3-host-sign-in-target; the SDK renders the button with the
same definition used by Start. For a Host Sign In only entry, set
data-ac3-show-anonymous-button="false". To also remove the in-panel VIVERSE Me Library entry,
set data-ac3-hp-show-library-button="false". To skip the prompt when the host already knows the
user, set data-ac3-remember-host-sign-in="true".
<div id="viverse-me-button-slot"></div>
<div id="host-sign-in-button-slot"></div>
<script
src="https://viverse-me.com/sdk.js"
data-ac3-mode="sdk-happy-path"
data-ac3-target="#viverse-me-button-slot"
data-ac3-host-sign-in-target="#host-sign-in-button-slot"
data-ac3-host-sign-in-label="Host Sign In"
data-ac3-show-anonymous-button="false"
data-ac3-remember-host-sign-in="true"
data-ac3-partner-id="partner_..."
async
></script>
When the SDK-owned Host Sign In button is clicked, the SDK dispatches viverse-me:host-sign-in-click
and calls onHostSignInClick for manual init users. Open your own sign-in dialog from that event,
then call openWithHostSignIn(account) after the host identity is known.
window.addEventListener('viverse-me:sdk-ready', (event) => {
const sdk = event.detail.instance;
window.addEventListener('viverse-me:host-sign-in-click', () => {
hostSignInDialog.hidden = false;
});
window.addEventListener('viverse-me:host-identity-request', (identityEvent) => {
identityEvent.detail.respond(hostAuth.currentUserId || '');
});
hostSignInForm.addEventListener('submit', async (submitEvent) => {
submitEvent.preventDefault();
const account = hostAccountInput.value;
await sdk.openWithHostSignIn(account);
});
});
Button styling
The default button follows the VIVERSE Me landing primary CTA: Roboto at 1.12rem, 700
weight, 0.04em letter spacing, a 48px minimum height, and a pill radius. Hosts can
override its CSS custom properties without replacing the SDK-owned button:
:root {
--viverse-me-button-background: #111;
--viverse-me-button-color: #fff;
--viverse-me-button-font: 700 1.12rem/1.2 Roboto, sans-serif;
--viverse-me-button-letter-spacing: 0.04em;
--viverse-me-button-min-height: 48px;
--viverse-me-button-padding: 0 24px;
--viverse-me-button-border-radius: 999px;
}
Panel Customization
Happy Path panel customization is an allowlisted client option. It does not require an API key and does not accept arbitrary CSS, HTML, JavaScript, or external stylesheet URLs. The SDK sanitizes the options and applies them as copy values, CSS variables, card visibility flags, and saved-avatar UI limits.
The saved avatar card, default avatar cards, and Create card can each be hidden independently. The panel defaults to one fetched and one displayed saved avatar; hosts can raise those counts without changing backend retention rules.
<script
src="https://viverse-me.com/sdk.js"
data-ac3-mode="sdk-happy-path"
data-ac3-target="#viverse-me-button-slot"
data-ac3-partner-id="partner_..."
data-ac3-label="Start"
data-ac3-hp-show-saved-avatar="false"
data-ac3-hp-show-default-avatars="false"
data-ac3-hp-show-create-avatar="false"
data-ac3-hp-show-library-button="true"
data-ac3-hp-saved-avatar-fetch-limit="3"
data-ac3-hp-saved-avatar-display-limit="2"
data-ac3-hp-panel-background="rgba(255, 255, 255, 0.92)"
data-ac3-hp-title-color="#0f172a"
data-ac3-hp-card-background="rgba(16, 24, 40, 0.82)"
data-ac3-hp-create-label="Create"
async
></script>
The same settings can be passed during manual initialization under the nested happyPath option:
const instance = await window.ViverseMeSDK.init({
mode: 'sdk-happy-path',
target: '#viverse-me-button-slot',
partnerId: 'partner_...',
label: 'Start',
happyPath: {
cards: {
showSavedAvatar: false,
showDefaultAvatars: false,
showCreateAvatar: false,
savedAvatarFetchLimit: 3,
savedAvatarDisplayLimit: 2
},
auth: { showLibraryButton: true },
layout: { maxVisibleRowsBeforeScroll: 2, disableDynamicPanelWidth: false },
copy: { title: 'Choose a character', createLabel: 'Create' },
theme: {
panelBackground: 'rgba(255, 255, 255, 0.92)',
titleColor: '#0f172a',
cardBackground: 'rgba(16, 24, 40, 0.82)'
}
}
});
Copy & card attributes
| Script attribute | Init option | Description |
|---|---|---|
data-ac3-hp-title | happyPath.copy.title | Panel title. |
data-ac3-hp-subtitle | happyPath.copy.subtitle | Subtitle before a saved avatar exists. |
data-ac3-hp-subtitle-saved | happyPath.copy.subtitleSaved | Subtitle when a saved avatar exists. |
data-ac3-hp-saved-label | happyPath.copy.savedLabel | Saved avatar card label. |
data-ac3-hp-create-label | happyPath.copy.createLabel | Create card label. Defaults to Create. |
data-ac3-hp-default-avatar-1-label | happyPath.copy.defaultAvatar1Label | First default avatar card label. |
data-ac3-hp-default-avatar-2-label | happyPath.copy.defaultAvatar2Label | Second default avatar card label. |
data-ac3-hp-show-saved-avatar | happyPath.cards.showSavedAvatar | Set false to hide the saved avatar card. |
data-ac3-hp-show-default-avatars | happyPath.cards.showDefaultAvatars | Set false to hide default avatar cards. |
data-ac3-hp-show-create-avatar | happyPath.cards.showCreateAvatar | Set false to hide the Create card. |
data-ac3-hp-show-library-button | happyPath.auth.showLibraryButton | Set false to hide the library / Sign Out button. Defaults to true. |
data-ac3-hp-saved-avatar-fetch-limit | happyPath.cards.savedAvatarFetchLimit | How many saved avatars the panel fetches. Defaults to 1. |
data-ac3-hp-saved-avatar-display-limit | happyPath.cards.savedAvatarDisplayLimit | How many fetched saved avatars render. Defaults to 1; cannot exceed the fetch limit. |
data-ac3-hp-max-visible-rows-before-scroll | happyPath.layout.maxVisibleRowsBeforeScroll | Grid rows visible before the grid scrolls. Defaults to 2. |
data-ac3-hp-disable-dynamic-panel-width | happyPath.layout.disableDynamicPanelWidth | Set true to rely on your own panelWidth/grid styling. |
Theme attributes
A large set of data-ac3-hp-* theme attributes map to happyPath.theme.* — including
overlay, panel, title, subtitle, close-button, grid, and card values. Common ones are listed here.
| Script attribute | Init option | Description |
|---|---|---|
data-ac3-hp-font-family | theme.fontFamily | Panel font family. |
data-ac3-hp-overlay-background | theme.overlayBackground | Modal overlay background. |
data-ac3-hp-overlay-backdrop-filter | theme.overlayBackdropFilter | Modal overlay backdrop filter. |
data-ac3-hp-panel-width | theme.panelWidth | Panel width. |
data-ac3-hp-panel-padding | theme.panelPadding | Panel padding. |
data-ac3-hp-panel-radius | theme.panelRadius | Panel border radius. |
data-ac3-hp-panel-background | theme.panelBackground | Panel background. |
data-ac3-hp-panel-border | theme.panelBorder | Panel border color/value. |
data-ac3-hp-title-color | theme.titleColor | Panel title color. |
data-ac3-hp-subtitle-color | theme.subtitleColor | Panel subtitle color. |
data-ac3-hp-card-background | theme.cardBackground | All card backgrounds. |
data-ac3-hp-card-border | theme.cardBorder | All card borders. |
data-ac3-hp-card-text-color | theme.cardTextColor | All card text colors. |
data-ac3-hp-card-hover-background | theme.cardHoverBackground | Card hover/focus backgrounds. |
data-ac3-hp-card-hover-border | theme.cardHoverBorder | Card hover/focus borders. |
data-ac3-hp-grid-template-columns | theme.gridTemplateColumns | Card grid columns. |
data-ac3-hp-grid-gap | theme.gridGap | Card grid gap. |
data-ac3-hp-create-icon-background | theme.createIconBackground | Create icon background. |
Script Attributes & Init Options
Every script attribute has an equivalent manual init option.
| Script attribute | Init option | Description |
|---|---|---|
data-ac3-mode | mode | Use sdk-happy-path for the no-login game-start picker + developer-owned anonymous storage. |
data-ac3-target | target | CSS selector for the host button slot. |
data-ac3-partner-id | partnerId | Required SDK Integration ID. |
data-ac3-user-id | userId | Stable host user ID. Recommended for production. |
data-ac3-session-id | sessionId | Optional host session ID. |
data-ac3-character-id | characterId | Optional host character ID. |
data-ac3-locale | locale | Optional locale forwarded to embedded flows. Defaults to en-US. |
data-ac3-label | label | Optional host launch button label. |
data-ac3-button-class | buttonClass | Optional class added to the host launch button. |
data-ac3-show-anonymous-button | showAnonymousButton | Set false to hide the anonymous Start button. |
data-ac3-host-sign-in-target | hostSignInTarget | CSS selector for an SDK-owned Host Sign In button slot. |
data-ac3-host-sign-in-label | hostSignInLabel | Host Sign In button label. Defaults to Host Sign In. |
data-ac3-host-sign-in-button-class | hostSignInButtonClass | Class added to the SDK-owned Host Sign In button. |
data-ac3-remember-host-sign-in | rememberHostSignIn | Set true to request a verified host identity before showing sign-in UI. |
data-ac3-auto-init | n/a | Set false for manual initialization. |
userId. The
backend uses the subject with partnerId to maintain partner active-avatar records.
Manual Initialization
Manual initialization is useful when the host needs to load the SDK dynamically or wire callbacks directly.
Set data-ac3-auto-init="false" on the script tag, then call window.ViverseMeSDK.init().
async function loadViverseMeSdk() {
const script = document.createElement('script');
script.src = 'https://viverse-me.com/sdk.js';
script.async = true;
script.dataset.ac3AutoInit = 'false';
document.head.append(script);
await new Promise((resolve, reject) => {
script.addEventListener('load', resolve, { once: true });
script.addEventListener('error', reject, { once: true });
});
return window.ViverseMeSDK;
}
const sdk = await loadViverseMeSdk();
const instance = await sdk.init({
mode: 'sdk-happy-path',
target: '#viverse-me-button-slot',
partnerId: 'partner_...',
label: 'Start',
locale: 'en-US',
onOpen() { hostScene.pause(); },
onClose() { hostScene.resume(); },
onAvatarSelected(event) { hostScene.loadAvatar(event.avatar.vrmUrl); },
onAuthCleared() { hostScene.clearSelectedAvatar(); },
onAuthorizationDenied(event) { console.warn(event.message); }
});
init() resolves to null, the SDK does not create
its iframe UI, and it dispatches viverse-me:authorization-denied.
Events
The SDK dispatches browser events on the host window and also supports equivalent callback options in manual initialization.
| Event | When it fires | Payload |
|---|---|---|
viverse-me:open | The SDK opens a modal or embedded flow. | SDK state details. |
viverse-me:close | The SDK closes the modal or embedded flow. | SDK state details. |
viverse-me:avatar-selected | A Happy Path or default avatar is selected. | { avatar } (also flattened on detail) plus animations and controlsHint. |
viverse-me:controls-changed | SDK control settings change. | { animationEnabled, controlEnabled, controllerSize } |
viverse-me:selector-state | The panel mounts, collapses, expands, or changes placement. | { mode, collapsed, placement, expanded } |
viverse-me:host-sign-in-click | The SDK-owned Host Sign In button is clicked. | { instance, button } |
viverse-me:host-identity-request | The SDK checks whether the host has a verified signed-in user. | { instance, respond }; call respond(externalUserId) or respond(''). |
viverse-me:authorization-denied | The partner ID and origin are not authorized. | partnerId, origin, code, message |
Hosts with active rendering, audio, physics, or expensive polling should pause work on open and resume on close.
window.addEventListener('viverse-me:open', () => { hostScene.pause(); });
window.addEventListener('viverse-me:close', () => { hostScene.resume(); });
Avatar Selection Payload
A selected Happy Path avatar payload can include:
{
tenantId: '[email protected]',
key: 'accounts/user-example-com/avatars/avatar.vrm',
fileName: 'avatar.vrm',
partnerId: 'partner_...',
userId: 'host-user-123',
sessionId: 'optional-host-session',
characterId: 'optional-character',
vrmUrl: 'https://api.viverse-me.com/api/ac3/download?...',
thumbnailUrl: 'https://api.viverse-me.com/api/ac3/download?...',
vrmExpires: 1770000000,
thumbnailExpires: 1770000000,
animations: {
idle: 'https://viverse-me.com/sdk/animations/idle.vrma',
walk: 'https://viverse-me.com/sdk/animations/walk.vrma',
run: 'https://viverse-me.com/sdk/animations/run.vrma',
jump_start: 'https://viverse-me.com/sdk/animations/jump_start.vrma',
jump_up: 'https://viverse-me.com/sdk/animations/jump_up.vrma',
jump_loop: 'https://viverse-me.com/sdk/animations/jump_loop.vrma',
jump_down: 'https://viverse-me.com/sdk/animations/jump_down.vrma'
},
controlsHint: { animationEnabled: true, controlEnabled: true, controllerSize: 'default' }
}
Default avatar selections are returned through the Worker sdk/default-avatar proxy, so
third-party hosts do not need direct R2 bucket CORS access for shared default assets. If a host manually
builds a default-avatar proxy URL, include both partnerId and the current host origin — this is
especially important for thumbnails rendered with <img>, because image requests may not
include an Origin header.
const url = new URL('/api/ac3/sdk/default-avatar', apiBase);
url.searchParams.set('partnerId', partnerId);
url.searchParams.set('origin', window.location.origin);
url.searchParams.set('file', 'default-avatar-1.png');
Animations & Integration Tiers
The SDK ships a complete set of optional VRMA motion clips. Pick one of two integration tiers depending on whether you want the SDK to run animations and jump physics for you, or you only want the raw assets.
| Question | Pick Tier A | Pick Tier B |
|---|---|---|
| Do you already have a game loop / physics engine? | No — SDK runs everything | Yes — SDK only loads VRM + VRMA |
| Do you need ground/wall collision, slopes, platforms, multi-floor? | No (flat ground is fine) | Yes (Tier A is flat-plane only) |
| Want default Space-jump + WASD + orbit camera + mobile joystick? | Yes | You'll build your own |
| Embedding into an existing Three.js / Unity / Unreal world? | No | Yes |
Tier A — bundled avatar controller (recommended)
Load avatar-controller.js through window.ViverseMeSDK.avatarControllerUrl and you
get walk / run / jump out of the box, including a gravity-driven jump state machine, the Space key, and an
on-screen Jump button on touch devices. The controller also creates and drives a perspective camera for you.
Tier A is optimised for a single avatar on flat ground and does not include world collision,
floor cutouts, slopes/stairs, ceilings, or NPC interaction.
Tier B — clips with your own engine
If you already have your own Three.js / Unity / Unreal pipeline, take the URLs and ignore the controller. The
SDK never modifies position.y on its own when you don't use the controller.
window.ViverseMeSDK.animations
// { idle, walk, run, jump_start, jump_up, jump_loop, jump_down } → absolute VRMA URLs
window.ViverseMeSDK.getAnimationUrl('idle');
// → absolute URL to the bundled idle VRMA
Clip roles:
idle,walk,run— locomotion loops.jump_start— one-shot crouch / take-off before leaving the ground.jump_up— one-shot rising phase right after take-off.jump_loop— looping in-air pose for variable airtime.jump_down— one-shot landing phase.
The recommended jump sequence is jump_start → jump_up → jump_loop → jump_down. The animations are hosted with permissive CORS so they can be fetched directly from any host origin.
Avatar Controller
The SDK ships an optional ES module that wires VRMA playback (idle / walk / run plus optional gravity jump), pointer / touch orbit, wheel and pinch zoom, WASD or arrow-key movement, a touch-device virtual joystick, the Space key, and a Jump button onto a host-provided Three.js scene.
| Mode | When to use | What the controller does |
|---|---|---|
physics: 'builtin' (default) | Tier A simple integrations. | Owns position.y, applies gravity, plays the jump state machine, binds Space, mounts a Jump button on touch, and creates + drives a perspective camera. |
physics: 'none' | Tier B integrations with their own physics. | Never modifies position.y, never binds Space, never mounts a Jump button, never touches the camera, never installs orbit/zoom listeners. Only handles VRM + mixer + idle/walk/run. |
Peer dependencies & importmap
The controller never bundles Three.js or the VRM libraries. The host must provide them through an importmap so a single shared Three.js instance is used.
<script type="importmap">
{
"imports": {
"three": "https://unpkg.com/[email protected]/build/three.module.js",
"three/addons/": "https://unpkg.com/[email protected]/examples/jsm/",
"@pixiv/three-vrm": "https://esm.sh/@pixiv/[email protected]?bundle&external=three",
"@pixiv/three-vrm-animation": "https://esm.sh/@pixiv/[email protected]?bundle&external=three"
}
}
</script>
const { createAvatarController } = await import(window.ViverseMeSDK.avatarControllerUrl);
const controller = createAvatarController({
scene, // THREE.Scene (host owns)
domElement: canvas, // input target; touch-action set to 'none'
animations: window.ViverseMeSDK.animations
// physics: 'builtin', enableJump: true, bindSpaceKey: true — all default on
});
controller.setAvatar(vrm); // call again on avatar change
function loop() {
controller.update(clock.getDelta());
renderer.render(scene, controller.camera); // SDK created the camera for you
requestAnimationFrame(loop);
}
Constructor options
| Option | Default | Notes |
|---|---|---|
scene | required | THREE.Scene provided by the host. |
domElement | required | Canvas / element used for pointer, wheel, touch, and gesture listeners. |
camera | undefined | Optional PerspectiveCamera. Tier A: omit to let the SDK create one; pass one to drive a host-owned camera. Tier B: SDK never touches it. |
physics | 'builtin' | 'builtin' owns position.y + jump; 'none' leaves both untouched. |
enableJump | true | Only meaningful when physics: 'builtin'. Disables Space binding, Jump button, and jump machine. |
bindSpaceKey | true | Bind Space to jump. Set false if Space is used elsewhere. |
gravity | 18 m/s² | Game-feel gravity. 9.81 matches Earth. |
jumpVelocity | 6.5 m/s | Initial upward velocity. Height ≈ v² / (2g). |
variableJumpCutFactor | 0.45 | Rising velocity multiplier when the jump input releases early. |
groundY | 0 | Y of the single ground plane. Tier A is flat-plane only. |
enableAnimation | true | Mixer master switch. Disabling also forces input off. |
enableControl | true | Master switch for orbit / zoom / movement / Space / Jump. |
input.joystick | 'auto' | 'auto' mounts on touch devices; true always; false never. |
moveSpeed | 2.4 m/s | Walking speed. Run uses moveSpeed * runSpeedMultiplier. |
runSpeedMultiplier | 2.0 | Multiplier when double-tap to run is active. |
cameraOffset | 1.35 | Vertical look-at offset above the anchor (head height). |
initialCameraDistance | 4.5 | Starting orbit distance, clamped to min/max. |
minCameraDistance | 1.75 | Closest zoom. |
maxCameraDistance | 6.5 | Farthest zoom. |
Public methods
update(delta)— advance mixer, VRM, camera, movement, and jump physics; call once per frame.setAvatar(vrm, animations?)— attach or replace the current VRM. Passnullto detach.setAnimations({ idle, walk, run, jump_* })— replace VRMA URLs and reload clips.setEnableAnimation(boolean)— toggle clip playback.setEnableControl(boolean)— toggle orbit / zoom / movement / Space / Jump input.setEnableJump(boolean)— toggle the jump machine, Space binding, and Jump button. No-op whenphysics: 'none'.jump()/cutJump()— programmatic jump trigger and variable-height release.setCameraDistance(distance)— clamp the current orbit distance.setJoystickVisible(boolean)— show/hide the virtual joystick and the Jump button together.setControllerSize('default' | 'small')— switch on-screen control footprint.dispose()— detach listeners, restoretouch-action, stop the mixer, remove joystick / Jump button and the anchor.
Read-only getters
camera— the camera the SDK drives in Tier A; the host-provided camera ornullin Tier B.anchor,visualRoot—THREE.Groupparents around the VRM. Move / rotateanchorfrom your own physics in Tier B.cameraState—{ yaw, pitch, distance, minDistance, maxDistance }.enableAnimation,enableControl,enableJump,physicsMode,isGrounded,jumpPhase.
Jump Behavior
The jump state machine is gravity-driven: pressing Space (or the on-screen Jump button) sets vertical
velocity to jumpVelocity, then each frame applies vy -= gravity * dt until the
avatar returns to groundY. Releasing Space while still rising multiplies vy by
variableJumpCutFactor for short hops.
- Standing jump (no movement input on Space press) — plays the full
jump_start→jump_up→jump_loop→jump_downsequence. Horizontal movement stays disabled duringjump_startandjump_down. - Running jump (movement input present) — skips
jump_startandjump_downso the legs keep cycling. Mid-air control is at 60% of ground speed (steering only).
If the player provides movement input while a standing-jump jump_down crouch is still playing, the
controller cuts the landing clip short and blends to walk / run immediately. On coarse-pointer devices the
controller mounts a virtual joystick and — when physics: 'builtin' and enableJump: true
— a Jump button. Both are hidden by default; toggle them with setJoystickVisible(true).
Avatar URL Lifecycle
Treat vrmUrl and thumbnailUrl as preview transport, not durable storage.
- Use signed URLs immediately to render or preview the avatar.
- Check expiry fields before reusing a cached URL.
- Store stable references in the partner backend.
- Resolve fresh signed URLs server-side when restoring a user's selected avatar later.
A browser-only demo may keep a short-lived local preview cache, but a production partner should not rely on browser storage as the source of truth.
Server-side Active Avatar Fetch
Partner backends can resolve the current selected avatar for a host subject through the protected partner API.
GET https://api.viverse-me.com/api/ac3/partner/active-avatar?partnerId=partner_...&userId=host-user-123
Authorization: Bearer <PARTNER_API_TOKEN>
The subject can be userId, sessionId, or characterId. The response returns the stable avatar reference plus fresh signed asset URLs when an active avatar exists.
async function getViverseMeActiveAvatar({ partnerId, userId }) {
const url = new URL('https://api.viverse-me.com/api/ac3/partner/active-avatar');
url.searchParams.set('partnerId', partnerId);
url.searchParams.set('userId', userId);
const response = await fetch(url, {
headers: { Authorization: `Bearer ${process.env.PARTNER_API_TOKEN}` }
});
if (!response.ok) {
throw new Error(`Failed to fetch active avatar (${response.status})`);
}
return response.json();
}
PARTNER_API_TOKEN in browser code.Security Notes
- Do not put
PARTNER_API_TOKENor admin tokens in browser code. - Do not call account, draft, upload, claim, or download-url APIs directly from the third-party page.
- Use SDK events for browser preview and partner backend APIs for durable restoration.
- Keep production embeds on
https://viverse-me.com/sdk.jsunless a preview origin has been explicitly configured. - Allow VIVERSE Me in the host site's frame policy or CSP.
- Safari, private browsing, and enterprise privacy settings can still block third-party cookies or storage used by embedded flows.
Troubleshooting
init() returns null
Check that partnerId is present and the current origin is saved as an Allowed Website.
The Happy Path panel does not show a saved avatar
The browser may not have a stored Happy Path anonymous ID for this SDK Integration ID, or site data may have been cleared. The panel should still show public default avatars unless the host hides them with happyPath.cards.showDefaultAvatars.
A cached avatar URL stops loading
Signed URLs expire. Use the partner backend active-avatar API to resolve a fresh URL, or wait for the SDK to emit a fresh viverse-me:avatar-selected event.
A default thumbnail URL returns SDK_ORIGIN_REQUIRED
Add origin=window.location.origin to the sdk/default-avatar proxy URL. This is usually needed for thumbnails loaded through <img>.
Demo Host
The runnable third-party host example loads https://viverse-me.com/sdk.js in Happy Path mode,
listens for viverse-me:avatar-selected, loads the returned VRM into its own Three.js scene,
restores developer-owned anonymous avatars, and shows mobile joystick / Jump controls only after an avatar
is loaded and the panel is closed.
Run it locally with:
cd demo
python3 -m http.server 5500
Then open http://localhost:5500/.
No sections match your search. Try a different keyword.