🎩 You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP
Sign In

@martini-kit/core

Package Overview
Dependencies
Maintainers
1
Versions
3
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@martini-kit/core

Multiplayer without networking. Engine-agnostic multiplayer SDK with host-authoritative state sync.

latest
Source
npmnpm
Version
0.2.0
Version published
Weekly downloads
0
-100%
Maintainers
1
Weekly downloads
 
Created
Source

@martini-kit/core

Engine-agnostic multiplayer SDK with host-authoritative state synchronization.

Simple, clean, works with any game engine.

Features

  • Declarative API - Define state and actions, not networking code
  • Host-authoritative - Host runs the game, clients mirror state
  • Automatic sync - Efficient diff/patch algorithm for bandwidth optimization
  • Engine-agnostic - Works with Phaser, Unity, Godot, Three.js, etc.
  • Transport-agnostic - P2P, WebSocket, UDP - your choice
  • TypeScript - Full type safety

Installation

pnpm add @martini-kit/core

Quick Start

1. Define Your Game

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];
  }
});

2. Create Runtime

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']
});

3. Use in Your Game

// 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}`);
});

How It Works

Host-Authoritative Architecture

┌─────────────────────────────────────┐
│           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:

  • Host is authoritative (runs real physics/logic)
  • Clients send inputs, receive state updates
  • Efficient diff/patch algorithm minimizes bandwidth
  • Default 20 FPS state sync (configurable)

Core Concepts

State

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:

  • Must be JSON-serializable
  • No functions or class instances
  • Mutated directly (no immutability required)

Actions

The only way to modify state:

actions: {
  shoot: {
    apply(state, playerId, input) {
      state.bullets.push({
        x: input.x,
        y: input.y,
        ownerId: playerId
      });
    }
  }
}

Flow:

  • Player calls runtime.submitAction('shoot', { x: 100, y: 200 })
  • Host applies action immediately
  • Host broadcasts state patch to clients
  • Clients receive and apply patch

Lifecycle Hooks

Handle player join/leave:

onPlayerJoin(state, playerId) {
  state.players[playerId] = { x: 100, y: 100 };
},

onPlayerLeave(state, playerId) {
  delete state.players[playerId];
}

Integration with Game Engines

Phaser

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.

Other Engines

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);
  }
});

Transports

@martini-kit/core is transport-agnostic. Choose your backend:

P2P (Serverless)

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)

WebSocket (Coming Soon)

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

Custom Transport

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;
}

API Reference

Full documentation: API Reference

Examples

Fire Boy & Water Girl Demo

See @martini-kit/demo-vite for a complete working example:

  • URL-based host selection (Jackbox-style)
  • Phaser physics integration
  • P2P networking via Trystero
  • Automatic sprite syncing

Run it:

cd @martini-kit/demo-vite
pnpm dev

Testing

# Run tests
pnpm test

# Watch mode
pnpm test:watch

# Coverage
pnpm test:coverage

Current coverage: 96%+ on core algorithms

Development

# Build
pnpm build

# Watch mode
pnpm dev

# Clean
pnpm clean

Architecture

@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

Design Philosophy

Host-Authoritative

Host runs the real game, clients mirror state. Simple, works with any physics engine.

Why not deterministic?

  • Most games don't need it
  • Works with existing Phaser/Unity code
  • AI can generate code easily
  • Faster development

Declarative

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;
    }
  }
}

Transport-Agnostic

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' });

Roadmap

  • Host-authoritative mode
  • P2P transport (Trystero)
  • Phaser adapter
  • Comprehensive tests (96%+ coverage)
  • WebSocket transport
  • Unity C# bindings
  • Godot GDScript bindings
  • Client prediction (optional advanced mode)

License

MIT - See LICENSE

Contributing

See CONTRIBUTING.md

Areas needing help:

  • WebSocket transport implementation
  • Unity/Godot adapters
  • Example games
  • Documentation improvements

Support

Keywords

multiplayer

FAQs

Package last updated on 04 Dec 2025

Did you know?

Socket

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.

Install

Related posts