Documentation
¶
Overview ¶
DST (Deterministic Simulation Testing) provides a game-logic-agnostic fuzzer and structural state checker for Cardinal. It generates random commands by introspecting registered command types (via reflection), injects engine operations (tick, restart, snapshot/restore) with randomized weights, and validates structural ECS invariants after every tick. Game logic correctness is irrelevant — only engine correctness matters.
Usage from a game shard's test directory:
func TestDST(t *testing.T) {
cardinal.RunDST(t, func(w *cardinal.World) {
w.RegisterSystem(system.MySystem)
// ... register all systems
}, []cardinal.Command{system.BootstrapCommand{Seed: 42}})
}
E2E test helper with real (in-memory) NATS. Similar to the DST harness in dst.go, but runs the full World.run loop (ticking, snapshotting, restoring) while a separate goroutine sends randomized commands through the real ConnectRPC service.
Index ¶
- Constants
- Variables
- func RunDST(t *testing.T, setup DSTSetupFunc, preTestCommands []Command)
- func RunE2E(t *testing.T, setup E2ESetupFunc)
- type AuthMode
- type BaseSystemState
- func (b *BaseSystemState) Create[T any]() Entity
- func (b *BaseSystemState) Entity(id EntityID) Entity
- func (b *BaseSystemState) Logger() *zerolog.Logger
- func (b *BaseSystemState) SendToShard(to OtherWorld, cmd command.Payload)
- func (b *BaseSystemState) Tick() uint64
- func (b *BaseSystemState) Timestamp() time.Time
- type Command
- type CommandContext
- type Contains
- type DSTSetupFunc
- type E2ESetupFunc
- type Entity
- type EntityID
- type Event
- type Exact
- type OtherWorld
- type Plugin
- type SearchResult
- type Slice
- type System
- type SystemHook
- type SystemOption
- type Tick
- type User
- type WithCommand
- type WithComponent
- type WithEvent
- type WithSystemEventEmitter
- type WithSystemEventReceiver
- type World
- func (w *World) RegisterArchetype[T any]()
- func (w *World) RegisterPlugin(plugin Plugin)
- func (w *World) RegisterSystem[T any](system func(*T), opts ...SystemOption)
- func (w *World) RegisterSystemV2[S System](s S, opts ...SystemOption)
- func (w *World) StartGame()
- func (w *World) Tick(timestamp time.Time)
- type WorldOptions
Constants ¶
const ( // PreUpdate runs before the main update. PreUpdate = ecs.PreUpdate // Update runs during the main update phase. Update = ecs.Update // PostUpdate runs after the main update. PostUpdate = ecs.PostUpdate // Init runs once during world initialization. Init = ecs.Init )
const MinEpochFrequency = 10
Variables ¶
var ( ErrSingleNoResult = eris.New("expected exactly 1 result, got 0") ErrSingleMultipleResult = eris.New("expected exactly 1 result, got more than 1") )
Functions ¶
func RunDST ¶ added in v0.10.2
func RunDST(t *testing.T, setup DSTSetupFunc, preTestCommands []Command)
RunDST executes a deterministic simulation test. The setup function registers game-specific systems; the harness handles everything else: randomized engine config, command generation, ticking, restart/restore operations, and structural invariant checking.
preTestCommands are enqueued before randomized fuzz operations begin. This supports worlds that require deterministic bootstrap commands before entering an active state.
The number of ticks defaults to 1000 and can be overridden with -dst.ticks:
go test ./pkg/cardinal/... -dst.ticks=5000
func RunE2E ¶ added in v0.10.2
func RunE2E(t *testing.T, setup E2ESetupFunc)
RunE2E executes an end-to-end test. The setup function creates the world the same way production does; the harness transparently injects the in-memory test NATS URL. The harness starts the world's run loop in a background goroutine and sends randomized commands over ConnectRPC from the test goroutine.
E2E tests are skipped by default. Use the -e2e.run flag to enable them:
go test ./pkg/cardinal/... -e2e.run
The run duration defaults to 30s and can be overridden with -e2e.duration:
go test ./pkg/cardinal/... -e2e.run -e2e.duration=2m
Types ¶
type AuthMode ¶ added in v0.12.0
type AuthMode uint8
AuthMode selects the authentication mode for the client-facing ConnectRPC service.
func ParseAuthMode ¶ added in v0.12.0
type BaseSystemState ¶
type BaseSystemState struct {
// contains filtered or unexported fields
}
func (*BaseSystemState) Create ¶ added in v0.16.9
func (b *BaseSystemState) Create[T any]() Entity
Create creates an entity with the zero-valued components declared by T. Register T with World.RegisterArchetype or a Contains[T]/Exact[T] system field before starting the world, so snapshots can restore its component types.
func (*BaseSystemState) Entity ¶ added in v0.16.9
func (b *BaseSystemState) Entity(id EntityID) Entity
Entity binds a numeric ID to this system's world. It does not require the entity to exist. Use Alive or Has to check before accessing an optional entity.
func (*BaseSystemState) Logger ¶
func (b *BaseSystemState) Logger() *zerolog.Logger
TODO: pass init args (similar to boot info) to get system name in logger. Logger returns the logger for the world.
func (*BaseSystemState) SendToShard ¶ added in v0.15.0
func (b *BaseSystemState) SendToShard(to OtherWorld, cmd command.Payload)
SendToShard sends cmd to another shard, addressed by to. This is the first-class shard-to-shard send: the world performs the send (it owns the event queue) and the address `to` is plain data with no behavior of its own. It mirrors the client-facing SendCommand RPC — a shard sending to a shard is the same operation, initiated in-engine.
Fire-and-forget: the actual network send happens when events flush at end-of-tick, so cmd must not be mutated after this call — a *Command whose fields change before the flush would send the mutated value. A send that fails is not returned (it must not block the tick) but is logged at error level, because a dropped shard-to-shard command is serious.
func (*BaseSystemState) Tick ¶
func (b *BaseSystemState) Tick() uint64
Tick returns the current tick of the world.
func (*BaseSystemState) Timestamp ¶
func (b *BaseSystemState) Timestamp() time.Time
Timestamp returns the current timestamp of the world.
type CommandContext ¶ added in v0.10.0
type Contains ¶
type Contains[T any] struct { // contains filtered or unexported fields }
Contains matches entities with all components declared in T, allowing extras. T is a struct of WithComponent[C] fields, registered before the world starts.
func (*Contains) Create ¶ added in v0.11.0
func (s *Contains) Create() Entity
Create returns a new entity with the components declared in T, initialized to zero.
func (*Contains[T]) GetByID ¶ added in v0.11.0
GetByID returns a handle if the entity contains every declared component.
func (*Contains[T]) Iter ¶ added in v0.11.0
func (c *Contains[T]) Iter() SearchResult
Iter yields each matching entity as a world-bound handle.
type DSTSetupFunc ¶ added in v0.10.2
type DSTSetupFunc func(w *World)
DSTSetupFunc registers systems, components, and commands on a World. It is called once during fixture creation, before the first tick.
type E2ESetupFunc ¶ added in v0.10.2
type E2ESetupFunc func() *World
E2ESetupFunc builds and returns the world used by the E2E harness. The harness injects a test NATS URL via environment variables before invoking setup.
type Entity ¶ added in v0.16.9
type Entity struct {
// contains filtered or unexported fields
}
Entity is a runtime handle to an entity in one world. Store ID() in components, commands, and snapshots, then bind that ID with BaseSystemState.Entity when needed. Like EntityID, a handle must not be retained after destruction or a world reset: numeric IDs can be reused. Entity methods must run on the world's system goroutine.
func (Entity) Alive ¶ added in v0.16.9
Alive reports whether the entity exists in its world. A zero handle is not alive.
func (Entity) Destroy ¶ added in v0.16.9
Destroy removes the entity and all its components. It returns false if absent.
func (Entity) Get ¶ added in v0.16.9
Get returns a copy of a component. It panics if the entity or component is absent. Use Has when the component is optional, and Set to write a modified copy back.
func (Entity) ID ¶ added in v0.16.9
ID returns the numeric identifier used by storage, commands, and physics.
type Exact ¶
type Exact[T any] struct { // contains filtered or unexported fields }
Exact matches entities with exactly the components declared in T. T is a struct of WithComponent[C] fields, registered before the world starts.
func (*Exact) Create ¶ added in v0.11.0
func (s *Exact) Create() Entity
Create returns a new entity with the components declared in T, initialized to zero.
func (*Exact[T]) GetByID ¶ added in v0.11.0
GetByID returns a handle if the entity has exactly the declared components.
func (*Exact[T]) Iter ¶ added in v0.11.0
func (c *Exact[T]) Iter() SearchResult
Iter yields each matching entity as a world-bound handle.
type OtherWorld ¶
OtherWorld is a type that represents the address of an external service.
type Plugin ¶ added in v0.11.0
type Plugin interface {
Register(w *World)
}
Plugin defines a self-contained extension that can register systems, components, commands, and events with a World. Plugins allow reusable game logic to be packaged and shared across projects.
Components, commands, and events are automatically registered when referenced by system state fields (via WithComponent[T], WithCommand[T], WithEvent[T]), so a plugin's Register method typically only needs to call RegisterSystem.
Example:
type MyPlugin struct{ config MyConfig }
func (p *MyPlugin) Register(w *cardinal.World) {
w.RegisterSystem(MyInitSystem, cardinal.WithHook(cardinal.Init))
w.RegisterSystem(MyTickSystem)
}
type SearchResult ¶ added in v0.11.0
SearchResult is a chainable iterator over entities.
func (SearchResult) Filter ¶ added in v0.11.0
func (s SearchResult) Filter(predicate func(Entity) bool) SearchResult
Filter returns a new iterator that only yields values that satisfy predicate. A nil predicate returns the original iterator unchanged.
func (SearchResult) Limit ¶ added in v0.11.0
func (s SearchResult) Limit(limit uint32) SearchResult
Limit returns a new iterator that yields at most limit values. A limit <= 0 yields no values.
func (SearchResult) Single ¶ added in v0.11.0
func (s SearchResult) Single() (Entity, error)
Single returns the single value in the iterator. It returns an error if the iterator yields zero or more than one result.
type Slice ¶ added in v0.16.7
Slice is world-engine's sequence with a private backing array, re-exported so a component can declare one without a second import. It is an alias, so cardinal.Slice[T] and immutable.Slice[T] are the same type and either spelling works.
Its derivations edit in place; see immutable.Slice for what that means at a call site.
type System ¶ added in v0.16.5
type System interface {
Run()
}
System is a stateful Cardinal system. Implement it with a Run method on a pointer to a struct that embeds BaseSystemState.
type SystemHook ¶ added in v0.10.0
type SystemHook = ecs.SystemHook
SystemHook defines when a system should be executed in the update cycle.
type SystemOption ¶ added in v0.10.0
type SystemOption func(*systemConfig)
SystemOption is a function that configures a SystemConfig.
func WithHook ¶
func WithHook(hook SystemHook) SystemOption
WithHook returns an option to set the system hook.
type User ¶ added in v0.12.0
type User struct {
jwt.RegisteredClaims
ID string `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
}
func UserFromContext ¶ added in v0.12.0
type WithCommand ¶
type WithCommand[T Command] struct { // contains filtered or unexported fields }
func (*WithCommand[T]) Iter ¶ added in v0.10.0
func (c *WithCommand[T]) Iter() iter.Seq[CommandContext[T]]
type WithComponent ¶ added in v0.16.9
WithComponent declares a component dependency. Use it as a system field to register an optional component, or as a field of a Contains/Exact archetype. Values are accessed through Entity, not through this declaration.
type WithEvent ¶
type WithEvent[T Event] struct { // contains filtered or unexported fields }
func (*WithEvent[T]) SendTo ¶ added in v0.16.0
SendTo enqueues a targeted event that is delivered only to the named recipient (a user ID), provided they have an open event stream subscribed to this event. If the recipient has no open stream, the event is silently dropped.
Example:
state.Results.SendTo(cmdCtx.Persona, Result{OK: true})
type WithSystemEventEmitter ¶
type WithSystemEventEmitter[T ecs.SystemEvent] struct { // contains filtered or unexported fields }
WithSystemEventEmitter is a generic system state field that allows systems to emit system events of type T. System events are automatically registered when the system is registered.
Example:
// Define a system event for player deaths.
type PlayerDeath struct{ Nickname string }
func (PlayerDeath) Name() string { return "player-death" }
type CombatSystemState struct {
PlayerDeathSystemEvents ecs.WithSystemEventEmitter[PlayerDeath]
// Other fields...
}
// Your system function receives a pointer to your system state.
func CombatSystem(state *CombatSystemState) error {
// Emit a player death event to be handled in another system.
state.PlayerDeathEvents.Emit(PlayerDeath{Nickname: "Player1"})
return nil
}
func (*WithSystemEventEmitter[T]) Emit ¶ added in v0.11.0
func (s *WithSystemEventEmitter[T]) Emit(systemEvent T)
Emit emits a system event of type T.
Example:
state.PlayerDeathEvents.Emit(PlayerDeath{Nickname: "Player1"})
type WithSystemEventReceiver ¶
type WithSystemEventReceiver[T ecs.SystemEvent] struct { // contains filtered or unexported fields }
WithSystemEventReceiver is a generic system state field that allows systems to receive system events of type T. System events are automatically registered when the system is registered.
Example:
// Define a system event for player deaths.
type PlayerDeath struct{ Nickname string }
func (PlayerDeath) Name() string { return "player-death" }
type GraveyardSystemState struct {
PlayerDeathSystemEvents ecs.WithSystemEventReceiver[PlayerDeath]
// Other fields...
}
// Your system function receives a pointer to your system state.
func GraveyardSystem(state *GraveyardSystemState) error {
// Receive system events emitted from another system.
for systemEvent := range state.PlayerDeathSystemEvents.Iter() {
// Process the system event.
}
return nil
}
func (*WithSystemEventReceiver[T]) Iter ¶ added in v0.11.0
func (s *WithSystemEventReceiver[T]) Iter() iter.Seq[T]
Iter returns an iterator over all system events of type T.
Example usage:
for systemEvent := range state.PlayerDeathEvents.Iter() {
// Process each system event
}
type World ¶
type World struct {
// contains filtered or unexported fields
}
World contains the game state and is the main Cardinal API.
func NewWorld ¶
func NewWorld(opts WorldOptions) (*World, error)
NewWorld creates a game world with the specified options.
func (*World) RegisterArchetype ¶ added in v0.16.9
RegisterArchetype registers the component declarations in T before world startup. Contains[T] and Exact[T] system fields also register their archetype automatically.
func (*World) RegisterPlugin ¶ added in v0.16.8
RegisterPlugin registers a plugin with the world. Must be called before StartGame(). Panics if the plugin fails to register, consistent with other registration functions.
func (*World) RegisterSystem ¶ added in v0.16.8
func (w *World) RegisterSystem[T any](system func(*T), opts ...SystemOption)
func (*World) RegisterSystemV2 ¶ added in v0.16.8
func (w *World) RegisterSystemV2[S System](s S, opts ...SystemOption)
RegisterSystemV2 registers a caller-owned system instance. The instance must be a non-nil pointer to a struct that embeds BaseSystemState.
type WorldOptions ¶
type WorldOptions struct {
Region string // Region that contains the shard
Organization string // Organization that owns the world
Project string // Project name within the organization
ShardID string // Unique ID of the world instance
TickRate float64 // Number of ticks each second
SnapshotStorageType snapshot.StorageType // Snapshot storage type
SnapshotRate uint32 // Number of ticks between snapshots
Debug *bool // Enables the debug server
Pprof *bool // Enables the pprof server
NATSConfig *micro.NATSConfig // Optional NATS configuration; nil uses environment values or defaults
AuthMode AuthMode // Authentication mode for the client ConnectRPC service
ArgusAuthURL string // Argus Auth service URL; required when AuthMode is ARGUS
}