Official Timing: How My GTR2 Replays Got a 90s TV Station

October 11, 2026 03:38pm

From a lost 2008 UI mod to a phone-driven TV control room: decoding GTR2 replay files, 90s F1 timing graphics in OBS, and everything we tried, threw away and kept along the way.

If you watched Formula 1 in the late 1990s, you can probably still picture it: the yellow squares with black position numbers, the surnames in white capitals, the team names in a pale blue, and the little "Official Timing" tag at the bottom of the screen. Those graphics were the language of race weekends for me, and when we started racing GTR2 online with friends in 2008, our make-believe league had a TV channel, FDTV, that existed only in our race reports.

GTR2 has lovely replays, but no timing graphics at all once you go full screen. So for years my replays were just cars going round, with no idea who was where. Over one long weekend in October 2026, that finally changed. This is the story of how FDTV got its graphics: what we tried, what failed, what got thrown away, and what ended up on screen.

FDTV starting grid graphic: front row with the F-Duude Championship badge, Starting Grid caption and a chequered line

The front row of the starting grid, FDTV style, with the 2009 F-Duude Championship badge.

Where it started: a mod I had lost

Back in 2008 or 2009 I had edited GTR2's interface files so that the full-screen replay page showed the selected driver, his position and the weather. That modded file is long gone, and the current interface (the game has had community patches and UI mods since) differs from the old one by about 30,000 lines, so there was nothing to recover from a diff. The first job was simply to do it again: the full-screen replay page now shows the driver readout, the track and session, the weather and track temperature, and the replay clock in the bottom-left corner.

That replay clock turned out to matter more than anything else in this project. Everything that follows is synchronised to it.

The dead end: plugins

The obvious way to get live data out of a racing sim is a shared-memory plugin: the game publishes positions and timing, and an overlay reads them. My GTR2 install even has a Plugins folder full of them. A probe on the game PC settled it in a few minutes: the retail executable I run (the original 2008 copy, not the Steam re-release) loads no plugins at all. The shared-memory block never appears. The community plugins only work with the Steam version.

So the live route was closed. Which left a stranger question: what is actually inside a replay file?

Cracking the replay file

A GTR2 replay (.vcr) turns out to be a gzip file. Unpacked, a 17 MB replay becomes about 128 MB of binary: a header with the track and one entry per car (driver name and car file), followed by a long stream of records. Most of that stream is noise for our purpose, but one kind of record holds plain world coordinates for each car, around 11 times a second for the AI and more often for the player. Another holds the session clock.

The proof that this was real came from plotting it. This is the 2017 Formula Duude race at Enna-Pergusa, drawn purely from the coordinates inside the replay: 31 laps of me in yellow and my friend tib in cyan, 117,000 position samples, pit lane included at the bottom.

Top-down trace of the Enna-Pergusa circuit drawn from replay coordinates, two cars in yellow and cyan

Enna-Pergusa, as the replay file remembers it. No map was used: this is every recorded car position, joined up.

Positions alone are not standings, though. To know who leads, you need to know how far along the lap each car is. For that I used the track's own AI path file, which the game ships with every circuit: it describes the racing line as waypoints with their distance from the start/finish line. Projecting each car onto that line gives lap distance, and laps times lap length plus distance gives a single "progress" number per car. Sort by progress and you have the running order. Ask how long ago the leader was at a car's current spot and you have the classic TV gap.

Then the moment of truth: comparing the computed line crossings with the race log GTR2 writes after every session. On a test race at Donington, all 24 crossings landed within about a tenth of a second of the log, and the finishing gaps within four hundredths. And the replay's on-screen clock is the session clock, which meant the overlay could be synchronised by doing something very low-tech: pause the replay at 5 seconds, set the overlay to 5 seconds, and press Play on both.

FDTV goes on air

The overlay is a web page, loaded into OBS as a browser source on top of the game. The first version showed the classic banner: the running order in pages of six, gaps refreshing at sector splits like the old TV timing did. It worked on the first try, which surprised both of us, and then OBS taught me a few lessons:

  • OBS uses an older Chromium, without the modern CSS units I had used for sizing, so the first version rendered at size zero. Sizing moved to a few lines of script.
  • OBS draws no pop-up lists, so every dropdown became a row of buttons (or kept arrow buttons beside it).
  • Opened as a local file, the page had no character set, and every multiplication sign and ellipsis turned to gibberish.

Then came the cards, straight out of a 1999 broadcast: the fastest lap card every time someone beats the race's best lap, and the winner card when the chequered flag falls, with race time, average speed and distance.

Classic FDTV banner showing positions 7 to 12 with gaps

The classic banner. The order pops up once a lap, runs through all its pages once, and gets out of the way.

Fastest lap card: Fernando Horta, Corvette Racing, lap 8, 1:02.587
Winner card: Sam Rolfe, HHF Hybrid Racing, race time, average speed and distance

The fastest lap and winner cards. Race time and lap times come from the race log, to the thousandth.

The race log becomes the referee

The replay knows where everybody is. It does not always know what happened. Testing with real races exposed some wonderful failures: a retired player "won" an interrupted replay, because GTR2 teleports a retired car to its garage and the jump was counted as a line crossing. The cool-down lap after the flag counted as a racing lap, so cars that had finished and parked looked retired. On the Nürburgring, the track file's start line is not the timing line, which put every lap count one ahead.

The fix was a simple rule: when a race log exists, the log is the authority on distance, flags, lap times and retirements, and the replay only fills in the positions between them. After that, every retirement matched the logs, and every frozen finishing interval landed within five hundredths of a second.

A modern twist: the scoring pylon

Scoring pylon with 22 rows, team colour stripes, a purple fastest-lap holder and intervals

The 90s banner is my favourite, but a full grid of 30 cars is easier to follow with today's timing tower, so the overlay has a second display: a scoring pylon down the left side of the screen. It shows up to 22 cars: P1 to P16 stay fixed and the rest rotate in blocks of six. Each row has the interval to the car ahead, which switches to "+1 LAP" once that car is a lap up. A small chequered flag appears for each car that has finished, and the holder of the race's fastest lap shows in purple until somebody beats it.

The thin coloured stripe between the position and the driver code is the team colour. It was one of the last additions, and I will come back to where it comes from.

One key on the control desk switches between the banner and the pylon. The race cards keep working in both.

 

Grid, classification and standings

For the start and the end of a race, I gave the overlay screenshots of real 1990s broadcasts as references, and asked for the same structure.

A 1990s Formula 1 broadcast starting grid graphic

The reference: a 90s broadcast grid.

FDTV starting grid page with four cars, qualifying times and team names

FDTV: two grid rows a page, staggered like the real grid.

The grid had one late refinement. When only some drivers set a qualifying time, the entries came out uneven, so now every car in a race with any times gets the same square box and a time line, and a driver without one reads "NO TIME SET", like it would on TV.

Starting grid page with P31 showing a lap time and P32 showing NO TIME SET

At the end of the race, the full classification takes the screen, ten cars a page: position, name, tyres, nationality, race time or gap, average speed and each driver's fastest lap. Retirements show DNF and the reason. The top-left corner, where the 1997 broadcasts put the FIA World Championship logo, now carries the original 2009 F-Duude Championship badge from the old league blog.

Full race classification after 3 laps at Donington, with tyres, nationality, race time, speed and fastest lap columns

The race classification, with its own Tyres and Fastest lap columns.

For races that belong to a championship on this site, the overlay then asks the site's database for the standings after the race and shows them in the classic banner: drivers first, then teams. To show it off properly, I decoded one of the surviving 2009 replays of Formula Duude season 2, the Suzuka round, and let the overlay find its championship. Seventeen years later, the title fight is still on screen: me on 141 points, tib on 132, with one race to go.

Race classification of the 2009 FD02 Suzuka race
FD02 drivers standings after round 19 of 20: Horta 141, J Flores 132, Dave 14, R Flores 8

Suzuka 2009, decoded from the original replay: tib's win, and the FD02 standings after round 19 of 20.

Those old replays paid off twice. The logs of season 2 were lost years ago, but six of the feature races survived as replays, so the same decoder recovered their lap times and lap charts for the racing section of this site: 251 laps that had been missing since 2009.

Hot laps

GTR2 also saves your best lap of a session as its own short replay. I record those twice in a loop, once onboard and once with the TV cameras, so they got their own graphic: a driver card with a running lap timer, which stops on the exact lap time at the line and holds it for a few seconds while the replay loops.

Hot lap card with a running timer at 0:52.0

Ideas that did not make it, or changed shape

  • The featured driver tag. An early version highlighted one chosen driver in the banner. It duplicated the game's own replay readout and got confusing when the camera followed someone else, so it went.
  • Controls inside OBS. At first, the only way to drive the overlay was OBS's "Interact" window on the game PC. That meant leaving the game, and GTR2 does not like losing focus: it sometimes crashes on alt-tab, and it drops to 30 frames per second in the background, which makes the recording stutter.
  • An OBS dock as a remote. A second copy of the page as an OBS dock was supposed to drive the first one. It did not work: OBS gives every browser instance its own storage, so the two copies never heard each other. Syncing through OBS's own websocket server was possible but parked.
  • Reading the game's memory live. The plugin route is closed, but reading the running game's memory from outside (the way some community tools do) could one day feed a TV director that cuts to battles by itself. That is still on the shelf, for another day.

The OBS dock idea did not die, though. It came back as the biggest change of the weekend.

The TV station and the TV at home

The question became: what if one page was the control room, on a tablet or phone next to the screen, and every other page just showed what the control room decided? A TV station and the TVs at home. I call them the Server and the Client.

Before building anything, I set a hard limit: a command from the tablet had to reach the screen in under 100 milliseconds, and anything over 200 would kill the idea. So the first thing built was a throwaway latency test: one page to send events, one to receive them and measure. The first run was terrible, 1.3 seconds on average and up to 5. The cause was not the network at all: the folder the relay used lives on a disk that the home server's web container reads through a slow translation layer, so a small file rewritten and re-read many times a second fell seconds behind. Moving that state into memory fixed it. A second surprise: the clocks of my PCs disagreed by about 300 milliseconds, so every page now measures its offset to the home server's clock, and all timing uses that one clock.

Latency test results: median 45.5 ms, maximum 106.8 ms over 228 events

The real test, from my phone to OBS on the game PC: 45 ms on average, 107 ms at worst over 228 events, never near 200. Most of it is the phone's Wi-Fi.

The FDTV Server page on a phone: preview, playback controls, sequence buttons and one button per car

The design that came out of it: the Server never streams the picture or the clock. It sends one small description of what should be on air: which race, where the replay was at a given moment, whether it is playing, which graphic is up and since when. Every page works the rest out by itself, frame by frame, on the shared clock. A Client that joins late, or reloads, lands on the same frame of the same graphic. The Server can be closed, put in a pocket and reopened, and nothing on air changes.

Testing found one more subtle bug: pressing seek, speed and play in quick succession sent three updates that could reach the home server out of order, leaving the screens one step behind. Now the Server sends one update at a time, always the newest, and every page ignores anything older than what it has.

The Server also brought back an old favourite from the 90s broadcasts: the driver caption. One small button per car number on the phone, and the caption appears for five seconds with the car's current position.

 
A 1990s Formula 1 broadcast driver caption: Fisichella, Jordan Peugeot, position 2

The reference.

FDTV driver caption: Horta, number 5, Williams 1991 Team, position 2, with a P tyre badge

FDTV's version, tyre badge included.

Team colours and tyre makers

The last two features came from the data I already had. Every team on this site has a picture of its car, usually a showroom shot. A small script looks at a ring around the car in each of the 1,309 pictures, ignores the background (whatever colours dominate the picture's edges), the shadows and the glare, and picks the car's main colour. A strong hue wins; a mostly black, silver or white car gets that instead, so a sponsor logo does not decide it. The result: 1,067 coloured cars, 149 silver, 80 black and 13 white, now stored with each team and editable when the script gets one wrong.

Williams Grand Prix Engineering Ferrari 360 GTC in a blue and yellow livery

My own Williams-liveried Ferrari came out as a deep blue (#2a4787), which is exactly what the pylon stripe should be.

Tyres were a treasure hunt through the game's files. Each car file points to a physics file, which names a tyre file, which sometimes has the maker in its name and sometimes only in the comments its author left inside. Following that chain for all 2,496 cars found the maker for 83 percent of them (Michelin, Dunlop, Pirelli, Bridgestone, Goodyear, Yokohama, Avon and Falken), and 1,785 of the site's teams now carry it. The rest are mod tyres whose authors never said, and that is fine: unknown stays unknown, with no badge.

On screen it is the 90s badge: a black circle with the maker's initial in white, on the driver and hot lap cards, in the teams standings, and in a Tyres column of its own in the classification. Even the letter got attention: the font's capitals sit a hair high, so the initial is nudged by a measured fraction to sit dead centre.

 

Results and learnings

  • Twelve races are on the overlay today, from a 2-car Formula Duude race in 2017 to 32-car grids, and adding one is a matter of dropping the replay and its log into a folder.
  • Everything is driven from my phone, and OBS on the game PC follows within about 45 milliseconds.
  • The most important decision was not a feature: it was measuring the latency before building the Server and Client split, with a number agreed in advance that would cancel the idea.
  • The best reference material was old TV. Every time we followed a real 90s broadcast frame, the result looked right; every time we improvised, it needed another pass.
  • Old data is worth keeping. A replay file I recorded in 2009 ended up filling gaps in this site's database and starring in the standings screen above.

I built all of this over one weekend with Claude as my pair programmer: I brought the memories, the references and the opinions, and we argued about pixels until the tyre letters were centred.

It feels a bit like finding an old VHS tape of a race you were in, and discovering that somebody, somewhere, recorded it with the full TV coverage after all.

go to racing