Darkwaar 33

Seedwake

Asynchronous Multiplayer
Without a Server

Same seed. Same dark. Different choices.

Seedwake tactical board with an amber player and cyan rival ghost
01 | 14 @matyo91

The question

Can multiplayer exist without a server?

Live authority

Player A→Server←Player B

Seedwake

Player A→Run code→Player B

Players share a simulation history, not a live session.

model two
02 | 14 @matyo91

Procedural generation as shared reference

One seed, one world

✦✦▣■■▶✦●✦◆

Player A · V9PNR2 · floor 2

✦✦▣■■▶✦●✦◆

Player B · V9PNR2 · floor 2

Actual generated layout: identical walls, enemies, glints, start and stair.

second board
03 | 14 @matyo91

Player choice is the variable

The same world does not mean the same outcome

Amber and cyan share a path until one action differs.

Deterministic rules make decisions the only source of divergence.

✦✦▣■■▶✦●✦◆
Y

First different action

cyan route
04 | 14 @matyo91

Deterministic simulation

Same state + same action = same result

State Nboard, HP, intents, floor turn
+
Action0…12
→
Rules.apply(state, action){ ok, reason, state, events }
static func apply(
    st_in: Dictionary,
    action: int
) -> Dictionary

Combat executes drawn intents.

No RNG calls during play. No Node calls, time, frame delta or engine RNG in rules.gd.

action
apply
05 | 14 @matyo91

Randomness without drift

Independent generation streams

SEED
Floor 1mapenemyoffer
Floor 2mapenemyoffer
Floor 7mapenemyoffer
static func stream(seed, floor_n, name):
    var h = fnv1a32(
        "dw33|%d|%d|%s" %
        [seed, floor_n, name]
    )
    return h if h != 0 else 1

static func xorshift32(s):
    s ^= (s << 13) & MASK
    s ^= s >> 17
    s ^= (s << 5) & MASK
    return s & MASK

No random stream advances during gameplay.

streams
all streams
06 | 14 @matyo91

A small architecture

Rules, rendering and interaction stay separate

main.gd

Input, screens, rival tracking. Calls Rules.apply().

rules.gd

Pure state transitions, generation, replay trace and ghost lookup.

board_view.gd

Reads state and events. Draws animation, intents, ghost and fork.

codec.gd

Encodes runs. Imports through Rules.simulate().

Dependency direction ends at rules.gd. The rules never call the view.

07 | 14 @matyo91

Replay as data

A replay is a list of actions

Seed K7Q2MX

NEEWAITWN

W resolves as PUSH when an enemy occupies the destination. PUSH is not a separate encoded action.

static func simulate(
    seed: int,
    actions: PackedByteArray
) -> Dictionary
Reconstructed run
final state + trace

No video recording. No saved state snapshots.

actions
more actions
08 | 14 @matyo91

Portable run codes

A complete game history in a short string

SW1-04V9P-NR2B5-…

Abbreviated for display. This fragment is not importable.

Versionrules v1
Seed32 bits
Player tag3 letters
Resultscore, floors, turns
Actions4 bits each
ChecksumFNV-1a, 16 bits

Crockford base32

Uppercase, copyable, tolerant of O/0 and I/L/1.

hi = actions[i] & 15
lo = actions[i + 1] & 15
byte = (hi << 4) | lo
09 | 14 @matyo91

Replay validation

Never trust the claimed result

Decode
Checksum
Version
Simulate
Compare
results
Decision
ACCEPT
same score, floors and turns
REJECT
corrupt, illegal, unfinished or mismatched

Consistency check, not cryptographic anti-cheat.

checksum
simulate
compare
decision
10 | 14 @matyo91

The rival ghost

Another player’s history, not another live player

Seedwake gameplay showing a cyan rival ghost, wake trail, enemy intentions and split marker
Cyan ghost + four-cell wakeRendered from the rival trace.
Floor and floor-turn synchronizationFloor 2, turn 6 maps to the rival’s floor 2, turn 6.
Exit and death markersThe last position remains visible after the rival stops walking.

The ghost never mutates your world.

11 | 14 @matyo91

Decision comparison

Where did our decisions become different?

Turn
1
2
3
4
5
6
Player A
N
E
E
N
W
S
Player B
N
E
E
W
N
S

The fork identifies a decision, not merely a score difference.

Rules.first_split(mine, theirs) returns the first unequal action index, or −1 while one run remains a prefix.

split
12 | 14 @matyo91

Infrastructure avoided by design

Why no backend?

Traditional online competition

  • Authentication
  • Persistent storage
  • Matchmaking
  • Server-side validation
  • Global leaderboards

Seedwake rivalry

  • Shared seed
  • Recorded actions
  • Deterministic simulation
  • Portable run code
  • Local verification

Trade-off: no opponent discovery, global leaderboard or authoritative anti-cheat.

13 | 14 @matyo91

Conclusion

Determinism makes gameplay portable

Seeddefines the world
Actionsdefine the history
Replayreconstructs the result

Darkwaar33 — Seedwake

darkwoodcom.itch.io/darkwaar33

Same seed. Same dark. Different choices.

Seedwake end screen comparing floor-by-floor results with a rival
14 | 14 @matyo91