@typeonce/ritmo 0.7.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (141) hide show
  1. package/README.md +3 -2
  2. package/dist/Clock.d.ts +19 -4
  3. package/dist/Clock.d.ts.map +1 -1
  4. package/dist/Clock.js +18 -2
  5. package/dist/Clock.js.map +1 -1
  6. package/dist/Command.d.ts +181 -40
  7. package/dist/Command.d.ts.map +1 -1
  8. package/dist/Command.js +31 -11
  9. package/dist/Command.js.map +1 -1
  10. package/dist/Condition.d.ts +12 -1
  11. package/dist/Condition.d.ts.map +1 -1
  12. package/dist/Condition.js.map +1 -1
  13. package/dist/Debug.d.ts +127 -13
  14. package/dist/Debug.d.ts.map +1 -1
  15. package/dist/Descriptor.d.ts +61 -10
  16. package/dist/Descriptor.d.ts.map +1 -1
  17. package/dist/Descriptor.js +22 -10
  18. package/dist/Descriptor.js.map +1 -1
  19. package/dist/Entity.d.ts +22 -7
  20. package/dist/Entity.d.ts.map +1 -1
  21. package/dist/Entity.js.map +1 -1
  22. package/dist/EntityScope.d.ts +20 -1
  23. package/dist/EntityScope.d.ts.map +1 -1
  24. package/dist/EntityScope.js +8 -2
  25. package/dist/EntityScope.js.map +1 -1
  26. package/dist/FixedLoop.d.ts +106 -0
  27. package/dist/FixedLoop.d.ts.map +1 -0
  28. package/dist/FixedLoop.js +97 -0
  29. package/dist/FixedLoop.js.map +1 -0
  30. package/dist/HostClock.d.ts +1 -1
  31. package/dist/HostClock.d.ts.map +1 -1
  32. package/dist/HostClock.js +2 -1
  33. package/dist/HostClock.js.map +1 -1
  34. package/dist/Machine.d.ts +59 -34
  35. package/dist/Machine.d.ts.map +1 -1
  36. package/dist/Machine.js +10 -3
  37. package/dist/Machine.js.map +1 -1
  38. package/dist/Query.d.ts +100 -14
  39. package/dist/Query.d.ts.map +1 -1
  40. package/dist/Query.js +34 -5
  41. package/dist/Query.js.map +1 -1
  42. package/dist/Relation.d.ts +5 -14
  43. package/dist/Relation.d.ts.map +1 -1
  44. package/dist/Relation.js +0 -32
  45. package/dist/Relation.js.map +1 -1
  46. package/dist/Runtime.d.ts +61 -15
  47. package/dist/Runtime.d.ts.map +1 -1
  48. package/dist/Runtime.js +594 -70
  49. package/dist/Runtime.js.map +1 -1
  50. package/dist/Schedule.d.ts +24 -15
  51. package/dist/Schedule.d.ts.map +1 -1
  52. package/dist/Schedule.js +2 -1
  53. package/dist/Schedule.js.map +1 -1
  54. package/dist/Schema.d.ts +233 -48
  55. package/dist/Schema.d.ts.map +1 -1
  56. package/dist/Schema.js +33 -7
  57. package/dist/Schema.js.map +1 -1
  58. package/dist/Snapshot.d.ts +90 -1
  59. package/dist/Snapshot.d.ts.map +1 -1
  60. package/dist/Snapshot.js +42 -1
  61. package/dist/Snapshot.js.map +1 -1
  62. package/dist/System.d.ts +132 -31
  63. package/dist/System.d.ts.map +1 -1
  64. package/dist/System.js +32 -0
  65. package/dist/System.js.map +1 -1
  66. package/dist/index.d.ts +4 -0
  67. package/dist/index.d.ts.map +1 -1
  68. package/dist/index.js +4 -0
  69. package/dist/index.js.map +1 -1
  70. package/dist/internal/cells.d.ts +2 -0
  71. package/dist/internal/cells.d.ts.map +1 -1
  72. package/dist/internal/cells.js +34 -8
  73. package/dist/internal/cells.js.map +1 -1
  74. package/dist/internal/debug.d.ts +10 -0
  75. package/dist/internal/debug.d.ts.map +1 -1
  76. package/dist/internal/debug.js +171 -12
  77. package/dist/internal/debug.js.map +1 -1
  78. package/dist/internal/equal.d.ts +8 -0
  79. package/dist/internal/equal.d.ts.map +1 -0
  80. package/dist/internal/equal.js +38 -0
  81. package/dist/internal/equal.js.map +1 -0
  82. package/dist/internal/fragments.d.ts +2 -6
  83. package/dist/internal/fragments.d.ts.map +1 -1
  84. package/dist/internal/fragments.js +36 -3
  85. package/dist/internal/fragments.js.map +1 -1
  86. package/dist/internal/game.d.ts +2 -0
  87. package/dist/internal/game.d.ts.map +1 -1
  88. package/dist/internal/game.js +48 -7
  89. package/dist/internal/game.js.map +1 -1
  90. package/dist/internal/order.d.ts.map +1 -1
  91. package/dist/internal/order.js +4 -2
  92. package/dist/internal/order.js.map +1 -1
  93. package/dist/internal/queries.d.ts.map +1 -1
  94. package/dist/internal/queries.js +57 -26
  95. package/dist/internal/queries.js.map +1 -1
  96. package/dist/internal/relations.d.ts +10 -0
  97. package/dist/internal/relations.d.ts.map +1 -0
  98. package/dist/internal/relations.js +32 -0
  99. package/dist/internal/relations.js.map +1 -0
  100. package/dist/internal/requirements.d.ts +26 -0
  101. package/dist/internal/requirements.d.ts.map +1 -0
  102. package/dist/internal/requirements.js +46 -0
  103. package/dist/internal/requirements.js.map +1 -0
  104. package/dist/internal/streams.d.ts +2 -0
  105. package/dist/internal/streams.d.ts.map +1 -1
  106. package/dist/internal/streams.js +10 -0
  107. package/dist/internal/streams.js.map +1 -1
  108. package/dist/internal/world.d.ts +7 -1
  109. package/dist/internal/world.d.ts.map +1 -1
  110. package/dist/internal/world.js +53 -3
  111. package/dist/internal/world.js.map +1 -1
  112. package/package.json +1 -1
  113. package/src/Clock.ts +29 -4
  114. package/src/Command.ts +281 -67
  115. package/src/Condition.ts +17 -1
  116. package/src/Debug.ts +124 -15
  117. package/src/Descriptor.ts +92 -21
  118. package/src/Entity.ts +23 -7
  119. package/src/EntityScope.ts +21 -2
  120. package/src/FixedLoop.ts +218 -0
  121. package/src/HostClock.ts +2 -1
  122. package/src/Machine.ts +72 -35
  123. package/src/Query.ts +118 -17
  124. package/src/Relation.ts +5 -67
  125. package/src/Runtime.ts +659 -85
  126. package/src/Schedule.ts +31 -19
  127. package/src/Schema.ts +297 -57
  128. package/src/Snapshot.ts +94 -2
  129. package/src/System.ts +151 -29
  130. package/src/index.ts +4 -0
  131. package/src/internal/cells.ts +42 -10
  132. package/src/internal/debug.ts +179 -14
  133. package/src/internal/equal.ts +31 -0
  134. package/src/internal/fragments.ts +35 -5
  135. package/src/internal/game.ts +69 -7
  136. package/src/internal/order.ts +8 -4
  137. package/src/internal/queries.ts +62 -12
  138. package/src/internal/relations.ts +52 -0
  139. package/src/internal/requirements.ts +71 -0
  140. package/src/internal/streams.ts +11 -0
  141. package/src/internal/world.ts +60 -4
package/src/Schema.ts CHANGED
@@ -54,7 +54,7 @@ import type { ConstructedDescriptor, Descriptor } from "./Descriptor.ts"
54
54
  import type * as Entity from "./Entity.ts"
55
55
  import type * as EntityScope from "./EntityScope.ts"
56
56
  import { buildSchema, emptySchema, mergeSchemas } from "./internal/fragments.ts"
57
- import { makeGame } from "./internal/game.ts"
57
+ import { type exitApplyDeferredMessage, makeGame } from "./internal/game.ts"
58
58
  import type * as Condition from "./Condition.ts"
59
59
  import type * as Inspector from "./Inspector.ts"
60
60
  import type * as Machine from "./Machine.ts"
@@ -66,6 +66,7 @@ import type * as Result from "./Result.ts"
66
66
  import type * as Debug from "./Debug.ts"
67
67
  import type * as Runtime from "./Runtime.ts"
68
68
  import type * as Schedule from "./Schedule.ts"
69
+ import type * as Snapshot from "./Snapshot.ts"
69
70
  import type * as System from "./System.ts"
70
71
 
71
72
  /**
@@ -73,22 +74,107 @@ import type * as System from "./System.ts"
73
74
  */
74
75
  export type Registry = Record<string, Descriptor.Any>
75
76
 
77
+ /** At least one: a permission that names nothing allows nothing. */
78
+ type NonEmpty<T> = readonly [T, ...ReadonlyArray<T>]
79
+
80
+ type Article<Kind extends string> = Kind extends "event" ? "an" : "a"
81
+
82
+ /**
83
+ * Rejects descriptors of another kind in a fragment's `components`,
84
+ * `resources`, or `events`, naming where each belongs.
85
+ */
86
+ type RegistryOfKind<R, Expected extends string> = {
87
+ readonly [K in keyof R]: R[K] extends { readonly kind: Expected }
88
+ ? unknown
89
+ : R[K] extends { readonly kind: infer Actual extends string }
90
+ ? {
91
+ readonly __fixSchema__: Actual extends "service"
92
+ ? `${K & string} is a service, not ${Article<Expected>} ${Expected}: provide services to Game.Runtime.make({ services }) instead of registering them`
93
+ : `${K & string} is ${Article<Actual>} ${Actual}, not ${Article<Expected>} ${Expected}: move it to ${Actual}s: { ... }`
94
+ }
95
+ : unknown
96
+ }
97
+
76
98
  /**
77
99
  * A closed schema: the components, resources, events, and relations a world
78
- * may contain.
100
+ * may contain, and the component requirements (`Schema.requires`) every
101
+ * entity of it keeps.
79
102
  */
80
103
  export interface SchemaDefinition<
81
104
  out Components extends Registry = {},
82
105
  out Resources extends Registry = {},
83
106
  out Events extends Registry = {},
84
- out Relations extends Record<string, Relation.Relation.Any> = {}
107
+ out Relations extends Record<string, Relation.Relation.Any> = {},
108
+ out Requires extends RequirementPair = never
85
109
  > {
86
110
  readonly components: Components
87
111
  readonly resources: Resources
88
112
  readonly events: Events
89
113
  readonly relations: Relations
114
+ /** Component requirements declared with `Schema.requires(...)`. */
115
+ readonly requires: ReadonlyArray<ComponentRequirement<Requires>>
116
+ }
117
+
118
+ /**
119
+ * One requirement carried in a schema type, by component name:
120
+ * `[requirer, required]`. One pair per required component, so lookups by
121
+ * either end stay exact.
122
+ */
123
+ export type RequirementPair = readonly [string, string]
124
+
125
+ /**
126
+ * One `Schema.requires(...)` declaration: every entity holding `component`
127
+ * also holds each of `required`.
128
+ */
129
+ export interface ComponentRequirement<out Pairs extends RequirementPair = RequirementPair> {
130
+ readonly kind: "requires"
131
+ readonly component: Descriptor<"component", string, any>
132
+ readonly required: ReadonlyArray<Descriptor<"component", string, any>>
133
+ readonly __pairs?: (_: never) => Pairs
90
134
  }
91
135
 
136
+ type AnyComponentDescriptor = Descriptor<"component", string, any>
137
+
138
+ /** One `[from, to]` pair per required name. */
139
+ type RequirementPairs<From extends string, To extends string> = To extends string ? readonly [From, To] : never
140
+
141
+ type PairsOf<R> = R extends ComponentRequirement<infer Pairs> ? Pairs : never
142
+
143
+ /**
144
+ * Declares that every entity holding `component` also holds each of
145
+ * `required`. List it in a fragment's `requires`.
146
+ *
147
+ * The compiler then rejects every command that would break it:
148
+ * `commands.spawn` / `spawnIn` of a draft missing a required entry,
149
+ * `commands.insert` of `component` on an entity not proven to hold the
150
+ * rest, and `commands.remove` of a required component while a requirer may
151
+ * stay. `Game.System.spawns(component)` also allows spawning what it
152
+ * requires. Nothing is ever filled in with a default: the draft must carry
153
+ * every value.
154
+ *
155
+ * Requirements chain (`A` requires `B`, `B` requires `C`) and may name
156
+ * components of other fragments of the same `Schema.bind`.
157
+ *
158
+ * A saved component cannot require a transient one (`Schema.bind` rejects
159
+ * it): a restore would bring the requirer back without it. A transient
160
+ * requirer may require a transient component.
161
+ *
162
+ * @example
163
+ * ```ts
164
+ * const Gameplay = Schema.fragment({
165
+ * components: { Player, Position, Velocity },
166
+ * requires: [Schema.requires(Player, Position, Velocity)]
167
+ * })
168
+ * ```
169
+ */
170
+ export const requires = <
171
+ D extends AnyComponentDescriptor,
172
+ const Required extends readonly [AnyComponentDescriptor, ...Array<AnyComponentDescriptor>]
173
+ >(
174
+ component: D,
175
+ ...required: Required
176
+ ): ComponentRequirement<RequirementPairs<D["name"], Required[number]["name"]>> => ({ kind: "requires", component, required })
177
+
92
178
  const schemaRootTypeId = "~ritmo/SchemaRoot" as const
93
179
 
94
180
  export type RootToken<Name extends string = string> = {
@@ -112,7 +198,8 @@ export type MergeSchemaDefinitions<
112
198
  Schema.Components<A> & Schema.Components<B>,
113
199
  Schema.Resources<A> & Schema.Resources<B>,
114
200
  Schema.Events<A> & Schema.Events<B>,
115
- Schema.Relations<A> & Schema.Relations<B>
201
+ Schema.Relations<A> & Schema.Relations<B>,
202
+ Schema.Requires<A> | Schema.Requires<B>
116
203
  >
117
204
 
118
205
  /**
@@ -186,6 +273,40 @@ export type ValidateFragments<Fragments extends ReadonlyArray<Schema.Any>> =
186
273
  readonly __schemaConflicts__: FragmentConflicts<Fragments>
187
274
  }
188
275
 
276
+ type UnregisteredRequirements<S extends Schema.Any> =
277
+ Exclude<Schema.Requires<S>[0] | Schema.Requires<S>[1], Schema.ComponentDescriptor<S>["name"]>
278
+
279
+ type TransientComponentName<S extends Schema.Any> = Extract<Schema.ComponentDescriptor<S>, { readonly transient: true }>["name"]
280
+
281
+ /** One message per saved requirer that requires a transient component. */
282
+ type TransientRequirements<S extends Schema.Any, Pairs = Schema.Requires<S>> =
283
+ Pairs extends readonly [infer From extends string, infer To extends string]
284
+ ? To extends TransientComponentName<S>
285
+ ? From extends TransientComponentName<S> ? never
286
+ : `${From} requires ${To}, which is transient and not saved: make ${From} transient too, or give ${To} a constructor`
287
+ : never
288
+ : never
289
+
290
+ /**
291
+ * Rejects requirements naming a component the bound schema does not
292
+ * register. Checked at `bind`, so a fragment may require a component another
293
+ * fragment registers. Also rejects a saved component requiring a transient
294
+ * one (a restore would bring the requirer back without it). Erased schemas
295
+ * (`Schema.Any`) are checked by `bind` at runtime instead.
296
+ */
297
+ export type ValidateRequires<S extends Schema.Any> =
298
+ string extends Schema.Requires<S>[1]
299
+ ? unknown
300
+ : [UnregisteredRequirements<S>] extends [never]
301
+ ? [TransientRequirements<S>] extends [never]
302
+ ? unknown
303
+ : {
304
+ readonly __schemaRequires__: TransientRequirements<S>
305
+ }
306
+ : {
307
+ readonly __schemaRequires__: `${UnregisteredRequirements<S>} is named in Schema.requires(...) but no fragment registers it: add it to a fragment's components`
308
+ }
309
+
189
310
  /**
190
311
  * Creates one explicit root token for schema-bound long-lived references.
191
312
  *
@@ -209,11 +330,6 @@ export const defineRoot = <const Name extends string>(name: Name): RootToken<Nam
209
330
  }
210
331
  }) as RootToken<Name>
211
332
 
212
- /**
213
- * Creates an empty schema.
214
- */
215
- export const empty = (): SchemaDefinition => emptySchema()
216
-
217
333
  /**
218
334
  * Creates a schema fragment.
219
335
  *
@@ -224,8 +340,9 @@ export const empty = (): SchemaDefinition => emptySchema()
224
340
  * @example
225
341
  * ```ts
226
342
  * const Combat = Schema.fragment({
227
- * components: { Health, Damage },
228
- * events: { Hit }
343
+ * components: { Health, Damage, Enemy },
344
+ * events: { Hit },
345
+ * requires: [Schema.requires(Enemy, Health)]
229
346
  * })
230
347
  * ```
231
348
  */
@@ -233,19 +350,22 @@ export const fragment = <
233
350
  const Components extends Registry = {},
234
351
  const Resources extends Registry = {},
235
352
  const Events extends Registry = {},
236
- const Relations extends Record<string, Relation.Relation.Any> = {}
353
+ const Relations extends Record<string, Relation.Relation.Any> = {},
354
+ const Requires extends ReadonlyArray<ComponentRequirement> = readonly []
237
355
  >(definition: {
238
- readonly components?: Components
239
- readonly resources?: Resources
240
- readonly events?: Events
356
+ readonly components?: Components & RegistryOfKind<Components, "component">
357
+ readonly resources?: Resources & RegistryOfKind<Resources, "resource">
358
+ readonly events?: Events & RegistryOfKind<Events, "event">
241
359
  readonly relations?: Relations
242
- } & ValidateFragments<[SchemaDefinition<Components, Resources, Events, Relations>]>): SchemaDefinition<Components, Resources, Events, Relations> =>
360
+ readonly requires?: Requires
361
+ } & ValidateFragments<[SchemaDefinition<Components, Resources, Events, Relations>]>): SchemaDefinition<Components, Resources, Events, Relations, PairsOf<Requires[number]>> =>
243
362
  mergeSchemas(emptySchema(), {
244
363
  components: definition.components ?? {},
245
364
  resources: definition.resources ?? {},
246
365
  events: definition.events ?? {},
247
- relations: definition.relations ?? {}
248
- }) as SchemaDefinition<Components, Resources, Events, Relations>
366
+ relations: definition.relations ?? {},
367
+ requires: definition.requires ?? []
368
+ }) as SchemaDefinition<Components, Resources, Events, Relations, PairsOf<Requires[number]>>
249
369
 
250
370
  /**
251
371
  * Composes one or more fragments and returns one bound `Game` surface.
@@ -262,18 +382,18 @@ export const fragment = <
262
382
  */
263
383
  export function bind<
264
384
  S extends Schema.Any
265
- >(schema: S & ValidateFragments<[S]>): Schema.Game<S, S>
385
+ >(schema: S & ValidateFragments<[S]> & ValidateRequires<S>): Schema.Game<S, S>
266
386
  export function bind<
267
387
  S extends Schema.Any,
268
388
  const Name extends string
269
- >(schema: S & ValidateFragments<[S]>, root: RootToken<Name>): Schema.Game<S, RootToken<Name>>
389
+ >(schema: S & ValidateFragments<[S]> & ValidateRequires<S>, root: RootToken<Name>): Schema.Game<S, RootToken<Name>>
270
390
  export function bind<
271
391
  const Fragments extends readonly [Schema.Any, Schema.Any, ...Array<Schema.Any>]
272
- >(...fragments: Fragments & ValidateFragments<Fragments>): Schema.Game<BuildFragments<Fragments>, BuildFragments<Fragments>>
392
+ >(...fragments: Fragments & ValidateFragments<Fragments> & ValidateRequires<BuildFragments<Fragments>>): Schema.Game<BuildFragments<Fragments>, BuildFragments<Fragments>>
273
393
  export function bind<
274
394
  const Fragments extends readonly [Schema.Any, Schema.Any, ...Array<Schema.Any>],
275
395
  const Name extends string
276
- >(...args: [...Fragments, RootToken<Name>] & ValidateFragments<Fragments>): Schema.Game<BuildFragments<Fragments>, RootToken<Name>>
396
+ >(...args: [...Fragments, RootToken<Name>] & ValidateFragments<Fragments> & ValidateRequires<BuildFragments<Fragments>>): Schema.Game<BuildFragments<Fragments>, RootToken<Name>>
277
397
  export function bind(...args: ReadonlyArray<Schema.Any | RootToken>) {
278
398
  const maybeRoot = args[args.length - 1]
279
399
  const fragments = (isRootToken(maybeRoot) ? args.slice(0, -1) : args) as Array<Schema.Any>
@@ -298,7 +418,7 @@ type QuerySelectionAccess<S extends Schema.Any, Root> =
298
418
 
299
419
  interface BoundSystemAccess<S extends Schema.Any, Root> extends System.SystemAccessInput {
300
420
  readonly queries?: Record<string, Query.Any<Root>>
301
- readonly resources?: Record<string, System.ResourceRead<ResourceDescriptor<S>> | System.ResourceWrite<ResourceDescriptor<S>>>
421
+ readonly resources?: Record<string, System.ResourceRead<ResourceDescriptor<S>> | System.ResourceChanged<ResourceDescriptor<S>> | System.ResourceWrite<ResourceDescriptor<S>>>
302
422
  readonly events?: Record<string, System.EventRead<EventDescriptor<S>> | System.EventWrite<EventDescriptor<S>>>
303
423
  readonly machines?: Record<string, Machine.MachineRead<Schema.BoundStateMachine<Root>>>
304
424
  readonly nextMachines?: Record<string, Machine.NextMachineWrite<Schema.BoundStateMachine<Root>>>
@@ -314,7 +434,7 @@ interface BoundSystemAccess<S extends Schema.Any, Root> extends System.SystemAcc
314
434
 
315
435
  interface BoundCheckAccess<S extends Schema.Any, Root> extends Condition.CheckAccessInput {
316
436
  readonly queries?: Record<string, Query.Any<Root>>
317
- readonly resources?: Record<string, System.ResourceRead<ResourceDescriptor<S>>>
437
+ readonly resources?: Record<string, System.ResourceRead<ResourceDescriptor<S>> | System.ResourceChanged<ResourceDescriptor<S>> | System.ResourceWrite<ResourceDescriptor<S>>>
318
438
  readonly machines?: Record<string, Machine.MachineRead<Schema.BoundStateMachine<Root>>>
319
439
  }
320
440
 
@@ -337,6 +457,16 @@ type BoundScheduleEntry<S extends Schema.Any, Root> =
337
457
  type BoundTransitionEntry<S extends Schema.Any, Root> =
338
458
  Exclude<BoundScheduleEntry<S, Root>, Schedule.ApplyStateTransitionsStep<any, Root>>
339
459
 
460
+ /**
461
+ * Rejects `applyDeferred()` in an exit phase (`onExit`, `onTransition`): the
462
+ * phase rolls back as one when a system in it fails, which applied commands
463
+ * could not. Nested schedules are checked when the schedule is built.
464
+ */
465
+ type NoApplyDeferredInExit<Entries extends ReadonlyArray<unknown>> =
466
+ [Extract<Entries[number], Schedule.ApplyDeferredStep>] extends [never]
467
+ ? unknown
468
+ : { readonly __fixTransition__: typeof exitApplyDeferredMessage }
469
+
340
470
  type BoundTransitionScheduleResult<
341
471
  S extends Schema.Any,
342
472
  Root,
@@ -345,7 +475,8 @@ type BoundTransitionScheduleResult<
345
475
  > = Machine.TransitionScheduleDefinition<
346
476
  S,
347
477
  M,
348
- Schedule.CompositionExactRequirements<Entries>,
478
+ // The machine itself: a transition schedule on a machine the runtime does not provide could never run.
479
+ Schedule.CompositionExactRequirements<Entries> | M,
349
480
  Root,
350
481
  Schedule.CompositionFailure<Entries>,
351
482
  Schedule.CompositionOrder<Entries>
@@ -376,12 +507,14 @@ export namespace Schema {
376
507
  /**
377
508
  * Any complete schema definition.
378
509
  */
379
- export type Any = SchemaDefinition<Registry, Registry, Registry, Record<string, Relation.Relation.Any>>
510
+ export type Any = SchemaDefinition<Registry, Registry, Registry, Record<string, Relation.Relation.Any>, RequirementPair>
380
511
 
381
512
  export type Components<T extends Any> = T["components"]
382
513
  export type Resources<T extends Any> = T["resources"]
383
514
  export type Events<T extends Any> = T["events"]
384
515
  export type Relations<T extends Any> = T["relations"]
516
+ /** The component requirements of a schema, as `[requirer, required]` name pairs (`Schema.requires`). */
517
+ export type Requires<T extends Any> = T extends SchemaDefinition<any, any, any, any, infer Pairs> ? Pairs : never
385
518
 
386
519
  export type ComponentValue<T extends Any, K extends keyof Components<T>> = Descriptor.Value<Components<T>[K]>
387
520
  export type ResourceValue<T extends Any, K extends keyof Resources<T>> = Descriptor.Value<Resources<T>[K]>
@@ -434,8 +567,9 @@ export namespace Schema {
434
567
  export type BoundStateMachine<
435
568
  Root,
436
569
  Name extends string = string,
437
- Values extends readonly [Machine.StateValue, ...Machine.StateValue[]] = readonly [Machine.StateValue, ...Machine.StateValue[]]
438
- > = Machine.StateMachineDefinition<Name, Values, Root>
570
+ Values extends readonly [Machine.StateValue, ...Machine.StateValue[]] = readonly [Machine.StateValue, ...Machine.StateValue[]],
571
+ Parent = unknown
572
+ > = Machine.StateMachineDefinition<Name, Values, Root, Parent>
439
573
 
440
574
  export type BoundTransitionSchedule<
441
575
  S extends Any,
@@ -473,11 +607,38 @@ export namespace Schema {
473
607
  readonly schema: S
474
608
  /**
475
609
  * Creates a typed ownership scope for scene or level entities: a flat
476
- * group despawned together with `commands.despawnScope(...)`. Use
477
- * `Descriptor.Hierarchy` instead for a parent-child tree with ordered
478
- * children, traversal, and recursive despawn.
610
+ * group whose members are spawned with `commands.spawnIn(...)` and
611
+ * despawned together. Use `Descriptor.Hierarchy` instead for a
612
+ * parent-child tree with ordered children, traversal, and recursive
613
+ * despawn.
614
+ *
615
+ * When the group lives exactly as long as a state, name the state change
616
+ * that ends it: `despawnOnExit: [Flow, "Playing"]` despawns every member
617
+ * when `applyStateTransitions(...)` commits leaving `Playing` (after its
618
+ * `onExit` schedules, which still see the members), and
619
+ * `despawnOnEnter: [Flow, "Playing"]` when it commits entering it (before
620
+ * its `onEnter` schedules, so what they spawn survives). Use
621
+ * `commands.despawnScope(...)` instead when the wipe is not tied to a
622
+ * state change. Scope names are unique per game.
623
+ *
624
+ * @example
625
+ * ```ts
626
+ * const RunScope = Game.EntityScope("Shooter/Run", { despawnOnExit: [Flow, "Playing"] })
627
+ * ```
479
628
  */
480
- readonly EntityScope: <const Name extends string>(name: Name) => EntityScope.EntityScope<Name, Root>
629
+ readonly EntityScope: <
630
+ const Name extends string,
631
+ Enter extends Machine.StateMachine.Any = never,
632
+ Exit extends Machine.StateMachine.Any = never
633
+ >(
634
+ name: Name,
635
+ options?: {
636
+ /** Despawns every member when the machine enters this state, before its `onEnter` schedules run. */
637
+ readonly despawnOnEnter?: readonly [Enter & Machine.SameGame<Enter, Root>, Machine.StateMachine.Value<Enter>]
638
+ /** Despawns every member when the machine leaves this state, after its `onExit` schedules run. */
639
+ readonly despawnOnExit?: readonly [Exit & Machine.SameGame<Exit, Root>, Machine.StateMachine.Value<Exit>]
640
+ }
641
+ ) => EntityScope.EntityScope<Name, Root>
481
642
  /** Declares a reusable, requirement-checked read-only world projection. */
482
643
  readonly Inspector: <
483
644
  const Name extends string,
@@ -497,6 +658,13 @@ export namespace Schema {
497
658
  target: Entity.HandleTarget<S, Root>,
498
659
  intent?: D
499
660
  ) => Entity.Handle<Root, D>
661
+ /**
662
+ * The entity id a handle names, to compare with ids or pass where an id
663
+ * is expected (`queries.x.get(id)`), instead of storing the id beside
664
+ * the handle. Like the handle, it proves nothing about liveness:
665
+ * resolve it through a query. Two handles of the same entity are `===`.
666
+ */
667
+ id: (handle: Entity.Handle<Root, ComponentDescriptor<S> | undefined>) => Entity.EntityId<S, Root>
500
668
  }
501
669
  readonly Query: {
502
670
  <
@@ -511,7 +679,7 @@ export namespace Schema {
511
679
  >(spec: {
512
680
  readonly selection: Selection
513
681
  readonly with?: With
514
- readonly without?: Without
682
+ readonly without?: Without & QueryModule.ExclusionsPossible<Selection, With, Without, Filters>
515
683
  readonly filters?: Filters
516
684
  readonly withRelations?: WithRelations
517
685
  readonly withoutRelations?: WithoutRelations
@@ -520,10 +688,17 @@ export namespace Schema {
520
688
  // Uninferred: an omitted list is `[]`, never inferred from the spec it is written in (`queries: { ... }`).
521
689
  }): QueryModule.QuerySpec<Selection, QueryModule.Uninferred<With, ReadonlyArray<ComponentDescriptor<S>>>, QueryModule.Uninferred<Without, ReadonlyArray<ComponentDescriptor<S>>>, QueryModule.Uninferred<Filters, ReadonlyArray<QueryModule.Filter<ComponentDescriptor<S>>>>, QueryModule.Uninferred<WithRelations, ReadonlyArray<Relation.Relation.Any>>, QueryModule.Uninferred<WithoutRelations, ReadonlyArray<Relation.Relation.Any>>, QueryModule.Uninferred<WithRelated, ReadonlyArray<Relation.Relation.Any>>, QueryModule.Uninferred<WithoutRelated, ReadonlyArray<Relation.Relation.Any>>, Root>
522
690
  read: <D extends ComponentDescriptor<S>>(descriptor: D) => QueryModule.ReadAccess<D>
523
- write: <D extends ComponentDescriptor<S>>(descriptor: D) => QueryModule.WriteAccess<D>
691
+ write: <D extends ComponentDescriptor<S>>(descriptor: D & QueryModule.Writable<D>) => QueryModule.WriteAccess<D>
524
692
  optional: <D extends ComponentDescriptor<S>>(descriptor: D) => QueryModule.OptionalReadAccess<D>
525
693
  added: <D extends ComponentDescriptor<S>>(descriptor: D) => QueryModule.AddedFilter<D>
526
694
  changed: <D extends ComponentDescriptor<S>>(descriptor: D) => QueryModule.ChangedFilter<D>
695
+ anyOf: <
696
+ const Filters extends readonly [
697
+ QueryModule.LifecycleFilter<ComponentDescriptor<S>>,
698
+ QueryModule.LifecycleFilter<ComponentDescriptor<S>>,
699
+ ...ReadonlyArray<QueryModule.LifecycleFilter<ComponentDescriptor<S>>>
700
+ ]
701
+ >(...filters: Filters) => QueryModule.AnyOfFilter<Filters[number]["descriptor"]>
527
702
  readRelation: <R extends RelationDescriptor<S>>(descriptor: R) => Relation.RelationReadAccess<R, S, Root>
528
703
  optionalRelation: <R extends RelationDescriptor<S>>(descriptor: R) => Relation.OptionalRelationReadAccess<R, S, Root>
529
704
  readRelated: <R extends RelationDescriptor<S>>(descriptor: R) => Relation.RelatedReadAccess<R, S, Root>
@@ -538,20 +713,25 @@ export namespace Schema {
538
713
  * Adds entries to a draft (a value not yet spawned). Use
539
714
  * `commands.insert` for an entity that already exists.
540
715
  */
541
- extend: <P extends Entity.ComponentProof, const Entries extends ReadonlyArray<Command.SpawnEntry<S>>>(
542
- draft: Entity.EntityDraft<S, P, Root>,
716
+ extend: <P extends Entity.ComponentProof, const Entries extends ReadonlyArray<Command.SpawnEntry<S>>, Relates = never>(
717
+ draft: Entity.EntityDraft<S, P, Root, Relates>,
543
718
  ...entries: Entries
544
- ) => Command.DraftFor<S, Entries, P, Root>
545
- entry: <D extends ComponentDescriptor<S>>(descriptor: D, value: Descriptor.Value<D>) => Command.Entry<D>
719
+ ) => Command.DraftFor<S, Entries, P, Root, Relates>
720
+ entry: <D extends ComponentDescriptor<S>>(descriptor: D, value: QueryModule.ReadonlyValue<Descriptor.Value<D>>) => Command.Entry<D>
546
721
  entryRaw: <D extends Extract<ComponentDescriptor<S>, ConstructedDescriptor<"component", string, any, any, any>>>(
547
722
  descriptor: D,
548
723
  raw: Descriptor.Raw<D>
549
724
  ) => Result.Result<Command.Entry<D>, Descriptor.ConstructionError<D>>
550
- relate: <P extends Entity.ComponentProof, R extends RelationDescriptor<S>>(
551
- draft: Entity.EntityDraft<S, P, Root>,
725
+ /**
726
+ * Stages a relation on a draft, created with the spawn. Spawning the
727
+ * draft needs the relation in `Game.System.relates(...)`, like
728
+ * `commands.relate`.
729
+ */
730
+ relate: <P extends Entity.ComponentProof, R extends RelationDescriptor<S>, Relates = never>(
731
+ draft: Entity.EntityDraft<S, P, Root, Relates>,
552
732
  relation: R,
553
733
  target: Entity.EntityId<S, Root>
554
- ) => Entity.EntityDraft<S, P, Root>
734
+ ) => Entity.EntityDraft<S, P, Root, Relates | R>
555
735
  }
556
736
  /**
557
737
  * Declares one game-wide mode (menu, playing, paused): queued with
@@ -564,10 +744,52 @@ export namespace Schema {
564
744
  const Name extends string,
565
745
  const Values extends readonly [Machine.StateValue, ...Machine.StateValue[]]
566
746
  >(name: Name, values: Values): Schema.BoundStateMachine<Root, Name, Values>
747
+ /**
748
+ * Declares a sub-state: a machine that exists only while `within[0]` is
749
+ * in state `within[1]` (a battle phase that only means something while
750
+ * playing). Whenever the parent enters that state, the sub-state enters
751
+ * the value given to `Game.Runtime.machine(...)`; when the parent leaves
752
+ * it, the sub-state exits and has no value: `get()` is `undefined`,
753
+ * `Game.Condition.inState(Sub, x)` is false, and `nextMachines.sub.set(...)`
754
+ * returns a `SubStateInactive` failure. So `inState(Sub, x)` alone
755
+ * implies the parent's state.
756
+ *
757
+ * The sub-state enters together with the parent's commit, before any
758
+ * `onEnter` schedule runs: the parent's `onEnter` systems already see its
759
+ * value, its `despawnOnEnter` scopes are cleared before they spawn, and
760
+ * a failing `onEnter` leaves it entered. Its own `onEnter` schedules
761
+ * run after the parent's.
762
+ *
763
+ * Use a second top-level machine instead when the two modes are
764
+ * independent (each keeps its value whatever the other does).
765
+ *
766
+ * @example
767
+ * ```ts
768
+ * const Phase = Game.StateMachine("Phase", ["Setup", "Battle"], { within: [Session, "Playing"] })
769
+ * ```
770
+ */
771
+ <
772
+ const Name extends string,
773
+ const Values extends readonly [Machine.StateValue, ...Machine.StateValue[]],
774
+ Parent extends Machine.StateMachine.Any
775
+ >(
776
+ name: Name,
777
+ values: Values,
778
+ options: { readonly within: readonly [Parent & Machine.SameGame<Parent, Root>, Machine.StateMachine.Value<Parent>] }
779
+ ): Schema.BoundStateMachine<Root, Name, Values, Parent>
567
780
  }
568
781
  readonly Condition: {
569
- inState: typeof Machine.inState
570
- stateChanged: typeof Machine.stateChanged
782
+ /**
783
+ * Passes while the machine's committed value is `value`. For a
784
+ * sub-state it fails while the parent is in another state. A machine
785
+ * of another game fails to compile with a message naming the fix.
786
+ */
787
+ inState: <M extends Machine.StateMachine.Any>(
788
+ machine: M & Machine.SameGame<M, Root>,
789
+ value: Machine.StateMachine.Value<M>
790
+ ) => Machine.InStateCondition<M>
791
+ /** Passes in the schedule run where `applyStateTransitions(...)` changed the machine. */
792
+ stateChanged: <M extends Machine.StateMachine.Any>(machine: M & Machine.SameGame<M, Root>) => Machine.StateChangedCondition<M>
571
793
  not: typeof Machine.not
572
794
  and: typeof Machine.and
573
795
  or: typeof Machine.or
@@ -594,6 +816,7 @@ export namespace Schema {
594
816
  ): BoundSystem<S, Root, System.SystemSpec<S, Access, Root>, E, Name, System.SystemAccessNeeds<Access>>
595
817
  readResource: <D extends ResourceDescriptor<S>>(descriptor: D) => System.ResourceRead<D>
596
818
  writeResource: <D extends ResourceDescriptor<S>>(descriptor: D) => System.ResourceWrite<D>
819
+ changedResource: <D extends ResourceDescriptor<S>>(descriptor: D) => System.ResourceChanged<D>
597
820
  readEvent: <D extends EventDescriptor<S>>(descriptor: D) => System.EventRead<D>
598
821
  writeEvent: <D extends EventDescriptor<S>>(descriptor: D) => System.EventWrite<D>
599
822
  /**
@@ -601,7 +824,7 @@ export namespace Schema {
601
824
  * same `runtime.tick(...)` call; checked at compile time by `tick`.
602
825
  * Accepts `readEvent(...)`, `readResource(...)`, or a query.
603
826
  */
604
- thisTick: <Read extends System.EventRead<EventDescriptor<S>> | System.ResourceRead<ResourceDescriptor<S>> | Query.Any<Root>>(
827
+ thisTick: <Read extends System.EventRead<EventDescriptor<S>> | System.ResourceRead<ResourceDescriptor<S>> | System.ResourceChanged<ResourceDescriptor<S>> | Query.Any<Root>>(
605
828
  read: Read
606
829
  ) => Read & System.ThisTick
607
830
  service: typeof System.service
@@ -617,18 +840,21 @@ export namespace Schema {
617
840
  * `Game.System.commands(Game.System.spawns(Enemy, Position), Game.System.despawns(Enemy))`.
618
841
  */
619
842
  commands: typeof Command.commands
620
- /** Allows spawning drafts whose components are all among these; see `Command.spawns`. */
621
- spawns: <const Components extends ReadonlyArray<ComponentDescriptor<S>>>(...components: Components) => Command.Permission<"spawns", Components[number]>
843
+ /** Allows spawning drafts whose components are all among these and what they require (`Schema.requires`); see `Command.spawns`. */
844
+ spawns: <const Components extends NonEmpty<ComponentDescriptor<S>>>(...components: Components) => Command.Permission<"spawns", Components[number] | Command.RequiredDescriptors<S, Components[number]["name"]>>
622
845
  /** Allows `insert` (add or replace at the next `applyDeferred()`) of these; see `Command.inserts`. */
623
- inserts: <const Components extends ReadonlyArray<ComponentDescriptor<S>>>(...components: Components) => Command.Permission<"inserts", Components[number]>
846
+ inserts: <const Components extends NonEmpty<ComponentDescriptor<S>>>(...components: Components) => Command.Permission<"inserts", Components[number]>
624
847
  /** Allows `remove` of these. */
625
- removes: <const Components extends ReadonlyArray<ComponentDescriptor<S>>>(...components: Components) => Command.Permission<"removes", Components[number]>
848
+ removes: <const Components extends NonEmpty<ComponentDescriptor<S>>>(...components: Components) => Command.Permission<"removes", Components[number]>
626
849
  /** Allows `despawn` of entities a query proves have one of these; see `Command.despawns`. */
627
- despawns: <const Components extends ReadonlyArray<ComponentDescriptor<S>>>(...components: Components) => Command.Permission<"despawns", Components[number]>
850
+ despawns: <const Components extends NonEmpty<ComponentDescriptor<S>>>(...components: Components) => Command.Permission<"despawns", Components[number]>
628
851
  /** Allows `despawnScope` of this scope. */
629
852
  despawnsScope: <const Scope extends EntityScope.EntityScope<string, Root>>(scope: Scope) => Command.Permission<"despawnsScope", Scope>
630
- /** Allows `relate`, `unrelate`, and `reorderChildren` of these relations. */
631
- relates: <const Relations extends ReadonlyArray<RelationDescriptor<S>>>(...relations: Relations) => Command.Permission<"relates", Relations[number]>
853
+ /**
854
+ * Allows `relate`, `unrelate`, and `reorderChildren` of these relations,
855
+ * and spawning drafts that stage them with `Game.Command.relate(...)`.
856
+ */
857
+ relates: <const Relations extends NonEmpty<RelationDescriptor<S>>>(...relations: Relations) => Command.Permission<"relates", Relations[number]>
632
858
  }
633
859
  readonly Schedule: {
634
860
  /**
@@ -646,20 +872,31 @@ export namespace Schema {
646
872
  state: Machine.StateMachine.Value<M>,
647
873
  plan: readonly [...Entries]
648
874
  ) => BoundTransitionScheduleResult<S, Root, M, Entries>
875
+ /**
876
+ * Systems run when `machine` leaves `state`. The exit phase of one
877
+ * transition (the exit schedules of the sub-states it ends, then of
878
+ * `state`, then `onTransition`) is one transaction: when a system in it
879
+ * fails, everything the phase did is undone (except `local`) and the
880
+ * transition stays queued, so the next attempt starts from the same
881
+ * state. Its commands are applied at the next `applyDeferred()` after
882
+ * the marker, so it cannot contain `applyDeferred()` itself.
883
+ */
649
884
  onExit: <M extends BoundStateMachine<Root>, const Entries extends ReadonlyArray<BoundTransitionEntry<S, Root>>>(
650
885
  machine: M,
651
886
  state: Machine.StateMachine.Value<M>,
652
- plan: readonly [...Entries]
887
+ plan: readonly [...Entries] & NoApplyDeferredInExit<Entries>
653
888
  ) => BoundTransitionScheduleResult<S, Root, M, Entries>
889
+ /** Systems run on one `[from, to]` transition, last in its exit phase (see `onExit`). */
654
890
  onTransition: <M extends BoundStateMachine<Root>, const Entries extends ReadonlyArray<BoundTransitionEntry<S, Root>>>(
655
891
  machine: M,
656
892
  transition: readonly [Machine.StateMachine.Value<M>, Machine.StateMachine.Value<M>],
657
- plan: readonly [...Entries]
893
+ plan: readonly [...Entries] & NoApplyDeferredInExit<Entries>
658
894
  ) => BoundTransitionScheduleResult<S, Root, M, Entries>
659
895
  /**
660
896
  * Builds a schedule whose systems run only while every condition
661
897
  * passes (in addition to their own `when`). Nested schedules are
662
- * gated too; marker steps still run. Gated systems drop the events
898
+ * gated too; marker steps still run, and so do the transition schedules
899
+ * an `applyStateTransitions(bundle)` in the group runs. Gated systems drop the events
663
900
  * published while gated; use `pauseWhen` to keep them. For one system,
664
901
  * use `when` in its spec.
665
902
  */
@@ -691,8 +928,9 @@ export namespace Schema {
691
928
  }
692
929
  readonly Runtime: {
693
930
  /**
694
- * Builds a runtime. Returns a `Result` exactly when a provided resource
695
- * goes through a constructed descriptor and can fail validation.
931
+ * Builds a runtime. Always returns a `Result`: a `Success` (read
932
+ * `.value`) when nothing provided can fail, and a `Result` to check
933
+ * when a provided resource goes through a constructed descriptor.
696
934
  */
697
935
  make: <
698
936
  const ProvidedServices extends Runtime.RuntimeServices<any>,
@@ -701,10 +939,12 @@ export namespace Schema {
701
939
  const Enabled extends Debug.Option | undefined = undefined
702
940
  >(options: {
703
941
  readonly services: ProvidedServices
704
- readonly resources?: Resources
942
+ readonly resources?: Resources & Runtime.ExactResources<S, Resources>
705
943
  readonly machines?: ProvidedMachines
706
944
  /** Attaches a `debug` handle to the runtime; see the `Debug` module. */
707
945
  readonly debug?: Enabled
946
+ /** The game's save migrations (`Snapshot.migrations`): `restore` upgrades older saves with them. */
947
+ readonly saves?: Snapshot.Migrations
708
948
  }) => Runtime.MakeRuntimeResult<S, RuntimeServicesOf<ProvidedServices>, Resources, Root, RuntimeMachinesOf<ProvidedMachines>, Enabled>
709
949
  service: typeof Runtime.service
710
950
  services: typeof Runtime.services