
Security News
Ruby's Bundler 4.0.18 Extends Cooldown to bundle lock and bundle cache
The supply chain control that delays freshly published gems now covers lockfile generation and gem vendoring in Ruby projects.
@martini-kit/core
Advanced tools
Multiplayer without networking. Engine-agnostic multiplayer SDK with host-authoritative state sync.
Engine-agnostic multiplayer SDK with host-authoritative state synchronization.
Simple, clean, works with any game engine.
pnpm add @martini-kit/core
import { defineGame } from '@martini-kit/core';
const game = defineGame({
setup: ({ playerIds }) => ({
players: Object.fromEntries(
playerIds.map(id => [id, { x: 100, y: 100, score: 0 }])
)
}),
actions: {
move: {
apply(state, playerId, input) {
state.players[playerId].x = input.x;
state.players[playerId].y = input.y;
}
}
},
onPlayerJoin(state, playerId) {
state.players[playerId] = { x: 100, y: 100, score: 0 };
},
onPlayerLeave(state, playerId) {
delete state.players[playerId];
}
});
import { GameRuntime } from '@martini-kit/core';
import { TrysteroTransport } from '@martini-kit/transport-trystero';
const transport = new TrysteroTransport({
roomId: 'game-room-123',
isHost: true
});
const runtime = new GameRuntime(game, transport, {
isHost: true,
playerIds: ['p1']
});
// Submit actions
runtime.submitAction('move', { x: 150, y: 200 });
// Listen for state changes
runtime.onChange((state) => {
console.log('Players:', state.players);
});
// Broadcast custom events
runtime.broadcastEvent('explosion', { x: 100, y: 200 });
// Listen for events
runtime.onEvent('explosion', (senderId, eventName, payload) => {
console.log(`Explosion at ${payload.x}, ${payload.y}`);
});
┌─────────────────────────────────────┐
│ HOST │
│ • Runs game logic │
│ • Applies actions │
│ • Syncs state to clients (20 FPS) │
└─────────────────┬───────────────────┘
│
state patches (diff)
│
┌────────┴────────┐
↓ ↓
┌─────────────────┐ ┌─────────────────┐
│ CLIENT 1 │ │ CLIENT 2 │
│ • Sends actions│ │ • Sends actions│
│ • Mirrors state│ │ • Mirrors state│
└─────────────────┘ └─────────────────┘
Key Points:
Plain JavaScript objects describing your game:
{
players: {
p1: { x: 100, y: 100, health: 100 },
p2: { x: 200, y: 200, health: 100 }
},
bullets: [],
gameState: 'playing'
}
Rules:
The only way to modify state:
actions: {
shoot: {
apply(state, playerId, input) {
state.bullets.push({
x: input.x,
y: input.y,
ownerId: playerId
});
}
}
}
Flow:
runtime.submitAction('shoot', { x: 100, y: 200 })Handle player join/leave:
onPlayerJoin(state, playerId) {
state.players[playerId] = { x: 100, y: 100 };
},
onPlayerLeave(state, playerId) {
delete state.players[playerId];
}
Use @martini-kit/phaser for automatic sprite syncing:
import { PhaserAdapter } from '@martini-kit/phaser';
class GameScene extends Phaser.Scene {
create() {
const adapter = new PhaserAdapter(runtime, this);
const player = this.physics.add.sprite(100, 100, 'player');
adapter.trackSprite(player, `player-${adapter.myId}`);
// That's it! Sprite automatically syncs across network
}
}
See Phaser Adapter docs for details.
For Unity, Godot, Three.js, etc.:
runtime.onChange((state) => {
// Update your game objects based on state
for (const [id, player] of Object.entries(state.players)) {
updateGameObject(id, player.x, player.y);
}
});
@martini-kit/core is transport-agnostic. Choose your backend:
import { TrysteroTransport } from '@martini-kit/transport-trystero';
const transport = new TrysteroTransport({
roomId: 'game-123',
isHost: true // URL-based host selection
});
Pros: Zero server costs, simple setup Cons: NAT traversal issues (5-10% of users)
import { WebSocketTransport } from '@martini-kit/transport-ws';
const transport = new WebSocketTransport({
url: 'wss://your-server.com'
});
Pros: Reliable, works for everyone Cons: Requires server hosting
Implement the Transport interface:
interface Transport {
send(message: WireMessage, targetId?: string): void;
onMessage(handler: (msg: WireMessage, senderId: string) => void): () => void;
onPeerJoin(handler: (peerId: string) => void): () => void;
onPeerLeave(handler: (peerId: string) => void): () => void;
getPlayerId(): string;
getPeerIds(): string[];
isHost(): boolean;
}
Full documentation: API Reference
See @martini-kit/demo-vite for a complete working example:
Run it:
cd @martini-kit/demo-vite
pnpm dev
# Run tests
pnpm test
# Watch mode
pnpm test:watch
# Coverage
pnpm test:coverage
Current coverage: 96%+ on core algorithms ✅
# Build
pnpm build
# Watch mode
pnpm dev
# Clean
pnpm clean
@martini-kit/core (this package)
↓
├─ defineGame() - Declarative game definition
├─ GameRuntime - State management, action execution
├─ sync.ts - Diff/patch algorithm
└─ transport.ts - Transport interface
Used by:
├─ @martini-kit/phaser - Phaser 3 adapter
├─ @martini-kit/transport-* - Transport implementations
└─ Your game - Direct usage
Host runs the real game, clients mirror state. Simple, works with any physics engine.
Why not deterministic?
Define state and actions once, not networking code.
// ❌ Imperative networking
socket.on('player-moved', (data) => {
players[data.id].x = data.x;
});
// ✅ Declarative actions
actions: {
move: {
apply(state, playerId, input) {
state.players[playerId].x = input.x;
}
}
}
Swap networking backends without changing game code:
// Development: P2P
const transport = new TrysteroTransport({ roomId: 'dev-123' });
// Production: WebSocket
const transport = new WebSocketTransport({ url: 'wss://game.com' });
MIT - See LICENSE
See CONTRIBUTING.md
Areas needing help:
FAQs
Multiplayer without networking. Engine-agnostic multiplayer SDK with host-authoritative state sync.
The npm package @martini-kit/core receives a total of 0 weekly downloads. As such, @martini-kit/core popularity was classified as not popular.
We found that @martini-kit/core demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
The supply chain control that delays freshly published gems now covers lockfile generation and gem vendoring in Ruby projects.

Security News
During a UK cyber test, a Mythos 5 agent used sockpuppets, social engineering, and prompt injection to try to get a maintainer to merge malware.

Company News
Socket is now in the AWS Security Hub Extended plan. Adopt it through AWS, apply committed spend, and block malicious open source packages.