{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "title": "Kinoca mocap frame",
  "description": "A single motion capture frame, as sent over UDP or written as one line of a file dump. A frame contains either 3D keypoints (features) or, when a 3D model is set up for inverse kinematics, bone transforms (bones).",
  "type": "object",
  "properties": {
    "version": {
      "description": "Version of Kinoca that produced the frame.",
      "type": "string",
      "examples": ["1.0.0"]
    },
    "timestamp_us": {
      "description": "Capture time of the frame in microseconds.",
      "type": "integer"
    },
    "frame": {
      "description": "Index of the frame, starting at 0 when motion capture starts.",
      "type": "integer",
      "minimum": 0
    },
    "features": {
      "description": "Triangulated 3D keypoints keyed by keypoint name (e.g. \"nose\", \"left_shoulder\"). Positions are [x, y, z] in meters, in the world reference frame. Keypoints that could not be triangulated are omitted, and the object is empty when nobody is detected.",
      "type": "object",
      "additionalProperties": { "$ref": "#/$defs/vec3" }
    },
    "bones": {
      "description": "Model-space transforms of the mapped bones, keyed by the node name in the 3D model.",
      "type": "object",
      "additionalProperties": { "$ref": "#/$defs/mat4" }
    }
  },
  "required": ["version", "timestamp_us", "frame"],
  "oneOf": [
    { "required": ["features"] },
    { "required": ["bones"] }
  ],
  "$defs": {
    "vec3": {
      "type": "array",
      "items": { "type": "number" },
      "minItems": 3,
      "maxItems": 3
    },
    "mat4": {
      "description": "4x4 homogeneous transform as an array of 4 rows.",
      "type": "array",
      "items": {
        "type": "array",
        "items": { "type": "number" },
        "minItems": 4,
        "maxItems": 4
      },
      "minItems": 4,
      "maxItems": 4
    }
  }
}
