Write multiplayer like React state.
Martini keeps clients in sync,
across engines and transports.
Declarative like React • Phaser helper today • Transport agnostic • Swap without refactors
Declarative like React
Define setup, actions, and ticks like components/hooks for networked state. Martini handles validation and sync.
Fewer race conditions, no manual diffing.
Before vs After
Move from brittle socket plumbing to a declarative API. Martini handles validation, ordering, and sync so you keep focus on the game.
Before (manual)
// Manual sockets + reconnection + routing
const socket = new WebSocket(url);
const handlers = {
move: ({ id, y }) => {
const p = players[id];
if (p) p.y = y;
},
join: ({ id }) => players[id] = { id, y: 0 },
leave: ({ id }) => delete players[id],
};
socket.onopen = () => socket.send(JSON.stringify({ type: "join" }));
socket.onclose = () => setTimeout(() => reconnect(), 1000);
socket.onmessage = (msg) => {
try {
const { type, ...payload } = JSON.parse(msg.data);
handlers[type]?.(payload);
} catch (e) { console.error(e); }
};
function move(y) {
socket.send(JSON.stringify({ type: "move", y, t: Date.now() }));
}
- Manual parsing and routing
- Race conditions and missing reconnection logic
- No shared structure for engines or transports
- Protocol rewrites per transport
After (with Martini)
import { defineGame } from "@martini/core";
const game = defineGame({
setup: () => ({ players: [] }),
actions: {
move: (state, id, y) => {
const player = state.players.find((p) => p.id === id);
if (player) player.y = y;
},
},
tick: (state) => { /* game loop */ },
});
- Declarative setup/actions/tick
- Swap WebSockets ↔ WebRTC/P2P without refactoring
- Phaser helper today; Godot adapter in development
- Ordering, validation, reconnection handled
Quick Start
npm install @martini/core @martini/phaser
2
Declare Your Game (~15 lines)
Plain objects for state, actions mutate directly. No classes, no RPC wrappers, no serialization chores.
Step 1
Describe state and actions
Declare setup, actions, and tick with plain objects. No RPC or serialization boilerplate.
setup + actions + tick // declarative game spec
Step 2
Martini syncs it
Validation, diffing, ordering, and reconnects handled across WebSockets or WebRTC/P2P.
transport: websocket | webrtc
Step 3
Clients stay in lockstep
Render with Phaser helper today. Godot adapter is in development, with more engines planned.
onChange(state => render(state))
import { defineGame } from '@martini/core';
const game = defineGame({
setup: () => ({
players: [],
ball: { x: 400, y: 300, dx: 2, dy: 2 }
}),
actions: {
move: (state, playerId, y) => {
const player = state.players.find(p => p.id === playerId);
if (player) player.y = y;
}
},
tick: (state, dt) => {
state.ball.x += state.ball.dx;
state.ball.y += state.ball.dy;
}
});
3
Pick Engine & Transport
Use the Phaser helper today. Godot and other engine adapters are on the way. Swap WebSockets or P2P without refactoring.
First-class DX: web IDE, live previews, and built-in dev tools today, with more helpers coming soon.
Interactive Code Previews
Comparison Snapshot
| Feature | Martini (Phaser today) | Colyseus | Photon | Rune | Manual |
|---|
| Declarative game logic | ✓ | RPC / manual | RPC / manual | Custom | x |
| Engine helper | ✓ (Phaser) | ~ | Per-engine SDKs | P2P SDK | x |
| Transport choice | ✓ | Server WS | Server UDP/WS | P2P only | x |
| Reconnect & ordering | ✓ | Manual | Manual | Manual | x |
| Bandwidth optimized | ✓ | Full state / manual | Manual | Batched | x |
| Optimistic updates | ✓ | Manual | Manual | Manual | x |
| Interpolation/reconciliation helpers | ✓ | Manual | Manual | Manual | x |
| Swap transports without refactor | ✓ | Server only | Server only | P2P only | x |
| Host-authoritative or P2P | ✓ | Server only | Server only | P2P only | x |
| Schema-free state | ✓ | Manual | Manual | Manual | x |
| Plugin adapters (Godot planned) | ✓ | ~ | ~ | ~ | x |
| Open source | ✓ | Varies | No | No | x |
| Local-first friendly | ✓ | Server only | Server only | P2P only | x |
Build in minutes
Ready to build multiplayer?
Ship real-time games with clean, readable code. Declarative logic, flexible transport, and a Phaser helper today (more adapters coming).
Read the API overview See a minimal example