@pieai/swimmer-game-server-kit
v0.2.6
Published
Shared Colyseus game server kit for PieAI Swimmer-based games.
Maintainers
Readme
SwimmerGameServerKit
TCP listeners honor config.host (local default 127.0.0.1) through the existing
WebSocket transport. The upstream Colyseus tools still own process-port offsets,
Cloud Unix sockets, matchmaking and shutdown. Keep the same config when creating
and starting an app. Binding is tested using real IPv4/IPv6 sockets, an HTTP health
request, WebSocket room joins and a Unix socket, not just configuration objects.
Shared Colyseus game-server kit for PieAI Swimmer-based games.
This repository is the central template for PieAI multiplayer game servers. Games such as Non-Heroes, Break, and future experiments should keep their own gameplay rules, rooms, protocols, and product data, while sharing the server shell that creates, deploys, verifies, and authenticates authoritative Colyseus rooms.
Why This Exists
PieAI game projects should not each invent a different multiplayer server. The standard server stack is:
- Colyseus / Colyseus Cloud for authoritative game rooms.
@colyseus/toolsapp config for cloud-compatible server boot.@colyseus/sdkon clients.- SwimmerBackend Supabase Auth JWTs for player identity.
- Project-prefixed environment variables.
/healthand/roomsHTTP probes.- A reusable cloud smoke path before owner playtest.
Non-Heroes is the first mature source project for this standard. This kit extracts the reusable shape without importing Non-Heroes gameplay.
Scope
This package owns:
- Generic Colyseus server app creation.
- Generic room registration.
- Project-prefixed server config resolution.
- SwimmerBackend JWT verification.
- Common
/health,/rooms, and optional Colyseus monitor routes. - Minimal Colyseus Cloud smoke helpers.
- A starter template for new game servers.
This package deliberately does not own:
- Game-specific protocol messages.
- Gameplay state machines or rules.
- Boss AI, card logic, chase logic, or combat logic.
- Product persistence schema.
- Supabase migrations.
- Cloud secrets or deployment credentials.
- A single shared production endpoint for every game.
Each game should deploy its own Colyseus Cloud application unless a later platform decision creates a multi-game runtime service.
Install From A Product
Install the exact reviewed npm version:
{
"dependencies": {
"@pieai/swimmer-game-server-kit": "0.2.6"
}
}Server Usage
import {
createSwimmerColyseusRoom,
createSwimmerGameServerApp,
defineSwimmerGame,
listenSwimmerGameServer,
resolveGameServerConfig
} from "@pieai/swimmer-game-server-kit";
import { createBreakWorld } from "./world.js";
const breakGame = defineSwimmerGame({
auth: { allowGuestInLocal: true, fallbackDisplayName: "Runner" },
createWorld: () => createBreakWorld(),
gameId: "break",
maxClients: 2,
roomName: "break_room",
tickRate: 30
});
const config = resolveGameServerConfig({
envPrefix: "BREAK",
serviceName: "break-game-server"
});
const app = createSwimmerGameServerApp({
config,
rooms: [{ name: breakGame.roomName, room: createSwimmerColyseusRoom(breakGame) }]
});
await listenSwimmerGameServer(app, config);The game project implements the world adapter. The Kit owns the Colyseus room wrapper, handshake, local/cloud server shell, HTTP probes, auth bridge, and smoke helpers.
Client Usage
import { connectSwimmerGameClient } from "@pieai/swimmer-game-server-kit/client";
const client = await connectSwimmerGameClient({
endpoint: import.meta.env.VITE_BREAK_COLYSEUS_ENDPOINT,
joinOptions: { requestedPlayerId: "runner-a" },
onState: (state) => render(state),
onWelcome: ({ playerId }) => setLocalPlayer(playerId),
roomName: "break_room"
});
client.sendInput({ jump: true });For an invitation or an exact-room return, pass roomId as a top-level option.
The Kit then uses the SDK's joinById, without falling back to joinOrCreate
when that room is full, expired, locked or unavailable. Omitting roomId preserves
existing creation behavior. The product owns invitation parsing, consent, protocol
compatibility and seat/reconnection rules; a public room ID grants no identity or
right to take an occupied seat. Actual loopback tests cover both admission paths.
onConnectionChange reports only connected, reconnecting or closed from
the official SDK's lifecycle events, not raw close reasons. A product that owns
explicit reauthenticated seat recovery can set reconnectAutomatically: false:
the SDK then makes no hidden retries, disconnected input/restart sends are dropped,
and the product may offer its own exact-room retry. Default SDK reconnection and
message buffering are otherwise unchanged. Automatic SDK recovery still requires
server-side allowReconnection; an application-specific seat reservation alone
does not enable that transport flow. Intentional leave() does not emit a user
connection-failure callback and remains safe after the transport already closed.
A world with product-owned, reauthenticated seat recovery may implement
emptyRoomGraceSeconds(): number. After its last admitted transport leaves, the
Kit keeps that exact world and its simulation alive for that bounded duration.
Only a successful new admission clears the timer; failed/unauthorized attempts
cannot extend it. The product chooses the remaining window (zero for deliberate
departure), and still validates who can take each seat. This grants neither SDK
reconnection tokens nor automatic retries. Default/zero keeps Colyseus's ordinary
empty-room disposal, including rooms where nobody ever completed admission.
Invalid/nonfinite or timer-overflow durations fail closed. Expiry and shutdown
retire the world once and clear the timer; no permanent empty rooms are created.
This uses the official room autoDispose, clock and disconnect lifecycle,
not private Colyseus fields: https://docs.colyseus.io/room
Auth Usage
import { Room } from "@colyseus/core";
import { authenticateRoomJoin } from "@pieai/swimmer-game-server-kit/auth";
export class BreakRoom extends Room {
async onAuth(_client, options) {
return authenticateRoomJoin(options, {
env: process.env,
fallbackDisplayName: "Runner",
// Optional: use the verified email prefix before the product fallback.
preferEmailPrefix: false
});
}
}The verified identity and the world's join context carry optional isAnonymous
from the signed top-level is_anonymous claim. Missing or ill-typed claims
remain unknown; neither email nor user-editable metadata nor join options prove
a permanent account. A linked-account product policy must explicitly require
context.verified === true && context.isAnonymous === false. Custom verifiers
may omit the optional field without breaking existing room admission. This claim
does not grant database privileges or make client progress trustworthy.
Upstream distinction: https://supabase.com/docs/guides/auth/auth-anonymous
Environment Contract
For a game prefix such as BREAK, the kit reads:
BREAK_DEPLOY_TARGET=cloud|local
BREAK_SERVER_PORT=2567
BREAK_E2E_SERVER_PORT=...
BREAK_SERVER_HOST=...
BREAK_ALLOWED_ORIGINS=https://break.pieaistudio.com
BREAK_ENABLE_COLYSEUS_MONITOR=0|1
SWIMMER_CORE_SUPABASE_URL=https://lgoknzuxefecikfyvpzk.supabase.co
SWIMMER_CORE_JWT_AUDIENCE=authenticatedThe SWIMMER_CORE_* variable names are retained as a deployment-compatibility
contract even though the platform repository is now named SwimmerBackend.
Client apps should still use a game-owned VITE_COLYSEUS_ENDPOINT.
Template
templates/colyseus-game-server/ is a starter server package. Copy it into a
game repo as server/game, replace the sample room and room name, then wire the
game's shared protocol and gameplay engine.
Verification
pnpm install
pnpm verifyUpgrade Model
Improvements learned by any game should land here first when they are generic:
- deployment and environment checks;
- Colyseus Cloud compatibility;
- auth verification;
- local/dev port hygiene;
- cloud smoke patterns;
- test helpers;
- server skeleton changes.
Game-specific behavior stays in the game. Shared experience goes back into SwimmerGameServerKit, then products upgrade to a reviewed npm version.
