# Client Integration Source: https://world.dev/cardinal/client-integration High-level overview of integrating game clients with Cardinal using the client SDKs. For the full SDK reference documentation, see: * [JavaScript/TypeScript](/javascript/introduction) * [Unity C#](/csharp/introduction) ## Initializing the Client Create a client with a configuration object that specifies the auth URL and region configs. For local development, use dev auth to bypass the normal authentication flow. ```csharp theme={null} using WorldEngine.SDK; using WorldEngine.SDK.Client; var config = new WorldClientConfig { AuthUrl = "http://localhost:3000", AuthClientOverride = new DevAuth("dev@argus.gg"), Regions = new Dictionary { ["us-west-2"] = new RegionConfig { GatewayUrl = "http://localhost:8080", DisplayName = "US West" } } }; var client = new WorldClient(config); ``` ```ts theme={null} // TODO: update ``` ## Authentication Sign in to authenticate the user. You can retrieve the current user's information and persona after signing in. ```csharp theme={null} var result = await client.SignInAsync(); if (result.Error == null) { var user = result.Data; Debug.Log($"Signed in as {user.Email} (Persona: {user.PersonaId})"); } // Get current user var currentUser = client.GetUser(); ``` ```ts theme={null} // TODO: update ``` Sign out when the user logs out of your game. ```csharp theme={null} await client.SignOutAsync(); ``` ```ts theme={null} // TODO: update ``` ## Connecting to a Shard A shard is a single instance of your game world running on the server. The shard client is your connection to that instance, allowing you to query entities, send commands, and subscribe to events. To connect, specify the shard's address (region, organization, project, and shard ID) and call connect to get a shard client. ```csharp theme={null} var addr = new ShardAddress { Region = "us-west-2", Organization = "my-org", Project = "my-game", ShardId = "game-shard" }; var shard = client.ConnectShard(addr); ``` ```ts theme={null} // TODO: update ``` Disconnect from the shard when you no longer need it. ```csharp theme={null} client.DisconnectShard(addr); ``` ```ts theme={null} // TODO: update ``` ## Querying Entities To query entities, define client-side classes with field mappings that correspond to component field names on the server. Build queries to specify which components to find and how to match them. See [Queries](/cardinal/queries) for more details. ```csharp theme={null} // Define entity classes with Field attribute mapping public class PlayerTag { [Field("nickname")] public string Nickname { get; set; } } public class Health { [Field("hp")] public float Hp { get; set; } } public class PlayerEntity { [Field("playertag")] public PlayerTag PlayerTag { get; set; } [Field("health")] public Health Health { get; set; } } // Build and execute query var query = new QueryInfo { Find = new List { "playertag", "health" }, Match = QueryMatch.Contains, }; var results = await shard.QueryAsync(query); foreach (var (entity, id) in results) { Debug.Log($"Player: {entity.PlayerTag.Nickname} Health: {entity.Health.Hp}"); } ``` ```ts theme={null} // TODO: update ``` ## Sending Commands Build a command payload and send it to the shard. See [Commands](/cardinal/commands) for more details. ```csharp theme={null} var payload = new Dictionary { ["target"] = "enemy-123", ["damage"] = 50 }; await shard.SendCommandAsync("attack-player", payload); ``` ```ts theme={null} // TODO: update ``` ## Subscribing to Events Subscribe to events with handler callbacks. See [Events](/cardinal/events) for more details. ```csharp theme={null} var handler = evt => { Debug.Log($"New player event: {evt.Payload}"); }; await shard.SubscribeEventAsync("new-player", handler); ``` ```ts theme={null} // TODO: update ``` Unsubscribe when you no longer need to receive events. ```csharp theme={null} await shard.UnsubscribeEventAsync("player-death", handler); ``` ```ts theme={null} // TODO: update ``` # Commands Source: https://world.dev/cardinal/commands Commands are how players interact with your game world. They represent actions that players want to perform, such as moving, attacking, or chatting. To handle commands in your game, you must first define them and create systems to handle them. Commands can come from either game clients or other shards. ## Defining Commands Commands are plain Go structs that embed `BaseCommand` and implement the `Command` interface. This interface requires a single `Name()` method that returns a **unique** string identifier. ```go theme={null} import "github.com/argus-labs/world-engine/pkg/cardinal" type AttackCommand struct { cardinal.BaseCommand // Required TargetID string `json:"target_id"` Damage int `json:"damage"` } func (AttackCommand) Name() string { return "attack" } ``` ## Handling Commands Just like with components, you must declare the commands a system can access in its state struct. Add a `WithCommand[T]` field to your system state type, where `T` is your command type: ```go theme={null} import "github.com/argus-labs/world-engine/pkg/cardinal" type AttackSystemState struct { cardinal.BaseSystemState AttackCommands cardinal.WithCommand[AttackCommand] } ``` A system cannot have multiple `WithCommand[T]` fields with the same command type. However, different systems can handle the same command type. This is useful for when you want a single command to trigger multiple game logic and/or side effects. ### Iterating Over Commands Use `Iter` to loop through all commands received this tick. Each iteration yields a `CommandContext` that provides access to the command data and metadata. To access the command's payload, use `Payload`: ```go theme={null} func AttackSystem(state *AttackSystemState) error { for cmd := range state.AttackCommands.Iter() { attack := cmd.Payload() // Use attack.TargetID, attack.Damage, etc. } return nil } ``` ### Personas A persona is the unique identity associated with a player’s account. All commands include metadata containing the sender’s persona, which you can use for authorization checks like verifying entity ownership. If your client is authenticated, commands sent from it will automatically include your account's persona. In multi-shard setups, personas remain consistent across shards, allowing any shard to verify and act on the same player identity. You can get a command's persona using `Persona`: ```go theme={null} func AttackSystem(state *AttackSystemState) error { for cmd := range state.AttackCommands.Iter() { attack := cmd.Payload() sender := cmd.Persona() // Get the player's entity and verify ownership. player, ok := state.Players.GetByID(attack.PlayerID) if !ok { continue } if player.Owner.Get() != sender { continue // Skip unauthorized commands. } // Process the command... } return nil } ``` ## Sending Commands Use the client SDK to send commands to the server: ```csharp theme={null} await shard.SendCommandAsync("attack", new Dictionary { ["target_id"] = "enemy-123", ["damage"] = 50 }); ``` ```ts theme={null} TODO: update ``` ### Command-Reply Pattern Commands are asynchronous by design. When you send a command, you receive an acknowledgment, but not the result of the command's execution. If you need a result, you typically emit an event from the system that processes the command and subscribe to it from the client. Some command types are more synchronous in nature, like updating game config, where every command has a corresponding result event. For these cases, use the synchronous command method, which handles the temporary event subscription for you and returns the result event directly: ```csharp theme={null} var result = await shard.SendCommandWithReplyAsync("buy-item", new Dictionary { ["item_id"] = "sword-01", ["quantity"] = 1 }); ``` ```ts theme={null} TODO: update ``` ## Inter-Shard Commands In a multi-shard setup, you can send commands from within a system to another shard. This allows you to trigger systems in other shards or coordinate game state. ### Defining Shard Targets To send an inter-shard command, you must first define an `OtherWorld` variable for each shard you want to communicate with: The convention is to place this in `pkg/other_worlds/other_world.go`. ```go theme={null} import "github.com/argus-labs/world-engine/pkg/cardinal" var MatchmakingShard = cardinal.OtherWorld{ Region: "us-west-2", Organization: "my-org", Project: "my-game", ShardID: "matchmaking", } ``` An `OtherWorld` represents a specific remote shard and is used to route commands to the correct destination. ### Sending Inter-Shard Commands Use `SendCommand` to dispatch a command to another shard. Pass the system's `BaseSystemState` and the command you want to send: ```go theme={null} import matchcmd "my-game/shards/matchmaking/command" func GameEndSystem(state *GameEndSystemState) error { for cmd := range state.EndGameCommands.Iter() { game := cmd.Payload() // Queue the winner for another match on the matchmaking shard. MatchmakingShard.SendCommand(&state.BaseSystemState, matchcmd.QueuePlayer{ PlayerID: game.WinnerID, Rating: game.NewRating, }) } return nil } ``` The receiving shard processes inter-shard commands like any other command, using `WithCommand` in its system state. The target shard must have a system that handles the command type you're sending. If no system handles the command, it will be discarded. # ECS Source: https://world.dev/cardinal/ecs Cardinal uses the Entity Component System (ECS) architecture to structure game code. ECS separates data from logic and encourages a data-driven design that scales well with complexity and performance demands. In ECS: * **Entities** are plain identifiers that represent "objects" in your game, e.g. players, projectiles, mobs, etc. * **Components** contain the data of the properties of your entities, for example: a projectile entity contains the position and velocity components. * **Systems** are the game logic that operates on your entities, for example: a physics system acts on all entities that have mass and position components, or a regeneration system acts on all entities that have a health component. ## The World Before getting into how to use ECS, we'll briefly cover the `World` type. This is your game world. It holds all your entities, components, and systems together. Here's what a typical `main.go` looks like: ```go main.go theme={null} package main import ( "my-game/shards/game/system" "github.com/argus-labs/world-engine/pkg/cardinal" ) func main() { world, err := cardinal.NewWorld(cardinal.WorldOptions{ TickRate: 1, EpochFrequency: 10, }) if err != nil { panic(err.Error()) } // Register systems. cardinal.RegisterSystem(world, system.MovementSystem) cardinal.RegisterSystem(world, system.CombatSystem) // Start the game loop. world.StartGame() } ``` Above, we also pass several options to configure the behavior of Cardinal: 1. `TickRate` sets how many times per second the game loop runs. A **tick** represents a single state change in Cardinal. 2. `EpochFrequency` sets how many ticks to include in an epoch. An **epoch** is a group of ticks that will be persisted to a blockchain. There are other options, but these are all you need to run your world. ## Components Components are plain Go structs that implement the `Component` interface. This interface requires a single `Name()` method that returns a **unique** string identifier. ```go theme={null} type Position struct { X int `json:"x"` Y int `json:"y"` } func (Position) Name() string { return "position" } ``` Component names must start with a letter or underscore, and contain only letters, digits, and underscores (e.g. `Health`, `player_health`). ### What a Component May Hold Components are copied. `Get` hands you a copy and `Set` stores one, and every snapshot is built from what went through `Set`. That only works if a copy is independent of the original, so a component may hold values only: * Numbers, booleans, and strings * Structs of those * Fixed-size arrays such as `[8]Vec2`, at any depth * `immutable.Slice[T]` for a list with no fixed bound (see below) * `time.Time`, the one sanctioned exception. It carries a location pointer, but it travels as a protobuf Timestamp, so a restored value comes back in UTC with no monotonic reading. Compare times with `Equal`, never `==`. A component may **not** hold pointers, maps, plain slices (`[]T`), interfaces, channels, or funcs. A plain slice inside a copy still shares its backing array with the stored component, so writing through it changes the world without a `Set`. `world sdk generate` refuses such fields and prints the fix for each one. The same rule applies to commands and events, which cross the same generator. A list with a known upper bound is a fixed array plus a count: ```go theme={null} const MaxItems = 16 type Inventory struct { Items [MaxItems]Item `json:"items"` Count int `json:"count"` } ``` ### Lists Without a Bound: `immutable.Slice` When the length has no bound you can defend, hold the list in `immutable.Slice[T]` from `github.com/argus-labs/world-engine/pkg/immutable`. On the wire it is the same repeated field a `[]T` would be. The difference is that the backing array is hidden: no other package can index into it, reslice it, or hold on to it, and every reader hands back a copy of the element. ```go theme={null} import "github.com/argus-labs/world-engine/pkg/immutable" type Buff struct { Kind string `json:"kind"` ExpiresAt uint64 `json:"expires_at"` // tick } type Buffs struct { Active immutable.Slice[Buff] `json:"active"` } func (Buffs) Name() string { return "buffs" } ``` The API follows Go's `slices` package closely. Operations are named for their result rather than for an action on the receiver — `Reverse` becomes `Reversed`, and `Sort` and `SortFunc` become `Sorted` and `SortedFunc`. Reads such as `At`, `All`, `IndexFunc`, and `MinFunc` are methods, and so are derivations such as `Append`, `Insert`, `With`, `Without`, `Sub`, `Filter`, `Delete`, `Reversed`, and `SortedFunc`. Anything that needs a type constraint is a package function, for example `Equal`, `Contains`, `Sorted`, `Min`, `Map`, and `Concat`. Derive, then `Set` the component that holds it: ```go theme={null} func BuffSystem(state *BuffSystemState) error { now := state.Tick() for _, mob := range state.Mobs.Iter() { buffs := mob.Buffs.Get() // Drop expired buffs and add a new one. buffs.Active = buffs.Active. Filter(func(b Buff) bool { return b.ExpiresAt > now }). Append(Buff{Kind: "haste", ExpiresAt: now + 60}) mob.Buffs.Set(buffs) } return nil } ``` `immutable.SliceOf(items...)` builds a Slice from a plain slice and copies it, so later changes to that slice never reach the component. To go the other way — a plain `[]T` for an API that needs one — range over `Values`, or use `immutable.Collect` to build a fresh Slice from the result: there is no `Clone` method, since a copy taken that way could look like it protects the original while derivations write through it regardless. **Derivations do not copy, so the `Set` is not optional.** Most of them edit the hidden array in place and return a Slice over that same array, which means the component has already changed by the time the derivation returns. Deriving and then dropping the result does not leave the world alone — it leaves the world holding an edit that no snapshot recorded. `Append`, `Repeat`, and `Sub` leave the receiver alone; `Insert` and `Replace` leave it alone only when they have to grow. The rest — `With`, `Without`, `Filter`, `Delete`, `Reversed`, `SortedFunc`, `CompactFunc`, `Sorted`, and `Compact` — always write through. Ones that shrink leave the receiver at its old length over a zero-filled tail, so the receiver is not just reordered but wrong. Two rules follow. Call `Set` after every derivation, even one you think is read-only: `s.SortedFunc(cmp).At(0)` reorders the stored list. And when the original has to survive, derive from a copy instead: `immutable.SliceOf(slices.Collect(s.Values())...)`. Each method's Go doc says whether it writes through. `immutable.Slice` needs a World CLI new enough to generate it. An older one reports the field as an unsupported type when you run `world sdk generate`. A Slice has no JSON form of its own — the wire format is protobuf. Keep a `json` tag on the field if you like, but do not expect a Slice to survive `encoding/json`. The element type follows the same rule as any field: values only. A Slice directly inside another Slice has no protobuf form, so wrap the inner one in a named struct, for example `immutable.Slice[Row]` where `Row` holds `immutable.Slice[Cell]`. ### Tag Components Components don't need to contain data. You can use empty structs as "tags" to mark entities: ```go theme={null} type Player struct{} func (Player) Name() string { return "player" } ``` This is useful for filtering entities without storing additional data, e.g. finding all player entities vs. NPC entities. ## Systems Systems are plain functions that take a single parameter and return an error. The parameter is a pointer to a user-defined struct type that embeds `BaseSystemState`. This struct defines a system's dependencies and what it can access, e.g. components, commands, events, etc. (We'll cover these in more detail soon.) This is the simplest possible system: ```go theme={null} import "github.com/argus-labs/world-engine/pkg/cardinal" type MySystemState struct { cardinal.BaseSystemState // Required } func MySystem(state *MySystemState) error { // Your game logic here. return nil } ``` You must register systems with the world to run them: ```go theme={null} cardinal.RegisterSystem(world, MySystem) ``` Registered systems run once every tick, in registration order. Cardinal's scheduler automatically runs systems without shared dependencies in parallel. A system's dependencies include the components and [system events](/cardinal/system-events) it accesses. ### System Hooks You can control when a system executes during a tick by specifying a hook when you register the system, for example: ```go theme={null} // This system now runs in the pre-update phase of the tick. cardinal.RegisterSystem(world, SetupSystem, cardinal.WithHook(cardinal.PreUpdate)) ``` By default, systems run on the update phase. Here are the different hooks you can use: | Hook | When it runs | | ------------ | -------------------------------------------------------------------------------------- | | `Init` | Once during world initialization (only once at tick 0), before any game loop starts | | `PreUpdate` | Every tick, before the main update phase | | `Update` | Every tick, during the main update phase (this is the default if no hook is specified) | | `PostUpdate` | Every tick, after the main update phase | Each of these corresponds to a tick phase, except `Init`, which runs only once in the first tick. ## Searches To work with entities and their components in your systems, you need to define a **search**. A search lets you find and manipulate entities with specific components. Add a search field to your system state struct using one of these types: * `Exact[T]`: finds entities that have exactly the specified components, nothing more. * `Contains[T]`: finds entities that have at least the specified components, but may have others. For example, say you have player and enemy entities that both have a `Health` component, but only enemies have an `AIBehavior` component. Using `Contains` with just `Health` would match both players and enemies. Using `Exact` with `Health` and `AIBehavior` would match only enemies. The type parameter `T` is a struct that lists the components you want to search for. Each field must use `Ref[C]`, where `C` is a component type: ```go theme={null} import "github.com/argus-labs/world-engine/pkg/cardinal" type MobSystemState struct { cardinal.BaseSystemState Mobs cardinal.Contains[struct { Health cardinal.Ref[Health] Position cardinal.Ref[Position] }] } ``` This defines a search called `Mobs` that matches all entities with at least `Health` and `Position` components. We'll use it to demonstrate entity operations below. ### Creating an Entity Use `Create` to spawn a new entity with the components defined in your search. It returns the entity ID and a handle to access its components. All components are initialized to their zero values. ```go theme={null} func MobSystem(state *MobSystemState) error { // Create a new entity. entityID, mob := state.Mobs.Create() // Set the entity's component values. Any fields unset remain at their zero values. mob.Health.Set(Health{Value: 100}) mob.Position.Set(Position{X: 0, Y: 0}) return nil } ``` `Create` always creates an entity with exactly the components in your search, even if you're using `Contains`. The difference between `Contains` and `Exact` only affects which entities are matched when iterating or querying. ### Destroying an Entity Use `Destroy` to remove an entity and all its components from the world. Returns `true` if the entity existed and was destroyed: ```go theme={null} func MobSystem(state *MobSystemState) error { ok := state.Mobs.Destroy(entityID) if !ok { // Entity doesn't exist or was already destroyed. } return nil } ``` ### Iterating Over Entities Use `Iter` to loop through all entities matching the search. It yields both the entity ID and a handle to access components: ```go theme={null} func MobSystem(state *MobSystemState) error { for entityID, mob := range state.Mobs.Iter() { // Use entity... } return nil } ``` ### Getting a Specific Entity Use `GetByID` to retrieve a specific entity's handle. Returns `false` if the entity doesn't exist or doesn't have the matching components in the search: ```go theme={null} func MobSystem(state *MobSystemState) error { mob, ok := state.Mobs.GetByID(entityID) if !ok { // Entity not found. return nil } health := mob.Health.Get() // ... return nil } ``` ### Reading and Writing Components Use `Get` and `Set` on component references to read and write component data. `Get` returns a copy, so nothing you do to it reaches the world until you `Set` it back (see [What a Component May Hold](#what-a-component-may-hold)): ```go theme={null} func MobSystem(state *MobSystemState) error { for _, mob := range state.Mobs.Iter() { // Read current health. health := mob.Health.Get() // Write new health. mob.Health.Set(Health{Value: health.Value - 10}) } return nil } ``` ### Adding a Component Because systems must declare all components they access upfront, adding a component requires including it in your search definition. Define a `Contains[T]` search with both the entity's existing components and the component you want to add, then use `Set` to add the new component: ```go theme={null} import "github.com/argus-labs/world-engine/pkg/cardinal" type MobSystemState struct { cardinal.BaseSystemState Mobs cardinal.Contains[struct { Health cardinal.Ref[Health] Position cardinal.Ref[Position] Poisoned cardinal.Ref[Poisoned] // Add the component }] } func MobSystem(state *MobSystemState) error { for _, mob := range state.Mobs.Iter() { // Set will add the component to the entity if it doesn't exist. mob.Poisoned.Set(Poisoned{Duration: 10}) } return nil } ``` Because you're using a `Contains` search, it may match entities you don't intend to modify. To avoid this, add a [tag component](#tag-components) so you can distinguish your target entities from the rest. ### Removing a Component Use `Remove` to detach a component from an entity: ```go theme={null} func MobSystem(state *MobSystemState) error { for _, mob := range state.Mobs.Iter() { // Remove the health component from this entity. mob.Health.Remove() } return nil } ``` # Events Source: https://world.dev/cardinal/events Events are notifications sent to game clients about things happening in your game, such as a player dying, an item dropping, or a match ending. While commands are input from players, events are output from systems. Events are useful for triggering real-time UI updates, playing sound effects, or synchronizing game state across clients. Note that events are distinct from [system events](/cardinal/system-events), which are internal messages for communication between systems within a single tick. ## Defining Events Events are plain Go structs that embed `BaseEvent` and implement the `Event` interface. This interface requires a single `Name()` method that returns a **unique** string identifier. ```go theme={null} import "github.com/argus-labs/world-engine/pkg/cardinal" type PlayerDeathEvent struct { cardinal.BaseEvent PlayerID string `json:"player_id"` KillerID string `json:"killer_id"` } func (PlayerDeathEvent) Name() string { return "player-death" } ``` ## Emitting Events Just like with commands, you must declare the events a system emits in its state struct. Add a `WithEvent[T]` field to your system state type, where `T` is your event type: ```go theme={null} import "github.com/argus-labs/world-engine/pkg/cardinal" type CombatSystemState struct { cardinal.BaseSystemState PlayerDeathEvents cardinal.WithEvent[PlayerDeathEvent] } ``` In your system, use `Emit` to emit the event: ```go theme={null} func CombatSystem(state *CombatSystemState) error { // Game logic that results in a player death... state.PlayerDeathEvents.Emit(PlayerDeathEvent{ PlayerID: "player-123", KillerID: "player-456", }) return nil } ``` Just like with commands, a system cannot have multiple `WithEvent` fields with the same event type. ## Subscribing to Events Events emitted during a tick are collected and broadcast to subscribers at the end of that tick. While events are broadcast, clients must explicitly subscribe to receive them. Subscribe to an event by passing the event name and a handler function: ```csharp theme={null} var handler = evt => { Debug.Log($"New player event: {evt.Payload}"); }; await shard.SubscribeEventAsync("new-player", handler); ``` ```ts theme={null} TODO: update ``` Unsubscribe from an event when you no longer need to receive updates: ```csharp theme={null} await shard.UnsubscribeEventAsync("new-player", handler); ``` ```ts theme={null} TODO: update ``` # Queries Source: https://world.dev/cardinal/queries Queries let game clients fetch entity data from the game world. They specify which components to match and optional filters to narrow results. ```json theme={null} { "find": ["player", "position"], "match": "contains", "where": "position.x > 100" } ``` ## Query Structure A query consists of three fields: `find`, `match`, and an optional `where` filter. ### `find` The `find` field is an array of component name strings (the value returned by `Component.Name()`). At least one component is required, and unregistered component names will cause an error. You can include tag components to narrow results. ### `match` The `match` field controls how components are matched: | Value | Description | | ------------ | ------------------------------------------------------------------------------- | | `"exact"` | Finds entities that have exactly the specified components, nothing more | | `"contains"` | Finds entities that have at least the specified components, but may have others | These have the same behavior as [ECS searches](/cardinal/ecs#searches). ### `where` The `where` field is an optional [expr-lang](https://expr-lang.org/) expression that filters entities. The expression is evaluated per entity and must return a boolean. Access components by name and fields by dot notation. The entity ID is available as `_id`. Examples: * Comparisons: `health.HP > 100` * Logic: `playertag.Level >= 10 && health.HP > 50` * String Operations: `playertag.Nickname contains "admin"` See the [expr-lang documentation](https://expr-lang.org/docs/getting-started) for full syntax. Standard operators include `==`, `!=`, `>`, `<`, `>=`, `<=`, `&&`, and `||`. ## Sending Queries Use the client SDK to query entities from the server: ```csharp theme={null} // Find all entities with player and position components var results = await shard.QueryAsync(new QueryRequest { Find = new[] { "player", "position" }, Match = "contains" Where = "health.value <= 20" }); ``` ```ts theme={null} TODO: update ``` The response contains an `entities` array. Each entity includes `_id` (the entity ID) and one property per component, keyed by component name: ```json theme={null} { "entities": [ { "_id": 42, "player": { "id": "alice" }, "position": { "x": 10, "y": 5 }, "health": { "value": 10 } } ] } ``` # Snapshots & Durability Source: https://world.dev/cardinal/snapshots A Cardinal shard keeps its whole world in memory. A **snapshot** is the only durable copy of that world: every `SnapshotRate` ticks the shard serializes its state and writes it to snapshot storage, replacing the previous snapshot. On boot the shard loads it back and continues from the tick it was taken at. A snapshot holds exactly the component data that went through `Set`, which is why components hold values only (see [What a Component May Hold](/cardinal/ecs#what-a-component-may-hold)). ## Configuration ```go theme={null} cardinal.NewWorld(cardinal.WorldOptions{ TickRate: 60, // ticks per second SnapshotRate: 300, // ticks per snapshot -> one snapshot every 5s at 60 TPS SnapshotStorageType: snapshot.StorageTypeS3, }) ``` | Option | Env var | Meaning | | --------------------- | -------------------------------- | --------------------------- | | `SnapshotRate` | `CARDINAL_SNAPSHOT_RATE` | Ticks between snapshots | | `SnapshotStorageType` | `CARDINAL_SNAPSHOT_STORAGE_TYPE` | `NOP`, `JETSTREAM`, or `S3` | `SnapshotRate` is the amount of play a crash may undo. At 60 TPS, a rate of 300 puts up to five seconds of the world at risk. `NOP` storage persists nothing at all — it is the default, and it is for local development only. ## What a snapshot promises, and what it does not Snapshot writes are **asynchronous and best effort**. This is deliberate: uploading a world state costs a serialization plus a network round trip, and doing that on the tick goroutine turns every snapshot tick into a latency spike for the whole shard. So the tick hands the snapshot to a background writer and moves on. Three consequences an operator should know: The tick that produced a snapshot returns before the upload finishes. If the process dies in that window, that snapshot is not in storage — the previous one is. Storage always holds a complete, valid snapshot (each write replaces the old object atomically); it may just be older than the last snapshot tick suggests. At most one upload runs at a time. A snapshot produced while an upload is in flight **replaces** the one waiting rather than queueing behind it, so a backlog can never grow and the shard cannot be pushed out of memory by a slow backend. The dropped snapshot is never written — but every snapshot that IS written is newer than the one before it, so storage never goes backwards. Setting `SnapshotRate` faster than the backend can absorb therefore does not make the stored snapshot more current. It just drops the snapshots in between. Shutdown takes a final snapshot, then waits for it and anything else outstanding to reach storage before tearing the shard down. If that wait fails or times out, it is logged at error: ``` snapshot writes did not finish before shutdown; the last snapshot of this run may be lost ``` ## Telling when storage cannot keep up A dropped snapshot is logged at **warn**, so it is visible at the default log level: ```json theme={null} { "level": "warn", "component": "cardinal.snapshot", "superseded_tick_height": 1200, "tick_height": 1500, "dropped_total": 1, "log_every": 100, "message": "superseded a pending snapshot: storage is slower than the snapshot rate" } ``` The line is rate limited — the first drop, then every 100th — because a backend that is behind drops one snapshot per snapshot tick. `dropped_total` is the running count, so gaps between lines are visible. At shutdown the run's total is logged once more: ``` snapshots were superseded before they could be written during this run ``` Seeing these means the shard is snapshotting faster than storage accepts. Either raise `SnapshotRate` (snapshot less often) or move to a faster backend. Individual write failures are reported separately, at warn, as `failed to store snapshot`. ## Versioning Snapshots carry a format version, and a shard reads only the version it writes. A snapshot from a newer build, or one with no version, is refused at boot rather than misread — the shard fails to start, and the stored snapshot is left untouched. The same applies when a restore fails for any other reason: the shard skips its final snapshot instead of overwriting a good snapshot with an empty world. # System Events Source: https://world.dev/cardinal/system-events System events are how systems communicate with each other inside a single shard. They are internal messages that one system emits and other systems receive during the same tick. Unlike [events](/cardinal/events), which are broadcast to game clients, system events never leave the server. They're useful for decoupling systems that need to react to each other's logic. System events are scoped to a single tick: they're produced and consumed within the tick, then cleared. ## Defining System Events System events are plain Go structs that implement the `SystemEvent` interface. This interface requires a single `Name()` method that returns a **unique** string identifier: ```go theme={null} type PlayerDeathSystemEvent struct { PlayerID string `json:"player_id"` Reason string `json:"reason"` } func (PlayerDeathSystemEvent) Name() string { return "player-death" } ``` ## Using System Events Just like with components and commands, you must declare the system events a system can access in its state struct. ### Emitting System Events Add a `cardinal.WithSystemEventEmitter[T]` field to your system state type, where `T` is your system event type: ```go theme={null} import "github.com/argus-labs/world-engine/pkg/cardinal" type CombatSystemState struct { cardinal.BaseSystemState PlayerDeathSystemEvents cardinal.WithSystemEventEmitter[PlayerDeathSystemEvent] } ``` Use `Emit` to send a system event: ```go theme={null} func CombatSystem(state *CombatSystemState) error { // Emit a player death event to be handled by other systems. state.PlayerDeathSystemEvents.Emit(PlayerDeathSystemEvent{ PlayerID: playerID, Reason: "health_zero", }) return nil } ``` A system cannot have multiple `WithSystemEventEmitter` fields with the same system event type. ### Receiving System Events Add a `cardinal.WithSystemEventReceiver[T]` field to your system state type: ```go theme={null} import "github.com/argus-labs/world-engine/pkg/cardinal" type GraveyardSystemState struct { cardinal.BaseSystemState PlayerDeathSystemEvents cardinal.WithSystemEventReceiver[PlayerDeathSystemEvent] } ``` Use `Iter` to loop through all system events of that type received this tick: ```go theme={null} func GraveyardSystem(state *GraveyardSystemState) error { for death := range state.PlayerDeathSystemEvents.Iter() { // Handle death.PlayerID, death.Reason, etc. } return nil } ``` A system cannot have multiple `WithSystemEventReceiver` fields with the same system event type. However, different systems can receive the same system event type. ## Scheduling and Ordering Systems run in the order they are registered. To ensure emitters run before receivers, register emitting systems first: ``` CombatSystem (emits) → GraveyardSystem (receives) → AnalyticsSystem (receives) ``` If a receiver runs before its emitter, it will not receive any system events for that tick, as they haven't been emitted yet. Multiple systems can receive the same system event type, and they will each get all events emitted that tick. # Deployment Source: https://world.dev/forge/deployment ## Prerequisites Before deploying your World Engine project, ensure you have: * [Created an organization](/forge/organization) * [Created a project](/forge/project) ## Deployment Overview World Engine provides a comprehensive deployment system that allows you to deploy your projects to different environments: * **Preview Environment**: For testing and development * **Live Environment**: For production deployments ## Deploy to Preview Environment Deploy your project to the preview environment for testing: ```bash theme={null} world deploy ``` This command will: 1. Build your Cardinal shards 2. Package your application 3. Deploy to the preview environment 4. Provide deployment status and URLs The preview environment is perfect for testing your game before promoting to production. ### Force Deploy If you need to force a deployment (e.g., after configuration changes): ```bash theme={null} world deploy --force ``` Force deployments will override any existing deployment and may cause downtime. ## Check Deployment Status Monitor the status of your deployed project: ```bash theme={null} world status ``` This command provides information about: * Deployment status (running, failed, building) * Environment URLs * Health checks * Resource usage ## Promote to Live Environment Once you're satisfied with your preview deployment, promote it to the live environment: ```bash theme={null} world promote ``` Promoting to live will make your game available to end users. Ensure thorough testing in preview first. ## View Application Logs Monitor your application in real-time by tailing logs: ```bash theme={null} # Default: us-west-2 region, preview environment world logs # Specify region and environment world logs us-east-1 preview world logs eu-central-1 live ``` ### Available Regions * `us-west-2` (default) * `us-east-1` * `eu-central-1` * `ap-southeast-1` ### Available Environments * `preview` (default) * `live` Logs are streamed in real-time. Use Ctrl+C to stop the log stream. ## Reset Deployment If you need to restart your deployment with a clean state: ```bash theme={null} world reset ``` This command will: 1. Stop the current deployment 2. Clear all data and state 3. Redeploy with a fresh environment Resetting will clear all game state and data. This action cannot be undone. ## Destroy Deployment Remove your project's infrastructure from the cloud: ```bash theme={null} world destroy ``` This command will: 1. Stop all running services 2. Remove all deployed resources 3. Clean up infrastructure Destroying your deployment will permanently remove all data and infrastructure. This action cannot be undone. # Organization Source: https://world.dev/forge/organization ## Organization Overview Organizations in World Engine allow you to group related projects and manage team access. Each organization can contain multiple projects and team members. ## Create a New Organization ```bash theme={null} world organization create ``` This interactive command will guide you through the organization creation process: 1. **Organization Name**: Enter a descriptive name for your organization 2. **Organization Slug**: Enter a unique identifier for your organization Organization slugs must be unique across all World Engine organizations. ## Invite Members to Your Organization Once you have created an organization, you can invite team members to collaborate on your projects. ### Invite a User ```bash theme={null} world user invite ``` This interactive command will guide you through the invitation process: 1. **User Email**: Enter the email address of the person you want to invite 2. **User Role**: Select the appropriate role for the user ### Available Roles * **Owner**: Full access to the organization, including deleting the organization * **Admin**: Can manage projects, invite users, and change user roles * **Member**: Can view and contribute to projects * **None**: No access (used for removing permissions) Only organization owners and admins can invite new members to the organization. ### Invite with Specific Role You can also invite users with a specific role using flags: ```bash theme={null} world user invite --email="user@example.com" --role="admin" ``` ### Change User Roles To change an existing user's role in your organization: ```bash theme={null} world user role ``` This command allows you to: * Update a user's role within the organization * Promote members to admin or owner * Demote users to member or none ### View Organization Members To see all members in your organization: ```bash theme={null} world organization members ``` This will display: * All current members * Their assigned roles * When they joined the organization ## Next Steps After creating your organization, you can: * [Create a project](/deployment/project) to start building your game * [Invite team members](/deployment/organization#invite-members-to-your-organization) to collaborate * [Manage user roles](/deployment/organization#change-user-roles) as your team grows Create your first World Engine project Deploy your project to the cloud # Project Source: https://world.dev/forge/project ## Project Overview Projects are where your World Engine games and applications live. Each project belongs to an organization and can contain multiple Cardinal shards. ## Create a New Project ```bash theme={null} world project create ``` The project creation process will guide you through several steps: ### 1. Project Details * **Project Name**: Enter a descriptive name for your project * **Project Slug**: Enter a unique identifier for your project Project slugs must be unique across all projects within an organization. ### 2. Repository Configuration * **Repository URL**: The Git repository URL where your project code is hosted * **Repository Path**: The path within the repository where your World Engine project is located Your project must be in a Git repository. If you haven't initialized a Git repository yet, do so before creating the project. ### 3. Region Selection Choose the regions where you want to deploy your project. Multiple regions can be selected for global distribution. ### 4. Notification Settings (Optional) Configure notifications for your project: * **Discord**: Set up Discord webhook for deployment notifications * **Slack**: Set up Slack webhook for deployment notifications ## Update Project Configuration To modify your project settings: ```bash theme={null} world project update ``` This allows you to update: * Project name and slug * Repository configuration * Region selection * Notification settings ## Download a Project For new team members, you can download a project from your organization: ```bash theme={null} world project download ``` This command will: 1. **Select Project**: Choose from available projects in your organization 2. **Choose Download Path**: Specify where to download the project locally 3. **Clone Repository**: Download the project code from the Git repository 4. **Set Up Environment**: Configure the project for local development You must have access permissions to the project's Git repository to download it successfully. If you don't have the necessary permissions to the repository, the download will fail. Contact your organization admin to grant you access. ### Prerequisites for Download Before downloading a project, ensure you have: * Access to the project's Git repository * [World CLI installed and authenticated](/quickstart) * Sufficient disk space for the project * Git installed on your local machine ## Delete a Project To remove a project from your organization: ```bash theme={null} world project delete ``` Deleting a project will permanently remove all associated data and deployments. This action cannot be undone. # Quickstart Source: https://world.dev/forge/quickstart **Forge** is World Engine's publishing platform that enables you to deploy your World Engine projects to the cloud with ease. It provides a complete workflow from authentication to production deployment, making it simple to get your games and applications live. ## What is Forge? Forge is the deployment and publishing arm of World Engine that handles: * **Cloud Infrastructure** — Managed cloud resources for your World Engine projects * **Deployment Pipeline** — Automated build and deployment processes * **Environment Management** — Preview and production environments * **Team Collaboration** — Organization and project management * **Monitoring & Logs** — Real-time application monitoring and debugging ## Getting Started with Forge The Forge workflow consists of five main steps: ### 1. Authentication Start by authenticating with World Engine using your Argus ID: ```bash theme={null} world login ``` This will open your browser to complete the authentication process and create or link to your Argus ID account. ### 2. Create an Organization Organizations group related projects and manage team access: ```bash theme={null} world organization create ``` This interactive process will guide you through creating your organization. ### 3. Set Up Your Project Repository Your project needs to be stored in a Git repository for deployment: ```bash theme={null} git init git branch -M main git add . git commit -m "Initial commit" git remote add origin git push -u origin main ``` ### 4. Create and Deploy Your Project Create a project in Forge and deploy it: ```bash theme={null} world project create world deploy ``` This will build your Cardinal shards and deploy to the preview environment. ### 5. Monitor Your Deployment Check the status and view logs of your deployed project: ```bash theme={null} world status world logs ``` These commands provide real-time monitoring and debugging capabilities for your deployed application. ## Development Workflow 1. **Local Development** → Use `world start` for local development 2. **Version Control** → Commit and push changes to your repository 3. **Preview Deployment** → `world deploy` for testing in preview environment 4. **Production Promotion** → `world promote` when ready for live users 5. **Monitoring** → Use `world status` and `world logs` for ongoing monitoring ## Next Steps Ready to get started with Forge? Follow these guides: Set up your organization for team collaboration Create your first World Engine project Deploy your project to the cloud Get started with World Engine # Installation Source: https://world.dev/installation ## Prerequisites Before you begin, make sure your local machine has the following installed: * **Go**: World Engine games are written in [Go](https://go.dev). This guide requires a minimum Go version of **`v1.25.4`**. To check that Go is installed on your computer, run `go version`. * **Docker**: The World Engine development server runs several local services inside containers. Follow the [official instructions](https://docs.docker.com/get-started/get-docker/) to install Docker. Alternatively, you can also use other Docker-compatible container runtimes. We recommend [OrbStack](https://orbstack.dev/) for macOS users and [Podman](https://podman.io/) for Linux users. ## Installing World CLI The World CLI is the command-line tool for creating, managing, and deploying World Engine projects. To install the latest version: ```bash theme={null} curl install.world.dev/install.sh | sh ``` ```powershell theme={null} iwr install.world.dev/install.ps1 -useb | iex ``` ## Creating a Project Now you are ready to create a new World Engine project. The `setup` command will launch an interactive tool to help you configure your project: ```bash theme={null} world setup my-game ``` When prompted, select the **Basic** template. TODO: Add an updated image of the selection UI Once the project has been created, you can start the local development server: ```bash theme={null} cd my-game world start ``` This command builds the game server and runs the World Engine stack. It will take some time during your first run to download the required container images. ## Stopping the Server Press Ctrl+C to stop the server, or run `world stop` from another terminal. To reset the world state, run `world purge`. This removes all game data and restores the world to a clean state, which is useful when you want to start fresh during development. # Introduction Source: https://world.dev/introduction Hero World Engine is a high-performance framework designed specifically for onchain, real-time games. It allows you to build games that run on their own dedicated "shards", giving you the speed and complexity of a traditional game engine, while keeping your game fully secure and verifiable on the blockchain. This architecture unlocks a new generation of onchain games. From real-time strategy to physics-heavy simulations, World Engine empowers you to build complex, persistent worlds that were previously impossible to run on general-purpose blockchains. ## Why World Engine? Traditional blockchains are built around event-driven execution: a transaction arrives, the chain processes it, and only then does the state change. This model is too slow and too unpredictable for real-time games, where the world must update continuously and on a fixed schedule. World Engine takes a different approach. It introduces a tick-driven rollup protocol where the world updates on a fixed schedule, just like a real game engine. The world advances every tick, deterministically, regardless of how or when inputs arrive. This gives developers real-time performance without giving up security or verifiability. Games stay responsive because the protocol follows the same timing model as modern game engines. ## Key Features **Built for Games** World Engine is purpose-built for real-time games. It supports execution sharding and horizontal scalability, so your game can scale across multiple shards as needed. **Built for Developers** We provide end-to-end tooling designed for a smooth developer experience across the full cycle: building, testing, debugging, and deploying. With client SDKs, built-in auth, and managed deployment, everything is set up to help you iterate quickly. **Built for Collaboration** The ecosystem supports community plugins and preset shards for common game types. You can share components, extend existing shards, or build on top of what others have created. ## The Cardinal Framework Cardinal is our first implementation of the World Engine protocol, written in Go. It's an ECS-based, tick-driven game server framework designed for writing high-performance, real-time games. If you've worked with engines like Unity, Unreal, or Bevy, Cardinal will feel familiar. It uses the same ECS architecture and tick-based execution model, so you can focus on building your game rather than wrestling with blockchain mechanics. The rest of this documentation will guide you through Cardinal's core concepts and patterns. ## Next Steps Head over the the next page to learn how to install the World Engine and create your first game! # Project Structure Source: https://world.dev/project-structure Cardinal projects are designed to support multiple shards within a single project. While the framework is unopinionated about folder structure, there are some defaults and common patterns. ## Minimal Structure Here is the minimal structure for a Cardinal project: ```text theme={null} . ├── shards/ │ └── / │ └── main.go ├── go.mod └── world.toml ``` | File / Directory | Description | | --------------------------- | --------------------------------- | | `shards/` | Source code for your game shards. | | `shards//main.go` | The entry point for the shard. | | `world.toml` | Project configuration. | | `go.mod` | Go dependency definition. | ## Registering Shards To include a shard in your project, you must define it in the `world.toml` file. The World CLI only builds and runs shards that are explicitly listed in this configuration. By default, the framework looks for a shard's source code in `shards/`. You can override this location by specifying the `path` key (relative to the project root). ```toml world.toml theme={null} [[shards]] id = "gameplay" # Defaults to: shards/gameplay/ [[shards]] id = "matchmaking" path = "./custom-path/to-your-shard" # Override default shards/matchmaking/ location, instead looks in: shards/custom-path/to-your-shard/ ``` TODO: Fix the link below. For a complete reference of available configuration options, see the [Configuration Reference](#). ## Examples ### Organization by Concept In this structure, code within a shard is organized by Cardinal concepts: components, systems, events, etc. ```text theme={null} . ├── pkg/ # Shared libraries ├── shards/ │ ├── gameplay/ # Gameplay shard │ │ ├── component/ # - Component definitions │ │ ├── event/ # - Event definitions │ │ ├── system/ # - Systems logic │ │ └── main.go │ └── matchmaking/ # Matchmaking shard ├── go.mod └── world.toml ``` This approach groups code by its architectural role (e.g., all components in one place). It is the recommended starting point for most projects as it provides a clear separation between data and logic. ### Organization by Domain For larger, more complex projects, you may prefer a domain-driven approach where code is grouped by feature (e.g., combat, movement). ```text theme={null} . ├── pkg/ # Shared libraries ├── shards/ │ ├── gameplay/ # Gameplay shard │ │ ├── types/ # - Public type aliases │ │ ├── internal/ # - Private implementation logic │ │ │ ├── combat/ │ │ │ └── movement/ │ │ └── main.go │ └── matchmaking/ # Matchmaking shard ├── go.mod └── world.toml ``` In this layout: * **`internal/`**: Contains the domain logic and definitions. The special `internal` directory name prevents these packages from being imported by other shards, as enforced by [Go's internal packages](https://pkg.go.dev/cmd/go#hdr-Internal_Directories) rule. * **`types/`**: Contains type aliases to the definitions in the `internal` domain packages. This acts as the public API for the shard. By exporting only specific types via aliases in `types/`, you allow other shards to interact with your data without exposing your internal logic or creating direct dependencies on your implementation packages. # Installation Source: https://world.dev/sdk/csharp/installation # Introduction Source: https://world.dev/sdk/csharp/introduction `ArgusLabs.WorldEngine.SDK` is a C# SDK for connecting to your World Engine Cardinal, providing powerful tools to interact with your shard. * Send **commands** to your shard * **Query** your shard * **Subscribe** to **events** from your shard * **Region selector** for connecting to shards in multiple regions * Full **Unity** support As well as out-of-the-box support for authentication with [Argus ID](https://auth.argus.gg/). ## Getting Started Install World Engine SDK and connect to your World Engine shard TODO: update # Quickstart Source: https://world.dev/sdk/javascript/installation # Getting Started This guide will help you set up and start using the **Argus Labs SDK** in your project. The SDK provides a powerful interface for building multiplayer games and real-time applications. ## Prerequisites Before you begin, make sure you have: * Scaffolded a boilerplate project ([World Engine quickstart](/quickstart)) * [Node.js](https://nodejs.org) version 22 or higher installed * A package manager (npm, yarn, pnpm, or bun) ### Project Setup If you're looking for a fully functioning example, you can [scaffold a full-fledged demo game](./examples/demo-game). If you don't have an existing project, you can create one using either [Phaser](https://docs.phaser.io/phaser/getting-started/installation) or an empty [Vite](https://vite.dev/guide/) project. The SDK is flexible and works seamlessly across the spectrum, from simple vanilla JavaScript projects to complex Phaser + React + TypeScript games. ## Installing the SDK Install the World SDK using your preferred package manager: ```bash npm theme={null} npm install @argus-labs/sdk ``` ```bash yarn theme={null} yarn add @argus-labs/sdk ``` ```bash pnpm theme={null} pnpm add @argus-labs/sdk ``` ```bash bun theme={null} bun add @argus-labs/sdk ``` ## Basic Setup First, set up your SDK instance. Create a new file `src/lib/sdk.ts` (or `src/lib/sdk.js` for JavaScript projects): ```typescript sdk.ts theme={null} import { createConfig, createSDK } from '@argus-labs/sdk' // Basic configuration export const config = createConfig() // Create the SDK instance export const world = createSDK(config) ``` TypeScript is optional, but highly recommended. ## Registering Shard Client A shard client connects to a specific Cardinal shard. It lets you send commands, query game state, and listen for events from that shard. ```typescript shard.ts theme={null} import { world } from './sdk' export const shard = world.shard.register({ organization: 'argus', project: 'demo', shardId: 'cardinal-demo-game-1', }) ``` ## Sending your first command With the `shard` registered, you can now `sendCommand` to the Cardinal shard. ```typescript spawn-player.ts theme={null} import { shard } from './shard' interface SpawnPlayerCommand { name: 'player-spawn' payload: { argus_auth_id: string argus_auth_name: string x: number y: number } } // Call the following function from an event handler, e.g. a button export async function spawnPlayer() { const { error } = await shard.sendCommand({ name: 'player-spawn', payload: { argus_auth_id: 'abc123', argus_auth_name: 'John Doe', x: 100, y: 200, }, } satisfies SpawnPlayerCommand) if (error) console.error(error) } ``` The example assumes you scaffolded the project using the World CLI, and have the following command and system on your Cardinal shard: ```go system/player_spawn.go theme={null} type PlayerSpawnCommand struct { cardinal.BaseCommand ArgusAuthID string `json:"argus_auth_id"` ArgusAuthName string `json:"argus_auth_name"` X uint32 `json:"x"` Y uint32 `json:"y"` } func (a PlayerSpawnCommand) Name() string { return "player-spawn" } func PlayerSpawnSystem(state *SpawnPlayerSystemState) error { // ... code omitted for brevity } ``` If you need help, please drop by our [Telegram channel](https://t.me/worldengine_dev). ## Making your first query You've now spawned your first player. With that, we can `query` the world state and see that the player exists: ```typescript player-query.ts theme={null} import { Match } from '@argus-labs/sdk' import { shard } from './shard' export async function playerQuery() { const { error, data } = await shard.query({ find: ['playertag', 'position'], match: Match.CONTAINS, }) if (error) { console.error(error) return null } return data } ``` The example assumes you scaffolded the project using the World CLI, and have the following components in your cardinal: ```go component/playertag.go theme={null} package component type PlayerTag struct { ArgusAuthID string `json:"argus_auth_id"` ArgusAuthName string `json:"argus_auth_name"` } func (PlayerTag) Name() string { return "playertag" } ``` ```go component/position.go theme={null} package component type Position struct { X int `json:"x"` Y int `json:"y"` } func (Position) Name() string { return "position" } ``` If you need help, please drop by our [Telegram channel](https://t.me/worldengine_dev). Congratulations! You've now set up your first World Engine game client using the Argus Labs SDK and learned the basics of **Shard Client**, **Command**, and **Query**. ## Next Step To build a complete multiplayer experience, you'll want to set up **authentication** using **Argus Auth**. World Engine integrates seamlessly with Argus Auth to handle user authentication and identity management, making it easy to create personalized gaming experiences for your players. Learn how to integrate Argus Auth into your game # Introduction Source: https://world.dev/sdk/javascript/introduction `@argus-labs/sdk` is a JavaScript SDK for connecting to your World Engine Cardinal, providing powerful tools to interact with your shard. * Send **commands** to your shard * **Query** your shard * **Subscribe** to **events** from your shard * **Region selector** for connecting to shards in multiple regions * Full **TypeScript** support with schema validation * **Devtool** for debugging and monitoring As well as out-of-the-box support for authentication with [Argus ID](https://auth.argus.gg/). ## Getting Started Install World Engine SDK and connect to your World Engine shard TODO: update when ts sdk is updated