Caputchin
Marketplace game development

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): void

The game factory

type GameFactory = (
  container: HTMLElement,
  bridge: Bridge,
  ctx?: GameContext,
) => (() => void) | void
ParameterMeaning
containerAn element inside the game's sandboxed iframe. Render here. Style is naturally scoped, the iframe is its own document.
bridgeThe 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.
ctxThe per-round context (seed plus resolved presets). Optional in the type because the widget may run a game outside a verified session.
returnAn 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:

MemberShapeEffect
passpass(result: { trace: string }): voidSignals 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.
errorerror(err: { code: string; message?: string }): voidThe 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.
setSizesetSize(width: number, height: number): voidExplicitly 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.
layoutreadonly layout: 'inline' | 'modal' | 'fullscreen' | nullThe 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
}
FieldMeaning
seedThe 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.
localeThe resolved language object: _lang (BCP-47), _direction, plus your flattened text keys. null if your manifest ships no locales block.
skinThe 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.
configThe 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:

TypeDescribes
GameManifestThe whole caputchin.json. Author + indexer source of truth; never read in the browser.
LocalePreset / ResolvedLocaleA declared locale preset / the resolved object the game receives.
SkinPreset / ResolvedSkin / SkinSchemaEntry / SkinValueTypeSkin presets, the resolved skin, and the per-key type schema.
ConfigPreset / ResolvedConfig / ConfigSchemaEntry / ConfigValueTypeConfiguration presets, the resolved config, and the per-key type schema.
LocalesFile / SkinsFile / ConfigurationsFileThe top-level object of each .caputchin/<axis>.json split file.
MarketplaceMetadata / PreferredPresentationThe 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 sessionId or platform identifiers reach the game.
  • No start / pause / resume lifecycle hooks; the factory call is the start.
  • No maxScore or platform score range; the verdict's score is whatever your run returns.

The contract is intentionally tiny so it stays stable across years of catalogue growth.

See also

On this page