Embed in an external Three.js application
Choose the smallest layer
| Need | Package/API | Dependencies deliberately absent |
|---|---|---|
| Static scene | @quay/projector SceneProjector | React, API, OpenAI, compiler, physics |
| Resolved interactive scene | @quay/runtime SceneRuntime / createEmbeddedPreview | Authoring environment and provider |
| Packed/exploded world | @quay/runtime/worldpack WorldPlayer | Authoring compiler at playback |
| Optional simulation | @quay/physics-rapier injected factory | No 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.
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 yoursThis 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:
npm run build
node scripts/worldpack-portability.mjs docs/site/examples/workshop.worldpackFor 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.