cappy.

Cappy Adapter Protocol

Version 1. This is the contract a game adapter implements to be driven by Cappy. The schemas in packages/protocol/src/messages.ts (private workspace @uppercut-labs/cappy-internal-protocol) are authoritative; this page explains them.

Transport

Handshake

The adapter's first message is hello:

{
  "type": "hello",
  "protocol": { "min": 1, "max": 1 },
  "token": "<CAPPY_SESSION_TOKEN>",
  "adapter": { "name": "cappy-godot", "version": "0.1.0" },
  "game": { "id": "fantasy-party", "name": "Fantasy Party" },
  "build": "optional build identity",
  "capabilities": ["scenarios", "freeform_recording", "replay"]
}

Cappy replies with one of:

Unknown hello fields and unknown capability names are accepted, so newer adapters work with older controllers. A missing capability fails the handshake only when the project requires it.

After welcome, the adapter should send its scenario registry (scenarios).

Cappy may send its first request immediately after welcome, often in the same network read. Adapters must keep reading and processing messages from the moment the socket opens; a message that arrives right behind the welcome must not be dropped.

Operations

At most one operation is active at a time. Cappy creates an operation ID (op), and every message about that operation carries it.

Operation Cappy sends Adapter replies
Authored scenario prepare_scenario {op, scenario, parameters, presentation?} ready, then after start: started, event*, completed or failed
Freeform recording record_start {op} started, event*, then after stop: completed with replay
Replay prepare_replay {op, replay, presentation?} ready, then after start: started, event*, completed or failed

Events

{ "type": "event", "op": "op_…", "t": 350, "event": "SPELL_CAST", "durationMs": 120, "payload": { "spell": "fireball" } }

t is operation-relative monotonic milliseconds, never wall-clock time. Cappy stamps each event with its receipt order (seq) and the operation's correlation ID.

t is presented time: what is on screen, which is the recorder's clock. It equals simulation time unless presentation changes the speed. At timeScale 0.5 an event 1000 ms into the simulation is sent with t 2000. An adapter may add the simulation time to the payload as simT.

Presentation

prepare_scenario and prepare_replay may carry presentation, an object from the capture preset. Cappy defines two keys; any others are the adapter's own:

Cappy checks those capabilities before recording. A replay played with a presentation must be the same simulation; only what is shown, and when, changes.

Replay payloads

Replay payloads belong to the adapter. Cappy stores and returns them without interpreting them.

Scenario registry

{
  "type": "scenarios",
  "scenarios": [
    {
      "id": "boss_intro",
      "name": "Boss intro",
      "parameters": { "difficulty": { "type": "integer", "minimum": 1, "maximum": 3, "default": 2 } },
      "requiredCapabilities": ["scenarios"]
    }
  ]
}

Parameter types are string, number, integer, and boolean, with optional required, default, enum, minimum, and maximum. Cappy may request the registry again at any time with list_scenarios.

Liveness

Cappy sends { "type": "ping", "nonce": "…" } every 2 seconds. The adapter must answer { "type": "pong", "nonce": "…" }. If no pong arrives for 6 seconds, the adapter is treated as unresponsive: the active operation fails and the connection is dropped.

Failures

Malformed JSON, schema violations, and messages that do not fit the current operation state fail the active operation with a structured error (PROTOCOL_INVALID_MESSAGE or PROTOCOL_OUT_OF_STATE). Cappy also tells the adapter with { "type": "error", "code", "message", "op"? }. A completed before started can never count as success. A disconnect fails the active operation with ADAPTER_DISCONNECTED.

Setup prompt

Read the guide