🎮 AnySync Games · Developer Portal

Build a game in one file.

Depend only on @anysync/game-sdk. Export a defineGame() with a TV present and a phone remote. We lazy-load it as its own chunk and pair phone↔TV over the fastest link. No fail states, big targets, instant feedback — made for ages 2–5.

1 · Getting started

  1. Copy the template folder packages/games/_template/ to packages/games/<your-id>/.
  2. Edit src/index.js — set meta, and write present (TV) and remote (phone).
  3. Put any images/sounds in assets/.
  4. Validate: validateGame(default).ok === true. Send us the folder — we add one catalog row and rebuild.
import { defineGame, makeCanvas, loop, ui, SND, emojiAt, addButtons, rTitle }
  from "@anysync/game-sdk";

export default defineGame({
  meta: { id: "my-game", title: "My Game", emoji: "🎮",
          control: "Buttons", group: "A", mode: "presenter", api: 1 },
  present(host, ch) { /* draw TV; ch.on(...) */  return () => {}; },
  remote(host, ch)  { /* phone control; ch.send(...) */ return () => {}; },
});

2 · The contract

A game is { meta, present, remote }. Each half receives a channel and returns a cleanup.

FieldTypeNotes
meta.idsluglowercase; chunk folder + pairing topic
meta.titlestringcard name
meta.emojistringcard thumbnail
meta.controlenumButtons · Roller · Wheel · Slider · Tilt · Pad · Custom
meta.groupA · BA physical, B gyroscope
meta.modepresenter · singlephone→TV, or one device
present(host, ch)fn → cleanupTV/iPad output
remote(host, ch)fn → cleanupphone control

The channel

ch.send(msg)send a message to the other half
ch.on(fn)subscribe; returns unsubscribe
ch.which?()"direct" | "ws" | null — live transport

Messages are tiny JSON objects with a type t and normalized coordinates (positions 0..1, axes −1..1). Never send pixels — the presenter de-normalizes to its own canvas.

3 · SDK API reference

Loading the live API surface from the SDK…

Everything above is imported from @anysync/game-sdk. The list is read live from the deployed SDK, so it can never drift from the docs.

4 · Assets

Ship files in your package's assets/. The build copies them to /js/games/<id>/assets/…. Load them relative to your module — no game needs to know its id or the origin:

import { assetUrl, loadImage } from "@anysync/game-sdk";
const url = assetUrl(import.meta.url, "sprite.png");
const img = await loadImage(import.meta.url, "sprite.png"); // decoded, or null

Always fall back if an asset is missing (draw an emoji). No game may ever fail.

5 · Interactive storybook

These run the real SDK, live. Poke them.

Buttons control

messages appear here…

Tilt control

tilt (drag on desktop) → messages…

Full game — present + remote wired over one channel

Exactly how a paired session behaves, but on one page.

6 · Handing it over

  1. Confirm your default export passes validateGame().
  2. Zip packages/games/<your-id>/ (src + assets).
  3. Send it. We review, drop it into packages/games/, add one catalog row, and rebuild — it lazy-loads in the APPS grid.

Backend optional: add a sibling <your-id>-worker/ exporting handle(request, env, ctx, sub) for /games/<id>/*.