JSON Schema (v1)
Canonical schema: https://opencoachingformat.org/schema/v1.json
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "https://opencoachingformat.org/schema/v1.json",
"title": "Open Coaching Format",
"description": "Open standard for basketball drill diagrams and animations. Semantic action model.",
"type": "object",
"required": [
"meta",
"court",
"entities",
"frames"
],
"definitions": {
"ruleset": {
"type": "string",
"enum": [
"fiba",
"nba",
"ncaa",
"nfhs",
"custom"
],
"description": "Basketball ruleset. Determines unit, field dimensions and named position coordinates."
},
"unit": {
"type": "string",
"enum": [
"m",
"ft"
],
"description": "Length unit used for all coordinates in this document. Derived from ruleset unless custom."
},
"coordinate_free": {
"type": "object",
"description": "Absolute coordinate in court units. Origin at center of court. x: negative=viewer's left, positive=viewer's right. y: positive=frontcourt (offense basket), negative=backcourt.",
"required": [
"x",
"y"
],
"properties": {
"x": {
"type": "number"
},
"y": {
"type": "number"
}
},
"additionalProperties": false
},
"coordinate_named": {
"type": "object",
"description": "Reference to a named court position resolved via the active ruleset. Custom positions use 'custom.' prefix.",
"required": [
"named"
],
"properties": {
"named": {
"type": "string"
}
},
"additionalProperties": false
},
"coordinate_relative": {
"type": "object",
"description": "Offset from a named court position, in court units.",
"required": [
"relative_to",
"dx",
"dy"
],
"properties": {
"relative_to": {
"type": "string"
},
"dx": {
"type": "number"
},
"dy": {
"type": "number"
}
},
"additionalProperties": false
},
"coordinate": {
"description": "A single point: absolute, named, or relative.",
"oneOf": [
{
"$ref": "#/definitions/coordinate_free"
},
{
"$ref": "#/definitions/coordinate_named"
},
{
"$ref": "#/definitions/coordinate_relative"
}
]
},
"entity_ref": {
"type": "string",
"description": "Reference to a player, coach, cone or station. Format '{offense|defense}_{1-9}', 'coach', 'cone_N', 'station_N'.",
"pattern": "^(offense|defense)_[1-9]$|^coach$|^(cone|station)_[1-9][0-9]*$"
},
"ball_ref": {
"type": "string",
"description": "Reference to a ball by id, e.g. 'ball_1'.",
"pattern": "^ball_[1-9][0-9]*$"
},
"color_role": {
"type": "string",
"enum": [
"offense",
"defense",
"black",
"grey",
"yellow",
"green",
"red",
"blue",
"white"
],
"description": "Semantic color role. Resolved to hex via color_scheme."
},
"entity_offense": {
"type": "object",
"description": "Offensive player.",
"required": [
"type",
"nr",
"x",
"y"
],
"properties": {
"type": {
"type": "string",
"const": "offense"
},
"nr": {
"type": "integer",
"minimum": 1,
"maximum": 9
},
"x": {
"type": "number"
},
"y": {
"type": "number"
},
"rotation": {
"type": "number",
"minimum": 0,
"maximum": 360,
"default": 0
},
"color": {
"$ref": "#/definitions/color_role",
"default": "offense"
},
"label": {
"type": "string"
}
},
"additionalProperties": false
},
"entity_defense": {
"type": "object",
"description": "Defensive player.",
"required": [
"type",
"nr",
"x",
"y"
],
"properties": {
"type": {
"type": "string",
"const": "defense"
},
"nr": {
"type": "integer",
"minimum": 1,
"maximum": 9
},
"x": {
"type": "number"
},
"y": {
"type": "number"
},
"rotation": {
"type": "number",
"minimum": 0,
"maximum": 360,
"default": 0
},
"color": {
"$ref": "#/definitions/color_role",
"default": "defense"
},
"label": {
"type": "string"
}
},
"additionalProperties": false
},
"entity_coach": {
"type": "object",
"description": "Coach position marker. May carry a ball.",
"required": [
"type",
"x",
"y"
],
"properties": {
"type": {
"type": "string",
"const": "coach"
},
"x": {
"type": "number"
},
"y": {
"type": "number"
}
},
"additionalProperties": false
},
"entity_cone": {
"type": "object",
"description": "Training cone / marker.",
"required": [
"type",
"nr",
"x",
"y"
],
"properties": {
"type": {
"type": "string",
"const": "cone"
},
"nr": {
"type": "integer",
"minimum": 1
},
"x": {
"type": "number"
},
"y": {
"type": "number"
}
},
"additionalProperties": false
},
"entity_station": {
"type": "object",
"description": "Numbered station for circuit training.",
"required": [
"type",
"nr",
"x",
"y"
],
"properties": {
"type": {
"type": "string",
"const": "station"
},
"nr": {
"type": "integer",
"minimum": 1
},
"label": {
"type": "string"
},
"x": {
"type": "number"
},
"y": {
"type": "number"
}
},
"additionalProperties": false
},
"entity": {
"description": "Any non-ball entity on the court.",
"oneOf": [
{
"$ref": "#/definitions/entity_offense"
},
{
"$ref": "#/definitions/entity_defense"
},
{
"$ref": "#/definitions/entity_coach"
},
{
"$ref": "#/definitions/entity_cone"
},
{
"$ref": "#/definitions/entity_station"
}
]
},
"ball": {
"type": "object",
"description": "A ball. Holds exactly one lifecycle state: carried_by, at, or dead.",
"required": [
"id"
],
"properties": {
"id": {
"$ref": "#/definitions/ball_ref"
},
"carried_by": {
"$ref": "#/definitions/entity_ref"
},
"at": {
"$ref": "#/definitions/coordinate"
},
"dead": {
"type": "boolean",
"const": true
}
},
"additionalProperties": false,
"oneOf": [
{
"required": [
"carried_by"
],
"not": {
"anyOf": [
{
"required": [
"at"
]
},
{
"required": [
"dead"
]
}
]
}
},
{
"required": [
"at"
],
"not": {
"anyOf": [
{
"required": [
"carried_by"
]
},
{
"required": [
"dead"
]
}
]
}
},
{
"required": [
"dead"
],
"not": {
"anyOf": [
{
"required": [
"carried_by"
]
},
{
"required": [
"at"
]
}
]
}
}
]
},
"ball_state": {
"type": "object",
"description": "A ball's lifecycle state inside a frame state, keyed by ball ref. Exactly one of carried_by/at/dead.",
"properties": {
"carried_by": {
"$ref": "#/definitions/entity_ref"
},
"at": {
"$ref": "#/definitions/coordinate"
},
"dead": {
"type": "boolean",
"const": true
}
},
"additionalProperties": false,
"oneOf": [
{
"required": [
"carried_by"
],
"not": {
"anyOf": [
{
"required": [
"at"
]
},
{
"required": [
"dead"
]
}
]
}
},
{
"required": [
"at"
],
"not": {
"anyOf": [
{
"required": [
"carried_by"
]
},
{
"required": [
"dead"
]
}
]
}
},
{
"required": [
"dead"
],
"not": {
"anyOf": [
{
"required": [
"carried_by"
]
},
{
"required": [
"at"
]
}
]
}
}
]
},
"state": {
"type": "object",
"description": "Positional + ball anchor. Entity refs map to a coordinate; the optional 'balls' map keys ball refs to ball states.",
"properties": {
"balls": {
"type": "object",
"propertyNames": {
"$ref": "#/definitions/ball_ref"
},
"additionalProperties": {
"$ref": "#/definitions/ball_state"
}
}
},
"patternProperties": {
"^(offense|defense)_[1-9]$|^coach$|^(cone|station)_[1-9][0-9]*$": {
"$ref": "#/definitions/coordinate"
}
},
"additionalProperties": false
},
"action_ref": {
"type": "string",
"description": "Reference to another action in the same frame: '<entity_ref>.<action_type>'.",
"pattern": "^((offense|defense)_[1-9]|coach|(cone|station)_[1-9][0-9]*)\\.(move|cut|screen|defend|dribble|pass|shoot|rebound|pickup)$"
},
"movement_intensity": {
"type": "string",
"description": "Relative tempo over distance for move/cut/dribble. Does not imply a concrete duration — a downstream application derives timing from this plus actual player speed.",
"enum": [
"slow",
"normal",
"fast",
"explosive"
]
},
"ball_intensity": {
"type": "string",
"description": "Relative ball speed for pass/shoot. Independent of the pass 'variant' (technique/flight path), e.g. variant 'lob' + intensity 'soft' are separate axes.",
"enum": [
"soft",
"normal",
"hard",
"bullet"
]
},
"physicality": {
"type": "string",
"description": "Contact/collision style for screen/defend/rebound/pickup, used by a renderer to pick the right animation. Independent of intensity — no effect on distance/timing.",
"enum": [
"passive",
"normal",
"aggressive",
"hard"
]
},
"move_step": {
"type": "object",
"description": "One step in a movement sequence. Without 'to' = a move on the spot. Reference fields override the action-level ones.",
"properties": {
"variant": {
"type": "string"
},
"to": {
"$ref": "#/definitions/coordinate"
},
"around_player": {
"$ref": "#/definitions/entity_ref"
},
"off_screen_by": {
"$ref": "#/definitions/entity_ref"
},
"intensity": {
"$ref": "#/definitions/movement_intensity"
}
},
"additionalProperties": false
},
"action_move": {
"type": "object",
"required": [
"player",
"type",
"moves"
],
"properties": {
"player": {
"$ref": "#/definitions/entity_ref"
},
"type": {
"const": "move"
},
"moves": {
"type": "array",
"minItems": 1,
"items": {
"$ref": "#/definitions/move_step"
}
},
"intensity": {
"$ref": "#/definitions/movement_intensity"
},
"tags": {
"type": "array",
"items": {
"type": "string"
}
},
"after": {
"$ref": "#/definitions/action_ref"
},
"with": {
"$ref": "#/definitions/action_ref"
},
"on_catch": {
"type": "boolean"
}
},
"additionalProperties": false
},
"action_cut": {
"type": "object",
"required": [
"player",
"type",
"moves"
],
"properties": {
"player": {
"$ref": "#/definitions/entity_ref"
},
"type": {
"const": "cut"
},
"moves": {
"type": "array",
"minItems": 1,
"items": {
"$ref": "#/definitions/move_step"
}
},
"variant": {
"type": "string",
"enum": [
"backdoor",
"give_and_go",
"flash",
"v_cut",
"l_cut",
"curl",
"flare",
"fade",
"basket"
]
},
"around_player": {
"$ref": "#/definitions/entity_ref"
},
"off_screen_by": {
"$ref": "#/definitions/entity_ref"
},
"intensity": {
"$ref": "#/definitions/movement_intensity"
},
"tags": {
"type": "array",
"items": {
"type": "string"
}
},
"after": {
"$ref": "#/definitions/action_ref"
},
"with": {
"$ref": "#/definitions/action_ref"
},
"on_catch": {
"type": "boolean"
}
},
"additionalProperties": false
},
"action_screen": {
"type": "object",
"required": [
"player",
"type",
"for_player"
],
"properties": {
"player": {
"$ref": "#/definitions/entity_ref"
},
"type": {
"const": "screen"
},
"for_player": {
"$ref": "#/definitions/entity_ref"
},
"on_player": {
"$ref": "#/definitions/entity_ref"
},
"at": {
"$ref": "#/definitions/coordinate"
},
"variant": {
"type": "string",
"enum": [
"ball_screen",
"back_screen",
"down_screen",
"flare_screen",
"cross_screen",
"pin_down"
]
},
"physicality": {
"$ref": "#/definitions/physicality"
},
"tags": {
"type": "array",
"items": {
"type": "string"
}
},
"after": {
"$ref": "#/definitions/action_ref"
},
"with": {
"$ref": "#/definitions/action_ref"
},
"on_catch": {
"type": "boolean"
}
},
"additionalProperties": false
},
"action_defend": {
"type": "object",
"required": [
"player",
"type",
"guards_player"
],
"properties": {
"player": {
"$ref": "#/definitions/entity_ref"
},
"type": {
"const": "defend"
},
"guards_player": {
"$ref": "#/definitions/entity_ref"
},
"variant": {
"type": "string",
"enum": [
"on_ball",
"deny",
"help",
"hedge",
"switch",
"box_out"
]
},
"physicality": {
"$ref": "#/definitions/physicality"
},
"tags": {
"type": "array",
"items": {
"type": "string"
}
},
"after": {
"$ref": "#/definitions/action_ref"
},
"with": {
"$ref": "#/definitions/action_ref"
},
"on_catch": {
"type": "boolean"
}
},
"additionalProperties": false
},
"action_dribble": {
"type": "object",
"required": [
"player",
"type",
"moves"
],
"properties": {
"player": {
"$ref": "#/definitions/entity_ref"
},
"type": {
"const": "dribble"
},
"moves": {
"type": "array",
"minItems": 1,
"items": {
"$ref": "#/definitions/move_step"
}
},
"ball_id": {
"$ref": "#/definitions/ball_ref"
},
"intensity": {
"$ref": "#/definitions/movement_intensity"
},
"tags": {
"type": "array",
"items": {
"type": "string"
}
},
"after": {
"$ref": "#/definitions/action_ref"
},
"with": {
"$ref": "#/definitions/action_ref"
},
"on_catch": {
"type": "boolean"
}
},
"additionalProperties": false
},
"action_pass": {
"type": "object",
"required": [
"player",
"type",
"to_player"
],
"properties": {
"player": {
"$ref": "#/definitions/entity_ref"
},
"type": {
"const": "pass"
},
"to_player": {
"$ref": "#/definitions/entity_ref"
},
"ball_id": {
"$ref": "#/definitions/ball_ref"
},
"variant": {
"type": "string",
"enum": [
"chest",
"bounce",
"overhead",
"lob",
"baseball",
"hand_off",
"outlet"
]
},
"intensity": {
"$ref": "#/definitions/ball_intensity"
},
"tags": {
"type": "array",
"items": {
"type": "string"
}
},
"after": {
"$ref": "#/definitions/action_ref"
},
"with": {
"$ref": "#/definitions/action_ref"
},
"on_catch": {
"type": "boolean"
}
},
"additionalProperties": false
},
"action_shoot": {
"type": "object",
"required": [
"player",
"type"
],
"properties": {
"player": {
"$ref": "#/definitions/entity_ref"
},
"type": {
"const": "shoot"
},
"ball_id": {
"$ref": "#/definitions/ball_ref"
},
"variant": {
"type": "string",
"enum": [
"jumper",
"three",
"layup",
"floater",
"dunk",
"hook",
"free_throw"
]
},
"result": {
"type": "string",
"enum": [
"make",
"miss"
]
},
"intensity": {
"$ref": "#/definitions/ball_intensity"
},
"tags": {
"type": "array",
"items": {
"type": "string"
}
},
"after": {
"$ref": "#/definitions/action_ref"
},
"with": {
"$ref": "#/definitions/action_ref"
},
"on_catch": {
"type": "boolean"
}
},
"additionalProperties": false
},
"action_rebound": {
"type": "object",
"required": [
"player",
"type"
],
"properties": {
"player": {
"$ref": "#/definitions/entity_ref"
},
"type": {
"const": "rebound"
},
"ball_id": {
"$ref": "#/definitions/ball_ref"
},
"variant": {
"type": "string",
"enum": [
"offensive",
"defensive"
]
},
"physicality": {
"$ref": "#/definitions/physicality"
},
"tags": {
"type": "array",
"items": {
"type": "string"
}
},
"after": {
"$ref": "#/definitions/action_ref"
},
"with": {
"$ref": "#/definitions/action_ref"
},
"on_catch": {
"type": "boolean"
}
},
"additionalProperties": false
},
"action_pickup": {
"type": "object",
"required": [
"player",
"type",
"ball_id"
],
"properties": {
"player": {
"$ref": "#/definitions/entity_ref"
},
"type": {
"const": "pickup"
},
"ball_id": {
"$ref": "#/definitions/ball_ref"
},
"physicality": {
"$ref": "#/definitions/physicality"
},
"tags": {
"type": "array",
"items": {
"type": "string"
}
},
"after": {
"$ref": "#/definitions/action_ref"
},
"with": {
"$ref": "#/definitions/action_ref"
},
"on_catch": {
"type": "boolean"
}
},
"additionalProperties": false
},
"action": {
"description": "Any action. Discriminated by 'type'.",
"oneOf": [
{
"$ref": "#/definitions/action_move"
},
{
"$ref": "#/definitions/action_cut"
},
{
"$ref": "#/definitions/action_screen"
},
{
"$ref": "#/definitions/action_defend"
},
{
"$ref": "#/definitions/action_dribble"
},
{
"$ref": "#/definitions/action_pass"
},
{
"$ref": "#/definitions/action_shoot"
},
{
"$ref": "#/definitions/action_rebound"
},
{
"$ref": "#/definitions/action_pickup"
}
]
},
"custom_position": {
"type": "object",
"required": [
"x",
"y"
],
"properties": {
"x": {
"type": "number"
},
"y": {
"type": "number"
},
"description": {
"type": "string"
}
},
"additionalProperties": false
},
"color_scheme": {
"type": "object",
"description": "Hex color values for each semantic role.",
"properties": {
"offense_fill": {
"type": "string",
"pattern": "^#[0-9a-fA-F]{6}$",
"default": "#003366"
},
"offense_stroke": {
"type": "string",
"pattern": "^#[0-9a-fA-F]{6}$",
"default": "#ffffff"
},
"defense_fill": {
"type": "string",
"pattern": "^#[0-9a-fA-F]{6}$",
"default": "#58001d"
},
"defense_stroke": {
"type": "string",
"pattern": "^#[0-9a-fA-F]{6}$",
"default": "#ffffff"
},
"black": {
"type": "string",
"pattern": "^#[0-9a-fA-F]{6}$",
"default": "#000000"
},
"grey": {
"type": "string",
"pattern": "^#[0-9a-fA-F]{6}$",
"default": "#7f7f7f"
},
"yellow": {
"type": "string",
"pattern": "^#[0-9a-fA-F]{6}$",
"default": "#ffff00"
},
"green": {
"type": "string",
"pattern": "^#[0-9a-fA-F]{6}$",
"default": "#7ce86a"
},
"red": {
"type": "string",
"pattern": "^#[0-9a-fA-F]{6}$",
"default": "#ff0000"
},
"blue": {
"type": "string",
"pattern": "^#[0-9a-fA-F]{6}$",
"default": "#5dd5ff"
},
"white": {
"type": "string",
"pattern": "^#[0-9a-fA-F]{6}$",
"default": "#ffffff"
}
},
"additionalProperties": false
},
"area": {
"type": "object",
"required": [
"form",
"x",
"y"
],
"properties": {
"form": {
"type": "string",
"enum": [
"rectangle",
"ellipse",
"triangle"
]
},
"color": {
"$ref": "#/definitions/color_role",
"default": "yellow"
},
"opacity": {
"type": "number",
"minimum": 0,
"maximum": 1,
"default": 0.4
},
"x": {
"type": "number"
},
"y": {
"type": "number"
},
"width": {
"type": "number"
},
"height": {
"type": "number"
},
"rotation": {
"type": "number",
"minimum": 0,
"maximum": 360,
"default": 0
},
"coords": {
"type": "array",
"minItems": 3,
"maxItems": 3,
"items": {
"$ref": "#/definitions/coordinate"
}
}
},
"additionalProperties": false
},
"label": {
"type": "object",
"required": [
"text",
"x",
"y"
],
"properties": {
"text": {
"type": "string"
},
"x": {
"type": "number"
},
"y": {
"type": "number"
},
"color": {
"$ref": "#/definitions/color_role",
"default": "black"
}
},
"additionalProperties": false
},
"outcome": {
"type": "string",
"enum": [
"make",
"miss",
"turnover",
"steal",
"foul"
],
"description": "Result that selects a branch target frame."
},
"frame": {
"type": "object",
"description": "A coaching phase: explicit anchors (start_state/end_state) plus semantic actions, with optional outcome branches.",
"required": [
"id",
"actions",
"end_state"
],
"properties": {
"id": {
"type": "string",
"description": "Unique frame id within the drill."
},
"label": {
"type": "string"
},
"description": {
"type": "string",
"description": "Coaching instruction; LLMs should generate this."
},
"duration_ms": {
"type": "integer",
"minimum": 0,
"description": "Deprecated: use per-action 'intensity' instead. Suggested animation duration for the phase."
},
"start_state": {
"$ref": "#/definitions/state",
"description": "Optional. Defaults to the previous frame's end_state."
},
"actions": {
"type": "array",
"description": "Semantic actions in this phase. May be empty.",
"items": {
"$ref": "#/definitions/action"
}
},
"end_state": {
"$ref": "#/definitions/state",
"description": "Explicit anchor at the end of the phase."
},
"branches": {
"type": "object",
"description": "Optional outcome -> target frame id. Without branches, the next frame in the array follows.",
"propertyNames": {
"$ref": "#/definitions/outcome"
},
"additionalProperties": {
"type": "string"
},
"minProperties": 1
}
},
"additionalProperties": false
}
},
"properties": {
"$schema": {
"type": "string",
"format": "uri"
},
"meta": {
"type": "object",
"required": [
"id",
"title"
],
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"title": {
"type": "string"
},
"description": {
"type": "string"
},
"author": {
"type": "string"
},
"tags": {
"type": "array",
"items": {
"type": "string"
}
},
"difficulty": {
"type": "string",
"enum": [
"beginner",
"intermediate",
"advanced"
]
},
"created": {
"type": "string",
"format": "date-time"
},
"modified": {
"type": "string",
"format": "date-time"
},
"source_format": {
"type": "string",
"enum": [
"fiba",
"fastdraw",
"open",
"custom"
]
},
"source_url": {
"type": "string",
"format": "uri"
}
},
"additionalProperties": false
},
"court": {
"type": "object",
"required": [
"ruleset",
"type"
],
"properties": {
"ruleset": {
"$ref": "#/definitions/ruleset"
},
"type": {
"type": "string",
"enum": [
"half_court",
"full_court"
]
},
"drill_focus": {
"type": "string",
"enum": [
"offense",
"defense",
"transition",
"neutral"
],
"default": "offense"
},
"wheelchair": {
"type": "boolean",
"default": false
},
"custom_dimensions": {
"type": "object",
"required": [
"unit",
"length",
"width",
"basket_from_baseline",
"three_point_distance",
"paint_width",
"paint_depth",
"free_throw_distance"
],
"properties": {
"unit": {
"$ref": "#/definitions/unit"
},
"length": {
"type": "number"
},
"width": {
"type": "number"
},
"basket_from_baseline": {
"type": "number"
},
"three_point_distance": {
"type": "number"
},
"paint_width": {
"type": "number"
},
"paint_depth": {
"type": "number"
},
"free_throw_distance": {
"type": "number"
}
},
"additionalProperties": false
}
},
"additionalProperties": false
},
"color_scheme": {
"$ref": "#/definitions/color_scheme"
},
"named_positions": {
"type": "object",
"properties": {
"custom": {
"type": "object",
"additionalProperties": {
"$ref": "#/definitions/custom_position"
}
}
},
"additionalProperties": false
},
"entities": {
"type": "array",
"description": "All non-ball entities and their initial (frame 0) positions.",
"items": {
"$ref": "#/definitions/entity"
}
},
"balls": {
"type": "array",
"description": "Balls present at the start of the drill. Each holds exactly one lifecycle state.",
"items": {
"$ref": "#/definitions/ball"
}
},
"frames": {
"type": "array",
"minItems": 1,
"items": {
"$ref": "#/definitions/frame"
}
},
"areas": {
"type": "array",
"items": {
"$ref": "#/definitions/area"
}
},
"labels": {
"type": "array",
"items": {
"$ref": "#/definitions/label"
}
}
},
"additionalProperties": false,
"if": {
"type": "object",
"properties": {
"court": {
"type": "object",
"properties": {
"ruleset": {
"const": "custom"
}
}
}
}
},
"then": {
"type": "object",
"properties": {
"court": {
"type": "object",
"required": [
"custom_dimensions"
]
}
}
}
}