KSON protocol — Kata docs
Kata docs

KSON protocol

KSON (“Kata Scene Object Notation”) is the data contract between KataEngine and any UI. The engine emits a KSONFrame on every update event; your UI reads that frame and renders it. UI code should never reach into engine internals directly — always consume frames.

KSONFrame

interface KSONFrame {
  meta: KSONMeta;          // current scene metadata
  action: KSONAction;      // what to render / execute right now
  state: {                 // engine state snapshot
    ctx: Record<string, any>;
    currentSceneId: string | null;
    currentActionIndex: number;
    history: string[];
  };
  a11y?: A11yHints;        // generated accessibility hints
}

Every frame is self-contained — it carries everything needed to render the current moment without querying the engine.

KSONMeta

interface KSONMeta {
  id: string;
  title?: string;
  layout?: string;
  assets?: Record<string, string>;
  multiplayer?: {
    mode?: "shared" | "branching";
    choicePolicy?: string;
    syncPoint?: string;
  };
}

KSONAction

A discriminated union. The type field tells you which renderer to dispatch to:

type KSONAction =
  | { type: "text"; speaker: string; content: string }
  | { type: "choice"; choices: Choice[] }
  | { type: "visual"; layer: string; src: string; effect?: string }
  | { type: "wait"; duration: number }
  | { type: "exec"; code: string }
  | { type: "condition"; condition: string; then: KSONAction[]; elseIf?: ...; else?: ... }
  | { type: "audio"; command: AudioCommand }
  | { type: "tween"; target: string; property: string; from?: number; to: number; duration: number; easing?: string }
  | { type: "tween-group"; mode: "parallel" | "sequence"; tweens: Array<...> };

When each type reaches your UI

TypeEngine behaviorUI behavior
textWaits for engine.next()Render dialogue; advance on click/keypress
choiceWaits for engine.makeChoice(id)Render buttons; advance on selection
visualAuto-advancesUpdate scene background/character layer
waitAuto-advances after durationOptional — delay transitions to match
execRuns code, auto-advancesNothing — state change will reflect in next frame
conditionExpands into then/elseIf/else branch at runtime; UI never sees this type directly
audioAuto-advancesPlay/stop via your AudioManager
tween, tween-groupAuto-advancesFire into your animation layer

Choice

interface Choice {
  id: string;          // auto-generated (c_0, c_1, ...) unless specified
  label: string;       // already interpolated
  target?: string;     // scene id — may be undefined for non-navigation
  action?: string;     // inline exec before navigation
  condition?: string;  // runtime gate — hidden when false
}

A11yHints

Generated by generateA11yHints(action) — a pure function exported from @kata-framework/core:

interface A11yHints {
  role?: string;                                     // "dialog", "group", ...
  liveRegion?: "assertive" | "polite" | "off";
  label?: string;                                    // human-readable
  description?: string;
  keyHints?: Array<{ choiceId: string; hint: string }>;
  reducedMotion?: boolean;
}

See the accessibility guide for consumption patterns.

Example payloads

Text frame

{
  "meta": { "id": "intro", "title": "The Opening" },
  "action": { "type": "text", "speaker": "Narrator", "content": "Welcome, Aria." },
  "state": {
    "ctx": { "player": { "name": "Aria", "hp": 100 } },
    "currentSceneId": "intro",
    "currentActionIndex": 0,
    "history": []
  },
  "a11y": { "role": "dialog", "liveRegion": "assertive", "label": "Narrator says: Welcome, Aria." }
}

Choice frame

{
  "meta": { "id": "intro" },
  "action": {
    "type": "choice",
    "choices": [
      { "id": "c_0", "label": "Enter the forest", "target": "forest" },
      { "id": "c_1", "label": "Turn back", "target": "town" }
    ]
  },
  "state": { "ctx": {}, "currentSceneId": "intro", "currentActionIndex": 3, "history": ["intro"] },
  "a11y": {
    "role": "group",
    "keyHints": [
      { "choiceId": "c_0", "hint": "Press 1 for Enter the forest" },
      { "choiceId": "c_1", "hint": "Press 2 for Turn back" }
    ]
  }
}

Tween frame

{
  "meta": { "id": "intro" },
  "action": {
    "type": "tween",
    "target": "hero",
    "property": "x",
    "to": 400,
    "duration": 800,
    "easing": "ease-in-out"
  },
  "state": { "ctx": {}, "currentSceneId": "intro", "currentActionIndex": 5, "history": ["intro"] },
  "a11y": { "description": "hero animates x", "reducedMotion": false }
}

Engine events

KataEngine extends EventEmitter. Subscribe with:

engine.on("update", (frame: KSONFrame) => {});
engine.on("end", () => {});
engine.on("audio", (cmd: AudioCommand) => {});
engine.on("error", (err: Error) => {});
engine.on("preload", (assetIds: string[]) => {});

update is the one your renderer cares about. audio is a convenience channel for UIs that route audio through a side-channel instead of through frame actions.

Snapshots

interface GameStateSnapshot {
  schemaVersion: number;
  ctx: Record<string, any>;
  currentSceneId: string | null;
  currentActionIndex: number;
  history: string[];
  expandedActions?: KSONAction[];
  undoStack?: UndoEntry[];
  locale?: string;
  localeFallback?: string;
}

SnapshotManager handles save/load with Zod validation and migrations. See the save & load guide.