npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@gamerstake/game-core

v0.1.0

Published

Reusable multiplayer game engine for GamerStake platform

Readme

@gamerstake/game-core

Reusable multiplayer game engine for the GamerStake platform.

Overview

game-core is a battle-tested, high-performance multiplayer game engine extracted from the GamerStake metaverse. It provides:

  • Game-agnostic multiplayer infrastructure (tick loop, networking, state management)
  • Pluggable game rules via the GameRules interface
  • Room/Match lifecycle management (persistent worlds OR match-based games)
  • Spatial partitioning with O(1) queries via grid system
  • Basic 2D physics (position, velocity, AABB collision)
  • Zero-allocation hot paths for optimal performance

Features

Core Systems

  • GameServer - Multi-room server management
  • Room - Single game instance with tick loop
  • GameLoop - Fixed 20 TPS with drift compensation
  • Entity System - Base entity class with dirty flag tracking
  • Registry - Entity lifecycle management

Spatial & Physics

  • Grid - Spatial partitioning for efficient queries
  • AABB Collision - Bounding box collision detection
  • Movement - Velocity integration and boundary constraints

Networking

  • Network Layer - Socket.io abstraction with opcodes
  • Snapshot System - Full and delta state synchronization
  • InputQueue - Buffered input processing with RingBuffer

Performance

  • Zero-allocation buffers - Reused arrays in hot paths
  • Dirty flag tracking - Only broadcast changed entities
  • RingBuffer - O(1) push/pop for input queues
  • Performance metrics - Real-time tick time monitoring

Installation

From Monorepo Workspace

If you're in the GamerStake monorepo:

{
  "dependencies": {
    "@gamerstake/game-core": "workspace:*"
  }
}

From NPM Registry

pnpm add @gamerstake/game-core

See PUBLISHING.md for publishing and distribution options.

Quick Start

1. Implement GameRules

import { GameRules, Room, Entity, MoveCommand } from '@gamerstake/game-core';

class MyGameRules implements GameRules {
  onRoomCreated(room: Room): void {
    console.log('Room created!');
  }

  onPlayerJoin(room: Room, player: Entity): void {
    console.log(`Player ${player.id} joined`);
    // Spawn player at random position
    player.setPosition(Math.random() * 1000, Math.random() * 1000);
  }

  onPlayerLeave(room: Room, playerId: string): void {
    console.log(`Player ${playerId} left`);
  }

  onTick(room: Room, delta: number): void {
    // Update all entities
    room.getRegistry().forEach((entity) => {
      entity.updatePosition(delta);
    });
  }

  onCommand(room: Room, playerId: string, command: Command): void {
    const player = room.getRegistry().get(playerId);
    if (!player) return;

    if (command.type === 'move') {
      const moveCmd = command as MoveCommand;
      // Set velocity (e.g., 200 units/second)
      const speed = 200;
      player.setVelocity(moveCmd.dir.x * speed, moveCmd.dir.y * speed);
    } else if (command.type === 'stop') {
      player.setVelocity(0, 0);
    }
  }

  shouldEndRoom(room: Room): boolean {
    // For persistent worlds, return false
    // For match-based games, check win condition
    return false;
  }
}

2. Create Server and Room

import { GameServer, Entity } from '@gamerstake/game-core';
import { Server } from 'socket.io';

// Create Socket.io server
const io = new Server(3000);

// Create game server
const gameServer = new GameServer();
gameServer.setServer(io);

// Create a room
const room = gameServer.createRoom('room-1', new MyGameRules(), {
  tickRate: 20, // 20 ticks per second
  cellSize: 512, // Grid cell size
  maxEntities: 1000, // Max entities per room
});

// Handle player connections
io.on('connection', (socket) => {
  const playerId = socket.id;

  // Create player entity
  const player = new Entity(playerId, 0, 0);
  room.addPlayer(player);

  // Register socket for networking
  room.getNetwork().registerSocket(playerId, socket);

  // Send initial state
  room.sendTo(playerId, {
    op: 'S_INIT',
    playerId,
    ...room.getSnapshot(),
  });

  // Handle player inputs
  socket.on('C_MOVE', (data) => {
    room.queueInput(playerId, {
      seq: data.seq,
      type: 'move',
      dir: data.dir,
      timestamp: Date.now(),
    });
  });

  socket.on('disconnect', () => {
    room.removePlayer(playerId);
    room.getNetwork().unregisterSocket(playerId);
  });
});

console.log('Game server running on port 3000');

3. Client Example

import { io } from 'socket.io-client';

const socket = io('http://localhost:3000');

let seq = 0;

socket.on('S_INIT', (data) => {
  console.log('Initial state:', data);
});

socket.on('S_UPDATE', (data) => {
  console.log('Delta update:', data);
  // Update local entities
});

// Send movement input
function move(dirX: number, dirY: number) {
  socket.emit('C_MOVE', {
    seq: ++seq,
    dir: { x: dirX, y: dirY },
    timestamp: Date.now(),
  });
}

// Example: Move right
move(1, 0);

Architecture

GameServer
 Room 1
 GameLoop (20 TPS)
 Registry (Entities)
 Grid (Spatial)
 InputQueue
 Network
 GameRules (Your logic)
 Room 2
 Room N...

API Reference

GameServer

const server = new GameServer();
server.setServer(io);
const room = server.createRoom(id, rules, config);
server.destroyRoom(id);
const metrics = server.getMetrics();

Room

room.start();
room.stop();
room.addPlayer(entity);
room.removePlayer(playerId);
room.spawnEntity(entity);
room.destroyEntity(entityId);
room.queueInput(playerId, command);
room.broadcast(event);
room.sendTo(playerId, event);
const snapshot = room.getSnapshot();

Entity

const entity = new Entity(id, x, y);
entity.setPosition(x, y);
entity.setVelocity(vx, vy);
entity.updatePosition(deltaMs);
entity.markDirty();

Grid

const grid = new Grid(cellSize);
grid.addEntity(id, x, y);
grid.removeEntity(id);
grid.moveEntity(id, oldX, oldY, newX, newY);
const nearby = grid.getNearbyEntities(x, y, range);

Configuration

interface RoomConfig {
  tickRate?: number; // Default: 20 TPS
  cellSize?: number; // Default: 512 units
  maxInputQueueSize?: number; // Default: 100
  maxEntities?: number; // Default: 1000
  visibilityRange?: number; // Default: 1 (3x3 cells)
}

Performance

Benchmarks

  • 20+ TPS with 100+ entities per room
  • <40ms average tick time (80% budget)
  • O(1) spatial queries via grid partitioning
  • Zero allocations in hot paths (tick loop)

Optimizations

  1. Dirty Flag Tracking - Only broadcast changed entities
  2. Buffer Reuse - Pre-allocated arrays for queries
  3. RingBuffer - O(1) input queue operations
  4. Spatial Grid - Avoid O(n²) collision checks

Testing

Automated Tests

# Run unit tests
pnpm test

# Run with coverage
pnpm test:coverage

# Run in watch mode
pnpm test:watch

# Type check
pnpm typecheck

# Build
pnpm build

Manual Testing

To manually test the game engine with a live server:

# 1. Build the package
pnpm build

# 2. Start the test server
node examples/simple-game/server.js

# 3. In another terminal, start a client
node examples/simple-game/client.js

See examples/simple-game/README.md for detailed manual testing instructions.

Getting Started

Choose your path:

  • ** Quick Start** → See QUICK_START.md - Get running in 5 minutes
  • ** Full Guide** → See DEVELOPER_GUIDE.md - Complete tutorial
  • ** Examples** → See examples/ directory - Working code samples

Examples

The examples/ directory contains:

  • simple-game/ - Manual testing server & client with automated movement tests

Migration from Metaverse Shard

If you're migrating from the metaverse shard:

  1. Install @gamerstake/game-core
  2. Implement GameRules with your metaverse logic
  3. Replace direct GameLoop, Grid, WorldState usage
  4. Keep metaverse-specific: persistence, zones, gateway

See docs/features/game-core/N2-architecture/game-core-rfc.md for details.

Documentation

For Developers

Architecture

License

UNLICENSED - Internal GamerStake package

Contributing

This is an internal package. For issues or feature requests, contact the GamerStake engineering team.