felix-blocks
felix-blocks is a voxel building game for the browser. You walk around a world generated from a seed, dig blocks out and place new ones, alone or with friends, and everyone in the same world sees each change as it lands. There is no game server. The browsers write the world, and Felix stores it, orders the writes and tells everyone what changed.
It is in design. The style frame fixes the look, by day and by night, and nothing is playable yet. The sections below describe the design.
| Day | Night |
|---|---|
![]() |
![]() |
How it uses Felix
Section titled “How it uses Felix”A world is split into chunks of 16 × 16 × 16 blocks. Every client generates the terrain from the world’s seed, so a chunk nobody has touched is not stored anywhere. A chunk someone changed is one key in the world’s log-backed cache, holding its difference from the generated terrain.
| Resource | Kind | What it holds |
|---|---|---|
| The world’s chunk cache | Log-backed cache, one shard | One key per edited chunk, holding that chunk’s edits |
| The presence stream | In-memory stream | Player positions, ten times a second while they move |
| The online list | Cache with a TTL | One entry per connected player, refreshed every 10 seconds |
Placing a block is a conditional write
on the chunk’s key. The client holds each chunk’s value and the version it last
saw, and every 100 ms writes its pending changes with cache_put_if at that
version. If another player wrote the chunk first, the write is refused, the
client takes the newer value from its watch, puts its own changes on top and
tries again. Two players digging in the same chunk at the same moment both keep
their blocks.
Every client watches the world’s cache. A retained watch delivers every edited chunk on join and then each change as it lands, so a player sees the world as it is the moment they arrive. Compaction keeps the cache at each chunk’s current value, so a world that has seen a million edits stores only what is built now.
Positions go on the in-memory stream, where a lost one is replaced 100 ms later. Who is online is the TTL cache: an entry that stops being refreshed expires and every watcher sees the player leave.
flowchart LR
P["Player browsers"] -- "WebSocket" --> G["felix-gateway"]
V["Visitor browser<br/>read only"] -- "WebSocket" --> G
G -- "conditional chunk writes" --> C[("chunk cache<br/>log-backed")]
C -- "retained watch" --> G
G -- "positions" --> S[("presence<br/>in-memory")]
T["blocks-tool"] -- "worlds and grants" --> CP["Control plane"]
Who may change a world is decided by Felix RBAC. A world has builders, who may write its cache, and visitors, who may only read and watch it, so a visitor’s browser cannot change a world whatever it sends. A single player world is a world with one builder, and opening it to a friend is a grant.
Browsers reach Felix through felix-gateway. The gateway relays cache puts, gets, deletes and watches today. Conditional writes through the gateway are planned (felix-gateway#31), and multiplayer waits for them; single player works without them. A gateway watch also covers the whole cache from the start, so until region watches arrive a client watches the whole world and downloads it again on reconnect.
What it demonstrates
Section titled “What it demonstrates”- Concurrent edits resolve cleanly. The second write to a chunk is refused and rebased, never applied over the first.
- Join and see the world at once. One retained watch gives every edited chunk, then live changes.
- Storage is what was built. Untouched terrain costs nothing, and compaction keeps the world at its current blocks.
- Solo and shared worlds are the same thing. Sharing a world is a grant, with no export or copy.
- Permissions are enforced by Felix, not by the browser.
- README
- Design: the data model, the Felix mapping, the gaps and the build order
- Art direction: day and night, the asset packs and the style frame


