Files
dungeoncraft/docs/MULTIPLAYER.md
T

5.5 KiB

Multiplayer - plan and status

Multiplayer is only available for users of a server (not in the standalone file).

Decisions

  • Modes: versus (every player a master with an own heart), coop (every player an own master, together against AI masters and/or the humans) and a coop campaign of 20 missions.
  • 1 to 4 players per match; free slots are empty unless the host marks them as AI with set_ai before the start.
  • The match pauses while a player is missing (connection lost); a player who leaves is replaced by an AI.
  • Before the start, the host manually marks a free seat as AI with {"t":"set_ai","side":n,"ai":true} and clears it again with ai:false; side 0 stays human.
  • The humans (hero waves of the Order) as a third party (can be switched off), chat, alliances/teams.
  • Coop is "separate but helpful": own heart, gold, mana, research and creatures; allies never fight each other, their creatures help each other, gold can be sent, allied ground can be walked; a fallen heart only knocks out that player (who may keep watching); the allies win when all enemies have fallen.
  • Lobby: an open list of matches under Multiplayer in the title menu; the host chooses mode, map, AI slots, humans on/off and teams; logged-in users join; the match starts when everybody is ready.

Architecture: deterministic lockstep

Every client runs the same simulation (the same WASM module, so floating point results are identical). The server does not simulate; it relays:

  1. Clients send their commands (dig, build, cast, hand, orders, ...) to the server.
  2. Every 100 ms the server sends turn N: the commands of all players for that turn.
  3. Each client applies turn N and simulates five fixed steps of 20 ms. Turns arrive in order; a client waits for the next turn, so a missing player stalls everybody - exactly the "pause while missing" rule.
  4. Clients send a checksum of the game state every few seconds; a mismatch (desync) is reported.

Only the commands travel over the network, so bandwidth is tiny and no game state has to be serialised per turn. Fog of war, particles and the camera are local and not part of the shared state.

What is there already

  • Per-master state (game.rs): every master has his own gold, mana, hand, research, payday, flag, core... Game::switch_to(side) puts one master in the active fields, the simulation walks through all masters step by step. A single player game has no stored masters and is unaffected.
  • Commands (commands.rs): every order a player gives is an Act (Dig, Room, Tower, Trap, Cast, Flag, Pickup, Drop, Slap, Attack, Guard, CancelOrders, ...) wrapped in a Command { side, seq, act }. Creature targets are ids, so they mean the same on every client. The tools in main.rs only queue commands now - nothing touches the world directly.
  • Steps: the simulation runs in fixed steps of STEP = 20 ms, 50 steps a second (Game::update feeds real time into them, Game::apply_turn(cmds, steps) is the lockstep entry point; one server turn of 100 ms is five steps). Commands are applied at step boundaries, so single player and a match run exactly the same path. Particles, effects and the camera keep running in real time: they are local and not part of the shared state.
  • Who speaks: messages, hints and interface sounds belong to the master who is being processed (Game::speaking); what another master orders never shows up on my screen.
  • Turns and checksums (netplay.rs): Game::start_match(sides, local) starts a match, Game::take_orders() hands the local orders to the server, Game::receive_turn(cmds) applies a confirmed turn (five steps) and Game::checksum() compares the worlds of the clients - a peer with a different number is reported once (net.desync). A client without the next turn stands still: that is the "pause while missing" rule, and the tests run two clients of one match side by side.
  • Savegames, tests and the standalone file are unchanged: cargo test --release covers the command path (including "the same actions on both paths build the same world").

Steps

# Step Status
A Simulation per master: gold, mana, research, rooms, hand, flag, traps, payday... per side; up to four masters (red, green, blue, yellow) built
B All player actions as commands (Command + Game::apply), single player uses the same path; fixed time step built
C Server: lobby and match relay over WebSocket (axum), turns, pause on disconnect, AI takeover on leave, chat next
D Client: Multiplayer menu (lobby, create, join, ready, teams), WebSocket bridge in dc_plugin.js, chat, gold sending planned
E Alliances and coop rules, AI masters for free and abandoned slots planned
F Coop campaign: 20 missions with story (EN/DE) planned

Still open on the way to C: heroes and alliances for the new sides (today the hero gate path always leads to side 0's core, and in free play 2 and 3 are the rivals' sides), and what a match leaves out of savegames.

Masters and sides

A match plays on the four master sides 0, 2, 3, 4 (side 1 is the heroes'): red (side 0, Owner::Player), green (Owner::Rival1), blue (Owner::Rival2) and yellow (Owner::Rival3). Game::start_masters(sides, local) carves a 9x9 starting area and a 3x3 soul core for every master and remembers his economy in a MasterState; the places are worked out from the map size alone, so all clients agree without passing them over the network.