Skip to content

Networked Multiplayer — design (Phase 3)

Networked Multiplayer — design (Phase 3)

Section titled “Networked Multiplayer — design (Phase 3)”

Status: parked — NOT in the first App Store release (v0.1 Crystals). Post-launch milestone. Date: 2026-06-30 Supersedes scope of: the local 2-player co-pilot (feat(coop): local 2-player co-pilot mode, BUILD 115) — that shared-screen model is retained as a “couch” mode but is no longer the target architecture.

Multiple players, each on their own device, in a shared world, each flying around anywhere with their own view. An iPhone can join an Apple TV over local WiFi (and later over the internet). Each player is a complete pilot — own ship, own HP, own dash + drone, own separate XP / level / weapon build. Cross-device works for free because every platform (iOS / tvOS / desktop / web) runs the same Godot engine and the same netcode.

This is the project’s long-decided Phase 3. It is enabled by the keystone property the whole sim was built around: a deterministic, input-driven, RefCounted sim with no engine physics, our own SpatialHash, and all randomness through SeededRng.

  • Separate per-player progression — each player has their own XP, level, and weapon build (NOT a shared pool). Each player picks their own level-up upgrades for their own arsenal.
  • Every player is a full pilot — own weapons, own Dash + Drone, own colour. (P1 cyan, P2 amber, P3 violet; co-pilots render slightly smaller. Final palette during M-C.)
  • Transport order: LAN host-authority first, then online via a dedicated server.
    • First playable: one device (e.g. the ATV) hosts the authoritative Sim; others join over WiFi, send input, receive state, and predict their own player.
    • Online: promote that same host logic into a headless Godot dedicated server on hetzner.
  • Authority model: authoritative-host + client prediction — the chess-moba reference (server/src/match-room.ts + src/net/prediction.ts), NOT lockstep relay. Because the sim is GDScript it cannot run in a Cloudflare DO, so authority is a Godot ENet host (LAN) or a headless Godot dedicated server (online). Mirror the moba-bakeoff/slice-godot per-tick-trace determinism discipline.
  • Couch co-op retained — the same-screen 2-player (shared/split camera) mode stays as a no-network option and is reworked + fixed inside this milestone (it currently has the dash/drone phantom-join bug; see below).
  • Dash/drone bug fix is folded into this milestone — no standalone fix. (For v0.1, co-op is gated OFF entirely via main.V01_LOCK_COOP so the bug cannot surface in the launch build.)
  • Join trigger: press a button on an unused real gamepad / an explicit lobby join — never the movement-stick auto-join (that is what causes the tvOS Siri-Remote phantom-join).

The dash/drone bug this supersedes (for the record)

Section titled “The dash/drone bug this supersedes (for the record)”

When the current local co-op is active, main.gd overwrites P1’s input with input_router.poll_device(0), which hardcodes dash=false, decoy=false — so P1 permanently loses dash + drone the moment a P2 exists. On tvOS the auto-join (`Input.get_connected_joypads().size()

= 2+ slot-1 stick push) fires spuriously because the Siri Remote also counts as a joypad and a single controller can land on slot 1, so a player's own movement can spawn a phantom P2 and leave the real controller as the ability-less co-pilot. The networked rework rebuilds this input path so every pilot carries a full per-deviceInputState` (move + aim + dash + decoy with per-device edge latches), which fixes it by construction.

Decomposition (each its own spec → plan → build cycle)

Section titled “Decomposition (each its own spec → plan → build cycle)”

M-A — Multi-player sim foundation (deterministic, transport-agnostic)

Section titled “M-A — Multi-player sim foundation (deterministic, transport-agnostic)”

Generalize the sim from one player to N independent full pilots in a shared world. The load-bearing constraint: keep the single-player determinism baseline byte-identical (current: both modes 2730172591 / 2950189379). Use a null-object/empty-list seam exactly like the existing player2 == null seam — pilots.size() == 1 in single-player executes the current code path with identical RNG draw order. Re-run the determinism + checksum tests after every chunk.

  • Per-pilot: PlayerState, own arsenal (active_weapon_ids + weapon instances), own XP/level/build, own dash state, own drone charge.
  • Enemies target the nearest pilot; spawns ring around each pilot; game-over only when all pilots are down.
  • Open problems to solve here: a world that spans multiple pilots’ regions (spatial hash + active area + perf with players far apart), per-pilot level-up flow, and whether weapons fire from a per-pilot origin override or per-pilot instances.
  • One device hosts: runs the authoritative Sim, owns the canonical tick.
  • Clients connect over WiFi (ENetMultiplayerPeer, connect-by-IP + optional UDP auto-discovery on the LAN), send their InputState each tick, receive authoritative snapshots/deltas.
  • Client prediction + reconciliation for the local player (chess-moba prediction.ts pattern): predict locally from input, correct on authoritative state.
  • First “it works” moment: iPhone joins ATV, both fly anywhere.
  • Each client renders its own camera following its own pilot, sees other pilots + enemies + the world around it. (Drops the shared-midpoint camera entirely for networked mode.)
  • Join/lobby screen, player colours + names, per-player level-up + HUD (each device shows only its own pilot’s picks/HUD).
  • Couch co-op (same-screen) is reconciled here too: shared or split camera, both controllers fixed to carry full input.
  • Promote the M-B host into a headless Godot dedicated server (hetzner) with a public address, so play works over the internet and scales toward the roadmap’s 2–32 target.
  • Connect/matchmaking flow; NAT is sidestepped by clients dialing the public server.
  • iOS over cellular reaches the dedicated server directly.

Build order: M-A → (M-B + M-C together — the first playable) → M-D.

  • Determinism is the asset — protect it. Every M-A chunk re-verifies the baseline; a desync in prediction/reconciliation is the classic failure (use state_checksum() per the existing position-level desync-hunting note).
  • World scale + perf when players are far apart (spawns/hash/culling per pilot) — the current SOFT_ENEMY_CAP and adaptive-quality ladder assume one focal player.
  • Per-platform input (touch vs controller) already abstracts via Platform.is_touch() / InputRouter; netcode is platform-agnostic so cross-device is mostly free, but lobby/HUD UX must suit both 10-foot (TV) and handheld (iPhone).
  • Two co-op models to maintain (couch + networked) — keep the per-pilot sim layer shared so only the transport/camera differs.

All of the above. v0.1 ships single-player Crystals with co-op gated off (main.V01_LOCK_COOP = true).