Darkwood Blog Blog
  • Articles
en
  • de
  • fr
Login
  • Blog
  • Articles

🔀 Darkwaar33 - Seedwake: Asynchronous Multiplayer Without a Server

on October 18, 2026

Log in to add a reaction to this post

🚀 1

Same seed. Same dark. Different choices.

What if multiplayer didn't require players to be online at the same time? What if comparing two games required neither a server nor a database, but simply a seed and a sequence of actions?

This is what I explored with Darkwaar33 - Seedwake, a small, turn-based tactical roguelike built with Godot 4.6.

Two players can explore exactly the same seven floors, make different decisions, and compare their runs afterward. An imported run becomes a ghost, showing where another player went, how they survived, and the exact moment their decisions diverged.

The interesting part isn't just the gameplay. It's the architecture that makes asynchronous competition possible without online infrastructure.

One Seed, One World

Procedural generation usually introduces variety. Each new game produces a different world, encouraging exploration and replayability.

But procedural generation can also create a common reference point between players.

In Seedwake, a 30-bit seed determines the entire environment:

  • Seven floors, each containing a 7×7 tactical grid.
  • Wall positions and staircase locations.
  • Enemy placements and initial behavior.
  • Collectible glints and their expiration timers.
  • Shrine offers between floors.

Two players starting with the same seed encounter the same initial conditions.

More importantly, no random numbers are generated during gameplay.

Once a floor has been generated, every outcome follows deterministic rules. Enemies act according to their current state, attacks are telegraphed, and the same action applied to the same state always produces the same result.

The seed determines the world. The player determines what happens inside it.

Determinism as an Architectural Constraint

Godot provides random number generation through RandomNumberGenerator, but its underlying algorithm is documented as an implementation detail.

For a conventional game, that distinction may not matter much. For a game that exchanges replay codes between players, it matters considerably.

A replay created today should not silently produce a different outcome after an engine update.

Seedwake therefore uses a small, explicitly defined xorshift32 generator, with independent streams derived using FNV-1a.

The streams are separated by responsibility:

Stream Responsibility
map Walls, stairs, glints and floor layout
enemy Enemy positions, orientations and cycles
offer Shrine rewards

Each stream is also isolated per floor.

Changing the generation of floor three should not unexpectedly change floor four.

This separation avoids a common procedural-generation problem: consuming an additional random number in one subsystem can shift the entire sequence used by another.

It also means that gameplay decisions cannot influence future generation through accidental RNG consumption.

A Simulation Independent of Its Interface

Seedwake separates its rules from the Godot presentation layer.

The architecture is deliberately small:

main.gd
    Input, screens, HUD, animations
           |
           v
rules.gd
    Generation, actions, combat,
    scoring and simulation
           |
           v
board_view.gd
    Grid, units, intentions,
    ghost and visual feedback

codec.gd
    Seeds, run codes and checksums

The central operation is conceptually simple:

Rules.apply(state, action)

It takes a state and an action, validates the move, and returns the resulting state together with events describing what happened.

The interface consumes those events to animate movement, damage, enemy destruction and transitions.

Animations do not modify the simulation.

This distinction is important. A tween lasting 120 milliseconds must not change the result of a turn depending on the frame rate or browser performance.

The game logic remains independent of rendering, timing and input devices.

That same architecture also makes the entire game replayable.

A Replay Is Just a List of Decisions

Traditional game recordings store images or video frames.

Seedwake doesn't need either.

Because the simulation is deterministic, reproducing a run requires only two things:

  1. The original seed.
  2. The ordered sequence of player actions.

A replay is reconstructed by applying those actions to a newly generated initial state.

Rules.simulate(seed, actions)

The result includes the final game state and a trace of the player's progression.

There is no separate replay engine and no second implementation of combat rules.

The same simulation is used for ordinary gameplay, replay validation and rival ghosts.

Replayability becomes a consequence of the rules rather than an additional subsystem built afterward.

Sharing a Run Without a Backend

At the end of a game, Seedwake generates a compact run code.

For example:

SW1-04V9P-NR2B5-…

The code contains the rules version, seed, player tag, recorded actions, claimed result and a checksum.

Actions use four-bit identifiers, allowing two actions to fit inside one byte. The resulting data is encoded using Crockford base32, a human-friendly alphabet that avoids several ambiguous characters.

A code can be copied into a message and imported by another player.

The importing game doesn't blindly trust the result.

It decodes the data, checks its integrity, validates the rules version, then executes the entire action sequence again.

If the simulated score, cleared floors or turn count differs from the claimed result, the code is rejected.

This is not cryptographic authentication or an anti-cheat system. A checksum cannot prevent deliberate forgery, and offline players can retry a seed as often as they like.

But it does provide a reproducible, self-consistent result without requiring accounts, databases, APIs or server-side simulation.

For a small game shared between friends, that is a useful trade-off.

The Ghost Is Another Player's History

Once a rival code has been validated, its simulation trace becomes a ghost.

The ghost appears as a hollow cyan diamond moving through the same floors as the player.

It doesn't participate in combat, block movement or influence enemy decisions.

Instead, it provides information.

On floor three, turn five, the game can display where the rival stood on floor three, turn five.

If the rival already reached the staircase, the game shows when they exited. If they died, their final position becomes a death marker.

A short trail of previous positions makes the ghost's movement readable.

This is asynchronous multiplayer reduced to its informational essence.

The two players never exchange messages with a server. Their games never need to execute simultaneously. They simply share a deterministic world and compare their histories.

Finding the Exact Moment Two Runs Diverge

The feature I find most interesting is the divergence marker.

Imagine two players beginning with the same seed.

They take the same first move, then the same second move, then the same third move.

Because the simulation is deterministic, their states remain identical.

On the fourth turn, one moves north while the other pushes an enemy east.

That is the first divergence.

Seedwake records that decision and marks its position on the board with a fork symbol.

From that moment onward, the two runs may develop completely different outcomes.

One player might collect an expiring glint. The other might eliminate an enemy, preserve health, or reach the staircase earlier.

The game can identify the precise input where the shared history separated into two different paths.

This is more interesting than comparing final scores alone.

A score tells you who performed better according to the scoring system.

A divergence marker tells you where their decisions first became different.

The comparison becomes a way to understand the game itself.

Making the Tactical Rules Readable

The deterministic architecture would mean little if players couldn't understand the consequences of their actions.

Seedwake therefore uses a compact, fully visible tactical board.

There are three enemy types:

  • Brutes pursue the player and attack adjacent cells.
  • Lancers fire along a fixed direction on a three-turn cycle.
  • Sentries periodically attack their four neighboring cells.

Their intentions are displayed before the player acts.

Red hatched cells indicate where attacks will land.

The player can move, wait, dash or push enemies. Pushing an enemy into a wall, another enemy or the board boundary destroys it.

Enemy attacks can also hit other enemies.

This creates small tactical problems where positioning matters more than reaction speed.

Glints add a second decision layer. They disappear after a limited number of turns and can either be saved as score or spent at shrines for survival upgrades.

The player must continuously choose between collecting resources, avoiding danger and progressing toward the staircase.

Because the rules contain no in-run randomness, the consequences of those decisions are reproducible.

Why No Server?

An online leaderboard would be possible.

A backend could collect scores, manage daily challenges, validate submissions and distribute rival runs.

But it would also introduce infrastructure, authentication, storage, abuse prevention, maintenance and deployment concerns.

For Darkwaar33, I wanted to test how far the asynchronous multiplayer concept could go without any of that.

The result supports three simple interactions:

  • Share a seed to play the same challenge.
  • Share a run code to compare complete performances.
  • Import a run code to play alongside another player's ghost.

The browser executes everything locally.

The limitation is equally clear: there is no automatic opponent discovery, global ranking or authoritative enforcement of daily attempts.

Those features would require a different infrastructure model.

For this experiment, they are unnecessary.

A Small Game, a Reusable Idea

Darkwaar is an ongoing experiment in creating and publishing small playable ideas regularly.

Seedwake follows that approach: a limited board, a small set of actions, seven floors, three enemy types and one distinctive asynchronous mechanic.

The project uses Godot 4.6 and GDScript, with an HTML5 export published through the existing Darkwaar pipeline.

The more general lesson extends beyond roguelikes.

When an application is built around deterministic state transitions, a sequence of inputs can become a compact, portable representation of an entire execution.

That opens possibilities for replay systems, debugging, reproducible tests, simulations and asynchronous collaboration.

In Seedwake, the result is a ghost walking beside you through a dungeon.

But underneath, it is simply another execution of the same rules.

Same seed. Same dark. Different choices.

Play Darkwaar33 - Seedwake

Play Seedwake on itch.io

Seven floors. One shared seed. No server required.

Play independently, share your run code, and challenge someone else to make better decisions in exactly the same world.

Log in to add a reaction to this post

🚀 1

Site

  • Sitemap
  • Contact
  • Legal mentions

Network

  • Hello
  • Blog
  • Apps
  • Photos

Social

Darkwood 2026, all rights reserved