@typeonce/ritmo 0.6.0 → 0.8.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 (148) hide show
  1. package/README.md +2 -0
  2. package/dist/Brand.d.ts +9 -4
  3. package/dist/Brand.d.ts.map +1 -1
  4. package/dist/Brand.js +9 -4
  5. package/dist/Brand.js.map +1 -1
  6. package/dist/Clock.d.ts +186 -0
  7. package/dist/Clock.d.ts.map +1 -0
  8. package/dist/Clock.js +111 -0
  9. package/dist/Clock.js.map +1 -0
  10. package/dist/Command.d.ts +250 -41
  11. package/dist/Command.d.ts.map +1 -1
  12. package/dist/Command.js +59 -23
  13. package/dist/Command.js.map +1 -1
  14. package/dist/Condition.d.ts +12 -1
  15. package/dist/Condition.d.ts.map +1 -1
  16. package/dist/Condition.js.map +1 -1
  17. package/dist/Debug.d.ts +163 -15
  18. package/dist/Debug.d.ts.map +1 -1
  19. package/dist/Debug.js.map +1 -1
  20. package/dist/Decode.d.ts +1 -1
  21. package/dist/Decode.js +1 -1
  22. package/dist/Descriptor.d.ts +67 -24
  23. package/dist/Descriptor.d.ts.map +1 -1
  24. package/dist/Descriptor.js +32 -15
  25. package/dist/Descriptor.js.map +1 -1
  26. package/dist/Entity.d.ts +22 -5
  27. package/dist/Entity.d.ts.map +1 -1
  28. package/dist/Entity.js +2 -2
  29. package/dist/Entity.js.map +1 -1
  30. package/dist/EntityScope.d.ts +26 -1
  31. package/dist/EntityScope.d.ts.map +1 -1
  32. package/dist/EntityScope.js +14 -2
  33. package/dist/EntityScope.js.map +1 -1
  34. package/dist/HostClock.d.ts +49 -0
  35. package/dist/HostClock.d.ts.map +1 -0
  36. package/dist/HostClock.js +46 -0
  37. package/dist/HostClock.js.map +1 -0
  38. package/dist/Inspector.d.ts +7 -1
  39. package/dist/Inspector.d.ts.map +1 -1
  40. package/dist/Inspector.js.map +1 -1
  41. package/dist/Machine.d.ts +67 -36
  42. package/dist/Machine.d.ts.map +1 -1
  43. package/dist/Machine.js +10 -3
  44. package/dist/Machine.js.map +1 -1
  45. package/dist/Query.d.ts +74 -26
  46. package/dist/Query.d.ts.map +1 -1
  47. package/dist/Query.js +32 -28
  48. package/dist/Query.js.map +1 -1
  49. package/dist/Runtime.d.ts +39 -16
  50. package/dist/Runtime.d.ts.map +1 -1
  51. package/dist/Runtime.js +556 -87
  52. package/dist/Runtime.js.map +1 -1
  53. package/dist/Schedule.d.ts +217 -10
  54. package/dist/Schedule.d.ts.map +1 -1
  55. package/dist/Schedule.js +5 -0
  56. package/dist/Schedule.js.map +1 -1
  57. package/dist/Schema.d.ts +196 -44
  58. package/dist/Schema.d.ts.map +1 -1
  59. package/dist/Schema.js +29 -11
  60. package/dist/Schema.js.map +1 -1
  61. package/dist/Snapshot.d.ts +14 -0
  62. package/dist/Snapshot.d.ts.map +1 -1
  63. package/dist/Snapshot.js.map +1 -1
  64. package/dist/System.d.ts +212 -58
  65. package/dist/System.d.ts.map +1 -1
  66. package/dist/System.js +84 -6
  67. package/dist/System.js.map +1 -1
  68. package/dist/index.d.ts +8 -7
  69. package/dist/index.d.ts.map +1 -1
  70. package/dist/index.js +8 -7
  71. package/dist/index.js.map +1 -1
  72. package/dist/internal/cells.d.ts +3 -3
  73. package/dist/internal/cells.d.ts.map +1 -1
  74. package/dist/internal/cells.js +9 -11
  75. package/dist/internal/cells.js.map +1 -1
  76. package/dist/internal/debug.d.ts +13 -0
  77. package/dist/internal/debug.d.ts.map +1 -1
  78. package/dist/internal/debug.js +192 -7
  79. package/dist/internal/debug.js.map +1 -1
  80. package/dist/internal/equal.d.ts +8 -0
  81. package/dist/internal/equal.d.ts.map +1 -0
  82. package/dist/internal/equal.js +38 -0
  83. package/dist/internal/equal.js.map +1 -0
  84. package/dist/internal/fragments.d.ts.map +1 -1
  85. package/dist/internal/fragments.js +11 -1
  86. package/dist/internal/fragments.js.map +1 -1
  87. package/dist/internal/game.d.ts.map +1 -1
  88. package/dist/internal/game.js +41 -6
  89. package/dist/internal/game.js.map +1 -1
  90. package/dist/internal/order.d.ts +10 -0
  91. package/dist/internal/order.d.ts.map +1 -0
  92. package/dist/internal/order.js +122 -0
  93. package/dist/internal/order.js.map +1 -0
  94. package/dist/internal/queries.d.ts.map +1 -1
  95. package/dist/internal/queries.js +32 -10
  96. package/dist/internal/queries.js.map +1 -1
  97. package/dist/internal/requirements.d.ts +26 -0
  98. package/dist/internal/requirements.d.ts.map +1 -0
  99. package/dist/internal/requirements.js +46 -0
  100. package/dist/internal/requirements.js.map +1 -0
  101. package/package.json +1 -1
  102. package/src/Brand.ts +9 -4
  103. package/src/Clock.ts +311 -0
  104. package/src/Command.ts +407 -89
  105. package/src/Condition.ts +17 -1
  106. package/src/Debug.ts +172 -14
  107. package/src/Decode.ts +1 -1
  108. package/src/Descriptor.ts +92 -37
  109. package/src/Entity.ts +24 -5
  110. package/src/EntityScope.ts +27 -2
  111. package/src/HostClock.ts +64 -0
  112. package/src/Inspector.ts +7 -2
  113. package/src/Machine.ts +81 -37
  114. package/src/Query.ts +110 -38
  115. package/src/Runtime.ts +607 -111
  116. package/src/Schedule.ts +380 -13
  117. package/src/Schema.ts +249 -63
  118. package/src/Snapshot.ts +4 -0
  119. package/src/System.ts +248 -93
  120. package/src/index.ts +8 -7
  121. package/src/internal/cells.ts +10 -12
  122. package/src/internal/debug.ts +208 -12
  123. package/src/internal/equal.ts +31 -0
  124. package/src/internal/fragments.ts +12 -2
  125. package/src/internal/game.ts +61 -7
  126. package/src/internal/order.ts +144 -0
  127. package/src/internal/queries.ts +37 -13
  128. package/src/internal/requirements.ts +71 -0
  129. package/dist/Definition.d.ts +0 -99
  130. package/dist/Definition.d.ts.map +0 -1
  131. package/dist/Definition.js +0 -68
  132. package/dist/Definition.js.map +0 -1
  133. package/dist/Feature.d.ts +0 -106
  134. package/dist/Feature.d.ts.map +0 -1
  135. package/dist/Feature.js +0 -87
  136. package/dist/Feature.js.map +0 -1
  137. package/dist/Fx.d.ts +0 -128
  138. package/dist/Fx.d.ts.map +0 -1
  139. package/dist/Fx.js +0 -112
  140. package/dist/Fx.js.map +0 -1
  141. package/dist/internal/definition.d.ts +0 -17
  142. package/dist/internal/definition.d.ts.map +0 -1
  143. package/dist/internal/definition.js +0 -25
  144. package/dist/internal/definition.js.map +0 -1
  145. package/src/Definition.ts +0 -106
  146. package/src/Feature.ts +0 -241
  147. package/src/Fx.ts +0 -176
  148. package/src/internal/definition.ts +0 -56
package/src/Schema.ts CHANGED
@@ -13,8 +13,11 @@
13
13
  * 3. bind one `Game` with `Schema.bind(...)`
14
14
  * 4. build a runtime from that bound `Game`
15
15
  *
16
- * `Schema.Feature` composes fragments and their schedules before binding; see
17
- * the `feature` module.
16
+ * There is no plugin or feature object. A reusable slice of a game is a
17
+ * fragment (its descriptors) plus plain functions `(Game, options) => System |
18
+ * Schedule` that build its systems from the bound `Game`, like
19
+ * `RenderSync.system(Game, ...)` and `InputCapture.system(Game, ...)`. List
20
+ * every fragment in one `Schema.bind(...)` call.
18
21
  *
19
22
  * @module Schema
20
23
  * @docGroup core
@@ -65,8 +68,6 @@ import type * as Runtime from "./Runtime.ts"
65
68
  import type * as Schedule from "./Schedule.ts"
66
69
  import type * as System from "./System.ts"
67
70
 
68
- export { Feature } from "./Feature.ts"
69
-
70
71
  /**
71
72
  * A mapping from schema keys to nominal descriptors.
72
73
  */
@@ -74,20 +75,80 @@ export type Registry = Record<string, Descriptor.Any>
74
75
 
75
76
  /**
76
77
  * A closed schema: the components, resources, events, and relations a world
77
- * may contain.
78
+ * may contain, and the component requirements (`Schema.requires`) every
79
+ * entity of it keeps.
78
80
  */
79
81
  export interface SchemaDefinition<
80
82
  out Components extends Registry = {},
81
83
  out Resources extends Registry = {},
82
84
  out Events extends Registry = {},
83
- out Relations extends Record<string, Relation.Relation.Any> = {}
85
+ out Relations extends Record<string, Relation.Relation.Any> = {},
86
+ out Requires extends RequirementPair = never
84
87
  > {
85
88
  readonly components: Components
86
89
  readonly resources: Resources
87
90
  readonly events: Events
88
91
  readonly relations: Relations
92
+ /** Component requirements declared with `Schema.requires(...)`. */
93
+ readonly requires: ReadonlyArray<ComponentRequirement<Requires>>
94
+ }
95
+
96
+ /**
97
+ * One requirement carried in a schema type, by component name:
98
+ * `[requirer, required]`. One pair per required component, so lookups by
99
+ * either end stay exact.
100
+ */
101
+ export type RequirementPair = readonly [string, string]
102
+
103
+ /**
104
+ * One `Schema.requires(...)` declaration: every entity holding `component`
105
+ * also holds each of `required`.
106
+ */
107
+ export interface ComponentRequirement<out Pairs extends RequirementPair = RequirementPair> {
108
+ readonly kind: "requires"
109
+ readonly component: Descriptor<"component", string, any>
110
+ readonly required: ReadonlyArray<Descriptor<"component", string, any>>
111
+ readonly __pairs?: (_: never) => Pairs
89
112
  }
90
113
 
114
+ type AnyComponentDescriptor = Descriptor<"component", string, any>
115
+
116
+ /** One `[from, to]` pair per required name. */
117
+ type RequirementPairs<From extends string, To extends string> = To extends string ? readonly [From, To] : never
118
+
119
+ type PairsOf<R> = R extends ComponentRequirement<infer Pairs> ? Pairs : never
120
+
121
+ /**
122
+ * Declares that every entity holding `component` also holds each of
123
+ * `required`. List it in a fragment's `requires`.
124
+ *
125
+ * The compiler then rejects every command that would break it:
126
+ * `commands.spawn` / `spawnIn` of a draft missing a required entry,
127
+ * `commands.insert` of `component` on an entity not proven to hold the
128
+ * rest, and `commands.remove` of a required component while a requirer may
129
+ * stay. `Game.System.spawns(component)` also allows spawning what it
130
+ * requires. Nothing is ever filled in with a default: the draft must carry
131
+ * every value.
132
+ *
133
+ * Requirements chain (`A` requires `B`, `B` requires `C`) and may name
134
+ * components of other fragments of the same `Schema.bind`.
135
+ *
136
+ * @example
137
+ * ```ts
138
+ * const Gameplay = Schema.fragment({
139
+ * components: { Player, Position, Velocity },
140
+ * requires: [Schema.requires(Player, Position, Velocity)]
141
+ * })
142
+ * ```
143
+ */
144
+ export const requires = <
145
+ D extends AnyComponentDescriptor,
146
+ const Required extends readonly [AnyComponentDescriptor, ...Array<AnyComponentDescriptor>]
147
+ >(
148
+ component: D,
149
+ ...required: Required
150
+ ): ComponentRequirement<RequirementPairs<D["name"], Required[number]["name"]>> => ({ kind: "requires", component, required })
151
+
91
152
  const schemaRootTypeId = "~ritmo/SchemaRoot" as const
92
153
 
93
154
  export type RootToken<Name extends string = string> = {
@@ -111,7 +172,8 @@ export type MergeSchemaDefinitions<
111
172
  Schema.Components<A> & Schema.Components<B>,
112
173
  Schema.Resources<A> & Schema.Resources<B>,
113
174
  Schema.Events<A> & Schema.Events<B>,
114
- Schema.Relations<A> & Schema.Relations<B>
175
+ Schema.Relations<A> & Schema.Relations<B>,
176
+ Schema.Requires<A> | Schema.Requires<B>
115
177
  >
116
178
 
117
179
  /**
@@ -185,6 +247,24 @@ export type ValidateFragments<Fragments extends ReadonlyArray<Schema.Any>> =
185
247
  readonly __schemaConflicts__: FragmentConflicts<Fragments>
186
248
  }
187
249
 
250
+ type UnregisteredRequirements<S extends Schema.Any> =
251
+ Exclude<Schema.Requires<S>[0] | Schema.Requires<S>[1], Schema.ComponentDescriptor<S>["name"]>
252
+
253
+ /**
254
+ * Rejects requirements naming a component the bound schema does not
255
+ * register. Checked at `bind`, so a fragment may require a component another
256
+ * fragment registers. Erased schemas (`Schema.Any`) are checked by `bind` at
257
+ * runtime instead.
258
+ */
259
+ export type ValidateRequires<S extends Schema.Any> =
260
+ [UnregisteredRequirements<S>] extends [never]
261
+ ? unknown
262
+ : string extends Schema.Requires<S>[1]
263
+ ? unknown
264
+ : {
265
+ readonly __schemaRequires__: `${UnregisteredRequirements<S>} is named in Schema.requires(...) but no fragment registers it: add it to a fragment's components`
266
+ }
267
+
188
268
  /**
189
269
  * Creates one explicit root token for schema-bound long-lived references.
190
270
  *
@@ -223,8 +303,9 @@ export const empty = (): SchemaDefinition => emptySchema()
223
303
  * @example
224
304
  * ```ts
225
305
  * const Combat = Schema.fragment({
226
- * components: { Health, Damage },
227
- * events: { Hit }
306
+ * components: { Health, Damage, Enemy },
307
+ * events: { Hit },
308
+ * requires: [Schema.requires(Enemy, Health)]
228
309
  * })
229
310
  * ```
230
311
  */
@@ -232,33 +313,22 @@ export const fragment = <
232
313
  const Components extends Registry = {},
233
314
  const Resources extends Registry = {},
234
315
  const Events extends Registry = {},
235
- const Relations extends Record<string, Relation.Relation.Any> = {}
316
+ const Relations extends Record<string, Relation.Relation.Any> = {},
317
+ const Requires extends ReadonlyArray<ComponentRequirement> = readonly []
236
318
  >(definition: {
237
319
  readonly components?: Components
238
320
  readonly resources?: Resources
239
321
  readonly events?: Events
240
322
  readonly relations?: Relations
241
- } & ValidateFragments<[SchemaDefinition<Components, Resources, Events, Relations>]>): SchemaDefinition<Components, Resources, Events, Relations> =>
323
+ readonly requires?: Requires
324
+ } & ValidateFragments<[SchemaDefinition<Components, Resources, Events, Relations>]>): SchemaDefinition<Components, Resources, Events, Relations, PairsOf<Requires[number]>> =>
242
325
  mergeSchemas(emptySchema(), {
243
326
  components: definition.components ?? {},
244
327
  resources: definition.resources ?? {},
245
328
  events: definition.events ?? {},
246
- relations: definition.relations ?? {}
247
- }) as SchemaDefinition<Components, Resources, Events, Relations>
248
-
249
- /**
250
- * Merges two schema fragments into a larger closed schema.
251
- *
252
- * Duplicate keys and descriptor names are rejected at the type level and at
253
- * runtime.
254
- */
255
- export const merge = <
256
- A extends Schema.Any,
257
- B extends Schema.Any
258
- >(
259
- left: A,
260
- right: B & ValidateFragments<[A, B]>
261
- ): MergeSchemaDefinitions<A, B> => mergeSchemas(left, right) as MergeSchemaDefinitions<A, B>
329
+ relations: definition.relations ?? {},
330
+ requires: definition.requires ?? []
331
+ }) as SchemaDefinition<Components, Resources, Events, Relations, PairsOf<Requires[number]>>
262
332
 
263
333
  /**
264
334
  * Composes one or more fragments and returns one bound `Game` surface.
@@ -275,18 +345,18 @@ export const merge = <
275
345
  */
276
346
  export function bind<
277
347
  S extends Schema.Any
278
- >(schema: S & ValidateFragments<[S]>): Schema.Game<S, S>
348
+ >(schema: S & ValidateFragments<[S]> & ValidateRequires<S>): Schema.Game<S, S>
279
349
  export function bind<
280
350
  S extends Schema.Any,
281
351
  const Name extends string
282
- >(schema: S & ValidateFragments<[S]>, root: RootToken<Name>): Schema.Game<S, RootToken<Name>>
352
+ >(schema: S & ValidateFragments<[S]> & ValidateRequires<S>, root: RootToken<Name>): Schema.Game<S, RootToken<Name>>
283
353
  export function bind<
284
354
  const Fragments extends readonly [Schema.Any, Schema.Any, ...Array<Schema.Any>]
285
- >(...fragments: Fragments & ValidateFragments<Fragments>): Schema.Game<BuildFragments<Fragments>, BuildFragments<Fragments>>
355
+ >(...fragments: Fragments & ValidateFragments<Fragments> & ValidateRequires<BuildFragments<Fragments>>): Schema.Game<BuildFragments<Fragments>, BuildFragments<Fragments>>
286
356
  export function bind<
287
357
  const Fragments extends readonly [Schema.Any, Schema.Any, ...Array<Schema.Any>],
288
358
  const Name extends string
289
- >(...args: [...Fragments, RootToken<Name>] & ValidateFragments<Fragments>): Schema.Game<BuildFragments<Fragments>, RootToken<Name>>
359
+ >(...args: [...Fragments, RootToken<Name>] & ValidateFragments<Fragments> & ValidateRequires<BuildFragments<Fragments>>): Schema.Game<BuildFragments<Fragments>, RootToken<Name>>
290
360
  export function bind(...args: ReadonlyArray<Schema.Any | RootToken>) {
291
361
  const maybeRoot = args[args.length - 1]
292
362
  const fragments = (isRootToken(maybeRoot) ? args.slice(0, -1) : args) as Array<Schema.Any>
@@ -311,7 +381,7 @@ type QuerySelectionAccess<S extends Schema.Any, Root> =
311
381
 
312
382
  interface BoundSystemAccess<S extends Schema.Any, Root> extends System.SystemAccessInput {
313
383
  readonly queries?: Record<string, Query.Any<Root>>
314
- readonly resources?: Record<string, System.ResourceRead<ResourceDescriptor<S>> | System.ResourceWrite<ResourceDescriptor<S>>>
384
+ readonly resources?: Record<string, System.ResourceRead<ResourceDescriptor<S>> | System.ResourceChanged<ResourceDescriptor<S>> | System.ResourceWrite<ResourceDescriptor<S>>>
315
385
  readonly events?: Record<string, System.EventRead<EventDescriptor<S>> | System.EventWrite<EventDescriptor<S>>>
316
386
  readonly machines?: Record<string, Machine.MachineRead<Schema.BoundStateMachine<Root>>>
317
387
  readonly nextMachines?: Record<string, Machine.NextMachineWrite<Schema.BoundStateMachine<Root>>>
@@ -322,11 +392,12 @@ interface BoundSystemAccess<S extends Schema.Any, Root> extends System.SystemAcc
322
392
  readonly pauseWhen?: ReadonlyArray<Machine.Condition<Root>>
323
393
  readonly transitions?: Record<string, Machine.TransitionRead<Schema.BoundStateMachine<Root>>>
324
394
  readonly local?: () => unknown
395
+ readonly commands?: Command.CommandAccess<ComponentDescriptor<S>, ComponentDescriptor<S>, ComponentDescriptor<S>, ComponentDescriptor<S>, EntityScope.EntityScope<string, Root>, RelationDescriptor<S>>
325
396
  }
326
397
 
327
398
  interface BoundCheckAccess<S extends Schema.Any, Root> extends Condition.CheckAccessInput {
328
399
  readonly queries?: Record<string, Query.Any<Root>>
329
- readonly resources?: Record<string, System.ResourceRead<ResourceDescriptor<S>>>
400
+ readonly resources?: Record<string, System.ResourceRead<ResourceDescriptor<S>> | System.ResourceChanged<ResourceDescriptor<S>> | System.ResourceWrite<ResourceDescriptor<S>>>
330
401
  readonly machines?: Record<string, Machine.MachineRead<Schema.BoundStateMachine<Root>>>
331
402
  }
332
403
 
@@ -341,10 +412,10 @@ interface BoundInspectorAccess<S extends Schema.Any, Root> extends Inspector.Ins
341
412
  }
342
413
 
343
414
  type BoundScheduleEntry<S extends Schema.Any, Root> =
344
- | Schema.BoundSystem<S, Root, any, any, any>
415
+ | Schema.BoundSystem<S, Root, any, any>
345
416
  | Schedule.ApplyDeferredStep
346
417
  | Schedule.ApplyStateTransitionsStep<any, Root>
347
- | Schema.BoundSchedule<S, Root, any, any>
418
+ | Schedule.ScheduleDefinition<S, any, Root, any, any, Schedule.AnyScheduleOrder>
348
419
 
349
420
  type BoundTransitionEntry<S extends Schema.Any, Root> =
350
421
  Exclude<BoundScheduleEntry<S, Root>, Schedule.ApplyStateTransitionsStep<any, Root>>
@@ -359,7 +430,8 @@ type BoundTransitionScheduleResult<
359
430
  M,
360
431
  Schedule.CompositionExactRequirements<Entries>,
361
432
  Root,
362
- Schedule.CompositionFailure<Entries>
433
+ Schedule.CompositionFailure<Entries>,
434
+ Schedule.CompositionOrder<Entries>
363
435
  >
364
436
 
365
437
  type BoundTransitionBundleInput<S extends Schema.Any, Root> =
@@ -376,7 +448,8 @@ type BoundTransitionBundleResult<
376
448
  Schedule.TransitionBundleRequirements<Schedule.FlattenTransitionEntries<Entries>>,
377
449
  Root,
378
450
  Schedule.TransitionBundleRequirements<Schedule.FlattenTransitionEntries<Entries>>,
379
- Schedule.TransitionBundleFailure<Schedule.FlattenTransitionEntries<Entries>>
451
+ Schedule.TransitionBundleFailure<Schedule.FlattenTransitionEntries<Entries>>,
452
+ Schedule.CompositionOrder<Entries>
380
453
  >
381
454
 
382
455
  /**
@@ -386,12 +459,14 @@ export namespace Schema {
386
459
  /**
387
460
  * Any complete schema definition.
388
461
  */
389
- export type Any = SchemaDefinition<Registry, Registry, Registry, Record<string, Relation.Relation.Any>>
462
+ export type Any = SchemaDefinition<Registry, Registry, Registry, Record<string, Relation.Relation.Any>, RequirementPair>
390
463
 
391
464
  export type Components<T extends Any> = T["components"]
392
465
  export type Resources<T extends Any> = T["resources"]
393
466
  export type Events<T extends Any> = T["events"]
394
467
  export type Relations<T extends Any> = T["relations"]
468
+ /** The component requirements of a schema, as `[requirer, required]` name pairs (`Schema.requires`). */
469
+ export type Requires<T extends Any> = T extends SchemaDefinition<any, any, any, any, infer Pairs> ? Pairs : never
395
470
 
396
471
  export type ComponentValue<T extends Any, K extends keyof Components<T>> = Descriptor.Value<Components<T>[K]>
397
472
  export type ResourceValue<T extends Any, K extends keyof Resources<T>> = Descriptor.Value<Resources<T>[K]>
@@ -413,11 +488,10 @@ export namespace Schema {
413
488
  S extends Any,
414
489
  Root,
415
490
  Spec extends System.AnySystemSpec = System.AnySystemSpec,
416
- A = void,
417
491
  E = never,
418
492
  Name extends string = string,
419
493
  Needs extends Requirement.Requirement = Requirement.Requirement
420
- > = System.SystemDefinition<Spec & { readonly schema: S }, A, E, Root, Name, Needs>
494
+ > = System.SystemDefinition<Spec & { readonly schema: S }, E, Root, Name, Needs>
421
495
 
422
496
  /** A schema-bound read-only world projection. */
423
497
  export type BoundInspector<
@@ -437,7 +511,7 @@ export namespace Schema {
437
511
  Root,
438
512
  Needs extends Requirement.Requirement = Requirement.Requirement,
439
513
  Failure extends System.SystemFailure = never
440
- > = Schedule.ScheduleDefinition<S, Needs, Root, Needs, Failure>
514
+ > = Schedule.ScheduleDefinition<S, Needs, Root, Needs, Failure, Schedule.ScheduleOrder>
441
515
 
442
516
  /**
443
517
  * A schema-bound finite-state machine.
@@ -445,24 +519,27 @@ export namespace Schema {
445
519
  export type BoundStateMachine<
446
520
  Root,
447
521
  Name extends string = string,
448
- Values extends readonly [Machine.StateValue, ...Machine.StateValue[]] = readonly [Machine.StateValue, ...Machine.StateValue[]]
449
- > = Machine.StateMachineDefinition<Name, Values, Root>
522
+ Values extends readonly [Machine.StateValue, ...Machine.StateValue[]] = readonly [Machine.StateValue, ...Machine.StateValue[]],
523
+ Parent = unknown
524
+ > = Machine.StateMachineDefinition<Name, Values, Root, Parent>
450
525
 
451
526
  export type BoundTransitionSchedule<
452
527
  S extends Any,
453
528
  Root,
454
529
  M extends BoundStateMachine<Root> = BoundStateMachine<Root>,
455
530
  Needs extends Requirement.Requirement = Requirement.Requirement,
456
- Failure extends System.SystemFailure = never
457
- > = Machine.TransitionScheduleDefinition<S, M, Needs, Root, Failure>
531
+ Failure extends System.SystemFailure = never,
532
+ Order extends Schedule.AnyScheduleOrder = Schedule.AnyScheduleOrder
533
+ > = Machine.TransitionScheduleDefinition<S, M, Needs, Root, Failure, Order>
458
534
 
459
535
  export type BoundTransitionBundle<
460
536
  S extends Any,
461
537
  Root,
462
538
  Entries extends ReadonlyArray<BoundTransitionSchedule<S, Root, any, any, any>> = ReadonlyArray<BoundTransitionSchedule<S, Root, any, any, any>>,
463
539
  Needs extends Requirement.Requirement = Requirement.Requirement,
464
- Failure extends System.SystemFailure = never
465
- > = Schedule.TransitionBundleDefinition<S, Entries, Needs, Root, Needs, Failure>
540
+ Failure extends System.SystemFailure = never,
541
+ Order extends Schedule.AnyScheduleOrder = Schedule.AnyScheduleOrder
542
+ > = Schedule.TransitionBundleDefinition<S, Entries, Needs, Root, Needs, Failure, Order>
466
543
 
467
544
  /**
468
545
  * A schema-bound runtime branded to one bound schema root.
@@ -480,8 +557,40 @@ export namespace Schema {
480
557
  */
481
558
  export interface Game<S extends Any, Root = S> {
482
559
  readonly schema: S
483
- /** Creates a typed ownership scope for scene or level entities. */
484
- readonly EntityScope: <const Name extends string>(name: Name) => EntityScope.EntityScope<Name, Root>
560
+ /**
561
+ * Creates a typed ownership scope for scene or level entities: a flat
562
+ * group whose members are spawned with `commands.spawnIn(...)` and
563
+ * despawned together. Use `Descriptor.Hierarchy` instead for a
564
+ * parent-child tree with ordered children, traversal, and recursive
565
+ * despawn.
566
+ *
567
+ * When the group lives exactly as long as a state, name the state change
568
+ * that ends it: `despawnOnExit: [Flow, "Playing"]` despawns every member
569
+ * when `applyStateTransitions(...)` commits leaving `Playing` (after its
570
+ * `onExit` schedules, which still see the members), and
571
+ * `despawnOnEnter: [Flow, "Playing"]` when it commits entering it (before
572
+ * its `onEnter` schedules, so what they spawn survives). Use
573
+ * `commands.despawnScope(...)` instead when the wipe is not tied to a
574
+ * state change. Scope names are unique per game.
575
+ *
576
+ * @example
577
+ * ```ts
578
+ * const RunScope = Game.EntityScope("Shooter/Run", { despawnOnExit: [Flow, "Playing"] })
579
+ * ```
580
+ */
581
+ readonly EntityScope: <
582
+ const Name extends string,
583
+ Enter extends Machine.StateMachine.Any = never,
584
+ Exit extends Machine.StateMachine.Any = never
585
+ >(
586
+ name: Name,
587
+ options?: {
588
+ /** Despawns every member when the machine enters this state, before its `onEnter` schedules run. */
589
+ readonly despawnOnEnter?: readonly [Enter & Machine.SameGame<Enter, Root>, Machine.StateMachine.Value<Enter>]
590
+ /** Despawns every member when the machine leaves this state, after its `onExit` schedules run. */
591
+ readonly despawnOnExit?: readonly [Exit & Machine.SameGame<Exit, Root>, Machine.StateMachine.Value<Exit>]
592
+ }
593
+ ) => EntityScope.EntityScope<Name, Root>
485
594
  /** Declares a reusable, requirement-checked read-only world projection. */
486
595
  readonly Inspector: <
487
596
  const Name extends string,
@@ -521,12 +630,20 @@ export namespace Schema {
521
630
  readonly withoutRelations?: WithoutRelations
522
631
  readonly withRelated?: WithRelated
523
632
  readonly withoutRelated?: WithoutRelated
524
- }): QueryModule.QuerySpec<Selection, With, Without, Filters, WithRelations, WithoutRelations, WithRelated, WithoutRelated, Root>
633
+ // Uninferred: an omitted list is `[]`, never inferred from the spec it is written in (`queries: { ... }`).
634
+ }): 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>
525
635
  read: <D extends ComponentDescriptor<S>>(descriptor: D) => QueryModule.ReadAccess<D>
526
- write: <D extends ComponentDescriptor<S>>(descriptor: D) => QueryModule.WriteAccess<D>
636
+ write: <D extends ComponentDescriptor<S>>(descriptor: D & QueryModule.Writable<D>) => QueryModule.WriteAccess<D>
527
637
  optional: <D extends ComponentDescriptor<S>>(descriptor: D) => QueryModule.OptionalReadAccess<D>
528
638
  added: <D extends ComponentDescriptor<S>>(descriptor: D) => QueryModule.AddedFilter<D>
529
639
  changed: <D extends ComponentDescriptor<S>>(descriptor: D) => QueryModule.ChangedFilter<D>
640
+ anyOf: <
641
+ const Filters extends readonly [
642
+ QueryModule.LifecycleFilter<ComponentDescriptor<S>>,
643
+ QueryModule.LifecycleFilter<ComponentDescriptor<S>>,
644
+ ...ReadonlyArray<QueryModule.LifecycleFilter<ComponentDescriptor<S>>>
645
+ ]
646
+ >(...filters: Filters) => QueryModule.AnyOfFilter<Filters[number]["descriptor"]>
530
647
  readRelation: <R extends RelationDescriptor<S>>(descriptor: R) => Relation.RelationReadAccess<R, S, Root>
531
648
  optionalRelation: <R extends RelationDescriptor<S>>(descriptor: R) => Relation.OptionalRelationReadAccess<R, S, Root>
532
649
  readRelated: <R extends RelationDescriptor<S>>(descriptor: R) => Relation.RelatedReadAccess<R, S, Root>
@@ -537,16 +654,15 @@ export namespace Schema {
537
654
  spawn: <const Entries extends ReadonlyArray<Command.SpawnEntry<S>> = readonly []>(
538
655
  ...entries: Entries
539
656
  ) => Command.DraftFor<S, Entries, {}, Root>
540
- /** Adds entries to a draft. */
541
- insert: <P extends Entity.ComponentProof, const Entries extends ReadonlyArray<Command.SpawnEntry<S>>>(
657
+ /**
658
+ * Adds entries to a draft (a value not yet spawned). Use
659
+ * `commands.insert` for an entity that already exists.
660
+ */
661
+ extend: <P extends Entity.ComponentProof, const Entries extends ReadonlyArray<Command.SpawnEntry<S>>>(
542
662
  draft: Entity.EntityDraft<S, P, Root>,
543
663
  ...entries: Entries
544
664
  ) => Command.DraftFor<S, Entries, P, Root>
545
665
  entry: <D extends ComponentDescriptor<S>>(descriptor: D, value: Descriptor.Value<D>) => Command.Entry<D>
546
- entryResult: <D extends ComponentDescriptor<S>, E>(
547
- descriptor: D,
548
- result: Result.Result<Descriptor.Value<D>, E>
549
- ) => Result.Result<Command.Entry<D>, E>
550
666
  entryRaw: <D extends Extract<ComponentDescriptor<S>, ConstructedDescriptor<"component", string, any, any, any>>>(
551
667
  descriptor: D,
552
668
  raw: Descriptor.Raw<D>
@@ -557,15 +673,57 @@ export namespace Schema {
557
673
  target: Entity.EntityId<S, Root>
558
674
  ) => Entity.EntityDraft<S, P, Root>
559
675
  }
676
+ /**
677
+ * Declares one game-wide mode (menu, playing, paused): queued with
678
+ * `nextState`, committed at `applyStateTransitions(...)`, gating systems
679
+ * with `Game.Condition.inState(...)`. Use `Descriptor.EntityState(...)`
680
+ * instead for a state each entity has on its own.
681
+ */
560
682
  readonly StateMachine: {
561
683
  <
562
684
  const Name extends string,
563
685
  const Values extends readonly [Machine.StateValue, ...Machine.StateValue[]]
564
686
  >(name: Name, values: Values): Schema.BoundStateMachine<Root, Name, Values>
687
+ /**
688
+ * Declares a sub-state: a machine that exists only while `within[0]` is
689
+ * in state `within[1]` (a battle phase that only means something while
690
+ * playing). Whenever the parent enters that state, the sub-state enters
691
+ * the value given to `Game.Runtime.machine(...)`; when the parent leaves
692
+ * it, the sub-state exits and has no value: `get()` is `undefined`,
693
+ * `Game.Condition.inState(Sub, x)` is false, and `nextMachines.sub.set(...)`
694
+ * returns a `SubStateInactive` failure. So `inState(Sub, x)` alone
695
+ * implies the parent's state.
696
+ *
697
+ * Use a second top-level machine instead when the two modes are
698
+ * independent (each keeps its value whatever the other does).
699
+ *
700
+ * @example
701
+ * ```ts
702
+ * const Phase = Game.StateMachine("Phase", ["Setup", "Battle"], { within: [Session, "Playing"] })
703
+ * ```
704
+ */
705
+ <
706
+ const Name extends string,
707
+ const Values extends readonly [Machine.StateValue, ...Machine.StateValue[]],
708
+ Parent extends Machine.StateMachine.Any
709
+ >(
710
+ name: Name,
711
+ values: Values,
712
+ options: { readonly within: readonly [Parent & Machine.SameGame<Parent, Root>, Machine.StateMachine.Value<Parent>] }
713
+ ): Schema.BoundStateMachine<Root, Name, Values, Parent>
565
714
  }
566
715
  readonly Condition: {
567
- inState: typeof Machine.inState
568
- stateChanged: typeof Machine.stateChanged
716
+ /**
717
+ * Passes while the machine's committed value is `value`. For a
718
+ * sub-state it fails while the parent is in another state. A machine
719
+ * of another game fails to compile with a message naming the fix.
720
+ */
721
+ inState: <M extends Machine.StateMachine.Any>(
722
+ machine: M & Machine.SameGame<M, Root>,
723
+ value: Machine.StateMachine.Value<M>
724
+ ) => Machine.InStateCondition<M>
725
+ /** Passes in the schedule run where `applyStateTransitions(...)` changed the machine. */
726
+ stateChanged: <M extends Machine.StateMachine.Any>(machine: M & Machine.SameGame<M, Root>) => Machine.StateChangedCondition<M>
569
727
  not: typeof Machine.not
570
728
  and: typeof Machine.and
571
729
  or: typeof Machine.or
@@ -584,17 +742,25 @@ export namespace Schema {
584
742
  <
585
743
  const Name extends string,
586
744
  const Access extends BoundSystemAccess<S, Root>,
587
- A = void,
588
745
  E = never
589
746
  >(
590
747
  name: Name,
591
748
  spec: System.ExactAccess<Access>,
592
- run: System.SystemRun<System.SystemSpec<S, Access, Root>, A, E>
593
- ): BoundSystem<S, Root, System.SystemSpec<S, Access, Root>, A, E, Name, System.SystemAccessNeeds<Access>>
749
+ run: System.SystemRun<System.SystemSpec<S, Access, Root>, E>
750
+ ): BoundSystem<S, Root, System.SystemSpec<S, Access, Root>, E, Name, System.SystemAccessNeeds<Access>>
594
751
  readResource: <D extends ResourceDescriptor<S>>(descriptor: D) => System.ResourceRead<D>
595
752
  writeResource: <D extends ResourceDescriptor<S>>(descriptor: D) => System.ResourceWrite<D>
753
+ changedResource: <D extends ResourceDescriptor<S>>(descriptor: D) => System.ResourceChanged<D>
596
754
  readEvent: <D extends EventDescriptor<S>>(descriptor: D) => System.EventRead<D>
597
755
  writeEvent: <D extends EventDescriptor<S>>(descriptor: D) => System.EventWrite<D>
756
+ /**
757
+ * Requires every writer of this event or resource to run earlier in the
758
+ * same `runtime.tick(...)` call; checked at compile time by `tick`.
759
+ * Accepts `readEvent(...)`, `readResource(...)`, or a query.
760
+ */
761
+ thisTick: <Read extends System.EventRead<EventDescriptor<S>> | System.ResourceRead<ResourceDescriptor<S>> | System.ResourceChanged<ResourceDescriptor<S>> | Query.Any<Root>>(
762
+ read: Read
763
+ ) => Read & System.ThisTick
598
764
  service: typeof System.service
599
765
  machine: <M extends BoundStateMachine<Root>>(machine: M) => Machine.MachineRead<M>
600
766
  nextState: <M extends BoundStateMachine<Root>>(machine: M) => Machine.NextMachineWrite<M>
@@ -603,6 +769,23 @@ export namespace Schema {
603
769
  readRemoved: <D extends ComponentDescriptor<S>>(descriptor: D) => System.RemovedRead<D>
604
770
  readDespawned: () => System.DespawnedRead
605
771
  readRelationFailures: <R extends RelationDescriptor<S>>(relation: R) => System.RelationFailureRead<R>
772
+ /**
773
+ * Declares the commands a system may queue, for its spec's `commands`:
774
+ * `Game.System.commands(Game.System.spawns(Enemy, Position), Game.System.despawns(Enemy))`.
775
+ */
776
+ commands: typeof Command.commands
777
+ /** Allows spawning drafts whose components are all among these and what they require (`Schema.requires`); see `Command.spawns`. */
778
+ spawns: <const Components extends ReadonlyArray<ComponentDescriptor<S>>>(...components: Components) => Command.Permission<"spawns", Components[number] | Command.RequiredDescriptors<S, Components[number]["name"]>>
779
+ /** Allows `insert` (add or replace at the next `applyDeferred()`) of these; see `Command.inserts`. */
780
+ inserts: <const Components extends ReadonlyArray<ComponentDescriptor<S>>>(...components: Components) => Command.Permission<"inserts", Components[number]>
781
+ /** Allows `remove` of these. */
782
+ removes: <const Components extends ReadonlyArray<ComponentDescriptor<S>>>(...components: Components) => Command.Permission<"removes", Components[number]>
783
+ /** Allows `despawn` of entities a query proves have one of these; see `Command.despawns`. */
784
+ despawns: <const Components extends ReadonlyArray<ComponentDescriptor<S>>>(...components: Components) => Command.Permission<"despawns", Components[number]>
785
+ /** Allows `despawnScope` of this scope. */
786
+ despawnsScope: <const Scope extends EntityScope.EntityScope<string, Root>>(scope: Scope) => Command.Permission<"despawnsScope", Scope>
787
+ /** Allows `relate`, `unrelate`, and `reorderChildren` of these relations. */
788
+ relates: <const Relations extends ReadonlyArray<RelationDescriptor<S>>>(...relations: Relations) => Command.Permission<"relates", Relations[number]>
606
789
  }
607
790
  readonly Schedule: {
608
791
  /**
@@ -633,7 +816,9 @@ export namespace Schema {
633
816
  /**
634
817
  * Builds a schedule whose systems run only while every condition
635
818
  * passes (in addition to their own `when`). Nested schedules are
636
- * gated too; marker steps still run.
819
+ * gated too; marker steps still run. Gated systems drop the events
820
+ * published while gated; use `pauseWhen` to keep them. For one system,
821
+ * use `when` in its spec.
637
822
  */
638
823
  when: <
639
824
  const Conditions extends readonly [Machine.Condition<Root>, ...Array<Machine.Condition<Root>>],
@@ -647,6 +832,7 @@ export namespace Schema {
647
832
  * fails: unlike `when`, a paused system keeps its place in the event
648
833
  * streams it reads and sees everything published meanwhile once it
649
834
  * runs again. For freezes whose events stay valid (hit-stop, pause).
835
+ * For one system, use `pauseWhen` in its spec.
650
836
  */
651
837
  pauseWhen: <
652
838
  const Conditions extends readonly [Machine.Condition<Root>, ...Array<Machine.Condition<Root>>],
package/src/Snapshot.ts CHANGED
@@ -115,12 +115,16 @@ export type RestoreError =
115
115
  | { readonly _tag: "UnsupportedVersion"; readonly version: unknown }
116
116
  | { readonly _tag: "UnknownComponent"; readonly entityId: number; readonly name: string }
117
117
  | { readonly _tag: "InvalidComponent"; readonly entityId: number; readonly name: string; readonly error: unknown }
118
+ /** A saved entity holds `requiredBy` but lacks `name`, which `Schema.requires` says it needs. */
119
+ | { readonly _tag: "MissingRequiredComponent"; readonly entityId: number; readonly name: string; readonly requiredBy: string }
118
120
  | { readonly _tag: "UnknownResource"; readonly name: string }
119
121
  | { readonly _tag: "InvalidResource"; readonly name: string; readonly error: unknown }
120
122
  | { readonly _tag: "UnknownRelation"; readonly name: string }
121
123
  | { readonly _tag: "InvalidRelation"; readonly name: string; readonly sourceId: number; readonly targetId: number }
122
124
  | { readonly _tag: "UnknownMachine"; readonly name: string }
123
125
  | { readonly _tag: "InvalidMachineState"; readonly name: string; readonly value: unknown }
126
+ /** A saved sub-state value while its parent machine is not in the state the sub-state exists in. */
127
+ | { readonly _tag: "InactiveSubState"; readonly name: string; readonly parent: string; readonly requires: unknown }
124
128
  | { readonly _tag: "DuplicateEntity"; readonly entityId: number }
125
129
  /** A scope member that is not a saved entity, or is already in another scope. */
126
130
  | { readonly _tag: "InvalidScope"; readonly name: string; readonly entityId: number }