DEV Community

Cover image for I built the same retro gaming platform two ways: WebRTC and WebAssembly
The Nicholas 035
The Nicholas 035

Posted on • Edited on

I built the same retro gaming platform two ways: WebRTC and WebAssembly

What I learned from building two editions of PIXELATED—one that streams games through WebRTC and one that runs them directly in the browser with WebAssembly.

I started PIXELATED with a fairly ambitious idea: what if a browser could be the front door to a retro gaming platform, while the actual game ran somewhere else?

That became PIXELATED Studio, a project built around a desktop engine, Docker, and WebRTC streaming. The browser handles the interface and sends controls; the engine runs the emulator and streams the result back.

It worked, but it also raised an obvious question:

For small 8-bit games, do I actually need the streaming layer at all?

That question eventually became a second project: PIXELATED User Edition, where compatible games run directly inside the browser through WebAssembly.

The two editions now share a catalog and much of the same product experience, but they take completely different paths from clicking Play to seeing the first frame. Building both has been a useful lesson in architecture, product boundaries, and the fact that “faster” does not always mean the same thing.

One idea, two editions

The easiest way to explain the split is this:

  • PIXELATED Studio treats the browser as a client for a separate game engine.
  • PIXELATED User Edition turns the browser into the game engine.

In Studio, the desktop application manages a local Docker runtime. That runtime launches the emulator, captures its video and audio, creates a WebRTC session, and accepts controls sent back by the browser.

In User Edition, a Libretro emulator core is compiled to WebAssembly and loaded on demand. The ROM is verified and passed to the core, then the browser renders the game locally to a canvas. There is no desktop application, Docker container, video encoder, or WebRTC media path involved.

Studio version diagram

Studio version diagram

PIXELATED Studio: the system-oriented version

Studio is the version I use when I want to explore the infrastructure around remote and local game streaming.

The packaged desktop app manages the engine lifecycle, including Docker checks, runtime startup, health polling, and secure browser pairing. Once paired, the web app can ask the engine to boot an approved catalog game or a ROM from the user's Local Vault.

Some of the more interesting Studio features are:

  • WebRTC video, audio, and input transport
  • Local and LAN engine pairing
  • A persistent Local Vault backed by the engine's Docker volume
  • Multiplayer lobbies with player slots and spectators
  • Stream-quality profiles, fullscreen play, audio controls, and keyboard remapping
  • Creator submissions, browser-compatibility checks, and admin review tools
  • Favorites, comments, reactions, reports, and moderation
  • Opt-in stream telemetry and exportable research bundles

Studio is more complicated to install and operate, but that complexity buys it flexibility. The browser does not need to emulate the target system itself, and the engine can eventually support runtimes that would be unrealistic inside a normal web page.

The tradeoff is that every extra stage can affect startup time, reliability, and perceived responsiveness. The engine must start, the emulator must boot, signaling must complete, the media pipeline must become ready, and the browser must receive and display a usable frame.

Inside the desktop engine

Inside the desktop engine

Gameplay with telemetry

Gameplay with telemetry

PIXELATED User Edition: the low-friction version

User Edition exists because NES, Game Boy, and Game Boy Color games are small enough that modern browsers can run them very well without streaming.

The first supported cores are FCEUmm for NES and Gambatte for Game Boy and Game Boy Color. They are loaded only when a compatible game starts, which keeps the regular catalog experience separate from the heavier emulator bundle.

The launch flow still does more than simply download a file and hope for the best. The app checks the selected build, enforces a size limit, verifies its expected SHA-256 checksum, resolves a compatible emulator core, and reports each stage to the player. Unsupported games remain visible in the shared catalog but are clearly marked as requiring Studio or a future browser core.

The User Edition currently includes:

  • Instant browser gameplay for eligible NES, GB, and GBC titles
  • Three local save-state slots with import and export
  • A Personal ROM flow that keeps ROM bytes in memory instead of uploading them
  • Local game history without silently persisting the ROM itself
  • Browser storage management and a PWA shell
  • Optional local performance measurements that stay in the tab until exported

The privacy boundary around personal ROMs was deliberate. Opening a local file does not upload it to the API, and the current version does not persist the ROM bytes after the session. Save states and harmless recent-file metadata can live locally, but persistent ROM storage will only be added later as an explicit opt-in feature with clear quota and deletion controls.

User edition

Why not replace Studio with the WASM version?

Because WebAssembly is not automatically a replacement for streaming.

For the systems User Edition supports today, running locally is usually the simplest experience. It avoids video compression, removes a network round trip from controls, produces a sharp native canvas, and requires no companion software.

Studio is solving a different problem. It separates the browser from the runtime, supports local-engine and LAN workflows, and gives me a real WebRTC media pipeline to instrument. Its architecture can grow beyond the limits of the browser, while User Edition is intentionally constrained by the emulator cores, memory, storage, and APIs available on the user's device.

Maintaining both also creates something more useful than either project alone: a controlled comparison.

The same catalog, visual design, game, and general interaction model can now be exercised through two very different execution paths. That makes architectural tradeoffs visible instead of theoretical.

My latency research

PIXELATED Studio is also connected to my thesis work. I am experimenting with a technique I currently describe as latency fingerprinting.

The basic idea is not to reduce an entire session to one latency number. A streamed game has several observable stages and behaviors: backend-session creation, engine startup, signaling, the first remote track, the first non-black frame, frame rate, bitrate, jitter, packet loss, stalls, disconnects, and recovery.

Together, those measurements form a kind of session fingerprint. Repeating runs under controlled localhost, LAN, and browser-only scenarios may help show where delay and instability are introduced, and whether different architectures produce consistently different patterns.

Studio can export versioned research bundles containing event timelines, telemetry samples, summary statistics, and graphs. User Edition provides a browser-only baseline with ROM download, verification, core-load, first-frame, frame-pacing, long-task, and supported memory measurements. Recording is opt-in, and the data remains local until the tester exports it.

What building both versions taught me

The biggest lesson was to stop treating architecture as a contest with one winner.

WebAssembly gives User Edition an excellent experience because the workload fits the browser. WebRTC gives Studio a useful boundary between the player and the runtime because Studio is meant to explore streaming, multiplayer, LAN access, and a broader engine.

I also learned that shared infrastructure needs explicit ownership. Studio is the migration authority for the shared database, while both editions consume a compatible API contract. Edition and runtime metadata are recorded deliberately, and Studio-only administration and publishing features are not copied into the User frontend just because the database is shared.

That separation has made both projects easier to reason about:

  • User Edition can optimize for opening a page and playing quickly.
  • Studio can optimize for engine capabilities, networking, and measurement.
  • Shared social and catalog features do not need to be rebuilt twice.
  • Experimental code stays out of the simpler product unless it has a reason to be there.

There is still plenty left to do. I want to test more browsers and devices, add browser cores one system at a time, improve the research procedure, and collect enough controlled runs to say something meaningful about the two architectures.

For now, though, both editions are real, playable projects, and they answer the same question in two very different ways.

Try them or explore the code

If you have worked on browser emulation, WebRTC streaming, or performance measurement, I would be interested to hear how you would compare these two paths, and which measurements you would trust most.


The catalog is intended for approved, public-domain, or appropriately licensed homebrew content. Original creators retain ownership of their games and are credited in the project. Personal ROM features are intended for files users are legally allowed to use.

Top comments (4)

Collapse
 
thuangf45 profile image
Thuangf45 • Edited

Interesting comparison. My main concern is the scalability and infrastructure cost of the Studio approach.

If every player effectively requires a dedicated runtime (an emulator, Docker container, VM, etc.), then the server has to carry almost all of the computational and memory burden while the client is mostly just viewing a video stream. That seems reasonable for LAN deployments, research, or experimentation, but it could become very expensive for a public service.

Imagine a game requiring around 1 GB of memory. With N concurrent players, the backend may need roughly N × 1 GB just for the game instances, before accounting for containers, video encoding, networking, monitoring, and other overhead. Even much smaller games become costly at scale because every session consumes dedicated resources.

WebAssembly solves the server-side scaling problem by moving execution to the client, but it introduces another trade-off. The initial download is perfectly reasonable for games that are only a few megabytes, but once games grow to tens or hundreds of megabytes—or even several gigabytes—you start consuming a huge amount of network bandwidth. Caching certainly helps, but it also means the browser may end up storing a significant amount of game data over time.

And if the platform only targets very small retro titles, the potential audience becomes more niche. Modern players are generally more attracted to larger, more sophisticated games that are already installed locally, so they don't have to wait for large assets to be downloaded before playing.

To me, it feels like both architectures are optimized for different use cases, but neither is an obvious solution for large-scale modern game distribution.

That said, I think many kids still gravitate toward downloaded, installed games because they typically offer richer worlds, larger amounts of content, and more advanced graphics without having to stream or repeatedly download large assets. Browser-based games make the most sense to me when the priority isn't high-end graphics but accessibility and convenience—for example, educational games, quick interactive experiences, or applications that need to deliver value immediately. In those cases, lightweight HTML, CSS, and JavaScript are often enough, and WebAssembly only becomes worthwhile when the application genuinely needs more computational performance or a more sophisticated runtime.

Collapse
 
nicholasthegreat profile image
The Nicholas 035 • Edited

Hey, thanks so much for taking the time to write a detailed and thoughtful response!
You hit on some really fundamental trade-offs between WebAssembly and Cloud Gaming, especially regarding bandwidth vs. client compute.
I wanted to clarify a quick architectural distinction regarding the "Studio" / Docker approach, as I might not have made the infrastructure topology clear enough in the post:
1. Decentralized Edge / Self-Hosted Infrastructure (No Central Server Costs)
There is actually no central backend running Docker containers here. The central hosted server/API only handles lightweight tasks like fetching game catalog metadata, auth, and ROM storage via Supabase. The Docker engine runs locally on the user’s own desktop machine. In short, it’s a personal "Edge Cloud", users turn their own hardware into the streaming server. Because compute happens on the user's host machine, the backend server cost is virtually zero and scales naturally.

2. Infrastructure demonstration over specific product
Using 8-bit retro games is simply a controlled, lightweight testbed for the research, not the end goal. The project is an architectural proof of concept for local/personal cloud streaming. With enough GPU/hardware resources on the host machine, the exact same Docker/WebRTC setup could stream heavier modern games from a user's main rig to any device. It's not trying to compete with GeForce NOW on a enterprise scale, but rather demonstrating a local, self-hosted streaming pipeline.

3. Offloading compute for heavy titles
The core promise of cloud/remote streaming is offloading heavy computational demands and storage requirements. For larger games, eliminating a multi-gigabyte client download by streaming the render loop is where this paradigm becomes valuable for low-spec client hardware.

4. Client flexibility
The browser client is used here as a convenient, zero-install testing environment for WebRTC, but the receiver pipeline isn't tied to browsers alone, the same streaming protocol can target desktop wrappers, mobile apps, or smart TVs.

Really appreciate your breakdown of the WASM side as well, the client-side storage vs. compute trade-off is exactly the kind of contrast I'm benchmarking in this project. Thanks again for the great discussion!

Collapse
 
thuangf45 profile image
Thuangf45

The architecture makes a lot more sense after your explanation. My remaining concern is the onboarding experience. If the target audience eventually includes non-technical users, requiring a desktop app, Docker (and potentially WSL on Windows), runtime setup, and pairing could become a significant barrier. For research or developer-focused experimentation, that's perfectly reasonable. For a consumer-facing product, though, I'd expect most of that complexity to be hidden behind a simple installer or automated setup process.

More generally, I tend to evaluate architectures not only by whether they're technically possible, but also by whether they're economically and operationally viable. A design can be elegant from an engineering perspective yet still struggle as a real product if it introduces high infrastructure costs, operational complexity, difficult onboarding, or limited scalability from the outset. Those trade-offs affect not only the provider but also how easily users can adopt the product and whether it can realistically compete in the market over the long term.

That's why I always find the business model just as interesting as the technology itself. A technically impressive architecture is only one part of the equation—the real challenge is making it practical, sustainable, and accessible enough for people to actually use.

Thread Thread
 
nicholasthegreat profile image
The Nicholas 035

Thanks for the follow-up! You make a fair point about the onboarding UX, consumer products definitely live or die by their setup process.
To clarify the current state of the project, I have actually abstracted away a good chunk of that manual work. The desktop engine currently has a technique to automatically locate the local Docker daemon and spin up the containerized environments with a single click. Users don't have to touch the CLI or manually configure the runtimes.
That being said, you are right that requiring them to download Docker Desktop (e.g., on Mac) alongside the engine is still an initial barrier. I am actively working on making this onboarding phase less rigorous, but right now, it is a deliberate trade-off. By accepting a slightly heavier one-time setup, the user gains massive flexibility and the permanent ability to offload heavy compute to that node from any device.
Your point about evaluating architectures through an economic and operational lens is totally valid. If this were to shift from an academic/research focus into a mainstream commercial product, hiding that virtualization layer completely would be step one. Appreciate the great discussion on the product side of things!