# Building a game for Santander Arcade

The arcade is a **lobby**. Each game is a folder. You add a folder, and on the
next deploy your game appears as a card in the lobby.

Games are not limited to 8-bit. Canvas, WebGL, audio, whatever the idea needs —
simple arcade toys and larger games both fit.

You never edit the arcade page, the CSS, or the JavaScript. That is deliberate:
the hall stays one thing, and games come and go independently.

---

## 1. Make a folder

```
games/<your-slug>/
  game.json
  index.html
  game.js        (optional but recommended)
```

- `<your-slug>` — lowercase letters, digits and hyphens. `2–41` characters.
- The folder name **is** the game's id and its URL. Choose it once.
- Folders starting with `_` are ignored, so `games/_template` never ships.

### Pixel art on the cabinet

A card can carry its own 8×8 pixel art instead of an emoji:

```json
{
  "sprite": "......../.##...../..#...../..###.../....#.../.####.../.#....../.##....",
  "spriteColors": { "#": "#39ff14" }
}
```

Row 1 is the top. `.` means empty. Any char with an entry in `spriteColors` is
drawn. Omit `sprite` and the emoji `icon` is used instead.

Start from the template:

```bash
cp -r games/_template games/my-game
```

## 2. Write the manifest

`game.json`:

```json
{
  "title": "My Game",
  "author": "Saturn",
  "description": "One line that appears under the cabinet name.",
  "controls": "ARROWS to move · SPACE to fire",
  "icon": "🛰️",
  "color": "#00e5ff",
  "order": 30
}
```

| field | required | notes |
|---|---|---|
| `title` | yes | shown on the card |
| `author` | yes | who built it — agents use their own name |
| `description` | yes | one sentence |
| `controls` | no | shown under the screen while playing |
| `icon` | no | a single emoji, drawn on the card if no sprite |
| `genre` | no | groups the card in the lobby filters (default `arcade`) |
| `sprite` | no | 8×8 pixel art for the cabinet: 8 rows of 8 chars separated by `/`, using `#` for a pixel |
| `spriteColors` | no | map of sprite chars to colours, e.g. `{"#":"#39ff14"}` |
| `color` | no | hex accent for the marquee. Defaults to a rotating palette |
| `order` | no | lower sorts first. Default `999` |

## 3. Write the game

`index.html` must load a canvas and your script:

```html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>My Game</title>
  <style>
    html,body{margin:0;height:100%;background:#0a0612;overflow:hidden}
    body{display:grid;place-items:center}
    canvas{width:100%;height:100%;display:block;image-rendering:pixelated;background:#05030a}
  </style>
</head>
<body>
  <canvas id="c" width="320" height="240"></canvas>
  <script src="game.js"></script>
</body>
</html>
```

**Pixel art:** drawing at 320×240 and scaling with `image-rendering: pixelated`
is the cheapest way to get crisp retro visuals. It is a style choice, not a
requirement — a full-resolution or WebGL game works just as well.

## 4. Rules of the hall

These are enforced or expected:

- **No dependencies.** No npm packages, no CDN scripts, no external fonts. One
  folder that works offline.
- **No network calls.** The game runs in a sandboxed frame and should not need
  to talk to anything.
- **No navigation.** Do not try to move the top window — you cannot, and it will
  look like a bug.
- **Stay under 1 MB** per game folder.
- **`localStorage` is not guaranteed.** Your game runs in a sandboxed iframe with
  an opaque origin, so `localStorage` **throws**. Always wrap it:

```js
function best() {
  try { return +localStorage.getItem('arcade.my-game.best') || 0; }
  catch (e) { return 0; }
}
```

- **Handle being hidden.** Pause on `visibilitychange` so a background cabinet
  does not burn CPU.
- **Respect reduced motion.** Cosmetic animation should stop under
  `prefers-reduced-motion: reduce`.
- **Keyboard and touch.** Aim for at least arrow keys; add swipe or pointer
  movement if the game suits it.

## 5. Build and check

```bash
npm run build      # regenerates games/registry.json
npm run serve      # then open http://localhost:8080
```

`npm run build` (which runs the Python builder) **validates** every manifest and fails loudly on a missing
`title`, a bad slug, invalid JSON or an oversized folder. If it fails, nothing
is written — fix the error and run it again.

## 6. Ship it

```bash
git checkout -b game/my-game
git add games/my-game games/registry.json
git commit -m "feat(arcade): add My Game"
git push -u origin game/my-game
```

Open a PR. CI rebuilds the registry and fails if the committed copy is stale, so
**commit the regenerated `games/registry.json`** in the same commit as the game.

On merge to `main`, the arcade deploys itself and the new cabinet is live.

---

## What the lobby gives you

The lobby handles everything around your game: the card, the genre filter, the
play frame, the deep link (`?game=my-game`), and the sandbox.

Your game only has to be the rectangle it runs in.

## Isolation, so you can be trusted

Each cabinet loads in:

```html
<iframe sandbox="allow-scripts allow-pointer-lock">
```

There is no `allow-same-origin`. Your game cannot read the hall's storage,
reach the parent document, or navigate the page. That cuts both ways — it is
also why `localStorage` throws. Work with it rather than around it.
