Nob ~/notas/un-solo-lugar

~/notas

A world that lives in exactly one place

How jukz keeps a Minecraft world from ever having two versions: one host at a time, tokens with a generation number and a snapshot that changes hands

published Oct 1, 20264 min readNob

abstract

question
How do you share a Minecraft world among friends without keeping a server running, and without two versions of the world ever existing?
method
One host at a time, chosen when the world is opened; tokens with a generation number to settle simultaneous opens; and the world handed to the next host as a JGit snapshot, or to the cloud if nobody is left
result
It works end to end, tested inside the game with an A → B → A round trip; the hard logic has 67 tests that run without Minecraft
limits
It's built for small groups of friends, not large servers; and the tests cover the logic and manual cases, not real networks at scale

jukz is a Minecraft mod for playing in the same world with friends without keeping a server running. The world lives on the PC of whoever is playing. The rule behind the whole design is simple: there are no copies to sync, the world lives in exactly one place at a time

The underlying problem: two leaders

In distributed systems this is known as split-brain: two nodes believe at the same time that they're the leader, and each one accepts changes on its own. When they meet again there isn't one correct version, there are two. In a Minecraft world that means lost builds or inventories

jukz avoids it in two ways: by asking before opening, and by using a generation number to settle the case where two people open the world at once

Opening a world is asking

Every world has a permanent UUID and a short share code. When you open it, jukz asks whether someone already has it open: on the same network, over multicast; from different homes, through a rendezvous server of my own written in Rust (Axum). If someone has it, you join that person's world live. If not, it opens on your PC and you're announced as the host

At first I had planned a DHT for finding each other, but it got dropped: the rendezvous server was simpler and more reliable

Never two hosts

Every announcement carries a token with a generation number. If two people open the same world at the same time, the newest generation wins and the other one finds out: they can keep their local copy or join the live game as a guest

terminal
Nob opens the world        → host, generation 7
Kai opens it at once       → host, generation 8
                             8 wins
Nob finds out              → keeps the local copy
                             or joins as a guest

It's the same idea Martin Kleppmann describes as a fencing token: a number that only goes up and travels with whoever holds the permission. Anything that arrives with an old number is recognized as old, even if its owner still thinks it's in charge

Getting through each home's NAT

For a friend to join the world, their PC has to reach the host's, and there's almost always a router doing NAT in between. jukz combines three pieces:

how it works
UPnP ──► the host asks its router to open a port
STUN ──► each PC finds out what its public address is
 │ if the port can't be opened (behind CGNAT, for example)
 ▼
WebSocket relay on the rendezvous server: joins the two outgoing connections

The last step is the same idea TURN standardizes (RFC 8656): if two machines can't connect directly, both connect outward to a third party that passes the data along. It costs a bit more latency, but it works even when neither can accept incoming connections

Handing the world over

When the host quits with guests connected, it saves, snapshots the world with JGit and hands it to a guest, who applies it and carries on as the host. If it quits with nobody around, the snapshot goes up to the cloud (R2) and the next person to open the world downloads it and picks up from there

This didn't work the first time. On June 11 the world fell out of sync: handing it to the next host was losing changes. The fix was a WebSocket relay on the rendezvous server, which carries the world copy

Testing it without opening the game

The hard logic (picking the host, the tokens, the protocol) lives in a pure Kotlin module, with no Minecraft in it, and 67 tests that run in seconds. Handing the world over was tested inside the game, including a round trip A → B → A

ideaKeeping the hard logic apart from the game made it possible to test the odd cases (two opens at once, a host that leaves) in seconds, without launching Minecraft every time

Limits

  • It's meant for small groups of friends: one host at a time means the world depends on the PC and connection of whoever is hosting at that moment
  • The automated tests cover the logic; the real network was tested by hand, not with many players or under hard network conditions at scale
  • The relay adds one more hop: it's the last resort, not the ideal

Frequently asked questions

How do you play Minecraft with friends without a dedicated server?

One option is for the world to live on the PC of whoever is playing and move to the next person when they leave. That's what jukz does: one host at a time, and the world is handed over as a snapshot or kept in the cloud until someone opens it again

What is split-brain?

When two nodes in a system believe at the same time that they are the leader and accept changes separately. When they meet again there are two versions instead of one

What is a fencing token?

A number that only goes up and travels with the permission to lead. If something arrives with an older number than the last one seen, it's discarded, even if whoever sent it still believes it holds the permission

How does a player behind CGNAT connect?

If a port can't be opened (UPnP) and no direct path is found (STUN), both machines connect outward to a relay that passes the data along, the same idea as TURN

What happens to the world when the last player leaves?

In jukz, the world is uploaded to the cloud (R2), and the next person to open it downloads it and picks up from there

References

  1. Martin Kleppmann · How to do distributed locking (2016) · fencing tokens · martin.kleppmann.com/2016/02/08/how-to-do-distributed-locking.html
  2. RFC 8489 · Session Traversal Utilities for NAT (STUN) · rfc-editor.org/rfc/rfc8489
  3. RFC 8656 · Traversal Using Relays around NAT (TURN) · rfc-editor.org/rfc/rfc8656
  4. JGit · Git implemented in Java · eclipse.org/jgit
  5. Nuulz/jukz · source code of the mod · github.com/Nuulz/jukz

Anything to fix or add? Write to me →