Core

system

System declarations, typed requirements, and execution context.

Systems are the main unit of gameplay behavior in the library. A system declares everything it is allowed to touch up front, and the runtime derives a context that exposes exactly that surface and nothing more.

This module is where ECS logic becomes explicit and reviewable:

  • queries describe which entities may be visited
  • resources, events, services, and machines are requested by name
  • lifecycle and transition reads stay gated by schedule boundaries
  • hidden ambient world access is impossible through the public API

Reach for this module whenever you are writing gameplay, simulation, reset, input, host sync, or transition logic that should run inside a schedule.

Examples

// Define one simulation step with explicit world access.
const Move = Game.System("Move", {
  queries: {
    moving: Game.Query({
      selection: {
        position: Game.Query.write(Position),
        velocity: Game.Query.read(Velocity)
      }
    })
  },
  resources: {
    dt: Game.System.readResource(DeltaTime)
  }
}, ({ queries, resources }) => Fx.sync(() => {
  const dt = resources.dt.get()

  for (const { data } of queries.moving.each()) {
    const velocity = data.velocity.get()
    data.position.update((position) => ({
      x: position.x + velocity.x * dt,
      y: position.y + velocity.y * dt
    }))
  }
}))

Functions

Public system authoring helpers for resources, events, lifecycle reads, and typed system definitions.

service

Source

Declares that a system needs a service from the external runtime environment.

Use this for host capabilities that should not live in ECS world storage: clocks, random generators, render bridges, audio sinks, persistence APIs, or network clients. The matching implementation must be supplied when the runtime is created with Game.Runtime.services(...).

readResource

Source

Creates a resource-read declaration for a system spec.

Use this for world-level singleton data the system needs to observe but must not mutate, such as delta time, score snapshots, configuration, or aggregated frame input. The resulting context slot is a read-only ReadCell.

writeResource

Source

Creates a resource-write declaration for a system spec.

Use this when the system owns mutation of one world-level singleton, such as score, UI summaries, accumulated damage, or frame-local caches. Declaring it here makes that authority visible in the system contract before the body is read.

const CountUp = Game.System("CountUp", {
  resources: {
    score: Game.System.writeResource(Score)
  }
}, ({ resources }) => Fx.sync(() => {
  // Mutate the singleton through the explicit write cell.
  resources.score.update((score) => score + 1)
}))

readEvent

Source

Creates an event-read declaration for a system spec.

Event reads observe the committed readable event buffer. New writes become visible only after an explicit Game.Schedule.updateEvents() boundary.

This is the usual second half of a deferred cross-system flow: one earlier system emits an event, updateEvents() commits the buffer, and a later system reads those events and re-validates any handles or lookups it needs.

writeEvent

Source

Creates an event-write declaration for a system spec.

Event writes append to the pending event buffer. They are not visible to readers in the same schedule phase until Game.Schedule.updateEvents().

If the payload needs to name an entity for later work, emit a durable Game.Entity.handle(...) or Game.Entity.handleAs(...) and let the later reader re-resolve it through lookup.getHandle(...) after the event buffer is committed.

const EmitHit = Game.System("EmitHit", {
  events: {
    hit: Game.System.writeEvent(Hit)
  }
}, ({ events }) => Fx.sync(() => {
  events.hit.emit({ amount: 1 })
}))

readState

Source

Creates a state-read declaration for a system spec.

Plain states are just singleton schema values. They do not have queued transition semantics, transition events, or enter/exit boundaries.

If the behavior depends on when a mode change commits, prefer machine(...) / nextState(...) on a Game.StateMachine(...) machine instead.

writeState

Source

Creates a state-write declaration for a system spec.

This updates a singleton schema value immediately in the current world state. It does not queue a transition. Use nextState(...) when the boundary of changing mode is part of the gameplay model.

machine

Source

Declares read access to the current committed value of a finite-state machine.

Use this when gameplay logic needs to branch on the current committed phase, but should not see queued next-state writes early. Machines are the intended default for menus, rounds, encounters, pause flows, and other discrete modes whose transition boundary matters.

nextState

Source

Declares queued write access to the next value of a finite-state machine.

This is the system-side request channel for a future phase change. It does not immediately switch the committed state; the queued value is applied only at an explicit Game.Schedule.applyStateTransitions(...) boundary.

Use this instead of writeState(...) when the transition timing itself is part of the gameplay model, such as restarting a round, leaving a menu, or entering a results screen after reset/setup schedules run.

transition

Source

Declares read access to the last applied transition payload of a machine.

readTransitionEvent

Source

Declares read access to committed transition events for one machine.

Transition events are committed together with normal events and become readable only after Game.Schedule.updateEvents().

This is one of the clearest signs that the modeled value should be a machine rather than a plain state descriptor.

readRemoved

Source

Declares read access to removed-component lifecycle records.

This reads the committed lifecycle buffer, not immediate removals. Systems usually pair this with Game.Schedule.updateLifecycle() and host cleanup logic such as removing renderer-owned nodes. readDespawned complements this for whole-entity teardown.

const DestroyRenderNodesSystem = Game.System("DestroyRenderNodes", {
  removed: {
    renderables: Game.System.readRemoved(Renderable)
  }
}, ({ removed }) => Fx.sync(() => {
  for (const entityId of removed.renderables.all()) {
    // destroy host-owned node here
  }
}))

readRelationFailures

Source

Declares read access to relation-mutation failure records.

readDespawned

Source

Declares read access to despawned-entity lifecycle records.

This reads the committed despawn buffer after Game.Schedule.updateLifecycle(). Use it when host-owned state must be destroyed even if no single removed component is the canonical trigger. readRemoved is often used alongside this in authoritative host mirrors.

const DestroyNodesSystem = Game.System("DestroyNodes", {
  despawned: {
    entities: Game.System.readDespawned()
  }
}, ({ despawned }) => Fx.sync(() => {
  for (const entityId of despawned.entities.all()) {
    // destroy host-owned node here
  }
}))

System

Source

Defines a system from an explicit spec and a typed implementation.

This is the public entrypoint for authoring systems. The implementation only receives the capabilities declared in the spec, and the returned effect keeps service dependencies tracked in the type system.

Use the string-name overload in normal code. The name is turned into an internal ordering token automatically, so the system can participate in schedule validation without extra user-authored identity plumbing.

const CountEnemies = Game.System("CountEnemies", {
  queries: {
    enemies: Game.Query({
      selection: {
        enemy: Game.Query.read(Enemy)
      }
    })
  },
  resources: {
    total: Game.System.writeResource(EnemyCount)
  }
}, ({ queries, resources }) => Fx.sync(() => {
  resources.total.set(queries.enemies.each().length)
}))