Skip to content
GEMSARCADIA
▸ MANUAL CONTENTS(Getting started)
GEMS ARCADIA · COIN-OP SERVICE MANUAL · FORM GA-DEV-1SEC. 1.1

Getting started

From an empty folder to a playable preview in four steps. You need a Gems Arcadia account with a developer profile: open the developer portal and follow the sign-up prompts first.

1. Lay out your game folder#

A build is a folder of static files with an index.html at the top. No server code runs on our side: the arcade serves your files from its games domain.

TEXT
gem-catcher/
├── index.html      ← the entry point (required)
├── game.js
├── style.css
└── assets/
    ├── sprites.png
    └── blip.ogg

2. Include the SDK#

Load the SDK before your game code. It defines a GemsArcadia global.

index.html
<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>Gem Catcher</title>
    <link rel="stylesheet" href="style.css" />
  </head>
  <body>
    <canvas id="screen" width="320" height="240"></canvas>
    <p>CREDITS <span id="credits">0</span></p>
    <button id="insert-coin" type="button">INSERT COIN</button>
    <p id="message" aria-live="polite"></p>

    <!-- 1. The SDK first: it defines the GemsArcadia global. -->
    <script src="https://gemsarcadia.com/sdk/v1.js"></script>
    <!-- 2. Then your game. Paths are relative to index.html. -->
    <script type="module" src="game.js"></script>
  </body>
</html>

Then wire your coin button. This is the whole coin flow: init(), listen for the cabinet's own coin button, call insertCoin() from yours, and call ready() once loaded.

game.js · state
const ui = {
  coinButton: document.querySelector("#insert-coin"),
  credits: document.querySelector("#credits"),
  message: document.querySelector("#message"),
};

let credits = 0;
let paused = false;

/** One coin went in: give the player a credit. */
function grantCredit(insertionId) {
  credits += 1;
  ui.credits.textContent = String(credits);
  // Show a receipt code players can quote to your support team.
  ui.message.textContent = `RECEIPT ${insertionId}`;
}
game.js · init
export const ready = GemsArcadia.init().then((session) => {
  const { player, coinsPerCredit, balance, testMode } = session;
  ui.message.textContent =
    `Hi ${player.displayName}! ${balance} coins, ` +
    `${coinsPerCredit} per credit${testMode ? " (test mode)" : ""}.`;

  // Coins inserted with the cabinet's own INSERT COIN button.
  GemsArcadia.on("coinInserted", ({ insertionId }) => grantCredit(insertionId));
  // The player switched tabs: stop the clock.
  GemsArcadia.on("pause", () => (paused = true));
  GemsArcadia.on("resume", () => (paused = false));

  GemsArcadia.ready(); // loaded and ready to take coins
  return session;
});
game.js · insert coin
const REASONS = {
  INSUFFICIENT_COINS: "Out of coins! Top up at the Token Booth.",
  NOT_SIGNED_IN: "Log in to play.",
  EMAIL_NOT_VERIFIED: "Verify your email to play.",
  SESSION_EXPIRED: "Session timed out. Reload the game.",
  RATE_LIMITED: "Easy there! One coin at a time.",
  BUSY: "A coin is already dropping...",
};

/** Coins inserted from inside your game. insertCoin() never rejects. */
export async function insertCoin() {
  ui.coinButton.disabled = true;
  try {
    const result = await GemsArcadia.insertCoin();
    if (result.ok) grantCredit(result.insertionId);
    else ui.message.textContent = REASONS[result.reason] ?? "The coin slot jammed. Try again.";
    return result;
  } finally {
    ui.coinButton.disabled = false;
  }
}

ui.coinButton.addEventListener("click", insertCoin);

Prefer a bundler? import { init, insertCoin } from "@gems-arcadia/sdk" gives you the same API with TypeScript types. Pass init({ host: "https://gemsarcadia.com" }) because a bundled copy can't tell where it was loaded from.

3. Test it locally#

Open index.html straight from your disk or any static server. Outside the arcade the SDK runs in mock mode: you get 9999 free test coins and every insertCoin() succeeds. See testing & preview.

4. Zip and upload#

Zip the contents of your build folder (or the folder itself: a single top-level folder is detected and stripped).

SHELL
cd gem-catcher
zip -r ../gem-catcher.zip . -x "*.DS_Store"

On Windows, select the files inside the folder, right-click and choose Send to → Compressed (zipped) folder.

Builds must follow these rules:

  • index.html at the root of the zip, or inside a single top-level folder (it is detected and stripped).
  • Zip up to 100 MB, at most 300 MB unpacked.
  • At most 2,000 files.
  • Relative paths with forward slashes only: no absolute paths, no .., no symlinks.
  • Hidden files and OS junk (__MACOSX/, .DS_Store, Thumbs.db), source maps (.map) and markdown files (.md) are skipped, not rejected.

Allowed file types:

.html.js.mjs.css.json.png.jpg.jpeg.gif.webp.svg.wasm.mp3.ogg.wav.ttf.woff.woff2.txt.glb.gltf.bin.data

Then, in the portal:

  1. Create a game at /developer/games/new: title, slug, genre, cover art and coins per credit.
  2. Upload the zip on the game's page. The arcade validates it, warns if index.html doesn't reference the SDK, and unpacks it.
  3. Preview it: you get a test-mode cabinet with free coins.
  4. Submit for review. Once approved, your cabinet goes live on the arcade floor.

Next: see exactly what happens when a coin drops, in the coin flow.

REV 1.0 · KEEP THIS MANUAL WITH THE CABINET · QUESTIONS? ASK GLINT