Core

query

Query declarations, matching semantics, and typed cell access surfaces.

Queries are the read-side center of ECS gameplay code. They describe which entities a system is allowed to see and exactly which component cells that system may read or write once a match is found.

In practice this module answers three different design questions at once:

  • which entities should this system iterate over
  • which components prove an entity belongs in that iteration
  • which slots are readable, writable, or only optionally available

Reach for this module whenever game logic needs to iterate entity state, perform host sync from ECS data, or resolve durable handles back into current-world entity proofs.

Examples

// Describe exactly which entities the movement system may see.
const MovingActors = Game.Query({
  selection: {
    // Writable slots declare mutation capability in the query result.
    position: Game.Query.write(Position),
    // Read-only slots prove presence without granting mutation.
    velocity: Game.Query.read(Velocity),
    // Optional slots do not affect matching and must be narrowed explicitly.
    sprite: Game.Query.optional(Sprite)
  },
  // Structural filters describe who participates in the system at all.
  with: [Movable],
  without: [Frozen]
})

const MoveActors = Game.System("MoveActors", {
  queries: { moving: MovingActors },
  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
    }))

    // Optional reads stay explicit at the use site.
    if (data.sprite.present) {
      void data.sprite.get()
    }
  }
}))

Functions

Query authoring helpers for selecting components and declaring explicit filters.

read

Source

Declares read-only access to a component in a query selection.

A required read slot is both a matching requirement and a typing decision: the entity must have that component, and the resulting slot becomes a ReadCell. Use this for data the system needs to inspect but must not mutate.

write

Source

Declares writable access to a component in a query selection.

Use this when the system is responsible for mutating component state in place. A write slot both requires component presence and exposes the slot as a WriteCell, so mutation capability stays visible in the query spec rather than appearing ad hoc in the loop body.

optional

Source

Declares maybe-present read-only access to a component in a query specification.

optional(...) is for enrichment, not matching. It keeps the entity set broad while letting one system opportunistically read extra data when present. The returned cell forces an explicit present check before use, so the possibility of absence remains visible in the type surface.

// Keep the main query focused on movers, but read sprite data when available.
const query = Game.Query({
  selection: {
    position: Game.Query.read(Position),
    sprite: Game.Query.optional(Sprite)
  }
})

added

Source

Declares a lifecycle filter that matches newly added components.

This depends on the readable lifecycle buffer, so it only changes after an explicit Game.Schedule.updateLifecycle() boundary.

This is the usual entrypoint for incremental host sync: create host-owned nodes only after lifecycle visibility has been advanced for the current schedule. changed complements this for later update passes.

// React only to renderables that became visible after the lifecycle boundary.
const AddedRenderableQuery = Game.Query({
  selection: {
    position: Game.Query.read(Position),
    renderable: Game.Query.read(Renderable)
  },
  filters: [Game.Query.added(Renderable)]
})

changed

Source

Declares a lifecycle filter that matches components written since the last lifecycle boundary.

This depends on the readable lifecycle buffer, so it only changes after an explicit Game.Schedule.updateLifecycle() boundary.

Use this for narrow host-sync passes after initial creation, for example one transform-sync system that should only touch entities whose position changed since the last lifecycle boundary. added is the matching creation-side lifecycle filter.

// React only to entities whose position changed since the last lifecycle update.
const MovedQuery = Game.Query({
  selection: {
    position: Game.Query.read(Position)
  },
  filters: [Game.Query.changed(Position)]
})

success

Source

Successful result constructor used by runtime query helpers.

failure

Source

Failed result constructor used by runtime query helpers.

missingEntityError

Source

Creates a typed missing-entity error.

queryMismatchError

Source

Creates a typed query-mismatch error.

noEntitiesError

Source

Creates a typed zero-match error.

multipleEntitiesError

Source

Creates a typed multi-match error.

Query

Source

Creates an explicit query specification.

Use this inside system specs instead of relying on callback parameter inference. The resulting value drives both runtime execution and the derived query result type.

A query spec is purely declarative. It does not access the world by itself; systems receive QueryHandles derived from the spec. In practice this is where you encode the exact shape of one gameplay iteration pass.

// Define one reusable iteration contract for a movement system.
const Moving = Game.Query({
  selection: {
    position: Game.Query.write(Position),
    velocity: Game.Query.read(Velocity)
  },
  with: [Position, Velocity],
  without: [Sleeping]
})