RaceMatrix Circuit Binary Format (BCF) Specification

Author: KiHyunKangKiHyunKang | Created: 2026-04-07 01:06
List
Type: technical
Security Level: public
Tags: bcf circuit format binary

RaceMatrix Circuit Binary Format (BCF) Specification

Status: current as of 2026-06-15

Canonical implementation sources:

  • app/services/bcf_generator.rb
  • app/controllers/circuits_controller.rb
  • app/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 0x03 extended BCF.
  • The browser BCF importer currently reads the core layout only.
  • The browser importer does not parse the 0x03 extension sections yet.
  • A 0x03 file 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_count is uint16, 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 = 0 to represent wrap-around to the start of a closed circuit
  • start_index and end_index must fit in uint16

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_index must fit in uint16

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_count is uint16, 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:

  1. collect all track points and pitlane points
  2. compute the maximum distance from the circuit center to any point
  3. add safety margin using:
    • max_distance * 1.3
    • or max_distance + 200
  4. use the larger of the two
  5. round to meters
  6. 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_mm as uint32
  • pitlane_length_mm as uint16

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 0x03 elevation section
  • does not decode the 0x03 road-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