In my downtime, I’ve picked up sim racing as a hobby. I’ve been playing the F1 games specifically for a few years now. I’m not entirely sure what compelled me to start, but I found myself getting pulled pretty quickly into both F1 and sim racing.
While I waited for my first wheel and pedal setup to arrive, I dove into YouTube videos on how to get started. My first goal was simple: complete a lap without any assists. Coming from an engineering background, part of what hooked me was how much the game rewarded understanding the car. The physics engine responds to things like weight transfer, tire temperature, braking, and traction in ways that at least approximate what would happen in real life.
As I got more comfortable driving, though, I started asking questions the in-game HUD was not particularly good at answering. Were my tires overheating? Were the brakes outside their operating window? Did I have damage somewhere on the car?
The information was technically there, but not always in a format that was easy to process while driving. Tire and brake temperatures were small, damage information lived elsewhere in the HUD, and getting a quick read on the state of the car was harder than it needed to be.
That led me to third-party dashboard apps.
Setup was surprisingly easy. The F1 games have a built-in UDP broadcast function, so all I had to do was point the game at the IP address and port used by the dashboard. From there, the app could consume telemetry directly from the game and display it in a format closer to what a driver might see on a steering wheel.
That solved one problem:
What is happening to the car right now?
The second problem was different:
What happened during the lap, and where did I lose time?
For that, I found RaceNet, EA’s official telemetry platform. RaceNet lets you compare laps side by side across things like braking points, throttle application, steering input, sector times, and other telemetry.
For a while, that combination worked really well. One app told me what the car was doing in the moment. RaceNet helped me understand what had happened after the lap was over.
But if you know anyone who works in tech, using two separate tools to accomplish one relatively simple goal tends to create an itch.
Mine was to combine them.
Building something for myself
The F1 game was already broadcasting the underlying data, which meant there was no reason I couldn’t consume the same feed myself.
So I started with a very small set of requirements.
For the live dashboard, I wanted:
- Tire temperatures, color coded
- Tire wear, color coded
- Brake temperatures, color coded
For lap analysis, I wanted:
- The ability to compare two laps
- The ability to analyze performance across different sessions
I intentionally left out things like RPM and gear at first because the in-game HUD already handles those reasonably well. The advantage of building the project myself was that anything else could always be added later.
That became the starting point for Pitwall.
At a high level, I broke the system into four pieces:
Receiver → Processor → Storage → Display
The receiver consumes telemetry from the game. The processor turns those packets into useful structures. Storage keeps both high-frequency telemetry and session metadata. The display handles the live dashboard and post-session analysis.
Getting telemetry out of F1
The UDP broadcast function made the receiver relatively straightforward. The game sends packets to a configured IP address and port, so the first step was simply creating a consumer and seeing what came through.
Before building much else, I wanted to understand the shape and volume of the data. That would determine how the rest of the system needed to work.
I ran a few laps and captured the packets as a test dataset.
There was a lot more in there than I expected: motion data, session information, lap data, car telemetry, events, setup data, weather, and dozens of other fields spread across different packet types.
At that point, simply writing every incoming packet directly to a database stopped making sense.
Some values, like weather or car setup, change infrequently. Others, like throttle, brake pressure, speed, or steering input, update dozens of times per second. Storing all of that at the same grain would create a huge amount of duplication.
More importantly, the packets were shaped around how the game broadcasts data, not around the questions I wanted to ask later.
A packet-oriented schema is useful if the question is, “What did the game send?”
It is much less useful if the question is, “How did lap 12 compare with lap 15?”
That led to a redesign around the actual analysis I wanted to perform.
Designing the data model around racing
The model ended up centered on a few natural grains:
Session
Track, weather, session type, setup information, and other metadata that applies broadly to the run.
Stint
Tire compound, stint boundaries, and other information that changes over a portion of the session.
Lap
Lap time, sector times, validity, and summary information for an individual lap.
Telemetry trace
The high-frequency observations recorded throughout the lap: speed, throttle, brake, steering, temperatures, and other signals.
Giving each grain its own shape made the system much easier to reason about. Session-level information no longer needed to be repeated hundreds or thousands of times, and lap analysis could operate on data that was already organized around a lap.
That sounds obvious in retrospect, but getting the storage model right ended up being one of the more important parts of the project.
Comparing two laps is harder than it sounds
At first, comparing two laps sounded trivial.
Take two telemetry traces, line them up, and calculate the differences.
The problem is that two laps almost never take exactly the same amount of time. If telemetry is being sampled at 60 Hz, comparing sample 192 from lap A with sample 192 from lap B only tells you what each car was doing 3.2 seconds into the lap.
That stops being useful the moment one car brakes half a second later than the other.
The comparison needs to happen by position on the track, not by elapsed time.
The better question is:
What were both cars doing when each had traveled 850 meters around the lap?
So every lap trace gets normalized by distance. From there, the telemetry can be downsampled or interpolated into fixed distance bins, which makes it possible to compare speed, throttle, brake, steering, and other signals at the same point on the circuit.
Once both laps share the same distance axis, the comparison becomes much more meaningful.
That was one of the more interesting problems in the project because it changed the way I thought about the data entirely. Time is obviously important in racing, but it is not necessarily the best index for comparing two trajectories.
Postgres and Parquet
Once the data model started taking shape, the next question was where everything should live.
I ended up splitting storage between Postgres and Parquet.
Postgres holds the relatively small, relational data: sessions, stints, lap summaries, metadata, and the things the application needs to query quickly when rendering lists or navigating between sessions.
Why Postgres?
Initially, because that is what I use at work and it was the tool I reached for by default.
Fortunately, it also happens to fit the workload well. Session and lap metadata are relational, compact, and frequently queried.
The high-frequency telemetry is different.
A single lap can contain thousands of observations across a large number of signals. Most analysis also only needs a subset of those fields. A lap comparison might only care about distance, speed, throttle, brake, and steering.
That made Parquet a much better fit.
Parquet is columnar, so an analysis does not need to read every field in the telemetry trace when only a handful are relevant. Full-resolution telemetry is stored one file per lap, which also means a two-lap comparison can read exactly the two files and columns it needs.
The physical layout of the data ends up matching the access pattern pretty naturally.
Postgres answers questions like:
What sessions and laps exist?
Parquet answers questions like:
What exactly happened throughout this lap?
Building the live dashboard
Once the receiver, processor, and storage layer were working, the live dashboard came together much faster.
Telemetry flows through the application and gets pushed to the frontend over WebSockets as the session runs.
The dashboard now shows things like speed, throttle and brake input, tire state, ERS, gear and RPM, track position, and weather.
Gear and RPM were not part of the original MVP since the game already displays them well, but once the telemetry pipeline existed, adding them was almost free. The dashboard naturally expanded beyond the first version.
Frontend development is not particularly close to my day-to-day work, so Claude ended up being useful here as a coding partner. I spent most of my time defining the data models, behavior, and architecture while leaning on it more heavily for the UI implementation.
There is still some perceptible latency in the live dashboard, which is one of the things I want to keep iterating on. For the current use case it is manageable, but eventually I would like the feedback loop to feel much closer to instantaneous.
Watching your own inputs and the state of the car render back in real time is still pretty satisfying.
Developing without having to drive
One practical problem showed up pretty quickly: working on the dashboard normally required running the game and driving laps.
That is not a great development workflow.
So I built a synthetic UDP sender.
It takes a previously recorded lap and replays representative telemetry over the same wire protocol used by the game. The stream includes things like braking zones, DRS zones, tire temperature drift, and fuel burn.
From Pitwall’s perspective, there is no meaningful difference between that stream and telemetry coming from an actual session.
That means I can work on the receiver, processing pipeline, dashboard, and visualization without launching F1 or touching a controller.
It has also become the easiest way to demo the entire system.
Where Pitwall goes next
At this point, Pitwall can handle both sides of the original problem.
During a session, the live dashboard gives a quick view into the current state of the car.
After a session, laps can be compared inside the same application using distance-aligned telemetry.
Most of the work so far has gone into getting the ingestion, data model, and storage architecture right. Once that foundation was in place, another possibility started to become much more interesting: using an LLM to interpret the telemetry.
I have done some preliminary experiments by manually feeding session data into ChatGPT, and the results have been surprisingly useful.
Instead of simply displaying two traces, the model can start turning the telemetry into coaching:
- You are consistently braking earlier than the reference lap into a particular corner.
- You are getting back to full throttle later on corner exit.
- Tire temperatures start drifting outside their ideal window later in the stint.
- A loss of rear grip appears to coincide with increased throttle corrections.
That is much closer to the experience I ultimately want from Pitwall.
Charts are useful, but they still require someone to interpret them. The more interesting version of the project is something that can look across an entire session, identify patterns, and explain what those patterns mean in the context of driving the car.
Right now, that analysis still involves manually copying data into ChatGPT. The next step is building that directly into the application.
Eventually, I would also like to make the interaction conversational: ask why a certain corner went poorly, compare two approaches, or get feedback during a session rather than only afterward.
That last piece will require solving the latency problem first.
What started as wanting a better way to read tire and brake temperatures has turned into an experiment in streaming telemetry, data modeling, analytical storage, realtime interfaces, and now AI-assisted analysis.
That was not really the plan when I started.
But that is also probably why I have enjoyed building it so much.
Pitwall is public on GitHub if you want to look through the implementation in more detail or boot it up for a race yourself.