Skip to main content

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.

The play page at play.arcadible.com holds your game in a sandboxed frame from its own site ID on arcadible.io play.arcadible.com/star-drift/ site-id.arcadible.io

  • 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_dir is 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 an arcadible folder at its root. See deploy_dir.

Static files​

Your build is served as static files, with no server-side code and no configuration.

LimitDetail
SizeSee Limits: 64 MB compressed, 256 MB uncompressed, and 10,000 files.
Entry pointindex.html at the root of deploy_dir.
Directory URLsA path ending in / serves that directory's index.html. The same path without the trailing slash isn't redirected, and fails.
No rewritesEvery 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 pathsShip.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 headersYou 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 codePHP, 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.

FeatureStatusWhat to do instead
JavaScript, WebAssembly, workersAllowed
localStorage, IndexedDB, Cache StorageAllowedRead Storage and saves first.
Pointer lockAllowedRequest it from a click or key press.
Screen orientation lockAllowedPrefer the orientation field.
window.open(), target="_blank" linksBlockedShow the URL, or a QR code, in the game.
Navigating the top windowBlockedKeep the game on a single page.
alert(), confirm(), prompt(), print()BlockedDraw your own dialogs. Blocked calls return immediately without showing anything.
beforeunload promptsBlockedSave continuously instead of asking players to stay.
<form> submissionBlockedHandle buttons and the Enter key with your own event listeners. The submit event doesn't fire.
File downloadsBlocked

Permissions​

The frame is granted these permissions, and no others.

GrantedEnables
autoplayAudio and video playback, subject to the browser's own autoplay rules. See Input, audio, and lifecycle.
fullscreenelement.requestFullscreen(), from a click or key press.
gamepadThe Gamepad API.
accelerometerDevice motion, for touch-motion and motion.
gyroscopeDevice orientation and rotation rate.
screen-wake-locknavigator.wakeLock.request("screen"), to keep the display on during gamepad play or cutscenes.
cross-origin-isolatedSharedArrayBuffer 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.clipboard reads 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 with crossorigin on the <script>, <img>, <audio>, <video>, or <link> element, or with fetch() in its default cors mode.

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.

Version 1.0 has its save; version 1.1 starts with empty storage and can't reach the old save v1.0 A version v1.1 A version save Its save Empty No save carried over

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:// and ws:// requests as mixed content.
  • CORS for fetch() and XHR. Your API must send Access-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 visibilitychange and blur; 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:

BehaviorBrowserArcadible desktop app
Window sizeThe browser tabA window shaped for your orientation, starting at 640 × 480 (landscape) or 480 × 640 (portrait), which players resize
FullscreenrequestFullscreen() fills the screenrequestFullscreen() fills the window. Players make the window fullscreen from the app's menu.
BackgroundYour game is throttled or paused in hidden tabsYour game keeps running unless you pause it
Audio autoplayNeeds a gestureAllowed without a gesture
StorageThe browser's storageThe app's own storage, separate from any browser
User agentThe browser'sA 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.io origin, 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.