Backyard RC racing in your browser: thirteen vehicles, native WebXR and AR, and private peer to peer multiplayer over Tailscale, with no app server and no install. Tripo API for on-demand props asset generation and car model generation. Tripo Studio to generate 2 preloaded car models for vehicle selection.

Inspiration

RC cars are the purest form of racing fun. You grab a controller, meet up somewhere, and go. We wanted that same walk-up-and-play feeling on the web, without the friction that usually comes with a multiplayer 3D game. No launcher, no account, no cloud game server billing by the minute, and no waiting on an app store review.

We also wanted to see how far a modern browser could be pushed on its own: real 3D, a real sense of handling, real cross-device multiplayer, and true mixed reality, all from a static site you can host anywhere. And because privacy matters to us, we wanted multiplayer that runs across your own private network rather than someone else's servers.

With the recent developments in GenAI, we were thinking of how we can make use of it, and came to customization. We used Tripo API to allow players specify what vehicle they would like to drive by prompting while in the vehicle selection screen, and what other props they would like to see in the map.

What it does

XRRC is a static-first Three.js RC racing game that runs on desktop, mobile, native WebXR headsets, and 8th Wall AR, all from the same code.

Players choose from thirteen vehicles in total: seven core types with distinct handling, including a rally car, dune buggy, 4x4 truck, motorcycle, tank, plane, and helicopter, plus six GLB skins. You can race solo against the clock, or open a private room and invite friends with a QR code that is generated locally in the browser.

Multiplayer connects players directly over WebRTC, with the signaling handshake carried privately through Tailscale Serve, so there is no public game server sitting in the middle. The game ships with a responsive lobby localized in English, Spanish, and French, fully synthesized audio for the engine, skids, impacts, countdown, and music, plus impact vibration and controller rumble. Input works however you like to play: keyboard, touch, gamepad, or XR controllers.

How we built it

The whole game is static-first. Every asset loads from a relative URL, so the build drops straight onto GitHub Pages with no server required, and GitHub Actions handles CI and deploys. There is no bundler and no build step. The code ships as browser-safe modules that also expose CommonJS exports, so the same files run under Node for testing.

Rendering is Three.js loaded from a CDN. To hold a healthy frame budget, we batch and instance the track props. On our procedural rally reference at 1440 by 900, that batching pass took the scene from 363 draw calls down to 74, at 16,222 rendered triangles. Vehicle thumbnails render on a separate 128 by 128 offscreen renderer so the lobby stays light. On Meta Quest we detect the device and switch to a reduced-cost quality profile automatically.

Multiplayer uses unordered WebRTC data channels for low-latency peer to peer state, with origin-restricted signaling, health checks, automatic reconnects, sequence validation, and interpolation to keep motion smooth. An optional lightweight Node service handles WebRTC signaling only, and private rooms route that signaling through Tailscale. We kept the whole thing honest with 40 Node tests and 11 Playwright browser tests.

We used Tripo API for on-demand props asset generation and car model generation. We also used Tripo Studio to generate 2 preloaded car models for vehicle selection.

Challenges we ran into

Making one codebase feel right on a phone, on a desktop, and inside a headset meant every input and rendering decision had to survive very different constraints. Hitting frame budget on standalone XR hardware like Quest pushed us into aggressive batching and a dedicated low-cost quality profile.

Real-time multiplayer over the open internet is unforgiving, so we spent real effort on reconnect logic, sequence validation, and interpolation, so that a dropped packet or a flaky connection does not turn into a teleporting car. Getting a private, no-public-server multiplayer path working meant wiring WebRTC signaling through Tailscale Serve and locking it down with origin-restricted signaling. And doing all of this with no bundler, as plain browser modules that also run under Node for tests, took discipline to keep clean.

Accomplishments that we're proud of

A full 3D racing game, with thirteen vehicles, mixed reality, and private multiplayer, that installs nothing and runs from a static site. Multiplayer that connects players directly and privately over their own Tailscale network instead of a rented game server. One code path that spans desktop, mobile, native WebXR, and 8th Wall AR. Real performance engineering with measurable wins, like taking the reference scene from 363 draw calls to 74. And a genuinely accessible feel: three languages, four input methods, synthesized audio, haptics, and QR invites that anyone can scan.

What we learned

The browser is a far more capable game platform than it gets credit for. You can ship serious 3D and real-time networking with no native app and no heavy backend. Static-first architecture is liberating: when everything is relative URLs and there is no build step, hosting, deploys, and testing all get simpler.

We learned a lot about the realities of peer to peer networking, and about how much a private networking tool like Tailscale can replace traditional server infrastructure for small multiplayer experiences. And we relearned that performance is a feature. The batching work is what makes the game playable on a headset at all.

What's next for XRRC

More tracks and a course editor, so players can build and share their own backyard rallies. Larger rooms and spectating. Deeper AR, including shared world anchors so several players in the same room see the same cars on the same table. More vehicles and community skins. Smoother matchmaking for private groups. And continued performance work to widen the range of devices that can join a race. And player specified map generation.

Built With

Share this project:

Updates