Preview Game
Preview Game (new tab) builds your scene into a real, self-contained game and opens it in a fresh browser tab - a throwaway run you never have to save.
Preview is the fastest way to answer "does the shipped game work?" without picking a filename. It runs the identical build pipeline as Export game.html - same used-asset collection, same compiled behaviours, same binary container - but instead of writing a file to disk it hands the HTML straight to a new tab. Nothing about the run is a special "editor" mode: it is the exported game, one step early.
Why preview instead of Play?
Editor Play ticks the same runtime, but it does so inside the editor's canvas with the editor's origin, camera helpers, and asset store already warm. Preview verifies the parts that only exist in a packaged game:
- The scene survives used-asset collection - nothing your game needs was dropped as "unused".
- Behaviours compiled to shipping IIFEs register and run (not the editor's live TypeScript path).
- Geometry survives quantization and the binary container round-trip.
- The game boots from a cold start - its own splash, its own mesh/texture upload, its own physics prewarm.
If Preview looks right, game.html will look right.
How it is served
Preview goes to some length to run the game over a real http origin rather than a file:// page, because the two behave differently in the browser.
The Vite dev server carries a small middleware (awakenPreview) mounted at /__awaken_preview. The editor POSTs the built HTML to it; the middleware stashes it in memory under a random id (keeping only the last few previews) and returns a URL like /__awaken_preview/ab12cd34. The editor opens that URL in a new tab, and the middleware serves it back as text/html - from the editor's own http://localhost:5180 origin.
Blob fallback
The middleware only exists in the dev server. If you are running a built editor (no dev middleware present), the POST fails and Preview falls back to a blob: URL created with URL.createObjectURL. The game still opens and plays; it just runs from a blob origin instead of a served http one.
Why http matters (vs file://)
A double-clicked file:// page is a unique opaque origin. In that context the browser:
- Refuses to execute
<script type="module">scripts ("Unsafe attempt to load URL…"), which is why the player template ships as a classic IIFE script. - Blocks
fetch()of sibling files, so ascene.fgsidecar cannot be loaded. - Logs assorted security noise to the console.
Serving Preview over http sidesteps all of that: the console is clean, the origin is real, and the run matches what a hosted game sees. (The exported game.html handles file:// differently - it inlines the scene as base64 so it never needs a fetch. See Export game.html.)
Using it
- Open File ▾ → Preview Game (new tab) in the Toolbar.
- A progress dialog shows Building preview → Compressing → Opening….
- A new tab opens with your game. It boots from its own splash screen, loads meshes/textures, and starts playing.
Press F3 or ` in the previewed game to toggle the stats overlay (FPS, GPU timings, draw calls) - the same numbers the editor shows, so you can profile the packaged game directly.
📸 Screenshot - save as img/shipping-preview-tab.png
A previewed game running in a new browser tab, with the stats overlay (F3) open in the top-left corner.
Debug overlay
Preview (and Export) run through the same options dialog, which has a Debug checkbox. Tick it and the build injects a small performance overlay into the game - a card in the top-left that measures where each frame goes. Leave it unticked and not one byte of the overlay is in the output: the timing hooks are globalThis.__AWAKEN_PERF__?.(…) calls that no-op when the collector was never installed, so a normal build carries zero overhead. This is distinct from the built-in F3 stats overlay (GPU timings / draw calls) - Debug adds CPU-side, per-system and per-script timing.
The card's ⚙ button toggles sections, and your choice is remembered (localStorage):
- FPS + frame - average FPS, average frame time, and the worst single frame in the last window (spike hunting).
- Frame phases - how many ms per frame go to physics, systems, scripts, anim, and render.
- Per-script - each behaviour's
onUpdatetime, heaviest first, with calls-per-frame. This is what tells you a specific script is your bottleneck. - Memory - JS heap in use (Chromium only).
- Frame graph - a rolling sparkline of recent frame times.
The per-script numbers survive minification and obfuscation (method names are preserved), so you can profile a shipping-representative build. Seeing one script dominate is the cue to give it a per-script obfuscation override or to optimise it.
📸 Screenshot - save as img/shipping-debug-overlay.png
The Debug overlay card in a previewed game - FPS, frame-phase breakdown, per-script timings - with its ⚙ settings open.
Requirements & gotchas
- The player template must be built. Preview needs
/player-template.html. If it is missing you will see "Preview needs the player template - runpnpm build:player-template" in the Console. Build it once and Preview works thereafter. See the stale-template gotcha. - Pop-up blocker. Preview uses
window.open; if your browser blocks it, allow pop-ups for the editor origin. - Nothing is saved. Preview is ephemeral - the in-memory copy is discarded when you close the tab or run another preview. To keep a game, use Export game.html.
See also
- Export game.html - save the same build as a permanent file
- The Player Runtime - what actually boots in the previewed tab
- Building & Shipping - all three outputs at a glance
- Troubleshooting & FAQ - preview will not open, blank tab, missing template