Skip to content

Custom adapters & the conformance kit

A game that isn’t built on vgai’s first-party scene format can still run — and be edited — inside the editor by shipping its own adapter: a module in the game folder implementing the small GameAdapter contract (the same seam described in the adapter seam). The editor mounts, renders, and authors the game through it; your game keeps its own architecture.

The editor authoring the conformance-kit fixture game: the adapter-provided Sentry node selected in the hierarchy, the gizmo bound to it, and the inspector showing an ungrouped Aware field plus Vitals (Health/Score) and AI (Mode) groups
A custom adapter's game in the editor — hierarchy, gizmo, and a domain-shaped grouped inspector, all driven through the adapter's own providers.
  1. Your vgai.game.json declares a world with "adapter": { "module": "src/adapter.ts" } (game-folder-relative path; no .., no absolute paths) and explicit "capabilities": { "local": …, "hosted": … } — module worlds have no derivable tier ceiling, so you self-certify one.
  2. The module default-exports { id, mount(host) }. Your manifest’s kind"threejs", "pixijs", or "react" — picks the host mount() receives and the mounted shape it returns (a real THREE.Scene + THREE.Camera for threejs, a real pixi Container stage for pixijs, the same container element it was handed for react), plus a dispose() and (what makes you editable) an optional authoring surface: hierarchy (required), transforms, inspector, persistence — a react adapter has no transforms surface by design (no 3D object to bind).
  3. Your self-certified tier is checked against reality after mount (packages/editor/src/achieved-tier.ts): no authoringopaque-embed; partial → shared; hierarchy **+ transforms + inspector
    • persistence** → first-party. At runtime a shortfall mounts anyway at the achieved tier with a structured tier-shortfall warning; in the conformance kit it’s a hard failure.
  4. Prove it — the quick way, from a monorepo checkout: vgai conformance /abs/path/to/your/game-folder (add --json for a machine-readable report). Or call the kit directly from a vitest test in this repo:
import { runAdapterConformanceKit } from './adapter-conformance-kit';
const report = await runAdapterConformanceKit('/abs/path/to/your/game-folder');
// throws a named `adapter-conformance: …` error on the first violation

The full contract — provider-by-provider assertions, the inspector probe table, the three.js identity/dedupe rules, worked examples — lives in docs/ADAPTER-AUTHORING-GUIDE.md in the repo. That guide is the canonical reference; this page is the map.

runAdapterConformanceKit(projectRoot) runs headless (Node, no GPU/DOM) and walks the whole contract in order: manifest loads → module path-guarded and imported → default export shape-checked → mount() against a headless host matching your world’s kind (HostContext for threejs, World2DHost for pixijs, { container, game } for react) → the mount-contract check for that kind (threejs: scene/camera real instances; pixijs: stage a real pixi Container; react: container is the same element handed to mount(), plus drivesOwnLoop: false) → hierarchy roots non-empty, ids unique, id↔object3D bridge bidirectional (threejs/pixijs — explicitly skipped, with a logged note, for react) → transforms get/apply round-trip, rotation is a quaternion (threejs/pixijs only, react has no transform surface) → every non-readonly inspector field survives a setget round-trip (every kind) → persistence is honest (destination named, save() resolves; every kind) → achieved tier ≥ declared → dispose() doesn’t throw.

There is no sandbox: a custom adapter module runs with full editor-realm privileges, exactly like game entry code (single-realm is a recorded decision — D5). The boundary is declaration honesty: the module path can’t escape the game folder, capabilities must be declared, and declared-vs-achieved is verified and logged. Don’t run game folders you don’t trust.