Runtime Limitations for Games on Arcadible
Games run in the Arcadible sandbox. WebGL, WebGPU, WebAssembly, threads, audio, gamepads, fullscreen, and pointer lock are available. Popups, native dialogs, downloads, top-level navigation, the camera, the microphone, and location are not.
The restrictions isolate games from each other and from Arcadible, and keep behavior consistent wherever Arcadible runs games.
How your game is loaded
Every play link, such as https://play.arcadible.com/star-drift/, is a page that embeds your build in a full-size, sandboxed frame.
- Each build has its own origin. Every deploy is published to a new
https://<site-id>.arcadible.io/, where<site-id>is unique to that build. Two versions of your game are two different origins, which matters for saves and for your servers. - Your game is at the root.
deploy_diris served at/, so relative and root-relative paths both work. Prefer relative paths, which also work when the build is served from a subdirectory during local testing. - The play link's query string and hash aren't passed on. The frame always loads
https://<site-id>.arcadible.io/, whatever follows the play link. - The desktop app uses the same play page, so everything here applies there too, with a few differences.
- Play pages can't be embedded in other sites.
/arcadible/is Arcadible's. Every build's origin serves the Arcadible SDK at/arcadible/sdk/v1.js, with its source map, and nothing else in/arcadible/, so your build can't have anarcadiblefolder at its root. Seedeploy_dir.
Static files
Your build is served as static files, with no server-side code and no configuration.
| Limit | Detail |
|---|---|
| Size | See Limits: 64 MB compressed, 256 MB uncompressed, and 10,000 files. |
| Entry point | index.html at the root of deploy_dir. |
| Directory URLs | A path ending in / serves that directory's index.html. The same path without the trailing slash isn't redirected, and fails. |
| No rewrites | Every URL maps to exactly one file. Client-side routes like /level/3 return an error, so use the hash (#/level/3) or in-memory state instead. |
| Case-sensitive paths | Ship.png and ship.png are different files. A build that works on macOS or Windows can fail on Arcadible because of a case mismatch. |
| No custom headers | You can't set headers per file. Builds that rely on the server adding Content-Encoding to precompressed .br or .gz files won't load; see below. |
| No server-side code | PHP, API routes, and server functions don't run. Host your backend separately; see Networking. |
Compressed engine builds
Some engines, including Unity and some Godot templates, can emit files that are already Brotli- or gzip-compressed and expect the server to label them with Content-Encoding. Arcadible serves each file as it is, so either:
- disable compression in the engine's build settings, or
- enable a decompression fallback, such as Unity's Decompression Fallback option, which decompresses the files in JavaScript.
The sandbox
The frame's sandbox allows scripts, same-origin storage, pointer lock, and orientation lock. No other sandbox flags are set.
| Feature | Status | What to do instead |
|---|---|---|
| JavaScript, WebAssembly, workers | Allowed | |
localStorage, IndexedDB, Cache Storage | Allowed | Read Storage and saves first. |
| Pointer lock | Allowed | Request it from a click or key press. |
| Screen orientation lock | Allowed | Prefer the orientation field. |
window.open(), target="_blank" links | Blocked | Show the URL, or a QR code, in the game. |
| Navigating the top window | Blocked | Keep the game on a single page. |
alert(), confirm(), prompt(), print() | Blocked | Draw your own dialogs. Blocked calls return immediately without showing anything. |
beforeunload prompts | Blocked | Save continuously instead of asking players to stay. |
<form> submission | Blocked | Handle buttons and the Enter key with your own event listeners. The submit event doesn't fire. |
| File downloads | Blocked |
Permissions
The frame is granted these permissions, and no others.
| Granted | Enables |
|---|---|
autoplay | Audio and video playback, subject to the browser's own autoplay rules. See Input, audio, and lifecycle. |
fullscreen | element.requestFullscreen(), from a click or key press. |
gamepad | The Gamepad API. |
accelerometer | Device motion, for touch-motion and motion. |
gyroscope | Device orientation and rotation rate. |
screen-wake-lock | navigator.wakeLock.request("screen"), to keep the display on during gamepad play or cutscenes. |
cross-origin-isolated | SharedArrayBuffer and threads. See Cross-origin isolation. |
Not available:
- Camera and microphone. No
getUserMedia(), so no voice chat or photo capture. - Location. No Geolocation API.
- Clipboard. No
navigator.clipboardreads or writes. - Sharing. No Web Share API. Players share your game from Arcadible.
- Payments. No Payment Request API.
- Device APIs. No WebXR, Web MIDI, WebUSB, WebHID, Web Serial, or Web Bluetooth.
On iOS and iPadOS, device motion also needs the player's consent: call DeviceMotionEvent.requestPermission() from a tap before you read the sensors.
Cross-origin isolation
Play pages are cross-origin isolated, and delegate isolation to your game. SharedArrayBuffer is available, so multithreaded engine exports, such as Godot with threads or Emscripten builds with pthreads, run.
As a result, every resource your game loads from another origin must explicitly allow embedding. Files in your own build are unaffected. For anything else, either:
- the server sends
Cross-Origin-Resource-Policy: cross-origin, or - the request uses CORS: the server sends
Access-Control-Allow-Origin, and you load the resource withcrossoriginon the<script>,<img>,<audio>,<video>, or<link>element, or withfetch()in its defaultcorsmode.
A resource that does neither fails to load, and the console reports a COEP error. The simplest fix is to include the file in your build. Public CDNs such as jsDelivr and cdnjs send CORS headers, so for them the crossorigin attribute is sufficient.
Storage and saves
Each version of your game starts with empty browser storage. Each build has its own origin, so whatever a version keeps in localStorage, IndexedDB, or Cache Storage stays with that version, and your next release can't read it. To keep progress across versions, use saves, which the play page keeps for your game.
Browser storage, saves included, is also separated:
- Per browser and per app. Progress in a browser isn't visible in the desktop app or another browser.
- Per embedding site. Browsers partition a frame's storage by the page it's in, so opening
https://<site-id>.arcadible.io/directly shows different data. Saves are the play page's, so they aren't partitioned. - Evictable. Browsers can clear it when space runs low or the player clears site data. Safari clears it after seven days of use without a visit to Arcadible (WebKit).
Builds don't support cookies. Every build's origin is under arcadible.io, which browsers treat as one site, so a cookie your game sets can be read or replaced by other games. Most browsers also block or partition a frame's cookies. Keep progress in saves, and use tokens for your servers.
Networking
Your game can talk to your own servers. The play page's content security policy doesn't apply to your game.
- HTTPS and WSS only. Your game is served over HTTPS, so browsers block
http://andws://requests as mixed content. - CORS for
fetch()and XHR. Your API must sendAccess-Control-Allow-Origin, as for any cross-origin request. - Isolation applies. Images, audio, and scripts from your servers need a CORS or CORP header, as described above.
- Authenticate with tokens, not cookies, which builds don't support.
The origin changes every build. Accept any origin ending in .arcadible.io, or send Access-Control-Allow-Origin: *. A fixed allowlist works for the build you test, and breaks on the next deploy.
WebSockets, WebTransport, and WebRTC data channels all work for multiplayer.
Input, audio, and lifecycle
- Gestures. Audio, fullscreen, pointer lock, and the iOS motion permission all need a click, tap, or key press, and must be requested in its handler. See Make the first touch part of the game.
- Focus. The play page focuses your frame when it loads, so key presses reach it immediately.
- Escape always exits fullscreen and pointer lock. You receive the key press, but can't stop it.
- Gamepads appear only after the player presses a button. Listen for
gamepadconnected. - Closing. Nothing warns players before they close your game, and there's no shutdown event to wait on. See Pause, save, and resume.
- Background. Browsers throttle hidden tabs, but the desktop app keeps your game running at full speed. Pause on
visibilitychangeandblur; see Pause, save, and resume.
Browsers and the desktop app
A game that works in a browser works in the desktop app, with these differences:
| Behavior | Browser | Arcadible desktop app |
|---|---|---|
| Window size | The browser tab | A window shaped for your orientation, starting at 640 × 480 (landscape) or 480 × 640 (portrait), which players resize |
| Fullscreen | requestFullscreen() fills the screen | requestFullscreen() fills the window. Players make the window fullscreen from the app's menu. |
| Background | Your game is throttled or paused in hidden tabs | Your game keeps running unless you pause it |
| Audio autoplay | Needs a gesture | Allowed without a gesture |
| Storage | The browser's storage | The app's own storage, separate from any browser |
| User agent | The browser's | A plain Chromium user agent |
Your game can't tell a browser from the desktop app, and shouldn't need to. Detect capabilities instead, such as matchMedia("(pointer: coarse)") for touch.
Checklist
- Loads every file from its own build, or from servers that send CORS or CORP headers
- Uses matching case in every file path
- Doesn't need precompressed files served with
Content-Encoding - Doesn't open links, popups, or native dialogs
- Keeps progress in saves, and copes with empty browser storage in every new version
- Doesn't use cookies
- Accepts requests from any
*.arcadible.ioorigin, if it talks to your servers
To test under the sandbox on your machine, run arc serve. To test on Arcadible itself, deploy a prerelease to staging and play it with arc play -r staging.