---
name: ringstack-game
description: Turn a game idea into a published RingStack game in one go. Scaffolds with the ringstack CLI, builds a mobile-friendly browser game, wires in the RingStack SDK (player accounts, leaderboards, achievements, cloud saves), tests it in the local mock portal, writes the version and changelog, and publishes it with `ringstack publish`. Use when the user wants to make, update, test or publish a game on RingStack (ringstack.co).
---

# RingStack game: idea to published

RingStack (https://ringstack.co) is a browser-game portal. Players have one account
across all games; games get leaderboards, achievements and per-player cloud saves through a
small JavaScript SDK. RingStack hosts each game as static files on its own Cloudflare Worker.
Publishing goes through the `ringstack` CLI, which is a thin client of RingStack's HTTP API.

Work through these steps in order. Do the whole thing without stopping to ask unless a step
says a human is needed. Prefer sensible defaults over questions.

## 0. Tools

- Node 18.17+.
- The CLI (not on the npm registry; it is served by RingStack):
  ```bash
  ringstack --version || npm i -g https://ringstack.co/cli/ringstack.tgz
  # no global install? use: npx -y https://ringstack.co/cli/ringstack.tgz <command>
  ```
- `ringstack whoami`. If it says "Not signed in", run `ringstack login` now: it prints a URL
  and a code and waits (up to 10 minutes) while **the human approves it in their browser**.
  Tell the user the URL and code, and keep working on the game while you wait (run the login
  in the background if your environment allows). The token is saved in the user's config dir.

## 1. Design brief (keep it short)

Write down, in a few lines: the core loop, the controls for **touch and keyboard/mouse**, how
a round ends, the score, 3 to 6 achievements (easy, medium, hard), what is saved per player
(best score, unlocked levels, settings, creations), the aspect ratio, and **the cosmetics**:
one to three slots (skin/colour of the main thing, a trail or effect, a hat or title) with 3 to 8
items between them. Cosmetics are how the game makes money: players buy them with Rings and the
publisher gets 70% of each sale. They must only change looks. Keep it small: one screen, clear feedback when something happens, and a quick restart is plenty.

## 2. Scaffold

```bash
ringstack init <folder> --slug <slug> --title "<Title>"
cd <folder>
```

- The slug is the game's permanent id and URL (`/games/<slug>`): lower-case, dashes,
  max 40 chars, unique on RingStack. Pick something distinctive.
- You get `public/` (a working starter game with the SDK fully integrated: copy its patterns),
  `ringstack.json`, `CHANGELOG.md`, `AGENTS.md` (read it: it is the rulebook) and this skill.

## 3. Build the game

- Replace the starter in `public/` with the real game. Plain HTML/CSS/JS with a `<canvas>` is
  usually best. If you use a bundler, set `"build"` and `"dir"` in `ringstack.json`.
- Static files only: no server code, no secrets, no files named `_headers`, `_redirects`,
  `_worker.js`, and nothing starting with `.`.
- Keep `<script src="https://ringstack.co/sdk/ringstack.js"></script>` in `index.html`.
- Fill the frame at any size, Pointer Events + `touch-action: none`, 44 px tap targets,
  keyboard too, `devicePixelRatio` for canvases, pause on `visibilitychange`, audio only after
  a gesture, safe-area insets for the HUD.
- Update `ringstack.json`: `title`, `tagline`, `description` (2 to 4 sentences, what the player
  does), `tags` (up to 8), `aspect`, and the `achievements` and `leaderboards` you designed.

## 4. Integrate the SDK (all of these)

```js
const rs = await RingStack.init({ game: '<slug>' });   // same slug as ringstack.json
```

- **Identity**: show the player's `displayName` or a "Sign in" button that calls `rs.signIn()`;
  listen to `rs.on('user', …)` to reload saves and refresh the UI.
- **Leaderboards**: `rs.submitStats({ score, … })` (or `rs.submitScore(n)`) at the end of each
  round when `rs.user` is set; show the top entries with `rs.getLeaderboard('score', { limit: 5 })`.
- **Achievements**: `rs.unlockAchievement('<key>')` at the moment it is earned; every key must
  be declared in `ringstack.json`.
- **Saves**: `rs.data.get/set` for what should follow the player across devices, with
  `ifVersion` and a merge on `conflict`; `localStorage` for guests.
- **Cosmetics**: declare items in `ringstack.json` (`sku`, `name`, `kind`, `slot`, `rarity`,
  `price` in Rings, `preview` image in the build). In the game: map sku to looks, read
  `rs.getLoadout()` on start and on sign-in, draw the equipped items, and offer an in-game shop
  (`rs.purchaseItem(sku)` then `rs.equip(sku)`, like the starter's basket skins). Multiplayer:
  draw others with `rs.getLoadouts(ids)`; a game server verifies `rs.cosmetics.attest()` tokens.
  The patterns and rules are in `AGENTS.md` ("Cosmetics").
- **Offline**: if `window.RingStack` is missing or `rs.mode === 'offline'`, the game still plays.
- Wrap SDK calls in `try/catch` (or `.catch`): never let them break the game loop.

The full API and error codes are in `AGENTS.md` of the scaffolded game.

## 5. Test

```bash
ringstack dev            # leave it running (background): http://127.0.0.1:5173
```

The page at :5173 is a mock RingStack game page: it frames the game from another origin and
answers the real SDK with a fake backend (signed-in "Dev Player", scores, achievements from
`ringstack.json`, saves), logging every SDK call; failed calls are red.

If Playwright is available (`npx playwright --version`), run the bundled check, then look at
the screenshots it writes:

```bash
node .claude/skills/ringstack-game/scripts/check-game.mjs        # or <skill dir>/scripts/check-game.mjs
```

It checks that the SDK connects in embedded mode, that there are no console errors or failed
SDK calls, takes desktop + phone screenshots, and exercises signed-in and signed-out play.
Also play it yourself through the page if you can drive a browser: tap/click to start, play a
round, confirm the score, achievement and save show up in the side panel. Fix everything it finds.

## 6. Version and changelog (every publish, including the first)

- `version` in `ringstack.json` is semver and must be greater than any version published
  before (never reuse one, even after a failed deploy). First release: keep `0.1.0` or use
  `1.0.0`. Later: patch for fixes, minor for new features, major for big changes.
- Add a section at the top of `CHANGELOG.md`:
  ```markdown
  ## [1.1.0] - 2026-10-06

  - New: gold stars worth 50 points appear after 30 seconds.
  - Fixed: the basket could leave the screen on narrow phones.
  ```
  Write it for players: concrete changes, no "misc improvements".
- `ringstack publish --bump minor` bumps the version for you (you still write the changelog).

## 7. Publish

```bash
ringstack publish
```

It validates the version and changelog, registers the game and syncs achievements and
leaderboards, then:

- **First publish only**: asks for the one-time **$5 publishing fee**. It prints a checkout URL
  and waits. **A human must open it and pay** (sandbox: a "Simulate successful payment" button,
  no real money). Tell the user exactly that, with the URL.
- Uploads only changed files, queues the deploy and streams the deploy log until it is
  `live`, then prints the game URL (`https://rs-game-<slug>.<account>.workers.dev/`).
- A new game then **waits for admin review** before it is listed on RingStack; later versions
  of an approved game go live immediately.

Finish by telling the user: the live URL, the listing URL, the version published, the review
status (`ringstack status`), and anything they need to do (approve login, pay, wait for review).

## Updating a published game

Change the game, bump `version`, add the `CHANGELOG.md` section, test with `ringstack dev`,
`ringstack publish`. `ringstack versions` lists the history; `ringstack rollback <version>`
puts an earlier version back (recorded in the public history; no new changelog needed).

## When something fails

| Error code | Fix |
| --- | --- |
| `version_required`, `invalid_version` | Set `"version": "1.2.0"` in `ringstack.json`. |
| `version_not_greater` | Bump past the version shown; numbers are never reused. |
| `changelog_required` | Add `## [x.y.z]` to `CHANGELOG.md` (matching the version) or pass `-m "…"`. |
| `slug_taken` | Pick another slug in `ringstack.json` **and** in `RingStack.init({ game })`. |
| `payment_required` | The fee isn't paid: rerun `ringstack publish` and have the user pay at the URL. |
| `reserved_path`, `no_index`, `file_too_large`, `build_too_large` | Fix the build folder (limits: 1000 files, 25 MB per file, 100 MB total). |
| `invalid_token` / "Not signed in" | `ringstack login` (a human approves it). |
| deploy `failed` | `ringstack logs` shows why; fix and publish a **new** version. |
| `not_found` on `unlockAchievement` (in the dev log) | Declare the key in `ringstack.json`. |

The publishing HTTP API (for CI or other tools) is in `references/publish-api.md`.
