RaceMatrix Circuit Binary Format (BCF) Specification
RaceMatrix Circuit Binary Format (BCF) Specification
Status: current as of 2026-06-15
Canonical implementation sources:
app/services/bcf_generator.rbapp/controllers/circuits_controller.rbapp/views/circuits/_form.html.erb
This document describes the binary .bcf circuit format currently used by RaceMatrix.
Important: RaceMatrix now has one canonical BCF generator implementation: BcfGenerator.
Generation Entry Points
| Entry point | Flow | Generator | Header version |
|---|---|---|---|
| editor export | browser sends current unsaved editor state to POST /circuits/export_binary |
BcfGenerator |
0x03 |
| circuit download | browser downloads a saved circuit from GET /circuits/:id/download_binary |
BcfGenerator |
0x03 |
Both entry points produce the same 0x03 extended profile.
The generated file always contains the same core sections:
- Header
- Name
- Track Points
- Sectors
- Corners
- optional Pitlane Points
- Checksum
The 0x03 profile may also append:
- Elevation section
- Road Width section
If this document and the code ever disagree, the code is authoritative.
File Extension and Encoding
File extension:
.bcf
MIME type:
application/octet-stream
Byte order:
- little-endian for every multi-byte field
Compatibility Summary
- Both browser editor export and circuit download now produce
0x03extended BCF. - The browser BCF importer currently reads the core layout only.
- The browser importer does not parse the
0x03extension sections yet. - A
0x03file still imports its core fields in the browser, while elevation and road-width extensions are ignored.
High-Level Layout
Core layout:
+------------------+
| Header | 100 bytes
+------------------+
| Name | name_length bytes
+------------------+
| Track Points | 8 * track_points_count
+------------------+
| Sectors | 4 * sectors_count
+------------------+
| Corners | 2 * corners_count
+------------------+
| Pitlane Points | 8 * pitlane_points_count (if has_pitlane)
+------------------+
| Checksum | 4 bytes
+------------------+
Extended layout (0x03 only):
+------------------+
| Header | 100 bytes
+------------------+
| Name | name_length bytes
+------------------+
| Track Points | 8 * track_points_count
+------------------+
| Sectors | 4 * sectors_count
+------------------+
| Corners | 2 * corners_count
+------------------+
| Pitlane Points | 8 * pitlane_points_count (if has_pitlane)
+------------------+
| Elevation | 2 * (track_points_count + pitlane_points_count) (if has_elevation)
+------------------+
| Road Width | 4 * width_overrides_count (if has_road_width)
+------------------+
| Checksum | 4 bytes
+------------------+
Header Layout (100 bytes)
| Offset | Size | Type | Field | Description |
|---|---|---|---|---|
0x00 |
4 | char[4] |
magic |
Always "BBCF" |
0x04 |
1 | uint8 |
version |
Currently 0x03 |
0x05 |
1 | uint8 |
flags |
Bitfield described below |
0x06 |
2 | uint16 |
reserved |
Always 0 |
0x08 |
4 | uint32 |
circuit_id |
Database ID when known, otherwise 0 |
0x0C |
1 | uint8 |
name_length |
UTF-8 byte length, max 64 |
0x0D |
1 | uint8 |
sectors_count |
0..255 |
0x0E |
1 | uint8 |
corners_count |
0..255 |
0x0F |
64 | profile-dependent | reserved2 |
See next section |
0x4F |
1 | uint8 |
reserved3 |
Always 0 |
0x50 |
2 | uint16 |
track_points_count |
Number of circuit points |
0x52 |
2 | uint16 |
pitlane_points_count |
Number of pitlane points |
0x54 |
4 | uint32 |
track_length_mm |
Track length in millimeters |
0x58 |
2 | uint16 |
pitlane_length_mm |
Pitlane length in millimeters |
0x5A |
4 | int32 |
center_lat |
Latitude multiplied by 10,000,000 |
0x5E |
4 | int32 |
center_lng |
Longitude multiplied by 10,000,000 |
0x62 |
2 | uint16 |
geofence_radius_m |
Geofence radius in meters, clamped to 65535 |
reserved2 in 0x03
For BcfGenerator output:
Offset inside reserved2 |
Size | Type | Field | Description |
|---|---|---|---|---|
0x00 |
4 | int32 |
base_elevation_cm |
Average elevation baseline in centimeters |
0x04 |
2 | uint16 |
default_road_width_cm |
Default road width in centimeters |
0x06 |
2 | uint16 |
width_overrides_count |
Number of sparse width override entries |
0x08..0x3F |
56 | reserved | unused | Zero-filled |
Flags
| Bit | Mask | Name | Meaning |
|---|---|---|---|
| 0 | 0x01 |
open circuit | 1 means circuit_type == "open" |
| 1 | 0x02 |
verified | 1 means verified |
| 2 | 0x04 |
has pitlane | 1 means pitlane section exists |
| 3 | 0x08 |
has elevation | used by 0x03 files |
| 4 | 0x10 |
has road width | used by 0x03 files |
| 5-7 | reserved | currently zero |
Name Section
Offset:
- immediately after the 100-byte header
Encoding:
- UTF-8
Length:
name_length- maximum 64 bytes
Behavior:
- names longer than 64 UTF-8 bytes are truncated on export
Track Points Section
Size:
8 * track_points_count
Point structure:
| Offset | Size | Type | Field |
|---|---|---|---|
0x00 |
4 | int32 |
lat |
0x04 |
4 | int32 |
lng |
Conversion:
stored_lat = round(lat * 10000000.0)
stored_lng = round(lng * 10000000.0)
lat = stored_lat / 10000000.0
lng = stored_lng / 10000000.0
Notes:
- coordinates are WGS84 decimal degrees
- point order is significant
track_points_countisuint16, so the section supports up to 65535 track points
Sectors Section
Size:
4 * sectors_count
Structure:
| Offset | Size | Type | Field |
|---|---|---|---|
0x00 |
2 | uint16 |
start_index |
0x02 |
2 | uint16 |
end_index |
Notes:
- indices reference
track_points - sector names are not stored in BCF
- sector colors are not stored in BCF
- the last sector may use
end_index = 0to represent wrap-around to the start of a closed circuit start_indexandend_indexmust fit inuint16
Corners Section
Size:
2 * corners_count
Structure:
| Offset | Size | Type | Field |
|---|---|---|---|
0x00 |
2 | uint16 |
point_index |
Notes:
- indices reference
track_points - corner names are not stored in BCF
- corner display numbers are not stored in BCF
point_indexmust fit inuint16
Pitlane Points Section
Present only when:
flags & 0x04 != 0
Size:
8 * pitlane_points_count
Structure:
- identical to track points
Notes:
- point order runs from pit entry to pit exit
pitlane_points_countisuint16, so the section supports up to 65535 pitlane points
Elevation Section (0x03 only)
Present only when:
flags & 0x08 != 0
BcfGenerator sets this flag when any track or pitlane point has elevation data.
Size:
2 * (track_points_count + pitlane_points_count)
Entry type:
- signed
int16
Meaning:
- each entry stores
delta_cm = point_elevation_cm - base_elevation_cm
Reconstruction:
point_elevation_m = (base_elevation_cm + delta_cm) / 100.0
Ordering:
- all track points first
- then all pitlane points
Notes:
- points without elevation data are serialized as zero-delta relative to the base and reconstruct to the base elevation
- values are clamped to signed 16-bit range
Road Width Section (0x03 only)
Present only when:
flags & 0x10 != 0
Size:
4 * width_overrides_count
Structure:
| Offset | Size | Type | Field |
|---|---|---|---|
0x00 |
2 | uint16 |
point_index |
0x02 |
2 | uint16 |
width_cm |
Notes:
- this section is sparse
- only track points with explicit per-point width overrides are stored
- entries are sorted by
point_index - the default width for points without overrides is stored in header
reserved2[4..5] - pitlane point widths are not stored here
Checksum
Type:
uint32
Algorithm:
- CRC-32 (IEEE 802.3 polynomial
0xEDB88320)
Coverage:
- every byte in the file before the checksum itself
Geofence Radius
RaceMatrix computes geofence radius as follows:
- collect all track points and pitlane points
- compute the maximum distance from the circuit center to any point
- add safety margin using:
max_distance * 1.3- or
max_distance + 200
- use the larger of the two
- round to meters
- clamp to
65535
Known Implementation Limits and Caveats
1. Sector and corner counts
Although the fields are uint8, RaceMatrix intentionally caps both at 255.
2. Index width
Sector and corner indices are uint16.
That means:
- BCF cannot address point indices above
65535
3. Pitlane length field is narrower than track length
Current implementations store:
track_length_mmasuint32pitlane_length_mmasuint16
This means pitlane_length_mm is currently clamped to:
65535 mm- approximately
65.535 m
This is a limitation of the current implementation, not an ideal design.
4. Browser importer only supports the core profile
The browser parseBCF() implementation:
- reads the common core sections
- does not decode the
0x03elevation section - does not decode the
0x03road-width section
If a 0x03 file is imported in the editor:
- core geometry may still load
- extension data will be ignored
- the CRC warning may be unreliable because the importer does not skip the extension sections before reading the checksum
File Size Formula
Current generated 0x03 profile:
size = 100
+ name_length
+ (track_points_count * 8)
+ (sectors_count * 4)
+ (corners_count * 2)
+ [pitlane_points_count * 8 if has_pitlane]
+ [(track_points_count + pitlane_points_count) * 2 if has_elevation]
+ [width_overrides_count * 4 if has_road_width]
+ 4
Practical Guidance for Third-Party Tools
If you want maximum compatibility with the current RaceMatrix editor:
- generate the shared core layout
- avoid extension sections if you need the browser importer to round-trip cleanly today
If you want parity with current RaceMatrix exports:
- generate
0x03 - fill
reserved2 - append optional elevation and road-width sections when flags require them
If you need round-trip compatibility today:
- JSON is the safest interchange format
- BCF is best treated as an embedded/runtime distribution format