Skip to main content
A live NYXERON night is one CEO’s club, open to anyone with the room code. Guests walk in, buy tokens at the bar and watch the crowd; a DJ can take the decks and play a set that everyone in the room hears. All of it runs through a relay of about 1,150 lines of Go (tests not counted) that never looks inside a message.

Host-authoritative by design

One browser owns the truth. The CEO’s browser already runs the full simulation for a solo night, so in a live room it simply keeps doing that and becomes the host:
  • Host (CEO) runs GameState, applies every action, and broadcasts the result.
  • Guests and the DJ send intents only: “I want to buy 2 TSLA”, “here is my mix”. They never edit state.
  • Relay routes frames between them. It has no game logic, no database, and does not parse payloads.
Why: there is exactly one simulation per club, so there is nothing to reconcile and no way for two browsers to disagree about the night. The relay stays cheap enough to host thousands of rooms on a small container.

The wire: a 3-byte header

Every game message is a binary WebSocket frame with a fixed 3-byte header followed by an opaque payload. Entering and leaving a room uses a handful of small JSON text messages: create, join, resume, kick, close from clients, and created, joined, peer, host, kicked, closed, error from the relay. Signed-in players only. Live clubs need a signed-in account: before opening a socket, a client gets a pass from the api that vouches for the player, and the relay refuses create, join or resume without one (the player sees a sign-in error). That pass also lets the relay apply its limits and kicks to a player’s account. A snapshot is the public club state serialized to JSON and gzipped with the browser’s built-in CompressionStream (no library). A busy night is about 100 KB of JSON and 10–15 KB on the wire. The host sends one at most every 250 ms, coalescing every action in between.

Room lifecycle

  • Room codes are 6 characters from a 31-symbol alphabet that leaves out look-alikes (0, O, 1, I, L), drawn with a cryptographic RNG: 316≈8.9×10831^6 \approx 8.9 \times 10^8 possible codes.
  • The host token is a secret that makes a browser the host.
  • Guests are admitted at night. A hello during the day waits until the club opens.
  • One DJ at a time. A second DJ is turned away until the decks are free.

DJ sync: state, not audio

Streaming a DJ set to every guest would cost each listener an audio stream. NYXERON sends the mix instead: every browser already has the same track crate, so the DJ only needs to say what the decks are doing. Four times a second the DJ’s browser sends a MixSync: for each deck the track id, play state, playhead, tempo, reverse flag, loop, and the channel’s EQ, filter and fader settings, plus the crossfader. One-shot moves (pads, brake, backspin) travel as separate events the moment they happen. The host relays both to the whole room as reliable events, and every browser plays the set on its own Web Audio engine.

Bandwidth

Blistener=f⋅(h+s)B_{\text{listener}} = f \cdot (h + s) where So Blistener≈4×303≈1.2 kB/s≈9.7 kbit/sB_{\text{listener}} \approx 4 \times 303 \approx 1.2\ \text{kB/s} \approx 9.7\ \text{kbit/s}. A modest 128 kbit/s audio stream would be about 13 times more, per listener, and would add encoding delay on top. For a room with NN listeners the relay sends roughly N⋅BlistenerN \cdot B_{\text{listener}}, so even a room of 50 listeners stays near 60 kB/s of mix traffic.

Staying on the beat

The mix arrives a little late, so each follower aims slightly ahead of where the DJ was when the message was sent, and only jumps when it is clearly off: a small drift is inaudible, a seek is not. τ={p+d⋅L⋅rif the deck is playingpif it is pausedd={−1reverse+1forward\tau = \begin{cases} p + d \cdot L \cdot r & \text{if the deck is playing} \\ p & \text{if it is paused} \end{cases} \qquad d = \begin{cases} -1 & \text{reverse} \\ +1 & \text{forward} \end{cases} seek to τ  ⟺  ∣x−τ∣>Δ\text{seek to } \tau \iff \lvert x - \tau \rvert > \Delta where Play, pause, track loads, tempo, loops and EQ are applied immediately; only the playhead uses the tolerance. A track that is still loading is never loaded twice, and a track a listener does not have simply stays silent on that deck.
The DJ’s deck A is playing forward at tempo r=1.04r = 1.04 and reports p=62.500p = 62.500 s.τ=62.500+1⋅0.12⋅1.04=62.6248 s\tau = 62.500 + 1 \cdot 0.12 \cdot 1.04 = 62.6248\ \text{s}A listener whose deck is at x=62.48x = 62.48 s is off by ∣62.48−62.6248∣=0.1448\lvert 62.48 - 62.6248 \rvert = 0.1448 s, below Δ=0.25\Delta = 0.25 s: it keeps playing untouched. A listener at x=62.30x = 62.30 s is off by 0.32480.3248 s and seeks to 62.6248 s.

Reconnects

Phones lose signal and laptops sleep; a live club has to survive that. Backoff. After a drop, a client waits with exponential backoff and equal jitter, so a relay restart is not hit by every player at the same instant: bk=min⁡(8000, 500⋅2k) ms,wk=bk2+Uk bk2,Uk∼U[0,1),k=0,1,2,…b_k = \min\left(8000,\ 500 \cdot 2^{k}\right)\ \text{ms}, \qquad w_k = \frac{b_k}{2} + U_k\,\frac{b_k}{2}, \quad U_k \sim \mathcal{U}[0,1), \qquad k = 0, 1, 2, \dots so the first wait falls in [0.25,0.5)[0.25, 0.5) s and the waits settle in [4,8)[4, 8) s. The counter kk resets only after a connection has held for more than 10 s, so a server that accepts and immediately drops can’t make clients spin. A client stops for good when it is kicked, its room is closed or gone, or another tab takes the room over (replaced). Guests and DJs get a new peer id on every reconnect, so the seat is tied to something else: a secret key chosen for that night. The host holds a disconnected guest’s seat for 60 seconds; when the guest’s hello comes back with the same key, the host binds the old seat, with everything in it, to the new id. The host reconnects with resume and its secret token. A room only comes back to the account that opened it. Peers are told host off and then host on; the relay re-announces every peer still in the room and they send hello again. If the host stays away longer than the 60-second grace period the room closes, but the same code can be resumed for up to an hour, so invite links and scheduled events keep working.

Built-in limits

The relay limits abuse: it bounds connections, rooms and message sizes and rates, keeps a kicked player out of the room for a while, and drops clients that stop reading so guest traffic can never crowd out the host. When the relay is full, the room abandoned longest closes early (it stays resumable) instead of a real CEO being refused. Each connection has exactly one reader and one writer goroutine; the writer is the only code that touches the socket. The relay is covered by Go tests run with the race detector, including adversarial tests that attack it on purpose. A bundled load-test tool drives many rooms of simulated peers against it: 10 clubs × 30 players get 100% of snapshots delivered in about 31 MB.