Multiplayer & Local Architecture Patterns
Multiplayer & Local Architecture Patterns
Scope note: MoonBark.GridPlacement handles grid state and placement logic. It does not implement network transport. This guide explains where the plugin’s seams are and how each topology maps to them. Connecting a transport layer (Godot ENet, WebSocket, Steam Networking, etc.) is your responsibility and is intentionally outside the plugin’s scope.
The Core Idea: Placement State as the “Physics Server”
In Godot’s built-in multiplayer model, the host machine runs the authoritative physics server. Remote clients do not simulate physics independently — they send inputs to the authority and receive state updates back.
MoonBark.GridPlacement uses the same mental model:
- The occupancy service is the authoritative state for placement. It knows what is placed and which cells are occupied.
- The authority (host or dedicated server) is the only process that calls into the placement pipeline to commit placements.
- Remote clients send a placement intent (placeableId + grid position) and receive a placement notification (accepted or rejected, with the confirmed position) back.
Nothing in the client’s local view is trusted. The authority’s occupancy service is the ground truth.
Topology 1: Single-Player / Local
The simplest case. All systems run in one process.
No seam overhead. This is the default demo setup.
Test reference: PlacementPipelineE2ETests, EventDrivenPlacementTests
Topology 2: Listen-Server (Client-Host)
One player also acts as the authoritative host. All other players are remote clients. This is the most common peer-to-peer multiplayer topology for small games.
The Four Seams
| Seam | What happens | Where your networking code goes |
|---|---|---|
| Catalog Seam | Before gameplay, all peers must load the same catalog of placeables. String IDs must match. | Lobby / loading screen: broadcast catalog IDs from host → clients load identical assets. |
| Intent Seam | Client captures input → sends (placeableId, gridPosition) to host. | rpc_id(host_id, "receive_placement_intent", placeable_id, position) |
| Authority Seam | Only the host calls InputBridge.ExecutePlacement() + PlacementService.Update(). Occupancy is never written by clients. | Guard the authority method: only execute if multiplayer.is_server(). |
| Event Relay Seam | After authority resolves, broadcast the outcome to all peers. | Subscribe to PlacementService.EntityPlaced (success) or the return value of ExecutePlacement() (failure); call rpc(...) on each client. |
Minimal GDScript sketch (intent + relay)
This is the shape of the glue code. The plugin provides everything except the rpc calls:
Test reference: ListenServerAuthorityTests — proves intent relay, authority isolation,
conflict resolution, and event relay using the real Core API with mock network stubs.
Topology 3: Dedicated Server (Headless)
The authoritative host is a separate headless process with no player. It runs the placement pipeline but not Godot’s rendering.
The plugin’s PlacementEventBus and PlacementService are pure C# with no Godot
dependencies, so they run headless without the Godot runtime.
The difference from listen-server is that the server has no local player — it only processes incoming intents and broadcasts outcomes.
Test reference: DedicatedServerAuthorityTests — proves headless placement authority using
only the Core library, with no Godot runtime required.
Topology 4: Local Multiplayer (Same Device, Split-Screen)
Multiple players on the same machine. Each player has a separate IInputSource registered
with a TargetingService. The occupancy service handles all players in a single update
because each player has a distinct OwnerKey.
No network seams are needed. The occupancy service natively handles multiple concurrent placers.
Test reference: MultiPlacerConcurrentTests — proves 100+ simultaneous placers
with independent validation per owner.
What the Plugin Gives You for Free
| Capability | Relevant type |
|---|---|
| Authoritative occupancy check | OccupancyService, IGridOccupancy |
| Conflict detection (two clients, same cell) | PlacementValidator |
| Headless server loop | PlacementEventBus, PlacementService (no Godot dependency) |
| Multiple concurrent placers | OwnerKey on SelectedPlaceable |
| GDScript-compatible event bridge | PlacementSignalBus |
| Save / restore placed state | PlacementSnapshot |
What the Plugin Does Not Provide
- Network transport (ENet, WebSocket, Steam, etc.)
- Lobby / matchmaking
- Player authentication
- Network clock synchronization
- Lag compensation or rollback
- Catalog content distribution (you must ensure all peers load the same assets)
These concerns belong to your game layer, not to a placement plugin.
Choosing Your Topology
| Scenario | Recommended topology | Key test suite |
|---|---|---|
| Singleplayer builder / city sim | Single-player | PlacementPipelineE2ETests |
| 2–8 players, one hosts | Listen-server | ListenServerAuthorityTests |
| Competitive / authoritative server | Dedicated server | DedicatedServerAuthorityTests |
| Couch co-op, same device | Local multiplayer | MultiPlacerConcurrentTests |
| Custom engine / Unity / headless | Custom authority path | GridAuthorityTests (Path C) |