A room over JetStream

July 20, 2026 · 4 min read
  • pulse
  • nats
  • multiplayer

Pulse is a multiplayer server for the class of game a room abstraction fits: turn-based, simple .io-style, anything that's fundamentally clients publishing and receiving on a channel. I'm building it for the tycoon sim, and also as something other people can run.

One Go binary: embedded NATS/JetStream, an HTTP/WebSocket gateway, a CLI, a read-only dashboard. Download it, run it locally or on your own box.

The barbell

Photon is easy to start with, but you rent it and shape your game around its model. Nakama is powerful and self-hostable, but it's a substantial system to learn, operate and extend. For a solo or small indie team building a turn-based or lightweight real-time game, both ends are a poor fit: one costs control, the other costs weeks.

Two jobs

Pulse has to serve the game and stand on its own. That's a design constraint, not a marketing line. The rule is to build capabilities and examples that both a real game and an external user can use, not vertical features that only serve one title.

The same discipline that makes a contract deliverable survive handover (containing dependencies, testing through the public interface, proving each capability with a worked example) is what makes a solo product buildable at all.

Where the room layer sits

The room is a thin translation over JetStream, not a server of its own. Game code never learns subject naming or consumer topology. Durability, replay and presence are inherited, not written.

Clients connect over WebSocket to a gateway, which maps them onto rooms, which map onto NATS JetStream subjects and streams game client browser SDK CLI all speak the same public protocol one self-hosted binary, NATS included gateway HTTP / WS rooms thin layer JetStream durable streams replay KV + presence the only package that imports NATS

The CLI speaks the same public protocol as the browser and the SDKs, so if the protocol regresses the tooling breaks first. There's no privileged internal path that only my code can use.

Custom server logic arrives as WASM modules via Wazero. People write it in anything that compiles to WASM, and the binary stays one file, no CGO.

Where it is

The core slice works: create, join, publish, receive, leave. A turn-based example and a dashboard exercise it from outside. NATS, a WebSocket library, and not much else.

I'll write more as it gets used for something that isn't a smoke test.