❓ QWAY Studio HelpQWAY · WRIGHT · RIDLEY · RAMS

Embed in an external Three.js application

Choose the smallest layer

NeedPackage/APIDependencies deliberately absent
Static scene@quay/projector SceneProjectorReact, API, OpenAI, compiler, physics
Resolved interactive scene@quay/runtime SceneRuntime / createEmbeddedPreviewAuthoring environment and provider
Packed/exploded world@quay/runtime/worldpack WorldPlayerAuthoring compiler at playback
Optional simulation@quay/physics-rapier injected factoryNo Rapier handles in persisted documents

Download the complete external-project kit: package.json, strict tsconfig, Vite config, four HTML hosts, TypeScript sources and public fixture data. It is installed and built outside the repository by node docs/manual/integration.mjs; browser checks cover static projection, resolved preview, WorldPack loading and reset with no authoring requests. Verification summary.

Unzip into a new directory. Install the twelve locally built @quay tarballs using npm 10.9.3 (no registry publication is assumed), then run npm run build and npm run dev. Open /projector.html for static projection, /preview.html?scene=/scene-v2.json&cinematic=/cinematic.json&experience=/experience.json for directed preview, or /worldpack.html and select public/workshop.worldpack. The included readme.txt gives the exact local-tarball workflow.

Static projection

Create a Three.js scene, camera and renderer in your host. Construct SceneProjector with these borrowed objects and an optional resolveAssetUri callback. loadDocument accepts projection data; a V2 document’s static representation is its projection field. SceneRuntime.loadResolved accepts the full V2 document. Do not pass an authoring project envelope to a projector.

TypeScript
import { SceneProjector } from "@quay/projector";
// scene, camera and renderer are existing Three.js objects.
const projector = new SceneProjector({ scene, camera, renderer });
await projector.loadDocument(resolvedScene.projection);
// In your existing frame loop:
projector.update(deltaSeconds);
renderer.render(scene, camera);
// On resize: projector.resize(width, height, devicePixelRatio);
// On teardown: projector.dispose(); // borrowed renderer remains yours

This fragment explains ownership; use the complete projector example for imports, host setup and lifecycle. Supply correct URIs/CORS and serve assets over HTTP. A missing resource is not a compiler problem.

Interactive scene

The complete preview example loads resolved scene/cinematic/experience documents and binds the shared player. createEmbeddedPreview(host, scene, options) exposes ready, reset, dispatch, setCamera and dispose. Without borrowed resources it owns its renderer/RAF; borrowed mode uses the editor/host loop. Never run two update loops over one player.

WorldPack host

The complete WorldPack host supports a .worldpack file or HTTP manifest, progress/status, mode selection, audio gesture, reset and disposal. Await firstFrame for initial visual readiness and ready for interactive readiness. Inspect player.state for errors and residency.

Choose static, experience or physics mode explicitly; they are not simultaneous transform owners. Supply createPhysics only when needed. Supply conversationTransport only when your host intentionally offers NPC service. Keep provider credentials off the browser.

Package and deploy

Build dependencies before packing local workspaces. The repository portability script installs tarballs into a fresh external directory and verifies public imports and runtime boundaries:

Shell
npm run build
node scripts/worldpack-portability.mjs docs/site/examples/workshop.worldpack

For your deployment, serve the built host plus .worldpack or exploded resources with correct MIME types and CORS. Do not deploy the Studio API just to play a static/compiled world. Inspect package licenses and run verify before shipping. See exact public declarations.