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
| Type | Engine behavior | UI behavior |
|---|---|---|
text | Waits for engine.next() | Render dialogue; advance on click/keypress |
choice | Waits for engine.makeChoice(id) | Render buttons; advance on selection |
visual | Auto-advances | Update scene background/character layer |
wait | Auto-advances after duration | Optional — delay transitions to match |
exec | Runs code, auto-advances | Nothing — state change will reflect in next frame |
condition | Expands into then/elseIf/else branch at runtime; UI never sees this type directly | — |
audio | Auto-advances | Play/stop via your AudioManager |
tween, tween-group | Auto-advances | Fire 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.