# Serverless P2P Multiplayer in Godot — No Backend, Just GenosDB

> **Try it first:** open the live demo in **two browser tabs** → https://estebanrfp.github.io/godot-genosdb/ Each tab is a different player. Walk around, chop a tree — it falls in *every* window. Say hi in the chat. No server is running. Anywhere.

![GenosDB Farm — two browser windows, one shared world, no backend](https://raw.githubusercontent.com/estebanrfp/godot-genosdb/main/media/demo.gif)

## The challenge

Multiplayer usually means servers: a backend, sockets, authoritative state, deployment, scaling, cost. For a small game — or just a prototype — that's a lot of plumbing before the *fun* part.

What if the players' browsers just talked **directly to each other**, and the shared world simply… persisted, on its own?

That's what this demo does: a top-down, Stardew-style co-op farm built in **Godot 4**, exported to the Web, where every connected browser is another farmer in the same world — running on **no backend at all**. The secret is [**GenosDB**](https://github.com/estebanrfp/gdb).

## What is GenosDB?

GenosDB is a **decentralized graph database that runs entirely in the browser**. Two parts matter here:

*   **GenosRTC** — peer-to-peer transport over **WebRTC**, with **decentralized signaling via Nostr relays** (no signaling server *you* run). Peers find each other and open direct data channels.
    
*   **A reactive, persistent graph** — you `put` nodes and `map` queries, and changes **sync to every peer and persist** (OPFS). New peers automatically receive the current state.
    

No backend. No database server. No signaling server. It's all client-side.

## The key idea: a hybrid model

Not all multiplayer state is equal, and GenosDB lets you treat it accordingly. The demo splits the world into two layers — and this is the pattern worth stealing:

| State | Example | How | Why |
| --- | --- | --- | --- |
| **Ephemeral** | Player positions **& chat messages** | a data channel (`channel.send`) | fast, ~16 Hz, throwaway |
| **Persistent** | Chopped trees + the shared wood total | the graph (`db.put` / `db.map` / `db.remove`) | synced **and** stored; late-joiners see the world as it *is*, and **Reset** wipes it for everyone |

That second row is the magic. When you chop a tree you don't *shout* "I chopped tree 3!" (only people currently listening hear it). You **write it into a shared graph** — and anyone who opens a new tab later gets the already-chopped world via GenosDB's `initial` event. Chops survive reloads. That's a database, working P2P, for free.

## It's a real little game now — and every feature is a GenosDB call

The demo grew from a tech sketch into a cozy co-op game, and each mechanic still maps cleanly to GenosDB:

*   🪓 **Chop trees with an axe** — a 4-directional animated farmer. Felled trees **regrow**, and the **shared wood counter** (real felled trees, race-free) reads the same for everyone, derived straight from the synced graph.
    
*   💬 **P2P chat** — type a message and it pops as a **speech bubble** over your farmer in *every* window (the ephemeral data channel again).
    
*   ♻️ **Reset** — one button wipes the shared world for all peers. That's `db.remove`, propagated P2P.
    
*   🎨 **Cozy pixel-art world**, sound effects and ambient music — and all art & audio are **CC0** (credits in the repo).
    

All of it serverless. All of it P2P.

## Connecting Godot to GenosDB

GenosDB is browser-only, and Godot reaches the browser through its **Web (HTML5) export** + `JavaScriptBridge`. So I built a small bridge and packaged it as a plugin — [**godot-genosdb**](https://github.com/estebanrfp/godot-genosdb) — that **auto-injects** the JS bridge into your exported `index.html`. Enable it, export to Web, done. Nothing to bundle, no HTML to edit.

## The API mirrors GenosDB

On purpose: learn the plugin and you've learned GenosDB.

```gdscript
func _ready():
    Net.peer_join.connect(func(id): spawn_farmer(id))
    Net.message.connect(_on_remote_state)      # ephemeral (positions + chat)
    Net.graph_changed.connect(_on_world)       # persistent
    Net.join("my-room")                         # = gdb("my-room", {rtc:true})
    Net.map({})                                 # = db.map({}, cb)

func _physics_process(_d):
    Net.send({ "x": position.x, "y": position.y })   # = channel.send(...)

func chop_tree():
    Net.put({ "type": "tree", "hp": 0 }, "tree_3")   # = db.put(node, id)

func reset_world():
    Net.remove("tree_3")                              # = db.remove(id)
```

| GenosDB (JS) | Godot (`Net`) |
| --- | --- |
| `gdb(name, {rtc:true})` | `Net.join(name)` |
| `channel.send(data)` | `Net.send(data)` → signal `message` |
| `db.put(node, id)` | `Net.put(data, id)` → signal `graph_changed` |
| `db.map(query, cb)` | `Net.map(query)` |
| `db.remove(id)` | `Net.remove(id)` |
| `room.on('peer:join' / 'leave')` | signals `peer_join` / `peer_leave` |

## Try it / use it

*   **▶ Live demo (two tabs!):** https://estebanrfp.github.io/godot-genosdb/
    
*   **Plugin + full source (MIT, assets CC0):** https://github.com/estebanrfp/godot-genosdb
    
*   **GenosDB:** https://github.com/estebanrfp/gdb
    

Install is three steps: copy `addons/godot_genosdb/`, enable it in *Project Settings → Plugins*, export to Web. P2P is Web-only; on desktop the API is a safe no-op, so the same code runs as single-player in the editor.

## Why this matters

For jams, prototypes, collaborative toys, or "presence" features, **serverless P2P removes an entire category of work**. And because GenosDB gives you a *persistent reactive graph* — not just a raw WebRTC pipe — your shared world is consistent, survives reconnects, and welcomes late-joiners with zero extra code.

**Two browser tabs. One shared world. No backend. Just GenosDB.**

