Docs
Everything to go from an empty folder to a running multiplayer game.
Quickstart
From an empty folder to a running server and a connected client in five steps.
Crossplay generates a complete, ready-to-run project: a .NET server that composes only the pieces you pick, and a Unity client wired to match. You never edit the framework — you snap pieces on and plug your own game into the sockets they expose.
- 1Create a project.Open New project, give it a name, choose a database (SQLite is zero-config and the default), and pick a transport. Everything else has a sensible default.
- 2Choose your pieces.Tick the features your game needs — Characters, World, Movement, Chat, Inventory, and so on. Dependencies are pulled in for you; a card game and an MMO start from the very same kernel.
- 3Generate & run the server.Create the project, then run the generated server. It boots on your chosen port and persists to your chosen database out of the box.
dotnet run --project Server - 4Open the Unity client.Open the generated Unity project. The Crossplay client packages are already referenced and the bootstrap flow is generated to match your pieces: connect → sign in → pick a character → enter the world.
- 5Drop in a panel & play.Every piece ships a drag-and-drop UI panel (e.g. the chat window or the inventory grid). Drag its prefab into a scene, press Play, and you're talking to your server.
Prefer real auth from the start? Pick a sign-in method (Username/Password, Steam, Google, …) when you create the project. The generated flow then waits for the Login panel and the Character panel instead of auto-signing-in — a real game flow, not a demo shortcut.
What you get
- A .NET 10 dedicated server composed of exactly the pieces you chose — nothing more.
- A Unity 6.3 client project with the matching packages and a generated bootstrap flow.
- Durable persistence on your chosen database, with account and character data saved out of the box.
- A drag-and-drop UI panel for every feature piece, ready to place in a scene.
Where the API docs live
The server pieces install as compiled DLLs, and each one ships its XML doc file beside it. Your IDE picks that up automatically — hover any Crossplay type in Rider, Visual Studio or VS Code and you get the same prose you see in the API reference. Nothing to configure.
How it works
The one mental model that makes everything else fall into place.
Crossplay moves data and syncs state. It does not know what game you are making.
The kernel — Core — is genre-agnostic. It gives you transport, identity, sessions, rooms, and a messaging SDK, and nothing else. It works unchanged for a card game, hide-and-seek, an MMO, or a rhythm game.
Pieces stack downward
Every feature is a piece that depends only on lower pieces, never higher ones. World builds on Characters; Movement builds on World; feature pieces like Chat depend on Core alone. You compose the ones you want and leave the rest out — removing a piece never breaks the others.
Movement → World → Characters → Coredependencies flow downward onlyEvery piece is the same trio
A piece is Contracts (the wire protocol both sides share), a Server half (.NET), and a Client half (a Unity UPM package). You add the server half with one line and reference the client package — the Hub does both when it generates your project.
services.AddCrossplayChat();server: one line composes the piecePresentation is always yours
Pieces sync abstract state — ids, numbers, and opaque blobs — never models, animations, or sounds. An emote is an id and a payload: one game renders it as a chat line, another as a UI sticker, another as a full 3D dance. The piece cannot tell the difference, and it just works under any of them. You bind meaning through the SPIs each piece exposes.
- The wire carries ids, numbers, and opaque blobs.
- The client gives you events and SPI sockets.
- The look — models, animators, UI, controls — is 100% your game's.
Composition recipes
Different games, the same kernel. Start here and add only what the genre needs.
Because pieces compose, you assemble a game by taking the kernel plus the handful of pieces the genre calls for. Here are three starting points — notice they share the same Core.
Card / turn-based game
No avatars, no world, no movement. Just the kernel, matchmaking to pair players, and your own card messages on top. Add the meta pieces (Economy, Achievements, Leaderboards) freely — they no longer require Characters.
Core + Matchmaking + (your card messages)
Hide & seek / party game
Now you want bodies in a space. Add Characters, World, and Movement, then layer your own role and tag messages. Emotes and Voice snap on for presence.
Core + Characters + World + Movement + (roles, tag)
MMO / persistent world
The full spatial stack plus the persistent-world pieces: zones and instancing, the Director for instance placement across nodes, Inventory and the Container family, Guild, Mail, and the combat family. Everything persists to your database and scales out over Redis.
Core + Characters + World + Movement + Director + Inventory + Guild + Combat …
Unsure which pieces a genre needs? Open any piece's API reference — each one lists what it depends on, the seams it expects you to implement, and the services it hands you.
Implementing an SPI
The pieces ship as compiled DLLs. The SPIs are where your game plugs in.
Crossplay runs the universal machinery — validation, ordering, persistence, broadcast — and calls out to your game whenever a decision is genuinely yours. Those call-outs are SPIs: interfaces Crossplay calls and you implement. Every piece ships an inert or permissive default, so a piece works the moment you install it and gets its meaning the moment you replace the default.
A service is something you CALL. An SPI is something you IMPLEMENT. The API reference labels every interface, so you never have to guess.
The two kinds of seam
- Game SPI — implementing it changes a game rule. IAbilityRules decides whether a cast is legal; IQuestCatalog decides what quests exist; IProgressionCurve decides what level 40 costs.
- Provider — implementing it changes infrastructure, never a rule. IDocumentStore decides which database the bytes land in; ICharacterStore decides where characters persist; IServerTransport decides how they move.
Register before you compose
Every piece registers its default with TryAdd, which means the first registration wins. Register your implementation BEFORE the piece's AddCrossplay* call and yours is the one that gets used. Register it after, and the default has already claimed the slot.
// your rules win because they are registered first
services.AddSingleton<IAbilityRules, MyAbilityRules>();
services.AddCrossplayAbilities();the TryAdd seam: register first, and the shipped default steps asideA worked example
Abilities handles the parts every game shares: the entity is in the world, the payload is a sane size, the cooldown has elapsed, and the observers get told. What an ability MEANS — damage, hit detection, combos, ammo — is the one thing only your game knows. So that is the seam.
public sealed class MyAbilityRules : IAbilityRules
{
public AbilityDecision Decide(ISession actor, WorldEntity self, ushort abilityId, byte[] targetData)
{
if (!_mana.TrySpend(actor.AccountId, Cost(abilityId)))
return AbilityDecision.Reject();
return AbilityDecision.Accept();
}
}the framework already ran the universal gates before calling youFinding the seams you need
Open a piece in the API reference. Its SPIs are listed first, split into the two kinds above, each with the prose from the source. Because the reference is generated from the same build that produces the DLL you installed, it can never describe a version you do not have.
You do not need the website to read these. Each shipped DLL carries its XML doc file beside it, so hovering an SPI in Rider, Visual Studio or VS Code shows the same text inline.
Release
Take a game to production: pick a hosting shape, then follow the path for your platform.
Releasing comes down to two decisions, and everything else follows from them: how long one server process lives, and where it runs. Get those right and the rest is configuration. This page walks the shapes in the order they get harder, so you can stop at the first one that fits your game.
Decision 1 — how long a server lives
- Persistent (always-on) — one long-lived server; players come and go, the world stays up. One process can host many independent matches side by side as separate zone instances. Right for persistent worlds, social spaces, and self-hosted servers.
- Session (per match) — one server process per game session. The platform hands out a standby server for each match; when the session ends the process drains and exits so the platform recycles it. Right for match-based games on PlayFab, GameLift or Agones.
This is the Server mode axis on the project, not a code change. Set it when you create the project or change it later in Project Settings, then regenerate. Session mode also upgrades an implicit SQLite choice to Postgres, because a per-match server's state has to outlive the process that wrote it.
Decision 2 — where it runs
A single VM 1 machine, 1 server persistent simplest, cheapest
B Kubernetes 1 pod, 1 server persistent self-healing, declarative
C Kubernetes + Agones pool of pods, 1 per match session allocation on demand
PlayFab / GameLift managed pool, 1 per match session platform runs the poolThe three shapes, simplest first. Pick the lowest row that fits.A single VM is not a lesser choice — for one always-on server it is less to run and less to go wrong. Move up a row only when you need what the next row buys: Kubernetes buys self-healing and declarative deploys; Agones and the managed platforms buy a server allocated per match on demand.
Licensing: what production actually requires
A licence is two products. A dev seat covers a developer's own machine. A prod seat covers one concurrent server on a hosting provider. They are mutually exclusive on purpose: a dev-kind server is refused once it can prove it is hosted, and a prod-kind server is refused on a workstation. Set the licence kind per deployment — dev locally, fleet on anything hosted.
- 1Buy a prod seat.Account → Devices → Prod seats. Deploying a dev licence to a cloud host is refused by design, and the refusal names the reason.
- 2Let the server prove where it runs.On boot a prod-kind server asks its cloud's own metadata service for a signed attestation and the Hub verifies that signature against the vendor's key. None of your cloud credentials are involved and there is nothing to connect — a workstation simply cannot produce one, which is exactly what a prod seat buys.
- 3Approve the cloud account it appeared in.An attestation proves 'a genuine cloud instance', not 'yours'. Your account page's Running servers card names the cloud account each instance is running in and offers a one-click Approve. Worth glancing at even when all is well: an account you do not recognise is what a leaked token looks like from your side.
- 4If the host cannot attest, approve the machine.Bare metal, a VPS with no metadata service, and containers whose metadata service is unreachable can never produce a vendor signature. Approve those explicitly as production hosts instead. A fleet can be approved by id PREFIX, so replacement pods are covered without a new approval each time.
Every RUNNING server holds a seat — including an idle standby that has never seen a player. Size prod capacity to your POOL, not to your concurrent players. A pool of eight standby servers needs eight seats even while nobody is playing.
One hard dependency to plan for: a prod lease is short and renewed continuously, so the server needs outbound HTTPS to the Hub for its whole life. If a firewall or security group blocks egress, the server stops when its current lease expires.
Path A — a single VM
- 1Create the VM and open the ports.A small general-purpose Linux VM is plenty for one server. Open your game's UDP port inbound, plus SSH for yourself. Check your quota for the size you want before you script anything — a capacity or quota refusal often surfaces as a confusing generic error.
az vm create -g <rg> -n <vm> --image Ubuntu2404 --size Standard_D2s_v3 --generate-ssh-keys az vm open-port -g <rg> -n <vm> --port 7777 --protocol Udp - 2Split artifact, secrets and state.Keep the three apart so a redeploy can never destroy identity or data. The build directory is wiped and replaced every deploy; secrets and state survive it.
/home/<user>/server ARTIFACT — replaced every deploy /etc/<game>/server.env SECRETS — root-owned, 0600, survives /var/lib/<game>/ STATE — database, survives - 3Pass configuration as environment variables.The generated server reads environment variables LAST, so Crossplay__* entries outrank both appsettings files. That is what lets one build run locally and in production with no file edits — and it keeps your licence token out of the artifact.
- 4Run it under systemd, then approve the account.Enable the unit so it restarts on boot and on crash. Start it once, watch the log for the lease being granted, and approve the cloud account the refusal names if it is the first deploy into that subscription.
Hardening a systemd unit can break licence identity. A read-only system path stops the server persisting its instance id, and it then looks like a brand-new machine on every restart. If you set ProtectSystem, add the state and Crossplay data directories to ReadWritePaths. Also watch for a byte-order mark when writing the env file from Windows: systemd silently drops the first line.
Path B — Kubernetes, one long-lived server
The unit of deployment becomes a container image instead of a machine. You get self-healing, rolling deploys and one declarative file for the whole stack — at the cost of a few failure modes that do not exist on a VM.
Service type: LoadBalancer protocol: UDP port: 7777
externalTrafficPolicy: Local (preserve the player's source IP)
Deployment replicas: 1 strategy: Recreate (never two servers on one address)
StatefulSet postgres + PVC (or a managed database)The shape: one replica, a UDP load balancer, and the database beside it.- One replica, deliberately. Each server is the authority over its own world; two replicas behind one address are two unrelated games sharing a door.
- Set the instance id from the pod name. In a container the filesystem is not evidence of machine identity, so identity is reported as ephemeral unless you say who the pod is.
- Expose the metrics/admin port and probe it. If nothing listens, the readiness probe never passes, the pod stays unready, and the load balancer ends up with an EMPTY backend — a public address answering nothing while the server log looks perfectly healthy.
- Wait for the database before starting. On a cold cluster the server can win the race and come up with no content: it licenses, it listens, and it serves an empty world without ever failing.
- Never put credential placeholders in the manifest you apply. The file you re-run is the file that overwrites your real secret; create secrets separately and verify them before applying.
Pod churn costs seats. Each replacement pod is a distinct instance taking its own lease, and a retired lease is not released instantly. Several rollout restarts in a row can exhaust a small capacity and refuse the newest pod. Iterate by scaling to zero and back, not by repeated restarts.
Path C — Kubernetes + Agones, one server per session
Agones turns a Kubernetes cluster into a game-server fleet: a pool of standby processes, each allocated to exactly one match and then recycled. This is the shape PlayFab and GameLift also implement, run on your own cluster.
Fleet replicas: N → N standby processes, each on its own dynamic port
↓ server calls Ready
Ready → allocatable
↓ allocation request (one per match)
Allocated → the server is told it has a session; the world is entered
↓ last player leaves + linger, or nobody joins before the idle timeout
Shutdown → the process drains and exits; Agones starts a replacementThe lifecycle. Your server drives it — no helper sidecar needed.- The generated server is SDK-aware: it reports Ready itself, watches for the allocation, and health-pings on the real server tick — so a wedged simulation fails health honestly. You do not need the generic readiness sidecar.
- Agones gives each server a DYNAMIC port on its node, so players connect to the node's own address, not through a load balancer. On managed Kubernetes that means node public IPs must be enabled and the port range opened in the firewall or security group — this is the step most often missed.
- Session end is policy, not magic: it fires on last-room-empty plus a linger, or an idle timeout if nobody ever joins. The generated monitor stays dormant until the platform actually allocates the server, so a process nobody assigned never decides to exit on its own.
- Give clients ONE endpoint. The Director allocates an instance, hands every rostered player an endpoint plus a single-use join token, validates arrivals and returns them to the hub afterwards — with the transfer flow shipped for the client too. Nothing about that flow edits client configuration per match.
Capacity rule, learned the hard way: a fleet is only stable while (pool size + in-flight recycles) ≤ leasable seats. Because a retired lease is held for a while and Agones replaces an exited pod within seconds, a pool sized exactly to your seat count can churn faster than seats free — every replacement is refused, exits, and is replaced again. Leave at least one spare slot.
Placement degrades quietly. Selecting external placement without registering an allocator falls back to placing matches on the same node, with only a startup warning. If matches are not landing on the fleet, check that warning first.
PlayFab and GameLift
Both are the managed form of Path C: the platform owns the pool and the allocation, and your server plays the same role it does under Agones. Choose Session server mode, compose the fleet-host adapter for that platform, and let the platform place matches. The licence rules are identical, with one caveat worth knowing up front: inside a managed container the cloud metadata service is often unreachable, so plan to approve those hosts explicitly rather than relying on attestation.
On PlayFab, drain-and-exit is the ONLY way a server slot is recycled — an active server can never re-enter standby. That is precisely what Session mode does, which is why the mode and the platform have to agree.
Pick a region before you tune anything
Host near your players. Remote players and any entity relayed between clients are rendered behind a buffer that grows to cover observed network gaps, so a distant region does not just add latency — it widens the gaps that inflate that buffer, and the buffer recovers slowly. Moving the server closer is usually a bigger win than any amount of interpolation tuning, and it costs nothing to choose correctly on day one.
Where the release settings live
- Project Settings — server mode, database, topology and hosting target. Changing these regenerates the server.
- Account → Devices — buy dev and prod seats, and see how much prod capacity is in use versus owned.
- Account → Running servers — every instance holding capacity, the cloud account it runs in, one-click Approve for an unrecognised account, approve or revoke a production host, and a Release button for a stale instance.
- The same surfaces exist in the desktop Hub and on the website, so you can approve a first deploy from whichever you already have open.
Before you call it released
- The server log says the lease was granted — not just that the licence is valid.
- The cloud account (or production host) is approved, and prod capacity covers your pool rather than your player count.
- Outbound HTTPS to the Hub is open, and stays open.
- The production database was migrated and VERIFIED: compare per-collection counts AND sequence positions, because a target missing its sequences re-issues ids that already exist.
- You have a backup you have actually restored once.
- Shutdown is graceful, so a redeploy releases its lease immediately instead of waiting out a stale window.
Exact option names, defaults and tunables are generated from the shipped binaries — read them in the piece reference rather than trusting a value copied into prose, here or anywhere else. The same text appears on hover in your IDE.
Piece reference
All 93 pieces. Open one for its generated API reference — the seams you implement, the services you call, and every public type.
Generated from the same build that produces the DLLs you install, so the reference can never describe a version you don't have. Each shipped DLL also carries its .xml doc file, so the identical text appears on hover in Rider, Visual Studio and VS Code. Looking for what to implement? See every seam, grouped by category.
Core
zenject Zenject DI Container The Zenject/Extenject dependency-injection container the whole client stack binds through. A must for Core — auto-installed, cannot be unselected. Client-only.
init Init & DI Framework The client initialization + DI framework: the [Singleton*]/IAppInitializable attributes, context-scoped installers, and the app bootstrap every Crossplay client package registers into. A must for Core — auto-installed, cannot be unselected. Client-only.
core Core Multiplayer 38 seams The always-on kernel: connect, login, sessions, rooms/groups, and the messaging SDK.
content Game Content 2 seams Genre-agnostic authored-content store: define content types + records (entities, items, levels, classes, quests, anything) over a metaschema — every domain type is DATA, not code. Saved in the DB, served to clients on demand (manifest -> lazy fetch -> live push), asset-load-path binding. A must for Core — auto-installed, cannot be unselected.
editor Crossplay for Unity (Editor) The free in-Editor companion window (Window -> Crossplay): sign in, browse the live catalog + guides, ask Relay in-Editor, wire client options, hand installs to the Hub. Includes the 'Multiplayer Test' tab (launch 2-4 local clients from one checkout — cloned Editors or standalone copies, each pinned to a distinct instance id — with one-click start/stop of the local .NET server) and, whenever the content piece is installed, the 'Content Sync' tab (two-way sync of authored Game Content between the game server and Unity ScriptableObjects: typed SO classes generated per content type, version-guarded publish back — the server store is master). Editor-only tooling, no runtime code. A must for Core — auto-installed, cannot be unselected.
Spatial
characters Characters 3 seams Create / select / roster of characters with an opaque appearance blob (ICharacterFactory SPI).
world World 19 seams Enter-world, spawn/despawn, spatial interest, zones/instancing, server entities, bounds, pathfinding (A*), and spatial enter/exit/dwell trigger volumes.
movement Movement 4 seams Server-authoritative movement: input, 20Hz broadcast, prediction/reconciliation, interpolation.
culling Network Culling (LOD) Per-pair broadcast-rate rings so distant entities cost less bandwidth — honoured by every broadcaster (server-authoritative Movement and the client-authoritative Motor relay alike, so Movement is NOT required). Off by default.
viewculling View Culling (Client LOD) Client-side distance tiers over entity views: full detail near, animator pose-write culling in the mid ring, deactivation beyond. Whatever the game spawns gets culled and the layer never knows what the objects are. No server half and no wire — usable on its own, including by a client that only moves a camera.
emotes Emotes Abstract game-defined emote/action events fanned out to observers as opaque data.
voice Voice Chat Relays opaque game-encoded audio frames by proximity (World interest) or voice channel; the framework never decodes — codec + playback are the game's.
interactions Interactions 1 seams "Player used entity X": a validated verb pipeline; the game's policy SPI decides meaning.
entitystate Entity State Mutable opaque per-entity state blob, broadcast on change and replayed on interest reveal.
attachments Attachments (Riding) One entity rides another: a rider's pose is derived from its carrier at a fixed offset in the carrier's own frame. Passengers in a vehicle, a patient on a stretcher, cargo in a hold, a player on a moving platform, a turret on a tank — the framework never knows which. The wire carries ONE reliable message per ride (replayed on interest reveal), not a pose per tick: a rider costs nothing while it rides and cannot drift against the thing carrying it. Server-decided and therefore unforgeable — there is no client verb.
rigidbodies Rigid Bodies Optimized networked transform sync for non-player physics bodies (crates/vehicles/debris/doors): quantized position + smallest-three quaternion + velocity, sleep-gated, interest-culled, batched. The game runs its own physics; this only syncs the result.
motor Avatar Motor 3 seams Generic client-authoritative avatar relay (Route B): the owning client simulates its avatar with its OWN controller and reports a full-3D pose (Vec3 + smallest-three quaternion + quantized velocity) plus an opaque game-state blob; the server validates (rate + gross-delta + optional IAvatarMoveValidator) and relays to observers with interpolation. Any bespoke-controller game (platformer/racer/ragdoll) gets multiplayer with near-zero netcode — it implements only IAvatarStateSource + IAvatarStateApplier and never defines a message or an id.
levelobjects Level Objects Authored static level objects with server-authoritative state — pickups, crates, chests, levers, doors, checkpoints, moving platforms, goals. A designer places them as CONTENT (the piece defines its own content types, so the editors show a ready-made form) and the piece owns spawning, stable ids, the 3D reach test, the state blob and its replay on interest reveal. Five built-in rules cover the recurring shapes — claim-once, durability, counter, flag, clock-epoch — with an ILevelObjectRule SPI for anything novel; which verb acts on which kind is authored, not coded. Zero new wire: spawns ride World, state rides EntityState, verbs ride Interactions.
collision3d Collision 3D 3D collision/query fidelity: baked triangle-mesh geometry (slopes/loops/ledges) answering raycast / sphere+capsule sweep / ground-probe via World's IWorldQueries3D, alongside the analytic volumes. Ships as content (.mesh3d, CBK envelope) — zero wire. The client package carries the mesh->.mesh3d editor BAKER, the shared collision math, and a RUNTIME mirror (IClientMeshGeometry) that preloads the same bakes by Addressables label so prediction asks the identical triangles authority does — which is what lets a client predict a vehicle's height and slope per frame instead of receiving them at the ack rate.
zonestate Zone State Mutable opaque per-zone blob (weather/time/siege), broadcast on change, replayed on entry.
kinematics Kinematics 1 seams Deterministic segment bodies: a body travels in straight legs and deflects off game-supplied surfaces by a configured rule set (hit-offset steer, capped surface-motion transfer, speed gain with a floor and a ceiling). The wire carries one leg per CONTACT, not a pose per tick, and both tiers evaluate the same line against the shared clock — so there is no interpolation delay and nothing to smooth at a bounce. For pong and its family, air hockey, breakout, arcade football.
worldgrid World Grid Shared spatial cell grid: per-zone chunked opaque cells (terrain edits, tiles, build pieces), streamed to each player's chunk window and delta-batched on change. Seed + persistence SPIs; server-set only.
spawner Spawner Population spawners over server entities: keep N alive in an area, respawn on removal. Server-only.
loot Loot 1 seams Dropped-item entities: opaque payload, atomic first-claim, timed expiry. Server-only.
agents Agents (NPC AI) 3 seams NPC behavior drivers: wander/patrol/chase built-ins + an IAgentBehavior game SPI. Server-only.
dungeons Dungeons 1 seams Instanced-zone lifecycle: create/seed via IDungeonContent, complete/reset, abandon sweep. Server-only.
mounts Mounts 1 seams A rideable STATE on an entity: mount id + opaque look blob + speed multiplier (applied through Movement's speed seam via a Hosting bridge). Horses, hoverboards, sprint stances, transformation forms — same state machine, the game's render. Ownership/eligibility = IMountPolicy; broadcast on change + replayed on interest reveal; empty catalog = inert.
travel Travel & Respawn 6 seams Bind/recall points, a warp destination table, and death→respawn location resolution through an ORDERED strategy chain (bind → nearest zone point → your own IRespawnStrategy). WHEN travel is allowed is the game's policy SPIs (the default DENIES client respawns until one is composed — the Stats-depletion bridge supplies the classic 'dead may go home'); moves ride the ITravelMover seam (Movement's prediction-aware Teleport when composed).
possession Possession Input redirection to server entities (vehicles / mind-control / GM drive) via Movement's seam.
pets Pets Companion entities: summon with an opaque look; follow / zone-travel / owner-leave handled. Server-only.
contact Contact 4 seams Generic entity-vs-entity contact: circle / yaw-oriented-box footprints, layer bits + mask, weight-split push-out. Hard corrections ride the swept movement pipeline (never through walls); predicted avatars push via the ISoftContactResolver seam. Zero wire — positions ride the existing movement broadcast. Server-only.
vehicles Vehicles 2 seams Server-authoritative heading+speed vehicle motion (cars/karts/boats/hover/tanks): sampled steer/drive/drift axes in through one validated path (wire AND AI), substepped shared-integrator sim over baked per-class LUT tables, swept through the world bounds every substep (no wall tunneling), full-state owner ack for client prediction. Wall + vehicle-vs-vehicle contact events (car-vs-car via the config-gated Contact bridge); per-entity multiplier SPI (nitro / upgrades). Drivable from CONFIG ALONE: AutoRegister says which entities entering which zone become vehicles of which class, and SurfaceHandling maps the world's baked per-cell surface ids to handling multipliers (surface 0 and unmapped ids stay neutral) — no server code required for either. Motion rides the Movement broadcast; requires the Movement piece composed. The client package predicts the local vehicle through the same shared integrator and reconciles on the owner ack.
Combat
stats Stats Numbered clamped stats with thresholds and regen.
abilities Abilities 4 seams Opaque verbs with server-tracked cooldowns and an IAbilityRules SPI.
projectiles Projectiles 2 seams Kinematic projectile entities with deterministic client rendering + click-frame prediction: one launch event per volley, no interpolation buffer.
statuseffects Status Effects Timed opaque markers (buffs/debuffs) applied to entities.
hostility Hostility (PvP Flags) 1 seams WHO MAY HARM WHOM, answered in one place: consent stances (opt-in verb), combat flags on aggression, escalation tiers on innocent kills, safe zones — one IHarmResolver seam every combat consumer asks (Crossplay:Hostility:DriveTargeting bridges Targeting). PvE pairs pass through untouched; defaults = the classic no-PvP baseline.
threat Threat / Aggro Server-only accumulated threat tables ("who do I hate most"): first-seen bonus, exact exponential decay, forget threshold, TargetChanged hooks. Feeding (damage/taunts) and consuming (Targeting selector, Agents brains) are composition bridges — Crossplay:Threat:DriveTargeting flips Targeting to highest-threat-first. Zero wire; inert until fed.
targeting Targeting (Auto-Attack + Selected Target) 5 seams Two halves of "what am I aiming at". (1) Auto-attack driver: enrolled attackers acquire a target (ITargetSelector) and attack on a cadence (IAttackAction) over World — survivors/auto-shooter auto-fire, turrets, sentries, pet auto-attack. (2) The SELECTED target (TargetSelection:Enabled): SetTargetRequest in, TargetChanged out, validated against the actor's interest set + an ITargetPolicy SPI, readable server-side through ITargetService — tab-target MMOs, RTS selection, a sports pass target, a card game's targeting step. DriveAutoAttack makes your swings land on what you clicked; RevealTargets drives target-of-target frames (off by default — who you selected is information).
duels Duels (1v1) 1 seams Consensual 1v1: challenge -> accept -> fight -> the winner is decided by a SERVER-reported defeat, a forfeit, or the time limit. There was no challenge/accept flow anywhere (matches is match containers, tournaments is brackets, and hostility models consent as a standing STANCE rather than "this person, now, then we stop"). The piece is CORE-ONLY: mutual harm, ending a duel just before the loser would die, and restoring the health they spent are the composition's job (Crossplay:DuelCombat over Hostility + Stats), so it also composes into a game with neither. IDuelPolicy = eligibility + stakes.
combathud Combat HUD Pooled nameplates + eased HP bars + effect chips + cooldown pips over the combat clients. Client-only UI.
Economy
container Container 3 seams The generic opaque-item-container engine every item piece sits on: containerId-keyed slots/stacks, policy SPI, persistence, and atomic cross-container moves. Server-only; inventory/bank/vault/equipment are thin roles over it.
inventory Inventory 2 seams The character's bag — a container role over the Container engine (opaque item blobs, policy SPI, persistence).
bank Bank / Stash 1 seams A personal item-storage container — a role over the Container engine (opaque item blobs; atomic bag<->bank deposit/withdraw via TryExchange; policy SPI; persistence). Depends on Container, not Inventory.
equipment Equipment 1 seams Worn/active gear in typed slots — a container role over the Container engine (opaque items + opaque slot ids, IEquipmentPolicy SPI). World-optional visible loadout broadcast; item effects via Equipped/Unequipped events (never references Stats).
trade Trade Escrowed player-to-player exchange (atomic TryExchange over Inventory).
stalls Player Stalls 2 seams A player's OWN shop, standing where they left it: open a stall that escrows stock out of your bag, and anyone the game's IStallPolicy says can reach it browses and buys — the seller does nothing, and may be afk or offline. Nothing else covered it (shop is system-owned, market is asynchronous listings, trade needs both parties present and consenting). Presentation-agnostic: a market stall, a vending machine you place, a shop sign over an avatar, or a text for-sale list are the same data. Currency = IStallWallet, proximity/zone rules = IStallPolicy, so it names nothing spatial and no Economy type.
crafting Crafting 2 seams Validated recipe execution (opaque in/out, all-or-nothing) over Inventory; catalog is a game SPI.
economy Economy (Currencies) 3 seams Atomic, policy-gated, ledgered currencies; read-only on the wire.
market Market 3 seams Escrowed listings + lock-guarded buyout over Inventory + Economy, with durable Scheduler expiry.
shop Shop / Vendors 5 seams System-owned stores selling/buying OPAQUE items for game currencies: catalog stock, per-buyer daily caps, finite stock, buyback ring. Currency/access/appraisal/pricing are seams (Economy bridges the wallet); WHAT a shop is — NPC, vending machine, terminal — is the game's. Server-only wire verbs; empty catalog = inert.
mail Mail 3 seams Offline delivery with escrowed item attachments.
guildvault Guild Vault Shared guild inventory of opaque item blobs with per-rank deposit/withdraw/view permissions and an audit trail.
sharedcontainers Shared Containers (Group Kit) Containers owned by a group rather than a character — a crew kit, a party stash, a team's shared crate — over the Container engine: the contents are pushed to every session the game's audience seam admits, and only those sessions may read or rearrange them. Items enter and leave through server-side game verbs (atomic exchanges with a member's bag); the wire carries request and move only, so a client is never trusted to say what it takes. Inert until an audience is composed.
purchases Purchases 1 seams Data-driven purchase catalog: wallet stat, per-buyer live price (stored as a stat, so it replicates) with growth, and capped stat effects. Server-only wire verb; empty catalog = inert.
upgrades Upgrades 2 seams Coin-priced, persistent, server-authoritative meta-upgrades: price from the buyer's stored tier (ceil(InitialPrice*Growth^tier)), atomic currency debit, tier persisted per-character. Currency + persistence via seams bridged to Economy + PlayerData.
Social
chat Chat 3 seams Channels and whispers over Core, with profanity masking and rate caps.
guild Guild 1 seams Persistent guilds: officer ranks, MOTD, invites/kicks, leader handoff.
party Party 4 seams Ephemeral parties: leader, ready checks, kicks.
teams Teams 1 seams Server-authoritative in-match sides per game-defined scope: opaque team ids, members, a rankable score + opaque blob, team-scoped broadcast, optional policy-gated self-service joins.
friends Friends 1 seams Friend lists, requests, and online presence.
matchmaking Matchmaking 3 seams Queue-based matchmaking with team/role quotas and MMR.
lobbies Lobbies 1 seams Create/join/list lobbies with slots and metadata.
director Instance Director 5 seams Allocate game instances (zone/room/lockstep) on match/lobby formation across nodes, hand each player a single-use join token, run the lifecycle, and return them to the hub.
Progression
leaderboards Leaderboards 3 seams Persistent ranked boards: keep-best, around-me, rolling seasons.
dailyrewards Daily Rewards Interval claims with grace-window streaks; reward is an IDailyRewardGrantor game SPI.
achievements Achievements 3 seams Server-reported counters against game-defined thresholds; catalog/reward are game SPIs.
progression Progression (XP/Levels) 3 seams Multi-track server-granted xp/levels; curve is config exponential or an IProgressionCurve SPI.
quests Quests 4 seams Accept/track/turn-in over game-defined objectives; catalog/policy/reward are game SPIs.
dialogs Dialogs 4 seams Server-resolved conversation graphs: opaque node/choice payloads, per-choice conditions evaluated SERVER-side and stripped from the wire, choice actions via game SPIs. NPC talk, visual novels, tutorials, quest givers — one engine; Core-only, stateless, empty graph table = inert.
Meta
signals Signals (Server Cues) Server-originated one-shot opaque events to a session, a room, or a zone — a bell everyone hears, a scripted cue, "somewhere a door opened". An EVENT, never state: never stored, never replayed, so a reconnecting player is told nothing about what they missed (which is what separates it from ZoneState/EntityState). No client verb exists, so a signal is unforgeable by construction. Spatial-optional: the zone route needs World, the rest does not.
playerdata Player Data Per-player key/value store of opaque blobs.
matches Matches 2 seams Match lifecycle (countdown/duration/return-zone) with results bridged to Leaderboards.
rounds Rounds 1 seams Server-authoritative phase/round state machine per game-defined scope: opaque phase ids, per-phase deadlines, IRoundFlow SPI transitions, and an authoritative elimination set. No client verbs (unforgeable).
voting Voting 1 seams Server-opened proposals over opaque choice ids: one ballot per eligible voter, live anonymous tallies, deadline/everyone-voted close, outcome decided by an IVoteRules SPI (plurality default). The game acts on Closed; the piece never executes outcomes.
roles Roles (Hidden/Asymmetric) 1 seams Server-assigned opaque role ids with per-viewer visibility filtering (a hidden assignment never touches the wire), per-holder private payloads, role-scoped sends, and an end-of-game reveal. IRoleVisibilityPolicy SPI; no client verbs (unforgeable).
draft Draft (Pick/Ban) Server-authoritative timed ordered selection over opaque option ids: the game supplies the step order (snake/alternating/captains — all just step lists) and the pool; the piece runs the clocks, validates turn + availability, resolves timeouts, and pushes idempotent snapshots.
remoteconfig Remote Config Live tuning values pushed to clients.
scheduler Scheduler 3 seams Persistent fire-at-time callbacks that survive restarts. Server-only.
analytics Analytics 1 seams Genre-agnostic gameplay-event pipeline: server-authoritative Track + a rate-capped client wire, batched off-thread to the game's IAnalyticsSink SPI (Segment/Amplitude/BigQuery/…). Ships inert (null sink counts + drops).
reports Reports 2 seams Report-a-player: rate-capped, recorded, surfaced via an admin decorator.
lockstep Lockstep (Rollback Netcode) 1 seams Server = input authority (order/relay/ack/checksum); client RollbackEngine + IDeterministicSim SPI.
tournaments Tournaments 6 seams Single-elimination brackets: winners reported by server code (never client-trusted); join policy + champion reward are game SPIs.
gameflow Game Flow Genre-agnostic boot -> connect -> auth -> character -> world lifecycle FSM (IGameFlow); the game binds its screens to StateChanged. Client-only, Core-only.
localization Localization Resolves keys (notification codes etc.) to localized, interpolated strings with locale fallback; strings are game data via ILocalizationCatalog. Client-only, Core-only.
replay Replay 1 seams Record broadcasts server-side (archive) + play a .cdrp recording back through the live client dispatch with play/pause/speed/seek.
Infra
pooling Object Pooling Generic rent/return instance pooling on the client. Client-only.
diagnostics Diagnostics Overlay Net/sim overlay: RTT, rates, interp delay, prediction error, cull tiers. Client-only.
contracts Contracts (Client SPIs) The always-present shared client SPIs a game binds to, independent of any feature piece: IGameplayInput + MovementIntent (feed input intent), IAnimatorParameterMapper (map locomotion onto your animator), and IAssetService + IAssetScope (load assets by key). Feature pieces (movement, input, assets, audio) implement or consume these contracts, so a game can bind any SPI without installing the feature that used to define it. A must for Core — auto-installed, cannot be unselected. Client-only, no wire.
ugui uGUI Panel Toolkit The MVP uGUI panel toolkit the client UI packages build on. Client-only.
audio Audio Service Channel/one-shot/music service over asset keys. Client-only.
input Input Schemes FPS/MMO/click-to-move/ARPG/drag-axis/cards control schemes over the intent SPI. Client-only.
assets Addressables Service Ref-counted Addressables asset service with load scopes. Client-only.
Browse and buy any piece in the store, or download the ones you own from downloads.