viewer's left viewer's right
| |
defense basket ─────┼─────────────────────┼───── y = -max
(backcourt) │ │
│ BACKCOURT │
│ y < 0 │
─────┼─────────────────────┼───── y = 0 (midline / origin)
x=-max x=0 x=+max
│ FRONTCOURT │
│ y > 0 │
offense basket ─────┼─────────────────────┼───── y = +max
(frontcourt) │ │
Open Coaching Format — Open Standard for Basketball Drill Diagrams
1. Abstract
{ocf} v1.0.0 is an open, ruleset-aware JSON schema for representing basketball drill diagrams and multi-step animations. It defines a court coordinate system based on real-world units (meters or feet), a registry of named court positions per ruleset, a semantic entity model, and a frame-based animation structure.
The format is designed to be:
-
Human-readable — coaching terminology maps directly to named positions
-
LLM-friendly — structured enough for text-to-diagram generation
-
Renderer-agnostic — SVG, Canvas, PDF, mobile
-
Ruleset-aware — FIBA, NBA, NCAA, NFHS, custom
-
FIBA-compatible — import/export path for existing FIBA Europe diagrams
2. Introduction
2.1. Motivation
No open standard exists for basketball drill diagrams. All major tools (FIBA Europe Coaching App, FastDraw, Sportplan.net) use proprietary formats with image-only export. This prevents programmatic drill creation, LLM-assisted coaching tools, and cross-platform interoperability.
{ocf} addresses this gap by providing:
-
A well-defined JSON Schema (Draft-7)
-
A coordinate system grounded in real-world units
-
A named position registry aligned with official ruleset geometry
-
A frame model for multi-step animated drills
-
A clear import path from the FIBA Europe internal format
2.2. Design Principles
| Principle | Decision |
|---|---|
Court units = real units |
Coordinates in meters (FIBA) or feet (NBA/NCAA). No arbitrary pixel grids. |
Origin = midcourt center |
Positive y = frontcourt (offense). Negative y = backcourt (defense). Zero = midline. |
Viewer perspective |
"Left" and "right" are always from the viewer’s top-down perspective. |
Named positions over raw coordinates |
Standard court landmarks (e.g. |
Entity keys = team + number |
|
Frames are anchored |
Each frame carries explicit |
Colors are semantic roles |
|
Registry over fixed enum |
Named positions use a known-good standard catalog + user-extensible custom block. |
3. Coordinate System
3.1. Overview
The {ocf} coordinate system places the origin at the center of the court (midcourt center circle).
| Axis | Definition |
|---|---|
x |
Horizontal. |
y |
Vertical. |
3.2. Coordinate Ranges per Ruleset
| Ruleset | Unit | x range | y range (half / full) |
|---|---|---|---|
|
m |
−7.5 … +7.5 |
0 … +14.0 / −14.0 … +14.0 |
|
ft |
−25.0 … +25.0 |
0 … +47.0 / −47.0 … +47.0 |
|
ft |
−25.0 … +25.0 |
0 … +47.0 / −47.0 … +47.0 |
|
ft |
−25.0 … +25.0 |
0 … +42.0 / −42.0 … +42.0 |
For half court diagrams, only the frontcourt (y ≥ 0) is rendered.
Entities placed at negative y coordinates in a half_court document are outside the visible area.
|
3.3. Field Dimensions Reference
FIBA Half Court — values in meters, origin at midline center
┌─────────────────────────────────────────────────────────┐ y = 0.0
│ x=0 │
│ midcourt.left midcourt.center midcourt.right
│ (-7.5,0) (0,0) (7.5,0) │
│ │
│ TOP OF THE KEY │
│ (0.0, 5.68) │
│ left_wing right_wing │
│ (-6.75, 8.60) (6.75, 8.60) │
│ │
│ left_corner ─ ─ FREE THROW LINE ─ ─ right_corner │
│ (0, 8.20) │
│ left_elbow right_elbow │
│ (-2.45, 8.20) (2.45, 8.20) │
│ ┌───────────────────┐ │
│ │ PAINT AREA │ │
│ left_block │ │ right_block │
│ (-2.45, 11.00) │ paint_center │ (2.45, 11.00) │
│ │ (0, 10.50) │ │
│ └───────────────────┘ │
│ left_corner right_corner │
│ (-7.5, 13.98) basket (7.5, 13.98) │
│ (0, 12.43) │
└─────────────────────────────────────────────────────────┘ y = +14.0
3.4. Coordinate Types
A coordinate in {ocf} can be one of three forms:
3.4.1. Absolute
{ "x": 2.45, "y": 8.20 }
A fixed point in court units. Always valid.
3.4.2. Named
{ "named": "left_elbow" }
References a standard named position. Resolved to absolute coordinates at render time using the document’s ruleset.
Custom positions use the custom. prefix:
{ "named": "custom.dunker_spot" }
3.4.3. Relative
{
"relative_to": "left_elbow",
"dx": 1.0,
"dy": -0.5
}
An offset from a named position.
dx and dy are in the document’s court unit.
Positive dx = viewer’s right. Positive dy = toward offense basket.
4. Named Court Positions
4.1. Standard Positions (FIBA)
All coordinates in meters (m). Backcourt positions are the exact y-negation of their frontcourt counterparts.
| Name | x (m) | y (m) | Description |
|---|---|---|---|
Paint / Near Basket |
|||
|
0.000 |
12.425 |
Center of basket ring |
|
-2.450 |
11.000 |
Low post, left side |
|
2.450 |
11.000 |
Low post, right side |
|
0.000 |
10.500 |
Center of the lane |
|
-7.500 |
11.500 |
Baseline region, outside paint left |
|
7.500 |
11.500 |
Baseline region, outside paint right |
Mid-Range / Elbow |
|||
|
-2.450 |
8.200 |
Junction of free throw line and paint, left |
|
2.450 |
8.200 |
Junction of free throw line and paint, right |
|
0.000 |
8.200 |
Center of free throw line |
|
-2.450 |
7.000 |
Above the elbow, left side |
|
2.450 |
7.000 |
Above the elbow, right side |
3-Point Area |
|||
|
0.000 |
5.680 |
Apex of 3-point arc |
|
-6.750 |
8.600 |
3-point line, ~45° left |
|
6.750 |
8.600 |
3-point line, ~45° right |
|
-7.500 |
13.980 |
Corner 3, left |
|
7.500 |
13.980 |
Corner 3, right |
Midcourt |
|||
|
0.000 |
0.000 |
Center circle, origin |
|
-7.500 |
0.000 |
Left sideline at midline |
|
7.500 |
0.000 |
Right sideline at midline |
Backcourt (Full Court) |
|||
|
0.000 |
-12.425 |
Defense basket |
|
-2.450 |
-11.000 |
Defense left block |
|
2.450 |
-11.000 |
Defense right block |
|
-2.450 |
-8.200 |
Defense left elbow |
|
2.450 |
-8.200 |
Defense right elbow |
|
0.000 |
-8.200 |
Defense free throw line |
|
0.000 |
-5.680 |
Defense top of the key |
|
-6.750 |
-8.600 |
Defense left wing |
|
6.750 |
-8.600 |
Defense right wing |
|
-7.500 |
-13.980 |
Defense left corner |
|
7.500 |
-13.980 |
Defense right corner |
Inbound Positions |
|||
|
-3.000 |
14.000 |
Baseline inbound, left of basket |
|
3.000 |
14.000 |
Baseline inbound, right of basket |
|
0.000 |
14.000 |
Baseline inbound, center |
|
-7.500 |
8.200 |
Sideline inbound, frontcourt left |
|
7.500 |
8.200 |
Sideline inbound, frontcourt right |
|
-7.500 |
0.000 |
Sideline inbound, at midline left |
|
7.500 |
0.000 |
Sideline inbound, at midline right |
|
-7.500 |
-8.200 |
Sideline inbound, backcourt left |
|
7.500 |
-8.200 |
Sideline inbound, backcourt right |
4.2. Ruleset Differences
Key position differences across rulesets (all values in respective unit):
| Position | FIBA (m) | NBA (ft) | NCAA (ft) | NFHS (ft) |
|---|---|---|---|---|
|
5.68 |
20.75 |
20.75 |
19.75 |
|
±6.75 |
±22.15 |
±20.75 |
±19.75 |
|
±7.50 |
±25.0 |
±25.0 |
±25.0 |
|
13.98 |
46.58 |
46.58 |
41.58 |
|
8.20 |
19.00 |
19.00 |
19.00 |
|
±2.45 |
±8.0 |
±6.0 |
±6.0 |
NBA corners are defined by a straight sideline section, not an arc.
The left_corner and right_corner positions mark the junction point where the straight section meets the arc.
|
4.3. Custom Positions (Registry)
User-defined named positions are stored in the named_positions.custom block:
"named_positions": {
"custom": {
"dunker_spot": {
"x": 1.5,
"y": 12.8,
"description": "Dunker spot, weak side baseline"
},
"nail": {
"x": 0.0,
"y": 8.5,
"description": "Free throw line extended, center"
},
"short_roll": {
"x": -2.0,
"y": 9.5
}
}
}
Reference them in coordinates with the custom. prefix:
{ "named": "custom.dunker_spot" }
| Custom positions use the same unit as the document ruleset. No separate unit declaration is needed. |
5. Entities
Entities represent all non-ball objects that appear on the court.
Their initial position is defined in the top-level entities array.
Balls are stored separately in the top-level balls array (see Balls).
5.1. Entity Reference Keys
Each entity is uniquely identified by a natural key string:
| Entity | Key Format |
|---|---|
Offensive player nr. 1 |
|
Defensive player nr. 3 |
|
Coach |
|
Cone nr. 2 |
|
Station nr. 4 |
|
offense_1 and defense_1 are distinct entities. In transition drills, both can appear simultaneously.
|
5.2. Distinct Positions
Two distinct entities MUST NOT occupy the same position. Within any single state
(the top-level entities array, or a frame’s start_state / end_state), no
two entities may share coordinates that resolve closer than a minimum
separation to each other.
The minimum separation is one player diameter in the document’s court unit — the space a player symbol occupies — so that rendered symbols never overlap and each player remains individually identifiable:
| Ruleset | Minimum separation |
|---|---|
|
0.5 m |
|
1.6 ft |
A document that places two entities closer than this is invalid; a validator reports it. Two players genuinely "at the same spot" in a coach’s mind (e.g. a hand-off) are still distinct bodies and must be given distinct coordinates a symbol-width apart.
| A renderer SHOULD additionally apply a small visual nudge if symbols still touch after a valid layout (a rendering safety net), but this does not relax the data constraint above — the underlying coordinates remain the source of truth. |
5.3. Entity Types
5.3.1. Offensive Player
{
"type": "offense",
"nr": 1,
"x": 0.0,
"y": 5.68,
"rotation": 0,
"color": "offense"
}
Rendered as a filled circle with player number.
rotation is accepted for symmetry but is not rendered for offense (the
filled circle has no facing direction); it is typically 0. The field is
meaningful for defenders — see the defender rotation convention below.
color defaults to "offense" (resolved via color_scheme.offense_fill).
5.3.2. Defensive Player
{
"type": "defense",
"nr": 1,
"x": 0.5,
"y": 5.5,
"rotation": 45,
"color": "defense"
}
Rendered as a filled shape (an arc — the defender’s outstretched arms — above an
inner circle carrying the player number). rotation indicates the direction the
defender is facing, i.e. the direction the arms point.
|
Defender
rotation convention
A renderer applies |
5.3.3. Balls
Balls are no longer entities. They live in the top-level balls array and each
holds exactly one lifecycle state:
| State | Meaning |
|---|---|
|
An entity ref (player or |
|
A coordinate where the ball rests (floor, rim after a miss). Still in play — can be |
|
|
"balls": [
{ "id": "ball_1", "carried_by": "offense_1" },
{ "id": "ball_2", "carried_by": "coach" },
{ "id": "ball_3", "at": { "named": "right_corner" } }
]
Ball possession moves automatically: pass, shoot, pickup and rebound
actions transfer the ball. After a shoot with no following rebound/pickup
on the same ball, the ball becomes dead.
5.3.4. Coach
{
"type": "coach",
"x": -7.5,
"y": 6.0
}
Rendered as a circle labeled "C".
5.3.5. Cone
{
"type": "cone",
"nr": 1,
"x": 2.0,
"y": 7.0
}
Rendered as a small triangle. Used as training markers or obstacles.
5.3.6. Station
{
"type": "station",
"nr": 3,
"label": "Rebound jumps",
"x": -4.0,
"y": 11.0
}
Rendered as a numbered marker with optional description. Used for circuit training drills.
6. Actions
Movement and ball events are expressed as semantic actions, not geometric
lines. A renderer derives the drawn arrows/paths from the actions. Every action
has a required player and type; everything else is optional, so a coach can
sketch quickly or annotate in full detail.
6.1. Action Types
| Type | Ball? | Required | Optional (variant enum / references) |
|---|---|---|---|
|
no |
|
|
|
no |
|
|
|
no |
|
|
|
no |
|
|
|
yes |
|
|
|
yes |
|
|
|
yes |
|
|
|
becomes carrier |
|
|
|
becomes carrier |
|
|
Any action may also carry a free tags array (e.g. eurostep, reverse).
Curated variant values are what a renderer can distinguish visually; most tags
are open coaching metadata that need no schema change.
|
Curated tags:
left_handed / right_handed (ball-handling side)Two tags are curated because a renderer interprets them visually: on a
ball-handling action ( This drives where the ball is drawn relative to its carrier:
These tags remain ordinary entries in the free A loose ball ( |
6.2. Movement Sequences
move, cut and dribble carry a moves[] sequence. A move without to is
a move on the spot (ball handling / fake); a move with to carries the player
(and ball, for dribble) to a waypoint. The final to must match the player’s
position in the frame end_state. Reference fields (around_player,
off_screen_by) may sit on an individual move and override the action level.
For dribble (and move), each move’s variant describes that step’s character — a single dribble action can chain e.g. crossover then speed — whereas cut also has an action-level variant for the overall cut type (e.g. backdoor).
{
"player": "offense_1",
"type": "dribble",
"ball_id": "ball_1",
"moves": [
{ "variant": "crossover" },
{ "variant": "speed", "to": { "named": "basket" }, "around_player": "defense_1" }
]
}
6.3. Intensity & Physicality
Two optional, independent enums express relative execution style. Neither implies a concrete duration — a downstream application combines them with actual player/roster data to derive timing or pick renderer animations.
| Field | Applies to | Enum |
|---|---|---|
|
|
|
|
|
|
|
|
|
intensity describes tempo over distance (how fast a player/ball moves).
physicality describes contact/collision style and has no bearing on
timing — it exists purely to help a renderer pick the right animation (e.g.
a clean screen vs. a moving-screen-style rammed one). The two are
deliberately separate so a distance→time calculation built on intensity
never has to reason about physicality values.
{
"player": "offense_1",
"type": "cut",
"intensity": "slow",
"moves": [
{ "to": { "x": -2.0, "y": 4.5 } },
{ "to": { "named": "basket" }, "intensity": "explosive" }
]
}
duration_ms (see Frame Model) is deprecated in favor of intensity.
It remains valid for backward compatibility with existing play files, but
new play files should express tempo via intensity instead.
6.4. Timing
Actions express ordering semantically, not in seconds (omitted = at frame start):
| Field | Meaning |
|---|---|
|
|
|
|
|
|
7. Frames
A frame is a coaching phase. It combines explicit anchors with semantic actions.
7.1. Frame Model
| Field | Required | Meaning |
|---|---|---|
|
yes |
Unique frame id within the drill. |
|
no |
Short display label. |
|
no |
Coaching instruction. LLMs should generate this. |
|
no |
Deprecated — use per-action |
|
no |
Positional + ball anchor at phase start. Defaults to the previous frame’s |
|
yes |
Semantic actions in this phase. May be empty. |
|
yes |
Positional + ball anchor at phase end. |
|
no |
Outcome → target frame id. |
7.2. States
start_state and end_state map entity refs to coordinates, plus an optional
balls map of ball ref → ball state. Because each frame carries an explicit
anchor, any frame can be rendered as a still without simulating history — which
is what enables PDF export, editor scrubbing and video overlay.
start_state is optional and inherits the previous frame’s end_state. Set it
explicitly only at jumps, steals, rebounds or continuum resets.
"end_state": {
"offense_1": { "x": -1.0, "y": 9.0 },
"offense_4": { "named": "right_block" },
"balls": { "ball_1": { "carried_by": "offense_4" } }
}
7.3. Branches
Without branches, the next frame in the array follows. With branches, the
outcome selects the next frame. Outcomes are make, miss, turnover,
steal, foul. A branch may point at an earlier frame to build a continuum
loop. For a single set play, omit branches and let the final frame set the
ball dead.
"branches": { "make": "frame_reset", "miss": "frame_oreb" }
8. Areas and Labels
8.1. Areas
Highlighted zones for marking training areas, defensive zones, or emphasis regions.
{
"form": "rectangle",
"color": "yellow",
"opacity": 0.4,
"x": 0.0,
"y": 10.0,
"width": 4.9,
"height": 4.0
}
{
"form": "ellipse",
"color": "green",
"opacity": 0.3,
"x": 0.0,
"y": 8.2,
"width": 5.0,
"height": 3.0
}
{
"form": "triangle",
"color": "red",
"opacity": 0.25,
"x": 0,
"y": 0,
"coords": [
{ "named": "left_block" },
{ "named": "right_block" },
{ "named": "basket" }
]
}
8.2. Labels
Free text annotations on the court:
{
"text": "Pick & Roll",
"x": -7.0,
"y": 3.0,
"color": "black"
}
9. Color Scheme
9.1. Design
Colors are defined as semantic roles, not as direct hex values in entities or lines. This allows editors to customize the appearance globally without modifying individual elements.
| Role | FIBA Default | Usage |
|---|---|---|
|
|
Offensive player symbol fill |
|
|
Offensive player symbol outline + number |
|
|
Defensive player symbol fill |
|
|
Defensive player symbol outline + number |
|
|
Lines, cones, text (default) |
|
|
Subtle highlights, background areas |
|
|
Highlighted zones |
|
|
Secondary highlights |
|
|
Warning zones, alternative team color |
|
|
Alternative team color |
|
|
Backgrounds, inverted labels |
9.2. WCAG Accessibility
|
All color combinations used for rendered symbols must meet at minimum WCAG AA (4.5:1 contrast ratio). WCAG AAA (7:1) is recommended for printed materials. Validators should emit a warning when |
FIBA default — contrast analysis:
| Combination | Foreground | Background | Ratio | WCAG |
|---|---|---|---|---|
Offense number on fill |
|
|
12.6:1 |
✅ AAA |
Defense number on fill |
|
|
11.2:1 |
✅ AAA |
Black line on court |
|
|
4.6:1 |
✅ AA |
Offense fill on court |
|
|
3.8:1 |
⚠️ Fail (use stroke) |
Defense fill on court |
|
|
3.1:1 |
⚠️ Fail (use stroke) |
Renderers should apply a 2px white stroke around player symbols to compensate
for the low contrast between symbol fill and court background.
This matches the FIBA SVG renderer behavior.
|
9.3. Overriding Colors
"color_scheme": {
"offense_fill": "#1a4f8a",
"offense_stroke": "#ffffff",
"defense_fill": "#8a1a1a",
"defense_stroke": "#ffffff"
}
Only fields that differ from the FIBA defaults need to be included.
10. Court Configuration
10.1. Supported Rulesets
ruleset |
unit |
Court (L × W) | Notes |
|---|---|---|---|
|
|
28.0 × 15.0 |
International standard |
|
|
94.0 × 50.0 |
NBA-specific 3-point geometry (straight corners) |
|
|
94.0 × 50.0 |
Shorter 3-point line than NBA |
|
|
84.0 × 50.0 |
US high school, shorter court |
|
* |
defined |
Requires |
10.2. Half Court vs Full Court
type |
Rendered region |
|---|---|
|
y = 0 to +max. Offense basket at top. Midline forms the baseline. |
|
y = -max to +max. Both baskets rendered. |
10.3. Drill Focus
drill_focus |
Meaning |
|---|---|
|
Offensive drill. Positive y = direction of attack. Default. |
|
Defensive drill. Positive y = basket being defended. |
|
Full court drill with directional change. Implies |
|
No directional focus (e.g. conditioning drill, station circuit). |
10.4. Custom Ruleset
"court": {
"ruleset": "custom",
"type": "half_court",
"drill_focus": "offense",
"custom_dimensions": {
"unit": "m",
"length": 24.0,
"width": 13.0,
"basket_from_baseline": 1.575,
"three_point_distance": 5.77,
"paint_width": 3.6,
"paint_depth": 5.8,
"free_throw_distance": 5.8
}
}
11. Ruleset Conversion
Converting a drill between rulesets is an explicit, lossless operation for named positions and a best-effort proportional mapping for free coordinates.
11.1. Named Position Conversion
Named positions are looked up independently in each ruleset’s coordinate table. The semantic meaning is preserved exactly.
"left_wing" in FIBA → (-6.75m, 8.60m)
"left_wing" in NBA → (-22.15ft, 28.5ft) ← independent lookup, no math
11.2. Free Coordinate Conversion
Free absolute coordinates are converted proportionally:
x_target = x_source / (source.half_width) * target.half_width
y_target = y_source / (source.half_depth) * target.half_depth
|
Proportional conversion of free coordinates may place entities at positions that are semantically incorrect (e.g. inside or outside the 3-point line) relative to the target ruleset’s geometry. Editors should surface a warning: "X free coordinates were proportionally scaled. Please verify their positions against the target ruleset." |
11.3. FIBA Legacy Import
Existing FIBA Europe diagrams use a proprietary pixel coordinate system (384 × 540). Conversion to {ocf} FIBA coordinates:
x_m = (fiba_x - 192) / 192.0 * 7.5
y_m = (270 - fiba_y) / 270.0 * 14.0
| The FIBA coordinate origin is at the top-left of the SVG canvas. The y-axis is inverted (y increases downward in FIBA, upward in {ocf}). |
12. Complete Example
12.1. Pick & Roll — Half Court, FIBA
{
"$schema": "https://opencoachingformat.org/schema/v1.json",
"meta": {
"id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"title": "Basic Pick & Roll",
"description": "Ball handler uses a ball screen at the top of the key. Screener rolls to the basket for the finish.",
"author": "OCF Examples",
"tags": ["pick-and-roll", "offense", "half-court", "2-player", "beginner"],
"difficulty": "beginner",
"created": "2025-02-27T10:00:00Z",
"source_format": "open"
},
"court": { "ruleset": "fiba", "type": "half_court", "drill_focus": "offense" },
"entities": [
{ "type": "offense", "nr": 1, "x": 0.0, "y": 5.68 },
{ "type": "offense", "nr": 4, "x": -2.45, "y": 8.20 },
{ "type": "defense", "nr": 1, "x": 0.5, "y": 5.20 },
{ "type": "defense", "nr": 4, "x": -2.80, "y": 7.80 }
],
"balls": [ { "id": "ball_1", "carried_by": "offense_1" } ],
"frames": [
{
"id": "frame_1",
"label": "Step 1 — Screen",
"description": "offense_4 moves up from the left elbow to set a ball screen for offense_1 at the top of the key.",
"duration_ms": 1500,
"actions": [
{ "player": "offense_4", "type": "screen", "for_player": "offense_1", "on_player": "defense_1", "variant": "ball_screen", "at": { "x": -0.5, "y": 6.7 } }
],
"end_state": {
"offense_1": { "named": "top_of_the_key" },
"offense_4": { "x": -0.5, "y": 6.2 },
"balls": { "ball_1": { "carried_by": "offense_1" } }
}
},
{
"id": "frame_2",
"label": "Step 2 — Attack",
"description": "offense_1 uses the screen and dribbles hard left toward the elbow; offense_4 rolls to the right block.",
"duration_ms": 2000,
"actions": [
{
"player": "offense_1",
"type": "dribble",
"ball_id": "ball_1",
"moves": [
{ "variant": "hesitation" },
{ "variant": "speed", "to": { "named": "left_elbow" }, "around_player": "defense_1" }
]
},
{ "player": "offense_4", "type": "cut", "variant": "basket", "moves": [ { "to": { "named": "right_block" } } ], "tags": ["roll"] }
],
"end_state": {
"offense_1": { "named": "left_elbow" },
"offense_4": { "named": "right_block" },
"balls": { "ball_1": { "carried_by": "offense_1" } }
}
},
{
"id": "frame_3",
"label": "Step 3 — Finish",
"description": "offense_1 throws a bounce pass to the rolling offense_4, who finishes with a layup.",
"duration_ms": 1500,
"actions": [
{ "player": "offense_1", "type": "pass", "to_player": "offense_4", "ball_id": "ball_1", "variant": "bounce" },
{ "player": "offense_4", "type": "shoot", "ball_id": "ball_1", "variant": "layup", "result": "make", "on_catch": true }
],
"end_state": {
"offense_1": { "named": "left_elbow" },
"offense_4": { "named": "right_block" },
"balls": { "ball_1": { "dead": true } }
}
}
],
"areas": [
{ "form": "rectangle", "color": "yellow", "opacity": 0.2, "x": 0.0, "y": 10.5, "width": 4.9, "height": 4.0, "rotation": 0 }
],
"labels": [ { "text": "Roll!", "x": 3.2, "y": 9.5, "color": "black" } ]
}
13. Schema Reference
13.1. Top-Level Structure
@startuml
skinparam backgroundColor white
skinparam defaultFontName Helvetica
skinparam classBackgroundColor #f8f8f8
skinparam classBorderColor #aaaaaa
class Drill {
+meta: Meta
+court: Court
+entities: Entity[]
+balls: Ball[]
+frames: Frame[]
--
+color_scheme: ColorScheme?
+named_positions: NamedPositions?
+areas: Area[]?
+labels: Label[]?
}
class Meta {
+id: uuid
+title: string
--
+description: string?
+author: string?
+tags: string[]?
+difficulty: enum?
+created: datetime?
+source_format: enum?
}
class Court {
+ruleset: enum
+type: enum
--
+drill_focus: enum
+wheelchair: boolean
+custom_dimensions: CustomDimensions?
}
class Entity {
+type: enum
+x: number
+y: number
--
+nr: integer?
+rotation: number?
+color: ColorRole?
+label: string?
}
class Frame {
+id: string
+actions: Action[]
+end_state: State
--
+label: string?
+description: string?
+duration_ms: integer?
+start_state: State?
+branches: map?
}
class Action {
+player: EntityRef
+type: enum
--
+ball_id: string?
+moves: Move[]?
+variant: string?
+tags: string[]?
}
class Coordinate {
<<oneOf>>
FreeCoordinate
NamedCoordinate
RelativeCoordinate
}
Drill *-- Meta
Drill *-- Court
Drill *-- Entity
Drill *-- Frame
Frame *-- Action
Action *-- Coordinate
@enduml
13.1.1. Top-Level Fields
| Field | Required | Description |
|---|---|---|
|
yes |
Drill metadata (id, title, author, tags, etc.). |
|
yes |
Ruleset, court type, drill focus. |
|
yes |
Non-ball entities: offense, defense, coach, cones, stations. |
|
yes |
Array of balls; each ball has |
|
yes |
Ordered coaching phases; each carries |
|
no |
Override FIBA default color roles. |
|
no |
Custom named court positions. |
|
no |
Highlighted zones on the court. |
|
no |
Free text annotations on the court. |
13.2. Coordinate oneOf
@startuml
skinparam backgroundColor white
skinparam defaultFontName Helvetica
abstract class Coordinate <<oneOf>>
class FreeCoordinate {
+x: number
+y: number
}
class NamedCoordinate {
+named: string
}
class RelativeCoordinate {
+relative_to: string
+dx: number
+dy: number
}
Coordinate <|-- FreeCoordinate
Coordinate <|-- NamedCoordinate
Coordinate <|-- RelativeCoordinate
@enduml
13.3. Enums Summary
| Enum | Values |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Action |
move, cut, screen, defend, dribble, pass, shoot, rebound, pickup |
Cut |
backdoor, give_and_go, flash, v_cut, l_cut, curl, flare, fade, basket |
Screen |
ball_screen, back_screen, down_screen, flare_screen, cross_screen, pin_down |
Defend |
on_ball, deny, help, hedge, switch, box_out |
Dribble per-move |
speed, hesitation, crossover, behind_back, between_legs, spin, retreat |
Pass |
chest, bounce, overhead, lob, baseball, hand_off, outlet |
Shoot |
jumper, three, layup, floater, dunk, hook, free_throw |
Rebound |
offensive, defensive |
Frame |
make, miss, turnover, steal, foul |
|
slow, normal, fast, explosive |
|
soft, normal, hard, bullet |
|
passive, normal, aggressive, hard |
Per-move variant values are recommended vocabulary; the schema accepts any string for a move’s variant so renderers can fall back gracefully.
|
14. LLM Generation Guide
14.1. Prompt Pattern
{ocf} is designed so that LLMs can generate valid drill JSON from natural language descriptions. The key is the named position vocabulary — it grounds spatial references in a controlled set of terms.
Example prompt structure:
Generate an Open Coaching Format v1 JSON for the following drill:
Ruleset: FIBA, Half Court, Offense
Title: "3-Man Weave"
Description:
offense_1 starts at midcourt.center with the ball.
offense_2 starts at inbound.sideline_left_mid.
offense_3 starts at inbound.sideline_right_mid.
Step 1: offense_1 passes to offense_2, cuts behind toward the right sideline.
Step 2: offense_2 passes to offense_3 who has filled the center lane.
Step 3: offense_3 finishes at the basket.
Output only valid JSON matching https://opencoachingformat.org/schema/v1.json
14.2. LLM Output Tips
-
Actions express intent, not geometry — prefer named positions in
end_stateover raw coordinates wherever a standard name applies -
Leave optional action fields out when the description is vague; add
variant,ball_id, and timing only when the description implies them -
Use
relative_tofor positions defined as offsets ("2 meters right of the elbow") -
Every frame requires an
end_state; list only the entities whose positions change in that frame (others inherit) -
Ball state in
end_state.ballsmust reflect the result of the actions: usecarried_byafter a pass/pickup,deadafter a made shot with no rebound -
Set the ball
deadinend_state.ballsafter ashootwithresult: "make"if noreboundorpickupfollows in that frame or the next -
Frame
descriptionshould mirror the coaching instruction in natural language — LLMs should always generate this field
14.3. Validation
After LLM generation, validate against the JSON Schema:
npx ajv-cli validate \
-s opencoachingformat-v1.schema.json \
-d my_drill.json
Appendix A: FIBA Import Mapping
| FIBA Field | OCF Field | Notes |
|---|---|---|
|
|
GUID → UUID |
|
|
|
|
|
Direct |
|
|
See type map below |
|
|
Direct |
|
|
See color map below |
|
|
|
|
|
|
|
|
See action type map below |
|
— (derived from action type) |
Renderer infers path style |
|
|
Coordinate conversion each point |
|
|
form, color, position |
|
|
text, position |
A.1. FIBA Dynamic Element Type Map
FIBA type |
OCF entity.type |
|---|---|
0 (offence) |
|
1 (defence) |
|
2 (ball) |
— (map to |
3 (coach) |
|
4 (cone) |
|
5 (shooting) |
— (map to |
6 (handoff) |
— (map to |
A.2. FIBA Line Type Map
FIBA type |
OCF action type |
|---|---|
0 (movement) |
|
1 (passing) |
|
2 (dribbling) |
|
3 (screen) |
|
4 (line) |
— (annotate with |
5 (free) |
— (annotate with |
A.3. FIBA Color Map
FIBA color |
FIBA name | OCF color_role |
|---|---|---|
-1 |
unknown |
|
0 |
yellow |
|
1 |
green |
|
2 |
red |
|
3 |
grey |
|
4 |
black |
|
5 |
blue |
|
6 |
offence |
|
7 |
defence |
|
Appendix B: Changelog
B.1. v2.1.0
Adds optional intensity (movement_intensity for move/cut/dribble,
ball_intensity for pass/shoot) and physicality (screen/defend/rebound/
pickup) enums. Deprecates frame-level duration_ms in favor of per-action
intensity; duration_ms remains valid for backward compatibility.
B.2. v2.0.0
Semantic action model. Replaces line-based frames with per-player actions
(move, cut, screen, defend, dribble, pass, shoot, rebound, pickup). The ball
entity is replaced by a top-level balls array with a carried_by/at/dead
lifecycle and automatic possession transfer. Frames gain explicit
start_state/end_state anchors and outcome branches. The lines array and
entity_states are removed.
B.3. v1.0.0 (2025-02-27)
-
Initial release
-
FIBA-compatible entity model
-
Normalized coordinate system (meters/feet, origin at midcourt)
-
Named position registry (FIBA, NBA, NCAA, NFHS)
-
Frame-based animation model with entity delta states
-
Registry approach for custom named positions
-
Semantic color roles with WCAG guidance
-
FIBA legacy import coordinate mapping