@gitcade/sdk
v1.12.0
Published
GitCade SDK — the frozen contract: schema, entity-component runtime, storage bridge, and validator CLI for AI-built, community-governed browser games.
Downloads
1,578
Maintainers
Readme
@gitcade/sdk
The GitCade SDK — the engine standard for AI-built, open-source, community-governed browser games. A lightweight TypeScript entity-component runtime for 2D Canvas games, plus the schema, the storage bridge, and the publish-time validator.
This package is the frozen contract every GitCade game, the component library, the build worker, the marketplace, and governance depend on.
Install
npm install @gitcade/sdkZero runtime dependencies beyond zod. Games pin an exact
sdkVersion in game.json.
What's inside
- Schema — Zod validators + inferred TS types for
game.json,config.json, scenes, entities, behaviors, and systems. Every export is both a runtime validator and a static type. - Runtime — a fixed-timestep entity-component game loop on HTML5 Canvas with built-in primitives (transform/velocity, AABB collision + events, keyboard + touch input, sprite/sheet/text rendering, audio that no-ops headlessly) and a registration API for adding new behavior/system types.
- Storage bridge — the
postMessageprotocol + adapters that are the only sanctioned persistence path for ecosystem games (no rawlocalStorage). - Validator —
gitcade validate <dir>, the publish gate.
Scene flow, input & economy
Capabilities for expressing more of a game as data. Each is an optional schema field or additive API, so a game that uses none of them runs on the bare runtime unchanged.
- Data-driven scene flow. A scene may declare
flow: { on: { <event>: <sceneId> }, persist: [<stateKey>] }— emitting the event transitions the scene, carrying the namedworld.statekeys. Parts request a change from data viaworld.requestScene(to, { keep? }); the host drains the queue between ticks, so the frozen in-tick order is untouched. - Pointer click edge + pick.
input.justPressed()/justReleased()/clicked()expose a one-tick click edge (cleared by the loop each tick);world.entityAt(x, y, tag?)/pick(...)return the topmost entity under a point. - Runtime tilemap.
TilemapSchema.propertiesadds per-index flags;world.tileAt(x, y)/isBuildable(x, y)/cellRect(col, row)query the active scene's tilemap (stored onworld.tilemap), and the renderer draws it. - Economy assist.
world.canAfford(key, cost)/world.spend(key, cost)— thin, non-contractual helpers over aworld.statebalance.
Cross-run persistence
manifest.persist: { keys, slot, everySeconds } is surfaced on world.persist;
the @gitcade/library persistence system round-trips those keys through the
frozen world.storage bridge (no wire-protocol change).
world.claimPersistKeys(keys) / isPersistPending(key) / resolvePersistKeys(keys)
let a persistence system claim its declared keys synchronously on boot; a
seed-once system (e.g. the library currency) consults isPersistPending and
defers seeding while the async storage.get is in flight, so a saved value
restores authoritatively. The claim set is scene-scoped (reset on loadScene).
Multi-level games
First-class support for games with multiple levels. Each piece is an optional
schema field or additive runtime/validator behavior; a game that sets neither
scene.extends nor manifest.levels runs without it.
- Scene inheritance. A scene may declare
extends: "<baseSceneId>"to inherit a base scene's shell (entities, systems, size, background, music, tilemap, flow) and overlay only its own content — so a multi-level game authors the shared stage ONCE and each level is a thin override. The merge is id-keyed (base first, then the child, overriding byid); chains resolve bottom-up, cycle-guarded. Resolved in theGameconstructor (resolveSceneInheritance), so the renderer/runtime never seeextends. - Level sequence.
manifest.levels: [<sceneId>, …](+ optionallevelsComplete) makes "a campaign of N levels" first-class. The reservedflow.ontargets@next/@firstresolve against it at emit time (a level never hard-wires its successor), and the runtime setsworld.state.levelto the active level's 1-based index — soscale-by-state/wave-spawnerdifficulty ramps track the stage with no per-scene config.game.requestNextLevel()is the programmatic companion to@next. - Validator cross-checks.
gitcade validatenow resolves every scene reference —flow.ontargets,extends,manifest.levels/levelsComplete, andentryPoint— against the actual scene-id set, so a broken link fails the publish gate instead of surfacing at runtime.
Quick start (composing a game from JSON)
import { createGame } from "@gitcade/sdk";
import manifest from "../game.json";
import config from "../config.json";
import main from "../src/scenes/main.json";
const canvas = document.getElementById("game") as HTMLCanvasElement;
const game = createGame({ manifest, config, scenes: [main] }, { canvas });
game.start(); // browser: requestAnimationFrame loop
// game.stepFrames(60); // headless: pure simulation (tests, validator)The behavior contract (frozen)
A behavior is a pure-ish function run once per entity per fixed update:
import type { BehaviorFn } from "@gitcade/sdk";
const drift: BehaviorFn = (entity, world, params, dt) => {
entity.x += (params.speed as number) * dt; // params are $cfg-resolved
};Register new types (never new schema shapes) onto a cloned default registry:
import { createDefaultRegistry } from "@gitcade/sdk";
const registry = createDefaultRegistry();
registry.registerBehavior("drift", drift);The two rules that make a game publishable
- No magic numbers. Balance values live in
config.jsonand are referenced as"$cfg.<key>". Numeric literals in params are allowed only for structural keys (position, size, layer, frame indices, …). - No raw storage. Persist via
world.storage(the bridge), neverlocalStorage/indexedDB.
CLI
gitcade validate path/to/game # exit 0 = publishableValidation: manifest + config + scene schemas, the storage rule (ecosystem tier),
the mechanical no-magic-numbers rule, partId@version catalog resolution against
the pinned libraryVersion, and a headless 60-frame smoke boot.
Node-only validator API
import { validateGame } from "@gitcade/sdk/validate"; // uses fs/child_process
const result = await validateGame("path/to/game");License
MIT (code) · CC-BY-4.0 (assets).
