Game SDK reference
@caputchin/game-sdk is the contract a game author writes against: a register helper, a push-only Bridge, the per-round GameContext, and the TypeScript types for the manifest's preset blocks. It is published separately from the user-facing widget so games never transitively bundle the widget runtime. First-party games use this same public API; there is no private contract.
For a guided build, see Build a marketplace game. For the manifest that declares the presets this SDK surfaces, see The caputchin.json manifest.
register(factory)
The single registration entry point. Pass your game factory; you do not pass the manifest (the server reads caputchin.json at index time and ships resolved presets down as the factory's context). Registering twice logs a console warning and the last write wins; there is no platform enforcement.
register(factory: GameFactory): voidThe game factory
type GameFactory = (
container: HTMLElement,
bridge: Bridge,
ctx?: GameContext,
) => (() => void) | void| Parameter | Meaning |
|---|---|
container | An element inside the game's sandboxed iframe. Render here. Style is naturally scoped, the iframe is its own document. |
bridge | The push-only channel to the host (see The bridge). The game emits upward; it does not subscribe. The factory being called is the start signal. |
ctx | The per-round context (seed plus resolved presets). Optional in the type because the widget may run a game outside a verified session. |
| return | An optional cleanup function the widget calls if it unmounts your game. |
The bridge
The bridge is push-only: the game reports events to the host and never listens. Its members:
| Member | Shape | Effect |
|---|---|---|
pass | pass(result: { trace: string }): void | Signals the round was passed, handing the host the opaque trace. The server re-runs the game's run under the issued seed to compute the authoritative verdict, so the game does not report a score here. The first call releases the held verification and locks the token; later calls are ignored. |
error | error(err: { code: string; message?: string }): void | The game itself failed (asset load, internal exception). Surfaces an error event to the user. Distinct from a player losing, which is signalled by calling nothing. |
setSize | setSize(width: number, height: number): void | Explicitly resize the iframe to fit content. Call after first paint; the widget also auto-measures the game's first child, so most games never need this. |
layout | readonly layout: 'inline' | 'modal' | 'fullscreen' | null | The resolved presentation the game runs under, or null when unknown. |
There is deliberately no complete, no start listener, no unmount method (return a cleanup function instead), and no failure signal beyond silence.
The context
interface GameContext {
seed: Seed | null
locale: ResolvedLocale | null
skin: ResolvedSkin | null
config: ResolvedConfig | null
}| Field | Meaning |
|---|---|
seed | The per-round replay seed (a Seed from the replay contract). Seed all game randomness from it. null when the game runs outside a verified session. |
locale | The resolved language object: _lang (BCP-47), _direction, plus your flattened text keys. null if your manifest ships no locales block. |
skin | The resolved skin: _theme plus your flattened color/asset keys, asset URLs already absolute. _theme is always the concrete mode the skin was resolved for, light or dark (never any), so a theme-agnostic preset reports the visitor's actual mode. null if no skins block. |
config | The resolved configuration: your flattened typed scalars. null if no configurations block. |
Each is null when the corresponding manifest block is absent, so always provide a built-in fallback.
Preset types
The SDK exports TypeScript types for authoring caputchin.json (and the split .caputchin/ files) with type-checking. These mirror the manifest blocks:
| Type | Describes |
|---|---|
GameManifest | The whole caputchin.json. Author + indexer source of truth; never read in the browser. |
LocalePreset / ResolvedLocale | A declared locale preset / the resolved object the game receives. |
SkinPreset / ResolvedSkin / SkinSchemaEntry / SkinValueType | Skin presets, the resolved skin, and the per-key type schema. |
ConfigPreset / ResolvedConfig / ConfigSchemaEntry / ConfigValueType | Configuration presets, the resolved config, and the per-key type schema. |
LocalesFile / SkinsFile / ConfigurationsFile | The top-level object of each .caputchin/<axis>.json split file. |
MarketplaceMetadata / PreferredPresentation | The marketplace block and the preferred footprint hint. |
Author a split file with a satisfies import so the compiler checks it:
import type { LocalesFile } from "@caputchin/game-sdk";
export default { schema: { /* ... */ }, presets: { /* ... */ } } satisfies LocalesFile;What is intentionally absent
- No
sessionIdor platform identifiers reach the game. - No
start/pause/resumelifecycle hooks; the factory call is the start. - No
maxScoreor platform score range; the verdict's score is whatever yourrunreturns.
The contract is intentionally tiny so it stays stable across years of catalogue growth.
See also
- Build a marketplace game: the tutorial that uses this surface.
- The replay contract: the
Seed, trace, and verdict the bridge and context refer to. - The caputchin.json manifest: declaring the presets the context resolves.