RaceMatrix Circuit JSON Format Specification
RaceMatrix Circuit JSON Format Specification
Status: current as of 2026-06-15
Canonical implementation sources:
app/models/circuit.rbapp/models/circuit_json_canonicalizer.rbapp/assets/javascripts/circuit_json.jsapp/views/circuits/_form.html.erbapp/controllers/circuits_controller.rbapp/models/operations_overlay.rbapp/models/circuit_package.rbapp/services/circuit_package_zip_exporter.rb
This document describes the JSON formats used by RaceMatrix for circuit layout export/import and for OBS-oriented operations data.
If this document and the code disagree, the code is authoritative.
Format Family
RaceMatrix now treats circuit data as three related JSON documents:
- Circuit Layout JSON
- The base geometry and casual-user circuit metadata.
- Used by the normal circuit editor and weekend-driver/data-logger workflows.
- Does not embed race-control or event/session operating data.
- Race Operations Overlay JSON
- A sidecar document attached to one exact base layout.
- Used for OBS/race-control data such as timing points, operational zones, pit-lane semantics, and verification metadata.
- Circuit Package JSON
- A manifest that declares compatible layout and overlay files.
- The preferred OBS import/export provenance unit.
Base layout variants are not stored in a layouts[] array inside a layout file. Each layout is an independent Circuit Layout JSON file. A Circuit Package JSON file groups those independent files.
Circuit Layout JSON
Scope
Circuit Layout JSON is used for:
- browser-based export from the circuit show page
- browser-based export from the circuit editor
- server-side layout download from
GET /circuits/:id/download_json - browser-based import into the circuit editor
- manual backup and exchange with third-party tools
- layout files referenced by Circuit Package JSON
File extension:
.json
Encoding:
- UTF-8
MIME type:
application/json
Current Export Version
RaceMatrix currently exports layout JSON with:
{
"export_version": "2.3"
}
The 2.3 layout shape adds stable layout identity and canonical content hashing fields while keeping the previous geometry fields compatible with the browser editor.
Top-Level Object
Typical shape:
{
"name": "Example Circuit",
"description": "Example export",
"center_lat": 37.1234567,
"center_lng": 127.1234567,
"geofence_radius": 640,
"zoom_level": 15,
"track_points": [],
"pitlane_points": [],
"sectors": [],
"corners": [],
"profile_id": "racematrix:circuit:...",
"layout_id": "racematrix:layout:example-circuit:...",
"layout_revision": 1,
"layout_content_hash": "sha256:...",
"length": 0,
"pitlane_length": 0,
"circuit_type": "closed",
"road_width": 20.0,
"verified": false,
"creator": {
"name": null,
"email": null
},
"created_at": "2026-06-15T00:00:00+09:00",
"updated_at": "2026-06-15T00:00:00+09:00",
"exported_at": "2026-06-15T00:00:00+09:00",
"export_version": "2.3"
}
Top-Level Fields
| Field | Type | Exported by RaceMatrix | Imported by editor | Notes |
|---|---|---|---|---|
name |
string | yes | yes | Circuit name |
description |
string | yes | yes | Free-form description |
center_lat |
number | yes | yes | WGS84 latitude |
center_lng |
number | yes | yes | WGS84 longitude |
geofence_radius |
number | yes | no | Computed on export if absent in source data |
zoom_level |
integer | yes | yes | Defaults to 15 |
track_points |
TrackPoint[] |
yes | yes | Main circuit polyline |
pitlane_points |
TrackPoint[] |
yes | yes | Optional pitlane polyline |
sectors |
Sector[] |
yes | yes | Optional sector metadata |
corners |
Corner[] |
yes | yes | Optional corner metadata |
profile_id |
string | yes | yes | Stable circuit/profile identity; generated when absent |
layout_id |
string | yes | yes | Stable layout identity; generated when absent |
layout_revision |
integer | yes | yes | Starts at 1; increments when the canonical layout hash changes |
layout_content_hash |
string | yes | yes | sha256: hash of canonical layout hash payload |
length |
number | yes | no | Track length in meters |
pitlane_length |
number | yes | no | Pitlane length in meters |
circuit_type |
"closed" or "open" |
yes | yes | Defaults to "closed" |
road_width |
number or null |
yes | yes | Default road width in meters |
verified |
boolean | yes | no | Show-page export preserves it; form export writes false |
creator |
Creator |
yes | no | Informational metadata only |
created_at |
string | sometimes | no | Exported when known |
updated_at |
string | sometimes | no | Exported when known |
exported_at |
string | yes | no | ISO 8601 timestamp created at export time |
export_version |
string | yes | no | Currently "2.3" |
Notes:
profile_id,layout_id, andlayout_content_hashare required by the model but generated automatically for existing records or imported layouts that do not have them.- The browser importer preserves
profile_id,layout_id,layout_revision, andlayout_content_hashwhen present. On save, the server recomputeslayout_content_hashfrom geometry and layout-affecting metadata. - Unknown extra fields are ignored by the browser importer. Operational data must use Race Operations Overlay JSON rather than relying on ignored unknown layout fields.
Layout Content Hash
layout_content_hash is computed by CircuitJsonCanonicalizer.content_hash over Circuit#layout_hash_payload.
Current hash payload:
{
"export_version": "2.3",
"circuit_type": "closed",
"road_width": 20.0,
"track_points": [],
"pitlane_points": [],
"sectors": [],
"corners": []
}
Included in the hash:
export_versioncircuit_typeroad_width- formatted
track_points - formatted
pitlane_points sectorscorners
Excluded from the hash:
namedescriptioncenter_latcenter_lnggeofence_radiuszoom_levellengthpitlane_lengthverifiedcreatorcreated_atupdated_atexported_atprofile_idlayout_idlayout_revisionlayout_content_hash- Race Operations Overlay JSON
- Circuit Package JSON
- event/session operating data
Canonical JSON rules:
- object keys are stringified and sorted lexicographically
- arrays keep their original order
BigDecimalandFloatvalues are rounded to 7 decimal places- integer-valued rounded numbers are emitted as integers
- non-finite numbers are invalid
- the hash is
sha256:plus the SHA-256 digest of the canonical JSON string
Nested Types
TrackPoint
{
"lat": 37.1234567,
"lng": 127.1234567,
"ele": 42.15,
"width": 11.5
}
| Field | Type | Required | Notes |
|---|---|---|---|
lat |
number | yes | Latitude in decimal degrees |
lng |
number | yes | Longitude in decimal degrees |
ele |
number | no | Elevation in meters |
width |
number | no | Per-point road width override in meters |
Both track_points and pitlane_points use this point shape. The current server formatter preserves point width for main track points and elevation for track and pit-lane points.
Sector
{
"name": "Sector 1",
"start": 0,
"end": 125
}
| Field | Type | Required | Notes |
|---|---|---|---|
name |
string | no | Exporters usually include it |
start |
integer | yes | Zero-based index into track_points |
end |
integer | yes | Zero-based index into track_points |
RaceMatrix validation rules:
- sectors are limited to 255 entries
startandendmust be validtrack_pointsindices- both indices must fit in
uint16so the circuit remains BCF-exportable - for closed circuits, the final sector may use
end: 0to mean wrap-around to the start point
Corner
{
"name": "Turn 1",
"number": 1,
"point": 87
}
| Field | Type | Required | Notes |
|---|---|---|---|
name |
string | no | Exporters usually include it |
number |
integer | no | Display/order metadata used by the editor |
point |
integer | yes | Zero-based index into track_points |
RaceMatrix validation rules:
- corners are limited to 255 entries
pointmust be a validtrack_pointsindexpointmust fit inuint16so the circuit remains BCF-exportable
Creator
{
"name": "Jane Doe",
"email": "jane@example.com"
}
| Field | Type | Required | Notes |
|---|---|---|---|
name |
string or null |
no | Informational only |
email |
string or null |
no | Informational only |
Race Operations Overlay JSON
Race Operations Overlay JSON is a first-class sidecar format for operational data. It is not stored as an unknown field in Circuit Layout JSON.
Typical shape:
{
"overlay_type": "racematrix.race_operations_overlay",
"schema_version": "1.0",
"overlay_id": "racematrix:ops-overlay:...",
"overlay_revision": 1,
"name": "Race Control",
"usage": "race_control",
"base_circuit": {
"profile_id": "racematrix:circuit:...",
"layout_id": "racematrix:layout:example-circuit:...",
"layout_revision": 1,
"layout_content_hash": "sha256:..."
},
"timing_points": [
{
"id": "timing_...",
"role": "start_finish",
"domain": "main_track",
"shape": {
"kind": "line",
"left": { "lat": 37.1234, "lng": 127.1234 },
"right": { "lat": 37.1235, "lng": 127.1235 },
"lateral_min_m": -20,
"lateral_max_m": 20
},
"valid_direction": "bidirectional"
}
],
"operational_zones": [
{
"id": "zone_...",
"kind": "flag_zone",
"domain": "main_track",
"shape": {
"kind": "polygon",
"points": []
}
}
],
"pit_lane": {},
"verification": {}
}
Required identity/reference fields:
overlay_typeschema_versionoverlay_idoverlay_revisionnamebase_circuit.layout_idbase_circuit.layout_content_hash
overlay_content_hash is intentionally not embedded in the overlay file. RaceMatrix stores it on the OperationsOverlay record and publishes it from Circuit Package JSON and OBS ledger/provenance fields. If an imported overlay JSON contains overlay_content_hash, RaceMatrix ignores it when exporting and hashing the overlay payload.
Validation and lifecycle:
overlay_typemust beracematrix.race_operations_overlaywhen present.timing_pointsandoperational_zonesmust be arrays when present.pit_lanemust be an object when present.- The overlay stores
base_circuit.profile_id,base_circuit.layout_id,base_circuit.layout_revision, andbase_circuit.layout_content_hash. - Timing lines use
shape.kind: "line"with explicitleftandrightendpoints, lateral range, domain, andvalid_direction; they are not inferred from centerline distance and width. - Operational zone shapes use
shape.kind, notshape.type. Current editor-created kinds aremarker,polygon, andtrack_range. - If
base_circuit.layout_content_hashmatches the current layout hash, the overlay can bevalid. - If the base layout hash differs, RaceMatrix marks the overlay
review_requiredunless it has been explicitly markedincompatible,remapped, ordeprecated. - Editing overlay content increments
overlay_revisionand recomputesoverlay_content_hash.
Overlay status values:
draftvalidreview_requiredincompatibleremappeddeprecated
Circuit Package JSON
Circuit Package JSON is the package-root manifest used to hand layouts and overlays to OBS.
The manifest uses layouts[] for forward compatibility, but RaceMatrix v1 is a single-layout package MVP. RaceMatrix v1 package validation requires exactly one layout entry and the ZIP exporter writes only that one current circuit layout. Multi-layout venue packages remain a future extension that will need file-backed importer/exporter support.
Typical shape:
{
"package_type": "racematrix.circuit_package",
"schema_version": "1.0",
"package_id": "racematrix:circuit-package:...",
"package_revision": 1,
"package_content_hash": "sha256:...",
"name": "Example Circuit",
"layouts": [
{
"layout_id": "racematrix:layout:example-circuit:...",
"name": "Example Circuit",
"file": "layouts/example-circuit.json",
"layout_revision": 1,
"layout_content_hash": "sha256:..."
}
],
"overlays": [
{
"overlay_id": "racematrix:ops-overlay:...",
"name": "Race Control",
"file": "overlays/race-control.json",
"overlay_revision": 1,
"overlay_content_hash": "sha256:...",
"base_layout_id": "racematrix:layout:example-circuit:...",
"base_layout_content_hash": "sha256:...",
"usage": "race_control"
}
],
"default_layout_id": "racematrix:layout:example-circuit:...",
"default_overlays_by_usage": {
"race_control": "racematrix:ops-overlay:..."
}
}
Required package fields:
package_typeschema_versionpackage_idpackage_revisionpackage_content_hashlayoutsoverlaysdefault_layout_iddefault_overlays_by_usage
RaceMatrix v1 package rules:
layoutsmust contain exactly one entry.- The single layout must match the current circuit
layout_idandlayout_content_hash. - Overlay entries may be empty, but every listed overlay must reference the package layout.
- Package validation is DB-backed manifest validation. It checks manifest shape, path safety, package hash, current layout hash, local overlay hashes, and overlay base layout references.
- RaceMatrix v1 does not import a loose package directory and read arbitrary layout/overlay files from disk. Actual loose-file content verification is part of OBS import, ZIP package import, or a future RaceMatrix file-backed importer.
Package path rules:
layouts[].fileandoverlays[].fileare relative to the package root.- Empty paths are invalid.
- Absolute Unix paths are invalid.
- Absolute Windows-style paths are invalid.
- URL or URI-like paths are invalid.
- Any path containing
..as a path segment is invalid.
Package hash rules:
layout_content_hashis the canonical content hash of the referenced Circuit Layout JSON.overlay_content_hashis the canonical content hash of the referenced Race Operations Overlay JSON, excluding anyoverlay_content_hashfield if a third-party file includes one.package_content_hashis computed from canonical package JSON withpackage_content_hashitself excluded.- ZIP/raw file hashes are not represented by these fields.
Default overlays:
- Use
default_overlays_by_usage, not a singledefault_overlay_id. - Typical usage keys include
race_control,trackday,drift, andendurance. - Each value must reference an overlay listed in
overlays.
Package status values:
draftvalidreview_requiredincompatibleinvaliddeprecated
Import modes:
package_loose_filespackage_zippackage_apidirect_with_synthetic_manifest
Hash mismatch status values:
invalidreview_requiredincompatibleremapped
OBS Import Policy
OBS should use Circuit Package JSON as the default import and provenance unit.
On package import, OBS should verify:
- package manifest canonical hash
- each layout file canonical hash
- each overlay file canonical hash
- each overlay
base_circuit.layout_id - each overlay
base_circuit.layout_content_hash - package manifest
base_layout_idandbase_layout_content_hashreferences
RaceMatrix v1 package validation is narrower: it validates a DB-backed single-layout manifest and generated ZIP export. OBS remains responsible for reading loose package files and verifying their actual canonical content hashes during the first integration path.
Mismatch handling:
- If
package_content_hash, a layout file hash, or an overlay file hash differs from the manifest, the import is invalid and should be rejected. - If an overlay
base_circuit.layout_content_hashdiffers from the actual layout hash, automatic remap is forbidden. - Base layout hash mismatch should be stored as
review_requiredorincompatible. - If a user explicitly remaps/approves, create a new overlay revision and hash and mark that result
remapped.
Direct layout+overlay import is allowed for simple workflows, but OBS should create an internal synthetic import manifest so ledger/provenance records remain equivalent to package import.
OBS ledger/provenance should store:
package_idpackage_revisionpackage_content_hashlayout_idlayout_revisionlayout_content_hashoverlay_idoverlay_revisionoverlay_content_hashimport_mode- hash mismatch status:
invalid,review_required,incompatible, orremapped
Initial integration order:
- loose package files
- ZIP package export
- API package pull
Compatibility Notes
- The normal circuit editor remains focused on the base layout used by amateur drivers and data-loggers.
- Operations overlay editing is intentionally separate from the normal circuit editor UI.
- Circuit Layout JSON remains independent of BCF compatibility, but RaceMatrix model validation still limits sectors and corners to 255 so the same circuit can be exported to BCF.
- Third-party tools may include extra top-level metadata, but RaceMatrix’s browser importer ignores unknown fields.
- Operational fields that must round-trip should be stored in Race Operations Overlay JSON, not as unknown fields in Circuit Layout JSON.
Minimal Layout Example
{
"name": "Example Circuit",
"center_lat": 37.1234567,
"center_lng": 127.1234567,
"track_points": [
{ "lat": 37.1234, "lng": 127.1234 },
{ "lat": 37.1235, "lng": 127.1235 }
],
"pitlane_points": [],
"sectors": [],
"corners": [],
"profile_id": "racematrix:circuit:...",
"layout_id": "racematrix:layout:example-circuit:...",
"layout_revision": 1,
"layout_content_hash": "sha256:...",
"circuit_type": "closed",
"export_version": "2.3"
}