Find the fix for common engine problems, then check a build before shipping.
Most blank screens are a missing native build, an unawaited warm-up or a view with no size. Log onError first.
| Symptom | Fix |
|---|---|
| Blank view | Give it a size; check onError, camera direction, entity scale and paused. |
| isEngineAvailable is false / core not linked | Expo Go or an old build: make a new development build. |
| "await warmUpEngine" error | Await it before new World or procedural meshes; useWorld does it. |
| WASM fails with a MIME or HTML error | Serve the real .wasm file as application/wasm from the right path. |
| No WebGPU or audio worklet | Use HTTPS or localhost; WebGL2 is the fallback. |
| Paused after navigation | Pass the real focus state to paused and clear held input. |
Guard and log
React and React Native · shared engine API
import { GameView, isEngineAvailable, type World } from "@nayan-ui/engine";export function SafeView({ world }: { world: World }) {if (!isEngineAvailable) return null; // show "needs a development build"return <GameView source={world} onError={(error) => console.error("Engine:", error.message)} style={{ flex: 1 }} />;}
After dispose, cleanup calls and queries are no-ops, but spawn, update, burst and effects throw. Check world.isDisposed after every await before spawning.
| Symptom | Fix |
|---|---|
| "World: used after dispose()" | Check world.isDisposed after awaits; never reuse a disposed world. |
| world.transforms moves don't show yet | They apply on the step; flush() first to read them back. |
| createTransformBatch moves never apply | Extra batches aren't flushed for you: call flush(). |
| Game runs at double speed | Remove extra world.update calls and rAF or setInterval loops. |
| toScreen / screenToWorld return null | Needs a drawn frame; null also means behind camera or edge-on. |
| Blank Android screenshot | Use captureScreen(..., { handleGLSurfaceViewOnAndroid: true }). |
Spawn safely after an await
React and React Native · shared engine API
import { loadModel, type World } from "@nayan-ui/engine";export async function addBoss(world: World, url: string) {const boss = await loadModel(url, { fit: 3 });if (world.isDisposed) return null; // the screen closed while loadingreturn world.spawn({ mesh: boss, position: [0, 0, -8] });}
When a clip or asset fails, print what the model actually contains before changing code.
| Symptom | Fix |
|---|---|
| Bundled asset fails in an Android release | Install expo-asset, add glb/gltf to assetExts, test a release build. |
| Web asset is a number | Use URL strings on the web instead of require(...). |
| Clip doesn't play | Check model.animations names; export clips into a self-contained GLB. |
| Model at the wrong origin | Review center, fit and the model's own coordinates. |
| pick returns null | Wait for a drawn frame, use view coordinates, keep pickable true. |
| No sound on the web | Output starts on the first click, touch or key; await bank.ready. |
| No vibration | Check haptics.enabled and haptics.supported; simulators have none. |
| Out of entities | Raise capacity; text letters and particles take slots. |
| Out of mesh ids | A mesh is built per spawn or frame: build once (getMeshIdLimit). |
| Loader freezes on first launch | Slice the job with afterNextFrame and cache it with ProceduralCache. |
Name the clips when one fails
React and React Native · shared engine API
import type { Entity, LoadedModel, World } from "@nayan-ui/engine";export function play(world: World, actor: Entity, model: LoadedModel, clip: string) {if (!world.playClip(actor, clip)) {console.warn(`No clip "${clip}". Available: ${model.animations.map((a) => a.name).join(", ")}`);}}
Test production builds on real devices: a rebuilt engine library doesn't prove the installed app binary was rebuilt.
| Check | What to verify |
|---|---|
| Web | Production build over HTTP: asset URLs, .wasm MIME, WebGPU and WebGL2. |
| Input and layout | Resizing, tap, pinch and overlay touches. |
| Lifecycle | Pause, resume, hidden tabs and backgrounding the app. |
| Feedback | First-interaction audio, sound and haptics settings. |
| Native | Fresh iOS and Android builds after engine or config changes. |
| Runs | Each run reports once, including cancel, and cleans up. |
Serve a production web build
Terminal / configuration
Vite shown; serve any framework's built output the same way, then test on real devices.
bun run build # vite buildbunx vite preview # serves dist/ as production will