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(...).
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.
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)
}))
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.
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 })
}))
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.
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.
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.
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.
Declares read access to the last applied transition payload of a machine.
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.
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
}
}))
Declares read access to relation-mutation failure records.
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
}
}))
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)
}))