ARCHITECTURE

Battleships on Calimero

A multi-service P2P battleship game using namespaces, private storage, and CRDT-based state sync

2WASM Services
P2PNo Server
CRDTAuto-Sync
E2EPrivate Boards

How It Works

Two players on separate nodes, syncing state via CRDTs. Ship placements stay private until hit.

NODE 1 Lobby Service lobby.wasm Game Service game.wasm Shared State CRDT-synced Private Board NOT synced Calimero Runtime · merod NODE 2 Lobby Service lobby.wasm Game Service game.wasm Shared State CRDT-synced Private Board NOT synced Calimero Runtime · merod gossipsub
Shared state replicates via CRDT deltas over gossipsub. Both nodes converge automatically.
Private boards never leave the node. Shots are resolved locally, only hit/miss results are shared.

Namespaces & Access Control

A namespace scopes identity, governance, and app instances. Each battleships lobby is a namespace.

NAMESPACE root group · identity + governance + app binding Lobby Context service: lobby All members Player Pool Ed25519 identities Match Subgroup 2 players only Match Context service: game P1 + P2 only Default Capabilities (bitmask = 11) CREATE_CONTEXT INVITE_MEMBERS MANAGE_MEMBERS Bit 0 · create match contexts Bit 1 · invite to namespace Bit 3 · add to subgroups

Multi-Service Bundle

One .mpk bundle, two WASM services. Each context picks its service at creation time.

battleships-0.3.0.mpk multi-service application bundle Lobby Service lobby.wasm 6 methods create_match, set_match_context_id, get_matches, get_player_stats, get_history, on_match_finished 3 events Game Service game.wasm 9 methods place_ships, propose_shot, acknowledge_shot, get_own_board, get_shots, get_active_match_id, ... 5 events manifest.json lobby-abi.json game-abi.json battleships-types GameError, PublicKey (no SDK dep)

Private Storage

Ship placements are the core secret. Each player's board is stored locally and never replicated.

Player 1's Node PRIVATE Ship positions + cell count SHARED (CRDT) Match state Current turn Shot results (hit/miss) placed_p1 / placed_p2 Winner Pending shots CRDT Player 2's Node SHARED (CRDT) Match state Current turn Shot results (hit/miss) placed_p1 / placed_p2 Winner Pending shots PRIVATE Ship positions + cell count

Cross-Context Calls (xcall)

When a game ends, the game context notifies the lobby to update stats and history.

Game Context All ships sunk → winner determined calimero_sdk::env::xcall(...) JSON payload { match_id, winner, loser } Lobby Context on_match_finished() Update player_stats Append to history

Complete Game Flow

Eight steps from namespace creation to final score update.

1
Create Namespace
Host creates a namespace with the battleships app. Sets default capabilities (bits 0+1+3 = 11).
2
Create Lobby
Lobby context created in namespace root group with service_name: lobby
3
Invite Player
Recursive namespace invitation covers root + all subgroups.
4
Player Joins
joinNamespace → auto-gets identity → joins lobby context.
5
Create Match
Lobby allocates match ID. Create subgroup → add P2 → create game context with service_name: game
6
Place Ships
Both players place ships in private storage. placed_p1/placed_p2 flags synced via CRDT.
7
Take Turns
propose_shot → event triggers acknowledge_shot_handler on target node → resolves against private board → result synced.
8
Game Ends
All ships sunk → winner set → xcall to lobby → stats and history updated.

CRDT State Sync

All shared state replicates automatically between nodes. Each mutation creates a delta broadcast via gossipsub.

DAG-based

Deltas form a causal DAG. Concurrent writes merge automatically without coordination.

Encrypted

Deltas are encrypted with the group's shared key before broadcast over the network.

Eventual

Nodes converge to the same root hash. No central coordinator needed.

The sync layer is service-aware: when applying a delta from a peer, the node loads the correct WASM service (lobby or game) based on the context's service_name to execute __calimero_sync_next.

Project Structure

battleships/
├─ app/ React + TS
│  ├─ src/hooks/ — useBattleshipsLobby, useNamespaceBootstrap
│  ├─ src/api/lobby/ — LobbyClient (codegen)
│  └─ src/api/game/ — GameClient (codegen)
├─ logic/ Cargo Workspace
│  ├─ crates/types/ — GameError, PublicKey (no SDK dep)
│  ├─ crates/lobby/ — LobbyState + 6 methods → lobby.wasm
│  ├─ crates/game/ — GameState + 9 methods → game.wasm
│  └─ build-bundle.sh — builds both + packages .mpk
└─ e2e/ merobox — E2E workflow

Calimero Platform Features

Namespaces

Identity scoping, recursive invitations, subgroup-based access control

Multi-Service Bundles

Two WASM services in one .mpk, service_name-based context creation

Private Storage

#[app::private] for ship boards — never replicated between nodes

CRDT Sync

Automatic state replication with DAG-based causal ordering

xcall

Cross-context calls from game → lobby for match results

Event System

app::emit! for real-time UI updates via SSE subscriptions

Capabilities

Fine-grained permission bits for member actions

Governance DAG

Membership, roles, and context registration via signed ops

Subgroups

Per-match access isolation — only 2 players per match group