# The Internet's Game Console Arcadible is a platform for publishing games. Bring your own agent, build with the tools you already use, and turn your game into a release, a store page, and a playable link with a single command. Play your game anywhere Arcadible is installed, or in any browser. Arcadible isn't an engine or a framework. It's closer to `npm publish` for games: if your game builds to a folder with an `index.html`, it ships. Build with Three.js, Phaser, PixiJS, Babylon.js, the web export of your engine, or your own code. The optional [Arcadible SDK](https://developer.arcadible.com/build/saves) adds saves that carry over from one version to the next. ```sh npm install -g arcadible arc login arc init star-drift && cd star-drift arc deploy arc play ``` That's a starter game, published and playable. ## Choose your path - **[Start a new game](https://developer.arcadible.com/getting-started):** from an empty directory to a public game, step by step. - **[Bring an existing game](https://developer.arcadible.com/existing-game):** add a manifest to a game that already builds, and tell Arcadible how to build it. - **[Release from GitHub](https://developer.arcadible.com/publish/deploying-from-github):** let GitHub Actions build each release from a tag. - **[Work with an AI agent](https://developer.arcadible.com/build/ai-agents):** give your coding agent these docs and an `AGENTS.md`. ## How it fits together The [Arcadible CLI](https://developer.arcadible.com/references/arcadible-cli) checks your project, builds it, and deploys it. Arcadible publishes each version as a build, and the store shows its store page, made from your README, your art in `.arcadible/`, and your `arcadible.toml`. [Arcadible Studio](https://developer.arcadible.com/studio) shows your games and their releases. [How Arcadible Works](https://developer.arcadible.com/concepts) defines each part. When something goes wrong, start with [Troubleshooting](https://developer.arcadible.com/troubleshooting), or look up a message in [Errors](https://developer.arcadible.com/references/errors). ## For AI agents These docs are also published as text: [llms.txt](https://developer.arcadible.com/llms.txt), [llms-full.txt](https://developer.arcadible.com/llms-full.txt), and a `.md` for every page. See [Working with AI Agents](https://developer.arcadible.com/build/ai-agents). # Getting Started with Arcadible Game Development This guide takes a new game from an empty directory to a public game on Arcadible, using a starter template. You deploy to staging first, make the store page your own, then release it to players. To publish a game you've already built, see [Bring an Existing Game](https://developer.arcadible.com/existing-game). ## Before you begin You need: - **An Arcadible account.** Join at [arcadible.com](https://arcadible.com). - **Node.js 24 or later**, to run the CLI. Your game doesn't need to use Node.js. ## Install the CLI The CLI is published to npm as `arcadible`, and its command is `arc`: ```sh npm install -g arcadible arc --version ``` ## Log in ```sh arc login ``` [`arc login`](https://developer.arcadible.com/references/arcadible-cli/arc-login) prints a link and a code. Open the link, log in, and enter the code, or scan the QR code with a phone that's already logged in. No password is typed into the terminal. Check which account you're using with: ```sh arc whoami ``` See [`arc login`](https://developer.arcadible.com/references/arcadible-cli/arc-login) for headless machines and where the session is stored. ## Create a project ```sh arc init star-drift cd star-drift ``` [`arc init`](https://developer.arcadible.com/references/arcadible-cli/arc-init) asks for a template: a blank canvas, the default, or a [starter](https://developer.arcadible.com/build/game-engines#starter-templates) for Three.js, Phaser, PixiJS, or Babylon.js. Name one with `-t` to skip the question: ```sh arc init star-drift -t arcadible/starters/phaser ``` Any public GitHub repository, or a folder in one, works as a template too: `arc init star-drift -t user/repo`. ## Project structure The blank starter looks like this: ```text star-drift/ ├── arcadible.toml # the manifest ├── README.md # the store page ├── AGENTS.md # instructions for coding agents ├── .arcadible/ # the store art │ ├── icon.png, cover.png, banner.png, poster.png, splash.png │ └── gallery/ # screenshots ├── .arcadible-public/ # the game itself │ ├── index.html │ └── main.js └── .github/workflows/ └── arcadible-release.yml # used by arc deploy git ``` - **`arcadible.toml`** holds what can't be derived from anywhere else. [`arc init`](https://developer.arcadible.com/references/arcadible-cli/arc-init) has already given it a unique `id`; commit it and never change it. - **`README.md`** is your store page: its headings and paragraphs become the title, tagline, bio, and description. - **`.arcadible/`** holds the art. Each file has a fixed name, so there's nothing to reference. The starter's placeholders are drawn at the recommended sizes, with each piece's [edge margin](https://developer.arcadible.com/publish/store-page#edge-margin) marked, and `gallery/` has three screenshots, each with a `_phone` one. - **`.arcadible-public/`** is what players load. The blank starter has no build step, so this is the game's source. The engine starters build into it with Vite, from `index.html` and `main.js` at the root. To play the game while you work on it, run [`arc serve`](https://developer.arcadible.com/references/arcadible-cli/arc-serve) and open the link it prints: a local play page that runs the game as Arcadible does, and reloads it as you edit. See [Testing Your Game](https://developer.arcadible.com/build/testing#locally). ## Deploy to staging Your first deploy goes to staging: playable by link, but not shown to players. Until the game's first production release, its store page says only "Not released yet". The starters begin at a prerelease version, which is what sends a build to staging. In the blank starter, it's in `arcadible.toml`: ```toml title="arcadible.toml" [game] version = "0.1.0-beta.1" ``` The engine starters have a `package.json`, so it's `"version"` there instead. Deploy, and play it: ```sh arc deploy arc play ``` [`arc deploy`](https://developer.arcadible.com/references/arcadible-cli/arc-deploy) validates the project, runs your build, packages the game with its store art, and starts a release. It waits until Arcadible has published the build, then prints where to play it, or says why the release failed. See [Releases](https://developer.arcadible.com/publish/releases). [`arc play`](https://developer.arcadible.com/references/arcadible-cli/arc-play) opens the game in the Arcadible desktop app, or a browser if the app isn't installed. Until the game has a production release, it opens staging. Each version publishes once, so bump the prerelease number, such as `0.1.0-beta.2`, before each deploy. See [Channels](https://developer.arcadible.com/concepts#channels). ## Make it yours Give the game its own name, handle, and art. None of it is required to deploy, so do it in any order, deploying to staging as you go. To see the store page each deploy would publish, open the game in [Arcadible Studio](https://developer.arcadible.com/studio#store), then its **Store** tab, under **Staging**. 1. **Name it.** Edit the headings and the bio in `README.md`: ```md title="README.md" # Star Drift ## Drift. Dodge. Survive. A one-thumb arcade game about surfing a dying star. --- Pilot a freighter through **collapsing orbits**. --- ``` The first heading is the title, the `##` heading after it the tagline, the paragraph after that the bio, and everything between the first and last `---` the description. See [Arcadible Markdown](https://developer.arcadible.com/references/arcadible-markdown). 2. **Claim a handle.** The handle is the name in your game's links, such as `play.arcadible.com/star-drift/`. It's unique among games, and a game keeps it for 90 days until its first production release ([Limits › Handles](https://developer.arcadible.com/references/limits#handles)): ```toml title="arcadible.toml" [game] handle = "star-drift" genres = ["arcade", "action"] ``` Pick up to three [genres](https://developer.arcadible.com/references/arcadible-manifest#game.genres) while you're there, and check the [inputs](https://developer.arcadible.com/references/arcadible-manifest#game.inputs) and [orientation](https://developer.arcadible.com/references/arcadible-manifest#game.orientation) your game supports. Check that [`audience`](https://developer.arcadible.com/references/arcadible-manifest#game.audience) and [`ai`](https://developer.arcadible.com/references/arcadible-manifest#game.ai) describe your game: the starter sets them to `everyone` and `unused`. 3. **Replace the art.** Swap the images in `.arcadible/` and `.arcadible/gallery/` for your own, keeping the file names and aspect ratios, and keeping what matters inside the placeholders' [edge margin](https://developer.arcadible.com/publish/store-page#edge-margin). See [Your Store Page](https://developer.arcadible.com/publish/store-page). Then check the project: ```sh arc validate # the same checks arc deploy runs arc inspect # the manifest as Arcadible sees it ``` [`arc validate`](https://developer.arcadible.com/references/arcadible-cli/arc-validate) lists every problem with a link to the rule it breaks. [`arc inspect`](https://developer.arcadible.com/references/arcadible-cli/arc-inspect) shows where each value ended up, such as `arc inspect game.title`. ## Release it When the game is ready, release it to production with a version without a suffix: ```toml title="arcadible.toml" [game] version = "0.1.0" ``` ```sh arc deploy arc open # open its store page ``` A production release becomes what players get, and builds the store page from your README, art, and manifest. Your game is **unlisted** to start with: anyone with the link can play it, but it doesn't appear in the store or in search. Share the link with testers at this stage: ```text https://play.arcadible.com/star-drift/ ``` To ship an update, change the game, bump the version, and deploy again. Try it on staging first with a prerelease, such as `0.2.0-beta.1`, then release `0.2.0`. ## Go public When you're ready for players to find it, set the visibility and deploy a release version: ```toml title="arcadible.toml" [game] visibility = "public" ``` Your game now appears in the Arcadible store and in search. [Going Public](https://developer.arcadible.com/publish/going-public) has a checklist for before you do. ## Release from GitHub Instead of deploying from your machine, GitHub Actions can build each release from a tag: [`arc deploy git`](https://developer.arcadible.com/references/arcadible-cli/arc-deploy#git). The starters include the workflow it needs. See [Deploying from GitHub](https://developer.arcadible.com/publish/deploying-from-github) to set it up. ## Next steps - [How Arcadible Works](https://developer.arcadible.com/concepts): every term these docs use. - [Testing Your Game](https://developer.arcadible.com/build/testing): locally, on staging, and on devices. - [Best Practices](https://developer.arcadible.com/build/best-practices): loading, performance, input, and lifecycle for games on the web. - [Runtime Limitations](https://developer.arcadible.com/build/runtime-limitations): what the sandbox allows and blocks. - [Arcadible Studio](https://developer.arcadible.com/studio): your games, their builds, and releases. # Bring an Existing Game to Arcadible This guide publishes a game you've already built. Arcadible needs two things from it: a folder with an `index.html` that runs the game, and an `arcadible.toml`. Your code, engine, and build stay as they are. For a new game, start with [Getting Started](https://developer.arcadible.com/getting-started) instead. ## Add a manifest Install the CLI and log in, as in [Getting Started](https://developer.arcadible.com/getting-started#install-the-cli), then run [`arc init`](https://developer.arcadible.com/references/arcadible-cli/arc-init) in the game's directory: ```sh cd my-game arc init ``` In a directory that exists, `arc init` doesn't copy a template. It creates `arcadible.toml` if there isn't one, and gives it a game [`id`](https://developer.arcadible.com/references/arcadible-manifest#game.id). It also adds a [`version`](https://developer.arcadible.com/references/arcadible-manifest#game.version), unless your `package.json` has one, which Arcadible uses instead. Commit `arcadible.toml`, and never change its `id`. ## Tell Arcadible how to build it Tell Arcadible the folder your build writes, and, if `package.json` doesn't build it, the command that does. A Vite project needs only the folder, since its `build` script runs as `npm run build`, and relative paths: ```toml title="arcadible.toml" [workflow] deploy_dir = "dist" # contains index.html ``` ```js title="vite.config.js" export default { base: "./" }; // relative paths ``` - **[`build`](https://developer.arcadible.com/references/arcadible-manifest#workflow.build)** runs before every deploy, and before [`arc serve`](https://developer.arcadible.com/references/arcadible-cli/arc-serve). Without it, a project with a `build` script in `package.json` builds with `npm run build`. Set it for anything else, such as `build = "./build.sh"`, leave it out if your game has no build step, or set `build = ""` to skip `package.json`'s. - **[`deploy_dir`](https://developer.arcadible.com/references/arcadible-manifest#workflow.deploy_dir)** is the folder with your `index.html`, which players load. It defaults to `.arcadible-public`. For an engine's web export, such as Godot's, see [Game Engines](https://developer.arcadible.com/build/game-engines). Then check the result: ```sh arc build # runs the build, and says if deploy_dir is missing arc validate # checks everything arc deploy will arc serve # plays the build locally, as Arcadible runs it ``` ## Describe it Your store page comes from your project's files, so most of it can come from what you already have: - **`README.md`:** its `#` heading becomes the title, and a `##` heading right after it becomes the tagline, so a README that starts `# My Game` then `## Installation` gets "Installation" as its tagline, and fails [`arc validate`](https://developer.arcadible.com/references/arcadible-cli/arc-validate) until its description is between two `---` rules. Either shape the README for the store (title, `##` tagline, one-line bio, then the description between two `---` rules; anything after the last rule isn't shown), or leave it as it is and put the store page in an `arcadible.md`, which Arcadible reads instead. See [Arcadible Markdown](https://developer.arcadible.com/references/arcadible-markdown). - **`.arcadible/`:** add the icon, cover, banner, poster, and splash, and screenshots in `.arcadible/gallery/`. See [Your Store Page](https://developer.arcadible.com/publish/store-page). - **`arcadible.toml`:** add a [`handle`](https://developer.arcadible.com/references/arcadible-manifest#game.handle), [`genres`](https://developer.arcadible.com/references/arcadible-manifest#game.genres), and the [`inputs`](https://developer.arcadible.com/references/arcadible-manifest#game.inputs) and [`orientation`](https://developer.arcadible.com/references/arcadible-manifest#game.orientation) your game supports. Only `id` and `version` are required to deploy, so you can fill these in while you test. ## Deploy to staging Deploy a prerelease version, such as `1.0.0-beta.1`, so the first build goes to [staging](https://developer.arcadible.com/concepts#channels) instead of to players. It's `version` in `package.json` when you have one, and in `arcadible.toml` otherwise. A `version` that [`arc init`](https://developer.arcadible.com/references/arcadible-cli/arc-init) added is already one, `0.1.0-beta.1`. A release version goes straight to production: ```sh arc deploy arc play ``` > **`0.0.0` is a release version.** Tools such as create-vite write it, so deploying the project as it is sends its first build straight to players. Change `version` to a prerelease, such as `0.1.0-beta.1`, before you deploy. ## Fix what the first deploy finds A game that runs on your machine can fail in the [Arcadible sandbox](https://developer.arcadible.com/build/runtime-limitations): case-sensitive file names, client-side routes, assets from other servers, precompressed builds, blocked dialogs, and saves kept in browser storage are the usual causes. Run [`arc serve`](https://developer.arcadible.com/references/arcadible-cli/arc-serve) to find most of them on your machine, play the staging build, and work through [My game works locally, but not on Arcadible](https://developer.arcadible.com/troubleshooting#my-game-works-locally-but-not-on-arcadible), which has each one and its fix. Fix them, bump the prerelease, and deploy again. ## Release it Set a release version, such as `1.0.0`, and deploy. The game is unlisted until you set `visibility = "public"`; see [Going Public](https://developer.arcadible.com/publish/going-public). ## Next steps - [Best Practices](https://developer.arcadible.com/build/best-practices): what makes a game feel at home on Arcadible. - [Deploying from GitHub](https://developer.arcadible.com/publish/deploying-from-github): release from GitHub Actions instead of your machine. - [How Arcadible Works](https://developer.arcadible.com/concepts): every term these docs use. # How Arcadible Works This page defines the parts of Arcadible and the terms the rest of the docs use. Each section is the one place a term is explained; other pages link here. ## The parts - **The [Arcadible CLI](https://developer.arcadible.com/references/arcadible-cli)** (`arc`) creates projects, checks them, and deploys them, from your machine or from CI. - **The store** is where players find games: [arcadible.com](https://arcadible.com) and the Arcadible desktop app show the same store pages. - **The play page** at `play.arcadible.com` runs a game. The store's Play button opens it, and so does a play link. - **[Arcadible Studio](https://developer.arcadible.com/studio)** is where you see your games, their builds and releases, and connect GitHub. - **The [Arcadible SDK](https://developer.arcadible.com/references/arcadible-sdk)** is optional: it gives your game [saves](https://developer.arcadible.com/build/saves), which the play page keeps across versions. ## Your game A game is a project with an [`arcadible.toml`](https://developer.arcadible.com/references/arcadible-manifest). Everything Arcadible knows about it comes from the project's files: - **`README.md`** gives the title, tagline, bio, and description, in [Arcadible Markdown](https://developer.arcadible.com/references/arcadible-markdown). - **`.arcadible/`** holds the store art: the icon, cover, banner, poster, splash, and screenshots. - **`arcadible.toml`** holds the rest: the `id`, `version`, `handle`, genres, visibility, and how to build the game. Together they resolve into the **manifest**, which [`arc inspect`](https://developer.arcadible.com/references/arcadible-cli/arc-inspect) prints and [`arc validate`](https://developer.arcadible.com/references/arcadible-cli/arc-validate) checks. There are no forms to fill in on Arcadible: the store page is versioned with the game, reviewed in pull requests, and released with the build. See [Sources](https://developer.arcadible.com/references/arcadible-manifest#sources) for how the three files merge. ### `id` and handle - **The [`id`](https://developer.arcadible.com/references/arcadible-manifest#game.id)** is a UUID that [`arc init`](https://developer.arcadible.com/references/arcadible-cli/arc-init) writes, and it never changes. The account that first deploys an `id` owns the game. - **The [`handle`](https://developer.arcadible.com/references/arcadible-manifest#game.handle)** is the game's name in links, such as `star-drift`. It's optional, unique among games, and can change. Without one, links use the `id`. ## Versions, releases, and builds - **A version** is the game's [`version`](https://developer.arcadible.com/references/arcadible-manifest#game.version), in semver: `1.2.0`, or a prerelease such as `1.2.0-beta.1`. - **A release** is one attempt to publish a version. [`arc deploy`](https://developer.arcadible.com/references/arcadible-cli/arc-deploy) starts one, from your machine or through GitHub Actions. It ends published or failed. See [Releases](https://developer.arcadible.com/publish/releases). - **A build** is a published version: your game's files, served exactly as they are from their own origin, `.arcadible.io`. Every build stays playable at its version link for as long as the game exists, and a build can't be deleted on its own. **Each version publishes once.** To ship a change, bump the version and deploy again. ## Channels The version decides where a build goes: ![A prerelease version goes to the staging channel for you to test; a release version goes to production for players](https://developer.arcadible.com/assets/images/arcadible-channels.46836492.svg) | Version | Channel | What it changes | | --- | --- | --- | | A release, such as `1.2.0` | `production` | What players get, and the store page, which is rebuilt from the release | | A prerelease, such as `1.2.0-beta.1` | `staging` | Nothing players see. It's playable at its staging and version links | Use staging to try a build on Arcadible itself before releasing it. A prerelease never changes the store page, so it can't make a game public. To see what it would publish, open the game's **Store** tab in [Arcadible Studio](https://developer.arcadible.com/studio#store) and choose **Staging**. ## Visibility [`visibility`](https://developer.arcadible.com/references/arcadible-manifest#game.visibility) decides where a game is listed. Either way, anyone with a link can open the store page and play. | Visibility | Listed in the store, search, the developer's page, and "More by" | | --- | --- | | `unlisted` (default) | No | | `public` | Yes | Like the rest of the store page, visibility changes with a production release. ## Links Every link a game has, for Star Drift: | Link | Opens | | --- | --- | | `https://arcadible.com/play/star-drift` | The store page | | `https://play.arcadible.com/star-drift/` | The play page, with the latest `production` build | | `https://play.arcadible.com/star-drift:staging/` | The latest `staging` build | | `https://play.arcadible.com/star-drift:1.2.0/` | One version's build, in either channel | | `https://arcadible.com/@` | The developer's page, with their public games | - **The `id` works wherever the handle does,** such as `play.arcadible.com//`. - **Play links are lowercase and end in a slash.** Other forms redirect to them. - [`arc open`](https://developer.arcadible.com/references/arcadible-cli/arc-open) opens the store page, and [`arc play`](https://developer.arcadible.com/references/arcadible-cli/arc-play) the play page. ## The sandbox On the play page, your game runs in the **Arcadible sandbox**: its build's own origin, embedded with restricted permissions and cross-origin isolation. A game that works on a local server can behave differently there; [`arc serve`](https://developer.arcadible.com/references/arcadible-cli/arc-serve) runs it the same way on your machine. See [Runtime Limitations](https://developer.arcadible.com/build/runtime-limitations). ## Glossary | Term | Meaning | | --- | --- | | Arcadible SDK | The optional library a game includes for saves. See [Saves](https://developer.arcadible.com/build/saves). | | Arcadible sandbox | Where a game runs: its build's own origin, embedded in the play page. See [The sandbox](https://developer.arcadible.com/concepts#sandbox). | | Arcadible Studio | Where developers see and manage their games. [studio.arcadible.com](https://studio.arcadible.com) | | browser storage | `localStorage`, IndexedDB, and Cache Storage together. It's per build: see [Storage and saves](https://developer.arcadible.com/build/runtime-limitations#storage-and-saves). | | build | A published version, served from its own origin. See [Releases](https://developer.arcadible.com/concepts#releases). | | channel | `production` or `staging`, chosen by the version. See [Channels](https://developer.arcadible.com/concepts#channels). | | deploy | What [`arc deploy`](https://developer.arcadible.com/references/arcadible-cli/arc-deploy) does: check, build, pack, and start a release. | | desktop app | The Arcadible desktop app, which shows the store and opens games in their own windows. | | direct, `git` | The two deploy methods: an upload from your machine, or a release tag that GitHub Actions builds. See [Deploying from GitHub](https://developer.arcadible.com/publish/deploying-from-github). | | handle | A game's name in links. See [`id` and handle](https://developer.arcadible.com/concepts#id-and-handle). | | `id` | A game's permanent UUID. See [`id` and handle](https://developer.arcadible.com/concepts#id-and-handle). | | manifest | The resolved description of a game, from the README, `.arcadible/`, and `arcadible.toml`. See [Your game](https://developer.arcadible.com/concepts#game). | | play page | `play.arcadible.com//`, which runs a build. See [Links](https://developer.arcadible.com/concepts#links). | | prerelease | A version with a suffix, such as `1.2.0-beta.1`, which goes to `staging`. | | publish | What Arcadible does to a release that passes its checks: the build goes live in its channel. | | release | One attempt to publish a version. See [Releases](https://developer.arcadible.com/publish/releases). | | save | A named document the play page keeps for a game, across its versions. See [Saves](https://developer.arcadible.com/build/saves). | | store page | A game's page in the store, built from its manifest. See [Your Store Page](https://developer.arcadible.com/publish/store-page). | | visibility | Whether a game is listed. See [Visibility](https://developer.arcadible.com/concepts#visibility). | # Best Practices for Games on Arcadible Players judge games on Arcadible the way they judge games on any other platform: how fast they load, how they feel, and how polished they are. This guide covers what's specific to shipping a game on the web platform, in the order a player meets your game. It assumes you already know how to make a game. Arcadible requires only what [`arc validate`](https://developer.arcadible.com/references/arcadible-cli/arc-validate) checks. Everything here is advice: take what fits your game. ## Load fast There's no install step: a new player downloads your game when they first open it. Set your targets for a phone on a mobile connection. ![The menu and first level download before play; later levels stream in while the player plays](https://developer.arcadible.com/assets/images/arcadible-bp-loading.26e4257f.svg) - **Paint within 1 second.** Put a small loading screen directly in `index.html`, with inline CSS, so it renders before any script loads. Use the same art as your [splash](https://developer.arcadible.com/references/arcadible-manifest#game.splash) so the transition from the store matches. - **Be playable within about 5 seconds.** Load only the first screen, the first level, and their assets, and stream the rest during play, from your build or your own CDN. - **Compress what you ship.** Choose compressed formats for textures, models, audio, and fonts, and minify your code. Most engines make these choices for you. - **Show progress.** Base it on bytes loaded where you can, such as for a bundled file loaded with `fetch`. Browsers don't report progress for ES module imports, so for those, animate the loading screen so it doesn't look stuck. - **Handle failure.** Time out and retry requests, and show an error with a retry option if loading fails. Test with your browser's network panel throttled to a slow mobile connection and the cache disabled, which matches a new player's first load. ## Make the first touch part of the game Browsers block audio, fullscreen, and pointer lock until the player interacts with the page. A common workaround is a "Tap to play" screen that exists only to collect that interaction. Instead, design the opening so the player's first natural input provides it: - **Open on something interactive,** such as an attract-mode title, a controllable character, or a menu. The player's first action, whether a jump, a steer, or selecting "New Game", is also the interaction the browser requires. - **Start audio on that input** by calling `audioContext.resume()` in your first `pointerdown` or `keydown` handler, and fade the music in. - **Design the opening to work without sound.** Nothing plays before the first input, so use motion to prompt it. ## Get to play quickly - **Give control early.** Make logos skippable, and keep story sequences and settings out of the way of the first moment of play. - **Teach through play.** A new player should understand the goal within a few seconds without reading instructions. Introduce one mechanic at a time, and show gestures with animation. - **Let players play before signing up.** Ask for an account once they have progress to keep. ## Fit every screen Players use phones in both orientations, tablets, ultrawide monitors, and resizable desktop windows. ![A phone held sideways: the game fills the screen, and the hearts and score stay inside the safe area, clear of the notch](https://developer.arcadible.com/assets/images/arcadible-bp-safe-area.246c6df8.svg) - **Handle resizing** with a [`ResizeObserver`](https://developer.mozilla.org/docs/Web/API/ResizeObserver) on the canvas, rather than reading the size once at startup. - **Choose a scaling strategy,** whether that's letterboxing, extending the view, or reflowing the UI, and test the extremes: tall phones, square windows, 21:9 monitors. - **Keep UI inside the safe area,** away from notches and home indicators, with [`env(safe-area-inset-*)`](https://developer.mozilla.org/docs/Web/CSS/env). Let the game world extend behind them. - **Declare your [`orientation`](https://developer.arcadible.com/references/arcadible-manifest#game.orientation),** so the Arcadible desktop app can shape its window for you. In a browser, show a "rotate your device" screen if your game only works one way. A canvas can't use `env()`. Copy the insets into custom properties, and read them in JavaScript when the canvas resizes: ```css :root { --safe-top: env(safe-area-inset-top, 0px); --safe-bottom: env(safe-area-inset-bottom, 0px); } ``` ```js const style = getComputedStyle(document.documentElement); const safeTop = parseFloat(style.getPropertyValue("--safe-top")); const safeBottom = parseFloat(style.getPropertyValue("--safe-bottom")); ``` ## Support every input you declare Declare your [`inputs`](https://developer.arcadible.com/references/arcadible-manifest#game.inputs) accurately, and support each one fully, menus included. Switch prompts to whichever device the player used last. - **Keyboard:** use `event.code`, which names the physical key, so WASD stays in place on AZERTY and other layouts. Let players rebind keys. - **Mouse:** for mouse-look, use [pointer lock](https://developer.mozilla.org/docs/Web/API/Pointer_Lock_API) with `requestPointerLock({ unadjustedMovement: true })` for raw input. Escape always releases the lock, so pause the game when `pointerlockchange` reports it's released. - **Gamepad:** the [Gamepad API](https://developer.mozilla.org/docs/Web/API/Gamepad_API) has no button events, so poll `navigator.getGamepads()` every frame. With the `"standard"` mapping, button 0 is the bottom face button. Show Xbox, PlayStation, or Switch glyphs based on `gamepad.id`. - **Touch:** use [Pointer Events](https://developer.mozilla.org/docs/Web/API/Pointer_events) for multi-touch. Make targets at least 44 CSS pixels, keep them clear of screen edges, which are reserved for system gestures, and hide touch controls on devices without touch. ## Turn off browser defaults Browsers scroll the page, select text on a double tap, open a context menu on right-click, and zoom on a pinch, which a game rarely wants. The starters turn these off already, in their [`index.html`](https://github.com/arcadible/starters/blob/main/blank/.arcadible-public/index.html) and [`main.js`](https://github.com/arcadible/starters/blob/main/blank/.arcadible-public/main.js). If you started elsewhere, here's how. In CSS, stop selection, scrolling, bouncing, and browser gestures: ```css html, body { margin: 0; height: 100%; overflow: hidden; overscroll-behavior: none; user-select: none; -webkit-user-select: none; -webkit-touch-callout: none; -webkit-tap-highlight-color: transparent; } canvas { touch-action: none; } ``` In the viewport tag, turn off zoom, and extend the page behind the notch and other display cutouts: ```html ``` In JavaScript, suppress the context menu, and keep the keys your game uses from scrolling the page: ```js const GAME_KEYS = new Set([ "Space", "ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight", ]); addEventListener("contextmenu", (e) => e.preventDefault()); addEventListener("keydown", (e) => { if (GAME_KEYS.has(e.code)) e.preventDefault(); }); ``` - **Set a background color** that matches your loading screen, so players never see a white flash. - **Style every element,** so no default browser buttons, links, or form controls show. - **Hide the cursor** when it isn't in use, such as during gamepad play. ## Run smoothly Aim for 60 frames per second, with a floor of 30 on the lowest-end device you support. Consistent frame times matter more than a high average. - **Drive the loop with `requestAnimationFrame`,** and never assume 60 Hz. Displays run at 90, 120, 144 Hz and more, so step your simulation at a fixed rate and interpolate when drawing. - **Avoid per-frame allocation.** Garbage collection pauses cause dropped frames. Pool objects, reuse arrays, and avoid creating closures in the loop. - **Warm up during loading.** Compile shaders, upload textures, and decode audio before play, not the first time they're needed. - **Move heavy work off the main thread** with [Web Workers](https://developer.mozilla.org/docs/Web/API/Web_Workers_API), and rendering too with [`OffscreenCanvas`](https://developer.mozilla.org/docs/Web/API/OffscreenCanvas). Arcadible games are [cross-origin isolated](https://developer.arcadible.com/build/runtime-limitations#cross-origin-isolation), so threads and `SharedArrayBuffer` are available. - **Budget memory.** Browsers on low-end phones can terminate a page using a few hundred megabytes. A 2048 × 2048 RGBA texture uses 16 MB of GPU memory, regardless of its file size. - **Mind the GPU.** Most engines pick WebGPU or WebGL for you. In WebGL, avoid calls that stall the GPU, such as `getError()` and synchronous `readPixels()`. See MDN's [WebGL best practices](https://developer.mozilla.org/docs/Web/API/WebGL_API/WebGL_best_practices). ### Scale resolution ![The same circle at pixel ratios 1, 2, and 3, with 2 marked as the cap](https://developer.arcadible.com/assets/images/arcadible-bp-pixel-ratio.12be49b0.svg) Phones have a device pixel ratio of up to 3, which is nine times the pixels of a ratio of 1. Above 2, the visual gain rarely justifies the fill-rate cost. - **Cap the pixel ratio** at 2, or lower for heavy 3D. - **Scale dynamically.** Lower the render resolution when frame time rises, and raise it when there's headroom. Render the UI at full resolution. - **For pixel art, scale by whole numbers instead.** Render to a small buffer, such as 320 × 180, and draw it at the largest whole-number multiple that fits the canvas at its full device resolution, with `imageSmoothingEnabled` off. Leave the pixel ratio uncapped: a small buffer costs little fill rate, and fractional scaling makes pixels uneven. ### Test on real hardware Development machines are much faster than typical player devices. Test on the phones you have, including an older or cheaper one if you can. ## Pause, save, and resume A page can be hidden or closed at any time, without warning. - **Pause on `visibilitychange` and `blur`,** audio included, and resume to a pause menu. In the desktop app, your game [keeps running](https://developer.arcadible.com/build/runtime-limitations#browsers-and-the-desktop-app) in the background unless you pause it. - **Save continuously,** at checkpoints and when the page is hidden. `visibilitychange` and `pagehide` are the last events you can count on. - **Keep progress in [saves](https://developer.arcadible.com/build/saves),** which carry over to your next version, where browser storage doesn't. ## Other recommendations - **Accessibility:** reduce screen shake and flashing when [`prefers-reduced-motion`](https://developer.mozilla.org/docs/Web/CSS/@media/prefers-reduced-motion) is set, don't convey information by color alone, and offer subtitles and control remapping. - **Language:** pick a default from `navigator.languages`, let players change it, and format numbers and dates with [`Intl`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Intl). - **Network:** let the game start without a server, retry with backoff, and show players a message they can act on rather than a raw error ([Networking](https://developer.arcadible.com/build/runtime-limitations#networking)). ## Next steps - [Runtime Limitations](https://developer.arcadible.com/build/runtime-limitations): what the platform allows, and its checklist. - [Testing Your Game](https://developer.arcadible.com/build/testing): staging, the desktop app, and real devices. - [Your Store Page](https://developer.arcadible.com/publish/store-page): the art and text players judge before they play. - [Going Public](https://developer.arcadible.com/publish/going-public): the checklist before players can find your game. # 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](https://developer.arcadible.com/assets/images/arcadible-rt-frames.15df6e32.svg) - **Each build has its own origin.** Every deploy is published to a new `https://.arcadible.io/`, where `` is unique to that build. Two versions of your game are two different origins, which matters for [saves](https://developer.arcadible.com/build/runtime-limitations#storage-and-saves) and for [your servers](https://developer.arcadible.com/build/runtime-limitations#networking). - **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://.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](https://developer.arcadible.com/build/runtime-limitations#browsers-and-the-desktop-app). - **Play pages can't be embedded** in other sites. - **`/arcadible/` is Arcadible's.** Every build's origin serves the [Arcadible SDK](https://developer.arcadible.com/references/arcadible-sdk#install) 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`](https://developer.arcadible.com/references/arcadible-manifest#workflow.deploy_dir). ## Static files Your build is served as static files, with no server-side code and no configuration. | Limit | Detail | | --- | --- | | Size | See [Limits](https://developer.arcadible.com/references/limits#package): 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](https://developer.arcadible.com/build/runtime-limitations#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](https://developer.arcadible.com/build/runtime-limitations#storage-and-saves) first. | | Pointer lock | Allowed | Request it from a click or key press. | | Screen orientation lock | Allowed | Prefer the [`orientation`](https://developer.arcadible.com/references/arcadible-manifest#game.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. | | `
` 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](https://developer.mozilla.org/docs/Web/HTTP/Permissions_Policy), and no others. | Granted | Enables | | --- | --- | | `autoplay` | Audio and video playback, subject to the browser's own autoplay rules. See [Input, audio, and lifecycle](https://developer.arcadible.com/build/runtime-limitations#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`](https://developer.arcadible.com/references/arcadible-manifest#game.inputs). | | `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](https://developer.arcadible.com/build/runtime-limitations#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](https://web.dev/articles/coop-coep), 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 `` | | An engine's web export, such as Unity or Godot | Not supported: the Arcadible SDK doesn't currently have a plugin for engines | How to add each is in [Install](https://developer.arcadible.com/references/arcadible-sdk#install). [`arc serve`](https://developer.arcadible.com/references/arcadible-cli/arc-serve) runs your game locally with either, as Arcadible does; under another server, see [Locally](https://developer.arcadible.com/build/saves#locally). ## Save and load A save is a name holding one string. Serialize it yourself: ```js // at startup: once the saves have loaded, the player's progress, or a new game. await arcadible.ready; const saved = await arcadible.saves.read("progress"); const progress = saved ? JSON.parse(saved) : { level: 1 }; // at a checkpoint: await arcadible.saves.write("progress", JSON.stringify(progress)); ``` - **Start with `arcadible.ready`, then load with `read`.** `ready` settles once the saves have loaded; `read` would wait for it anyway, and also waits for the saves when they're late. - **Keep what must stay consistent in one save.** Each save is written whole, at once, and saves never mix, so a level and the coins earned in it can't be torn apart. Use a save for progress, one for settings, and one for each slot, not one for each value. - **Save at checkpoints, and when the page is hidden,** on `visibilitychange` and `pagehide`. A write resolves once the save is kept on the device, and `readSync` shows it at once, in the same tick, even before then. When a player closes the tab, the last write may not finish, so don't wait for the end to save. See [Pause, save, and resume](https://developer.arcadible.com/build/best-practices#pause-save-and-resume). - **Moving from `localStorage`,** gather its keys into one object, and write it as one save. > **A failed `read` isn't a missing save.** When the saves can't be reached, `read` fails with `timeout` or `unavailable`. Don't start a new game and save over the player's progress: say so, and try again. ## Read during play `arcadible.ready` settles once, when the play page has answered and your game's saves have loaded, or after 10 seconds if they haven't. It never fails. From then on, the sync calls, `readSync`, `listSync`, and `infoSync`, answer at once from memory, so a read in the middle of a frame doesn't wait, and can't fail with `busy` or `timeout`: ```js // during play, after the startup above: const settings = JSON.parse(arcadible.saves.readSync("settings") ?? "{}"); ``` - **Call anything before it.** Calls made earlier wait for it. Only the sync calls throw before it, with `not_ready`. - **If the saves are late,** taking over 10 seconds to load, `ready` settles anyway, and the sync calls throw `unavailable` until they arrive, rather than answer as if there were no save. `read` waits for them. - **`arcadible.build` says what's running:** its `version`, from `arcadible.toml` or `package.json`, and its `channel`. It's `null` off Arcadible. ## Handle other versions' saves Every version of your game shares its saves, so each version must read what the others wrote. The play page stamps each save with the version that wrote it, which `infoSync` returns: ```js const info = arcadible.saves.infoSync("progress"); // isNewer is your own semver comparison. const fromNewer = info?.version && isNewer(info.version, arcadible.build?.version); ``` **Migrate older saves.** Only your game knows its format, so read a save from an older version and update it. > **Never overwrite a save from a newer version.** Every version stays playable at its link, so it happens after you release an older version again, or when a player opens an older version's link. Play on without writing, or ask the player to update. ## Channels `production` and `staging` keep separate saves, so a prerelease can't touch players' progress. A version link uses its build's channel. `build.channel` is a string, and other values may be added. Don't assume it's always one of the two. ## Two tabs A player can open your game in two tabs or windows at once. Each sees the other's saves as soon as they're kept, and the last write wins. Your game isn't told when the other tab saves. - **Read before you write.** Build each save from `readSync` at the moment you save, not only from what you loaded at startup, or one tab overwrites the other's progress. ## When a call fails A call that fails rejects with an `ArcadibleError`. Act on its `code`, not its message: - **Worth retrying:** `busy`, `timeout`, and `unavailable`. A write that timed out may still have been kept. - **Not worth retrying:** `invalid`, `too_large`, and `too_many`, which mean the save breaks a [limit](https://developer.arcadible.com/references/limits#saves), and `full`, which means the device has no room left. Tell the player. - **A code you don't know:** treat it as `unavailable`. New codes may be added. Every code is in the [reference](https://developer.arcadible.com/references/arcadible-sdk#codes). ## Where saves are kept The play page keeps saves in its own browser storage, on the player's device. Arcadible doesn't currently keep them in the player's account, so they follow the rules of browser storage: - **Per browser and per app.** A save in one browser isn't in another, or in the Arcadible desktop app. - **Cleared with the site's data.** Clearing `play.arcadible.com`'s data clears every game's saves. - **Evictable.** Browsers can clear them when space runs low. Safari clears them after seven days of use without a visit to Arcadible ([WebKit](https://webkit.org/blog/10218/full-third-party-cookie-blocking-and-more/)). - **Private windows** keep them until the window closes. Saves don't need the network: a game keeps saving offline for as long as it's open. ### Who can read them back [`saves.where`](https://developer.arcadible.com/references/arcadible-sdk/saves#saves.where) says, once `ready` has settled: - **`"game"`:** every version of your game can read them. The usual case, on Arcadible and under `arc serve`. - **`"memory"`:** the browser won't let the play page keep them, such as when it blocks site data, so they last only until the page closes. Tell the player their progress won't be kept. - **`"build"`:** only this build can read them. Off Arcadible, it's the package standing alone. On Arcadible, it means the build was opened directly at its `arcadible.io` address, or the play page didn't answer within about a second, so the game kept its saves on its own origin, where the next version can't see them. It's rare, and a reload fixes it; the SDK says so in the console. ```js await arcadible.ready; if (arcadible.saves.where === "memory") { showNotice("Progress won't be kept after you close the game."); } ``` More values may be added. Treat a value you don't know as `"game"`. ## Locally [`arc serve`](https://developer.arcadible.com/references/arcadible-cli/arc-serve) runs your game in a local play page, so saves work as on Arcadible, from the package or the hosted script. `build` is `{ version, channel: "local" }`, and the local page keeps saves apart from Arcadible's, carrying them over from one build to the next. See [`arc serve`'s SDK section](https://developer.arcadible.com/references/arcadible-cli/arc-serve#sdk). With [`--url`](https://developer.arcadible.com/references/arcadible-cli/arc-serve#url), the page your dev server serves gets the same saves through the package; the dev server doesn't serve the hosted script. Under another server, such as your engine's dev server: - **The package** stands alone, with the same rules and limits. There, `build` is `null`, saves aren't stamped (`info().version` is `null`), and every project on the same origin, such as `localhost:5173`, shares one set of saves. - **The hosted script** isn't found, and `arcadible` is undefined. To run the game there too, guard each use with `if (window.arcadible)`. ## Checklist - [ ] Adds the SDK the way its kind of game does, and runs locally under [`arc serve`](https://developer.arcadible.com/references/arcadible-cli/arc-serve) - [ ] Starts with `arcadible.ready`, loads with `read`, and doesn't start over when the saves can't be reached - [ ] Uses the sync calls only after `arcadible.ready` - [ ] Tells the player when `saves.where` is `"memory"` - [ ] Keeps what must stay consistent in one save - [ ] Saves at checkpoints and when hidden, without waiting for the player to leave - [ ] Migrates older versions' saves, and never overwrites a newer version's - [ ] Reads a save before writing it - [ ] Treats a code it doesn't know as `unavailable` - [ ] Keeps its saves across an update on staging: deploy a prerelease, save, deploy the next, and load ## Next steps - [`arcadible.saves`](https://developer.arcadible.com/references/arcadible-sdk/saves): every call, what it returns, and when it fails. - [Arcadible SDK](https://developer.arcadible.com/references/arcadible-sdk): installing it, a full example, `ready` and `build`, and every code. - [Limits](https://developer.arcadible.com/references/limits#saves): how much a game can save. # Testing Your Game on Arcadible Test in two places: locally while you build, with [`arc serve`](https://developer.arcadible.com/references/arcadible-cli/arc-serve), and on Arcadible's staging channel before you release, the only way to run it on Arcadible before players get it. ## Locally [`arc serve`](https://developer.arcadible.com/references/arcadible-cli/arc-serve) runs your game on your machine the way Arcadible runs it. It builds, then serves `deploy_dir` framed in a local play page, with the sandbox and the [Arcadible SDK](https://developer.arcadible.com/references/arcadible-sdk), and reloads the game when the build changes. For the starters: | Starter | While you edit | To check the build players get | | --- | --- | --- | | Blank | `arc serve` | The same: there's no build | | Three.js, Phaser, PixiJS, Babylon.js | `npm run dev`, with `arc serve --url http://localhost:5173` beside it | `arc serve` | Run `npm install` first in the engine starters. `npm run dev` is Vite's dev server, which reloads as you edit, and `arc serve --url` frames it in the local play page, with the sandbox and the SDK's host ([Framing a dev server](https://developer.arcadible.com/references/arcadible-cli/arc-serve#url)). `arc serve` on its own builds once, and serves what [`arc deploy`](https://developer.arcadible.com/references/arcadible-cli/arc-deploy) would publish. Any other local server, such as a dev server opened on its own, isn't the [Arcadible sandbox](https://developer.arcadible.com/build/runtime-limitations): - **Paths aren't case-sensitive** on most machines: your machine may find `Ship.png` when the game asks for `ship.png`. Arcadible won't. - **Cross-origin isolation is off,** unless the server sends Arcadible's headers, as the engine starters' `vite.config.js` does: assets from other servers can load, and be blocked on Arcadible. - **The sandbox's limits are off:** dialogs, popups, and form submission work, and are blocked on Arcadible. What no local server reproduces is a fresh origin per version: browser storage persists locally across builds, but each version on Arcadible starts with it empty. [Saves](https://developer.arcadible.com/build/saves) carry over on Arcadible and under `arc serve`; see [Locally](https://developer.arcadible.com/build/saves#locally). ## On staging Deploy a prerelease to try a build on Arcadible itself, without changing what players get: ```sh arc deploy # with a version such as 1.2.0-beta.1 arc play -r staging # the latest prerelease ``` A prerelease goes to the `staging` channel. It's playable at its link, isn't shown in the store, and doesn't change the store page. To see the store page it would publish, open the game's **Store** tab in [Arcadible Studio](https://developer.arcadible.com/studio#store) and choose **Staging**. Its play page shows that version's own title, icon, and orientation, as the installed game does. Share the staging link with testers: ```text https://play.arcadible.com/star-drift:staging/ ``` Deploy to staging as you develop, not only before a release, so each problem shows up soon after the change that caused it. See [Channels](https://developer.arcadible.com/concepts#channels). ## A specific version Every build stays playable at its version link, in either channel. Use it to compare two versions, or to find the release that broke something: ```sh arc play -r 1.1.0 ``` ```text https://play.arcadible.com/star-drift:1.1.0/ ``` ## On devices - **Phones and tablets:** open the staging link on the device. Test touch, rotation, and the safe area on real hardware; see [Best Practices](https://developer.arcadible.com/build/best-practices#test-on-real-hardware). - **The desktop app:** [`arc play`](https://developer.arcadible.com/references/arcadible-cli/arc-play) opens the game in the Arcadible desktop app when it's installed. Games there run in their own window, with a few [differences](https://developer.arcadible.com/build/runtime-limitations#browsers-and-the-desktop-app) from a browser. - **Browsers:** `arc play --browser` opens the play page in your default browser. Test the browsers your players use, including Safari. ## In CI [`arc validate`](https://developer.arcadible.com/references/arcadible-cli/arc-validate) exits with `1` when the project has issues, so it works as a check on every pull request: ```yaml title=".github/workflows/validate.yml" on: pull_request jobs: validate: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: 24 - run: npm install -g arcadible - run: arc validate ``` `arc validate` needs no login. To release from CI, see [Deploying from GitHub](https://developer.arcadible.com/publish/deploying-from-github). ## Checklist - [ ] The game runs under [`arc serve`](https://developer.arcadible.com/references/arcadible-cli/arc-serve), not only under a dev server - [ ] A prerelease plays on staging in a browser and in the desktop app - [ ] Tested on a phone, with touch and both orientations your game supports - [ ] No errors in the browser's console on the play page - [ ] Saves carry over from one prerelease to the next, if the game uses them # Game Engines on Arcadible Arcadible runs any game that builds to a folder of static files with an `index.html`. Whatever you build with, the setup is in `arcadible.toml`: the command that builds the game, if any, and the folder it builds into. ```toml title="arcadible.toml" [workflow] build = "npm run build" deploy_dir = "dist" ``` See [`workflow`](https://developer.arcadible.com/references/arcadible-manifest#workflow) for details, and [Runtime Limitations](https://developer.arcadible.com/build/runtime-limitations) for the environment your game runs in. ## Starter templates Each starter is a complete project that deploys as it is: | Template | Stack | Create it | | --- | --- | --- | | `arcadible/starters/blank` | Blank canvas | `arc init star-drift -y` | | `arcadible/starters/threejs` | Three.js | `arc init star-drift -t arcadible/starters/threejs` | | `arcadible/starters/phaser` | Phaser | `arc init star-drift -t arcadible/starters/phaser` | | `arcadible/starters/pixijs` | PixiJS | `arc init star-drift -t arcadible/starters/pixijs` | | `arcadible/starters/babylonjs` | Babylon.js | `arc init star-drift -t arcadible/starters/babylonjs` | Each keeps the direction its mark spins in as a [save](https://developer.arcadible.com/build/saves), so it carries over to the next version: the engine starters through the package, `@arcadible/sdk`, and the blank starter through the hosted script. Remove it if your game has nothing to save. For anything else, create the project with its own tools, then run [`arc init`](https://developer.arcadible.com/references/arcadible-cli/arc-init) in it. See [Bring an Existing Game](https://developer.arcadible.com/existing-game). ## JavaScript and TypeScript The engine starters build with [Vite](https://vite.dev). Set its `base` to `"./"` so the build uses relative paths, and point `deploy_dir` at the folder it writes, `dist` by default. `package.json`'s `build` script runs as `npm run build`: ```js title="vite.config.js" export default { base: "./" }; ``` ```toml title="arcadible.toml" [workflow] deploy_dir = "dist" ``` The engine starters set Vite's `outDir` to `.arcadible-public` instead, the default `deploy_dir`, so a new project keeps the same layout as the blank starter. Their Vite servers also send Arcadible's isolation headers, so `npm run dev` is cross-origin isolated, as on Arcadible, and [`arc serve --url`](https://developer.arcadible.com/references/arcadible-cli/arc-serve#url) can frame it. | Library | What it is | Docs | | --- | --- | --- | | [Three.js](https://threejs.org) | A 3D renderer, with WebGL 2 or WebGPU. Physics, audio, and game structure are up to you. MIT. | [Manual](https://threejs.org/manual/) | | [Phaser](https://phaser.io) | A 2D framework, with scenes, physics, tilemaps, input, and audio. MIT. | [Docs](https://docs.phaser.io) | | [PixiJS](https://pixijs.com) | A 2D renderer, with WebGL and WebGPU. Game structure, physics, and audio are up to you. MIT. | [Guides](https://pixijs.com/8.x/guides) | | [Babylon.js](https://www.babylonjs.com) | A 3D engine, with physics, a GUI, animation, and audio, on WebGL 2 or WebGPU. Apache 2.0. | [Docs](https://doc.babylonjs.com) | With Babylon.js, import each module from its own path, such as `@babylonjs/core/Engines/engine`, as the starter does. Importing from `@babylonjs/core` itself pulls in the whole engine, several megabytes, however little the game uses. ## Engines with web exports Export a web build into a folder in your project, and point `deploy_dir` at it. An engine that compresses its export and expects the server to label the files, as Unity does by default, needs that turned off; see [Compressed engine builds](https://developer.arcadible.com/build/runtime-limitations#compressed-engine-builds). > **These engines' saves don't carry over to your next version.** The Arcadible SDK doesn't currently have a plugin for them, so their saves stay in browser storage, which [starts empty in each version](https://developer.arcadible.com/build/runtime-limitations#storage-and-saves). For example, [Godot](https://docs.godotengine.org/en/stable/tutorials/export/exporting_for_web.html), with its export templates installed and a **Web** export preset: ```toml title="arcadible.toml" [workflow] build = "godot --headless --export-release Web build/web/index.html" deploy_dir = "build/web" ``` Its exports with threads work too, since Arcadible is cross-origin isolated. Godot 4 doesn't currently export C# projects to the web ([Godot](https://docs.godotengine.org/en/stable/tutorials/export/exporting_for_web.html)). ## Native code and WebAssembly Code in C, C++, or Rust compiles to WebAssembly, with an HTML page and JavaScript glue code. Name the page `index.html`, and point `deploy_dir` at its folder. For example, a [Bevy](https://bevy.org) game in Rust, built with [Trunk](https://trunk-rs.github.io/trunk/): ```toml title="arcadible.toml" [workflow] build = "trunk build --release --public-url ./" deploy_dir = "dist" ``` WebAssembly from Rust is large before optimization: build with `opt-level = "z"` or `"s"`, enable LTO, and run `wasm-opt`. ## Your own engine An engine is optional. The [blank starter](https://github.com/arcadible/starters/tree/main/blank) is a canvas and a script, with Canvas 2D, WebGL 2, and WebGPU for graphics, Web Audio for sound, and the Gamepad and Pointer Events APIs for input. It's the smallest download. ## Next steps - [Runtime Limitations](https://developer.arcadible.com/build/runtime-limitations): the environment your game runs in. - [Saves](https://developer.arcadible.com/build/saves): keep players' progress across versions. - [Testing](https://developer.arcadible.com/build/testing): try your game locally and on staging. # Working with AI Agents on Arcadible Coding agents can build, check, and publish an Arcadible game with the same `arc` commands you use. This page covers how an agent reads these docs, an `AGENTS.md` to add to your project, and how `arc` behaves when an agent runs it. ## Give the agent the docs These docs are published as text: - **[llms.txt](https://developer.arcadible.com/llms.txt)** lists every page as Markdown, with a one-line description. - **[llms-full.txt](https://developer.arcadible.com/llms-full.txt)** has every page in one file. - **Add `.md` to any page's URL** for its Markdown, such as [/references/arcadible-manifest.md](https://developer.arcadible.com/references/arcadible-manifest.md). Point the agent at the page for the file it's changing, rather than all of them. The table in the `AGENTS.md` below does that. ## Add an `AGENTS.md` Agents that support [`AGENTS.md`](https://agents.md) read it before working in a project. The [starters](https://developer.arcadible.com/build/game-engines) include one made for them. For a project of your own, save this at its root, and adjust it to your game, such as its commands and `deploy_dir`. **It holds only what doesn't change, and links to these docs for everything else.** An `AGENTS.md` is copied into each project and isn't updated after, so an agent reading an old one still follows the current docs, which it reads as it works. Keep yours the same way: add what's true of your game, and link to these docs rather than copying from them. ````md title="AGENTS.md" # AGENTS.md This project is a game for Arcadible, a platform for publishing games. Build it with anything that runs in a browser: if it builds to a folder with an `index.html`, the `arc` CLI checks and deploys it, whatever the engine. Players play it wherever Arcadible is installed, and in any browser. **This file was written when the project was created, and Arcadible's docs are kept current.** Where they differ, follow the docs. Every page is listed in https://developer.arcadible.com/llms.txt, and is all in https://developer.arcadible.com/llms-full.txt. ## Read the docs first Read the page that covers what you're changing, every time, rather than relying on what you remember of it. | Before you change | Read | | -------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | The game | https://developer.arcadible.com/build/best-practices.md and https://developer.arcadible.com/build/runtime-limitations.md | | How it saves progress | https://developer.arcadible.com/build/saves.md, then https://developer.arcadible.com/references/arcadible-sdk/saves.md and its codes in https://developer.arcadible.com/references/arcadible-sdk.md | | `arcadible.toml` | https://developer.arcadible.com/references/arcadible-manifest.md | | `.arcadible/` | https://developer.arcadible.com/publish/store-page.md and https://developer.arcadible.com/references/arcadible-manifest.md | | `README.md` | https://developer.arcadible.com/references/arcadible-markdown.md | | How you deploy, play, or release | https://developer.arcadible.com/references/arcadible-cli.md and https://developer.arcadible.com/publish/releases.md | | How it releases from GitHub | https://developer.arcadible.com/publish/deploying-from-github.md | ## Project structure | Path | Contains | | ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `arcadible.toml` | The game's id, version, and settings. Never change `id`. If it's missing, `arc init` adds one without scaffolding. | | `README.md` | The store page: the title, tagline, bio, and the description between the first and last `---`. | | `.arcadible/` | The store art, with fixed file names. | | `deploy_dir` | The game players load (`workflow.deploy_dir`, `.arcadible-public` by default), with `index.html` at its root. When the project has a build, the build writes it: don't edit it by hand. | ## Commands ```sh npm run dev # the project's dev server, when it has one arc serve # the game as Arcadible serves it, framed in a play page arc validate # the checks arc deploy runs, with a link to each broken rule arc deploy # validate, build, publish, and wait until it's published ``` Every `arc` command is in https://developer.arcadible.com/references/arcadible-cli.md. Add `--json` to read a command's result as one JSON document on stdout. A command that fails exits with 1, and its JSON has `error.code`: act on the code, and look it up in https://developer.arcadible.com/references/errors.md, which says what to do. ## What doesn't change - **Each version starts with empty browser storage,** since each build has its own origin. Keep progress in saves, through the Arcadible SDK: the `@arcadible/sdk` package for a game with a build step; or, for a game with none, the hosted script, ``, which Arcadible and `arc serve` serve. - **The game runs in the Arcadible sandbox,** which `arc serve` reproduces and other local servers don't. Read the runtime limitations before changing how it loads files, stores data, or talks to servers. - **`/arcadible/` is Arcadible's.** Don't put an `arcadible` folder at the root of `deploy_dir`. ## Rules - Run `arc validate` after every change to `arcadible.toml`, `README.md`, or `.arcadible/`. It exits with 1 when there are issues. - Bump `version` before every deploy: in `package.json` when there is one, otherwise in `arcadible.toml`. Each version publishes once. A prerelease, such as `1.2.0-beta.1`, goes to `staging`, and any other version to `production`. - Deploy prereleases to test. Deploy a release version only when the user asks to release: it's what players get, and it publishes the store page. - Change `visibility` to `public` only when the user asks. It lists the game in the store and in search. - When a code's entry says the user must act, such as logging in or connecting GitHub in Arcadible Studio, ask them, wait until they say it's done, then run the command again. ```` ## Running `arc` from an agent The CLI is built to be run by agents as well as people. See [Output](https://developer.arcadible.com/references/arcadible-cli#output) for the full rules. - **Pass `--json`** to read a command's result (every command but `login`, `logout`, `open`, `play`, and `serve` takes it). stdout holds exactly one JSON document, and stderr stays empty. - **Check the exit code.** Every command exits with `0` when it succeeds and `1` when it fails, including a deploy whose release fails. - **Act on `error.code`,** not the message. Each [code](https://developer.arcadible.com/references/errors#codes) has an entry in Errors, with what to do, and codes don't change between versions. - **Create projects without prompts.** [`arc init`](https://developer.arcadible.com/references/arcadible-cli/arc-init) asks questions in a terminal. Pass `-y` to accept the defaults, or `-t