Platform
Game
Matchmaking, D1 matches, authoritative ticks, parties, NPCs, quests, checkpoints, tournaments — not a netcode engine.
Game module
Lobbies, matches, players, parties, NPCs, quests, checkpoints and tournaments live in D1 behind the Worker /games/* REST surface. Every input advances an authoritative tick and fans out game.tick on the lobby room_id. Not rollback netcode and not a dedicated game server — see What you bring.
Scaffold
npx @fluxy-chat/create-fluxy-chat@latest my-game --example game-tickSDK
Create the client with a member JWT (JWT auth), then wrap it:
import { FluxyChatClient, createWorkerFluxyGameClient } from "@fluxy-chat/sdk";
const client = new FluxyChatClient({ baseUrl, userId, token });
const game = createWorkerFluxyGameClient(client);
await game.upsertPlayer({ playerId, username: "ada", skillRating: 1200 });
const lobby = await game.matchmake({ playerId, roomId, maxPlayers: 2 }); // roomId = the chat room that carries the ticks
await game.joinLobby(inviteLobbyId, playerId); // Colyseus joinById
const { matchId } = await game.startMatch(lobby.lobbyId); // needs 2+ lobby players
await game.submitInput(matchId, { playerId, actions: { move: "nudge" } }); // tick += 1 → game.tick
const snapshot = await game.getMatch(matchId); // reconnect: D1 snapshot, not a Colyseus seat token
const board = await game.listLeaderboard(20, playerId); // around-owner window
await game.endMatch(matchId, { winnerId: playerId }); // +16 skill_rating on the winner
// leaveLobby(lobbyId, playerId) while state is still "waiting"Realtime
Consume on the lobby room (useChat({ roomId, onServerEvent })):
| Event | Emitted by |
|---|---|
game.lobby_updated | matchmake, join/leave, startMatch |
game.party_updated | createParty, join/leave party |
game.match_started | startMatch |
game.tick | every submitInput (tick, state, last event) |
game.match_ended | endMatch |
Quest activity fans out game.quest_created and game.quest_progress on the quest's roomId.
Parties
A party is a pre-match roster on the same room, not a lobby:
const party = await game.createParty({ roomId, maxMembers: 4 });
await game.joinParty(party.id, friendId);NPCs
upsertNpc stores template replies and difficulty; interactNpc keeps per-player memory in D1. Rate limited (GAME_NPC_RATE_LIMIT_RPM, default 20 rpm): over the limit the call resolves to { reply: "", retryAfterSeconds } (HTTP 429 rate_limit_exceeded) — back off and retry.
Quests
const { quest, pendingModeration } = await game.createQuest({ roomId, title, description });
// keyword scan on title/description: clean → "approved", blocked → "pending"
await game.moderateQuest(quest.id, "approve"); // or "reject"
await game.updateQuestProgress(quest.id, { playerId, progress: { steps: 1 } });updateQuestProgress only works on approved quests.
Checkpoints
Cloud saves and mid-match state with optimistic concurrency, an optional CRDT-merged view for collab play, and room-to-room forwarding:
await game.upsertCheckpoint({ checkpointKey: "save-1", playerId, state, expectedVersion: 2 });
// stale expectedVersion → HTTP 409 and { conflict: true } instead of overwriting
const merged = await game.listCheckpoints(playerId, { roomId, crdt: true }); // merged via the room Yjs DO
const update = await game.fetchCheckpointCrdtSnapshot(roomId); // full Yjs update for your own merge
await game.federateCheckpoint("save-1", { sourceRoomId, targetRoomId, playerId });federateCheckpoint merges the checkpoint from sourceRoomId into targetRoomId; when FLUXY_FEDERATION_PEER_URL and FLUXY_FEDERATION_SECRET are set it also forwards it to the peer deployment. The receiver endpoint POST /games/federation/ingest is server-to-server (secret header, not a member JWT) — the one game route outside the JWT/SDK contract — and fails closed without a configured secret.
Tournaments
const t = await game.createTournament({ roomId, name: "Friday cup", maxPlayers: 8 });
await game.joinTournament(t.id, playerId); // registration
await game.startTournament(t.id); // bracket placeholder — no auto-advance
await game.reportTournamentMatch(t.id, matchId, winnerId); // you report every resultReadiness
| Piece | Status |
|---|---|
| Lobbies, matches, ticks, leaderboard, players | Beta — Worker + D1, route and SDK tests |
| Parties, NPCs, quests, checkpoints, tournaments | Beta — Worker + D1 |
createFluxyGame() in-memory engine | Demo — deterministic local sandbox, not the Worker path |
| Rollback netcode / dedicated game server | Not included — bring your own |
What you bring
- A realtime game server (Colyseus, Agones, your engine) if you need 60 fps authority, rollback or physics. FluxyChat keeps the room socket, chat and agents on the same room, and D1 as the source of truth for matchmaking, tick records, results and leaderboards.
- Member JWTs for players (JWT auth);
/games/*authenticates with Bearer tokens, not API keys. - Optional: a second deployment (peer URL + shared federation secret) for cross-deployment checkpoints.
Related: API reference — /games/* is in the OpenAPI spec; dashboard page /game; pair with war-room for chat + invokeAgent on the same socket.