Core
schema
Schema authoring, binding, and pre-bind feature composition.
This module is the bridge between raw descriptor declarations and the final
bound Game API that systems, schedules, queries, commands, and runtimes
actually use. It answers the question "what world is allowed to exist in
this game?" before any runtime value is built.
The normal authoring flow is:
- declare descriptors with Descriptor.*
- group them into reusable Schema.fragment(...) values
- bind one Game with Schema.bind(...)
- build a runtime or composed project from that bound Game
Schema.Feature lives on the same pre-bind layer. Features contribute schema
fragments and build schedules only after the final merged schema is known.
Reach for this module when a project grows beyond one file and needs schema
composition that stays explicit, local, and type-safe instead of relying on
mutable global registries.
Examples
// Declare the descriptor identities that define the allowed world contents.
const Position = Descriptor.Component<{ x: number; y: number }>()("Position")
const Velocity = Descriptor.Component<{ x: number; y: number }>()("Velocity")
const Score = Descriptor.Resource<number>()("Score")
// Package one gameplay slice as a reusable schema fragment.
const Core = Schema.fragment({
components: { Position, Velocity },
resources: { Score }
})
// Create one root brand so handles and bound APIs all agree on one world.
const Root = Schema.defineRoot("Game")
// Bind the final public ECS authoring surface for the project.
const Game = Schema.bind(Core, Root)
Creates one explicit root token for schema-bound long-lived references.
Root tokens exist before schema construction so durable entity handles can be
stored in descriptor payload types without widening to Schema.Any.
Use one root token for the whole application. Anything created from
Schema.bind(Core, Root) will carry the same hidden root brand.
const Root = Schema.defineRoot("Game")
const Target = Descriptor.Component<{
handle: Entity.Handle<typeof Root>
}>()("Target")
Creates an empty schema.
This is mostly useful as an implementation detail when folding fragments
together into a final closed schema.
Creates a schema fragment.
Fragments are the main composition unit for game modules. Use them to keep
each gameplay slice local, exportable, and mergeable without introducing
hidden registry mutation.
A fragment says "this subsystem contributes these components/resources/
events/states/relations", and nothing more. Binding and runtime assembly
happen later once the final application shape is known.
// Keep combat schema local to the combat module.
const Combat = Schema.fragment({
components: { Health, Damage },
events: { Hit }
})
// Other modules can export their own fragments independently.
const Movement = Schema.fragment({
components: { Position, Velocity }
})
Merges two schema fragments into a larger closed schema.
Duplicate keys are rejected both at the type level and at runtime so schema
composition stays predictable.
Composes one or more fragments and returns one bound Game surface.
This is the canonical public schema entrypoint. Fragment composition and
schema binding happen together so the common path does not need a separate
intermediate built-schema value.
Use Game.schema when lower-level code still needs the final closed schema.
// Compose several independent gameplay fragments into one final API surface.
const Root = Schema.defineRoot("Game")
const Game = Schema.bind(Core, Combat, Root)
Defines one pre-bind feature.
Features are pure typed values. They contribute a schema fragment, declare
structural dependencies on other features, and build schedules only after
the final merged schema has been bound.
Feature builders can only access descriptors from:
- the feature's own fragment
- the fragments of features listed in requires
const Combat = Schema.Feature.define("Combat", {
schema: CombatSchema,
requires: [Core],
build: (Game) => ({
update: [combatUpdate]
})
})
No description provided yet.
No description provided yet.