Best times (RECORDS/*.BRT)
One file per circuit, named after the circuit's file stem in CIRCUITS.BIN (lists.md). Each holds the circuit's best races, best laps and best sector times, with the drivers' names. The game writes these files: they are save state as well as data.
Reader: decoders/brt.py, crates/pod-formats/src/records.rs. State: decoded and round-trip for all 17 files (measured, 2026-10-23: every byte is accounted for, and writing the decoded tables gives the original file). Not yet tested in the game.
Container
One block of the block container (container.md), key 0xEAFA, block 0x1000: a 4,096-byte file, 4,092 bytes of stream. The stream starts with the usual header (raw size 0x1000, one entry, at 0xC). Observed: load_records (FUN_004b2300) opens the file with pod_file_open (FUN_004694b8, EDX = 0xEAFA, EBX = 0x1000) and reads 0xE5C bytes with pod_read_small (FUN_0046846c) into 0x693A84. save_records (FUN_004b23f4) creates the file with FUN_00468fa4 (block 0x1000, one entry, key 0xEAFA in EBX), writes the same 0xE5C bytes from 0x693A84 with FUN_0046859c and closes it with FUN_00469a1c.
After the 0xE5C bytes, the stream is zero to its end (measured, 17 files).
Layout
Offsets from the start of the 0xE5C bytes (stream offset 0xC, memory 0x693A84). All measured on 17 files; the meanings are observed in the functions named.
| Offset | Size | Content |
|---|---|---|
0x000 | 15 × 0x38 | Race table, best first: char name[20], i32 total, i32 lap[8] |
0x348 | 1 | Rows in use (15) |
0x349 | 1 | Laps per race (3 in every file; 0x693DCD) |
0x34A | 26 | Zero |
0x364 | 3 × 0x38 | Splits of the best race: lap k at 0x364 + k × 0x38, i32 sector[5]; the rest of each slot is zero |
0x3F4 | 15 × 0x38 | Lap table, best first: char name[20], i32 lap time, i32 sector[8] |
0x73C | 1 | Rows in use (15; 0x6941C0) |
0x73D | 1 | Timing sectors per lap (2 to 4; 0x6941C1) |
0x73E | 2 | Zero |
0x740 | 5 × 0x16C | Sector tables, one per sector slot: 15 × (char name[20], i32 time), then u8 rows in use and 3 zero bytes |
Only the first laps lap words of a race row and the first sectors sector words of a lap row are used; the others are zero. Sector slots past the sector count have 0 rows in use and all their bytes zero. Names are zero-padded; no file has bytes after a name's zero.
Who reads which part
FUN_004b28c8finds the rank of a race time in the race table (count at+0x348, times at+0x14of each0x38row).FUN_004b286cdoes the same for one sector table (count at+0x168,0x18-byte rows).FUN_004b2924andFUN_004b2978search the lap table by time and by name.FUN_004b2a34stores a new best race's splits at0x693DE8,0x693E20and0x693E58(+0x364,+0x39C,+0x3D4) when its total beats row 1.FUN_0045fd58shows the gap to the record during a race: it adds the record's lap times (+0x14 + 4 × lap) and the split of the current lap and sector.
Times
A time is seconds in 16.16 fixed point (observed: FUN_0047e4d0 multiplies it by 1 / (65,536 × 60) for the minutes, takes the seconds from what is left, and the hundredths as the fraction × 100 / 65,536, then prints %02u'%02u"%02u). For example 0x00F85EB7 is 248.37 s, shown as 04'08"37.
File names
load_records and save_records build the name from the circuit's file stem (build_data_path, FUN_004ae91c, folder 8 = Data\Records\) and then change two characters (observed, not yet seen on disk):
- The last character becomes the mode:
Twhen bit 2 of0x61D5B8is set (time attack), else the difficulty from0x61D5CC:E(1),H(4),M(anything else). The disc has only the.BRTfiles. - The first character of the name becomes
-for the mirrored circuit (save_records: when0x61D5E8> 0;load_records: when its second argument is non-zero).
So a race on a mirrored circuit at hard difficulty would write a file such as -ELTANE.BRH (inferred). In POD (observed, 0x4ad09c, docs/systems/front-end.md "Best times"): the same rules with the mode 0x633320 & 4 and the difficulty 0x633334; the extension is set by 0x4a9b98. How the game writes a file, row by row, is in that section, measured under the emulator against the port (crates/pod-ui/src/best_times/).
When a file is missing
FUN_004b1d84 fills the tables when the file cannot be opened (observed): 15 rows with names drawn at random from 21 built-in names (0x4F7CAC, 20 bytes each) and times rising from a base time (0x693A50) in steps of base / 60. The shipped files look made the same way: in every file each race row's laps are equal and rows rise by a constant step (measured).
Measured cross-check with CIRCUITS.BIN
15 of the 17 files have as many sector tables as their circuit has timing sectors in CIRCUITS.BIN. BANK.BRT and SPECIAL.BRT have 3 where CIRCUITS.BIN gives 4 (measured): the files may come from an older circuit list. What the game does with such a file is open.
Modding
Write the file with brt.py's encode() or records::write. Names longer than 19 characters do not fit. Keep the zero bytes the layout lists as zero: the readers here refuse a file that has data there, because the game's own writer never puts any.
Open questions
- A changed
.BRTloading in the game, and the game writing one (needs a writable copy ofDATA/RECORDSunder Wine). - The
-andE/M/Hfile names seen on disk. - What the game does when the sector count in the file differs from
CIRCUITS.BIN.