@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/Schedule.ts CHANGED
@@ -27,7 +27,7 @@
27
27
  import type { Condition, ConditionNeedsFromConditions, StateMachine } from "./Machine.ts"
28
28
  import * as Requirement from "./Requirement.ts"
29
29
  import type { Schema } from "./Schema.ts"
30
- import type { FailureOf as SystemFailureOf, SystemDefinition, SystemFailure } from "./System.ts"
30
+ import type { FailureOf as SystemFailureOf, SystemDefinition, SystemFailure, ThisTick } from "./System.ts"
31
31
 
32
32
  export interface ApplyDeferredStep {
33
33
  readonly kind: "applyDeferred"
@@ -46,22 +46,375 @@ export type ScheduleMarkerStep =
46
46
  | ApplyDeferredStep
47
47
  | ApplyStateTransitionsStep<any, any>
48
48
 
49
- type AnySystem = SystemDefinition<any, any, any, any, any, any>
49
+ type AnySystem = SystemDefinition<any, any, any, any, any>
50
50
 
51
51
  export type ScheduleStep = AnySystem | ScheduleMarkerStep
52
52
 
53
+ /**
54
+ * What a schedule carries for `thisTick` checks: its entries in order, and
55
+ * whether any of them has a `Game.System.thisTick` read. Building a schedule
56
+ * computes only the flag; `runtime.tick(...)` walks the entries, and only
57
+ * when some read exists. Schedules never report order errors themselves.
58
+ *
59
+ * `ScheduleOrder`'s defaults claim no `thisTick` reads: `Schema.BoundSchedule`
60
+ * (an annotation) uses it, so a schedule with a read cannot be widened into
61
+ * one and lose its check. Schedule types default to `AnyScheduleOrder`, and a
62
+ * schedule whose order was erased that way fails at `tick` as "not
63
+ * statically known", never silently.
64
+ */
65
+ export interface ScheduleOrder<
66
+ out Entries extends ReadonlyArray<unknown> = ReadonlyArray<unknown>,
67
+ out Reads extends boolean = false
68
+ > {
69
+ readonly entries: Entries
70
+ readonly reads: Reads
71
+ }
72
+
73
+ /** Accepts a schedule with any order summary (constraint positions only). */
74
+ export type AnyScheduleOrder = ScheduleOrder<ReadonlyArray<unknown>, boolean>
75
+
76
+ /**
77
+ * One `thisTick` read whose writers do not all run earlier in the tick, as
78
+ * found at runtime by `tryTick` and the `this-tick-order` lint. `message` is
79
+ * the same text `runtime.tick(...)` reports at compile time.
80
+ */
81
+ export interface OrderIssue {
82
+ /**
83
+ * `lateWriter`: the writer runs after the reader. `unflushedWriter`: a
84
+ * command writer runs before a query reader with no `applyDeferred()` in
85
+ * between.
86
+ */
87
+ readonly kind: "lateWriter" | "unflushedWriter"
88
+ readonly reader: string
89
+ /** The event or resource name, or `queries.<slot>` for a query read. */
90
+ readonly data: string
91
+ /** The writer at fault. */
92
+ readonly writer: string
93
+ readonly message: string
94
+ }
95
+
96
+ /** The message reported when the order of a tick cannot be known statically. */
97
+ export type UnknownOrderError =
98
+ "the order of this tick is not statically known (a schedule or tick argument spread from a widened array) and it contains Game.System.thisTick reads: list the systems and schedules as literal arguments or an `as const` tuple"
99
+
100
+ /** Plugin systems with an erased spec (`any`) take part in no order check. */
101
+ type IsErased<T> = 0 extends 1 & T ? true : false
102
+
103
+ type SpecAccessValues<Spec> =
104
+ Spec extends { readonly events: infer Events; readonly resources: infer Resources }
105
+ ? Events[keyof Events] | Resources[keyof Resources]
106
+ : never
107
+
108
+ type SpecQueryValues<Spec> =
109
+ Spec extends { readonly queries: infer Queries } ? Queries[keyof Queries] : never
110
+
111
+ type CarriedOrder<Entry> =
112
+ "__order" extends keyof Entry
113
+ ? Entry extends { readonly __order?: (_: never) => infer Order extends AnyScheduleOrder } ? Order : never
114
+ : never
115
+
116
+ /** The order an entry carries: a schedule's, or the bundle of an `applyStateTransitions(...)` step. */
117
+ type OrderOf<Entry> =
118
+ Entry extends { readonly kind: "applyStateTransitions"; readonly bundle?: infer Bundle }
119
+ ? CarriedOrder<NonNullable<Bundle>>
120
+ : CarriedOrder<Entry>
121
+
122
+ /**
123
+ * Whether one entry has a `thisTick` read: a cheap flag, computed once per
124
+ * schedule. `"unknown"` is a nested schedule whose type was widened.
125
+ */
126
+ type EntryHasReads<Entry> =
127
+ Entry extends AnySystem
128
+ ? IsErased<Entry["spec"]> extends true ? "no"
129
+ : [Extract<SpecAccessValues<Entry["spec"]> | SpecQueryValues<Entry["spec"]>, ThisTick>] extends [never] ? "no" : "yes"
130
+ : OrderOf<Entry>["reads"] extends infer Reads
131
+ ? boolean extends Reads ? "unknown" : Reads extends true ? "yes" : "no"
132
+ : "no"
133
+
134
+ /** `true` when some entry reads `thisTick`, `boolean` when that cannot be known. */
135
+ type HasReads<Entries extends ReadonlyArray<unknown>> =
136
+ EntryHasReads<Entries[number]> extends infer Flags
137
+ ? "unknown" extends Flags ? boolean : "yes" extends Flags ? true : false
138
+ : false
139
+
140
+ /** The order summary of entries run in this order. */
141
+ export type CompositionOrder<Entries extends ReadonlyArray<unknown>> = ScheduleOrder<Entries, HasReads<Entries>>
142
+
143
+ // The detailed check below runs only from `TickOrderErrors`, for ticks with a
144
+ // read. It never walks a tuple element by element: per read it finds the
145
+ // reader's position at each nesting level, slices the entries after it once,
146
+ // and looks for writers in the union of the systems there. A query read also
147
+ // walks back from the reader to the nearest marker, which is usually close.
148
+
149
+ /** An event or resource read. */
150
+ interface DataRead<Kind, Key, Reader> {
151
+ readonly kind: Kind
152
+ readonly key: Key
153
+ readonly reader: Reader
154
+ }
155
+
156
+ /**
157
+ * A query read: the components its matches must have (`Required`), and every
158
+ * component it names (`Named`: selection slots, optional ones included,
159
+ * `with`, `without`, and filters).
160
+ */
161
+ interface QueryRead<Slot, Required, Named, Reader> {
162
+ readonly kind: "query"
163
+ readonly slot: Slot
164
+ readonly required: Required
165
+ readonly named: Named
166
+ readonly reader: Reader
167
+ }
168
+
169
+ type DescriptorKind<D> = D extends { readonly kind: infer Kind } ? Kind : never
170
+
171
+ type Names<D> = D extends { readonly name: infer Name extends string } ? Name : never
172
+
173
+ type DescriptorsOf<Access> = Access extends { readonly descriptor: infer D } ? D : never
174
+
175
+ /** Every system an entry runs, nested schedules and transition bundles included, as a union. */
176
+ type SystemsIn<Entry> =
177
+ Entry extends AnySystem ? Entry
178
+ : OrderOf<Entry> extends infer Order extends AnyScheduleOrder
179
+ ? [Order] extends [never] ? never : SystemsIn<Order["entries"][number]>
180
+ : never
181
+
182
+ type QueryReadOf<Slot, Q, Reader> =
183
+ Q extends { readonly selection: infer Selection; readonly with: infer With extends ReadonlyArray<unknown>; readonly without: infer Without extends ReadonlyArray<unknown>; readonly filters: infer Filters extends ReadonlyArray<unknown> }
184
+ ? QueryRead<
185
+ Slot,
186
+ Names<{ readonly [K in keyof Selection]: Selection[K] extends { readonly mode: "read" | "write"; readonly descriptor: infer D } ? D : never }[keyof Selection] | With[number]>,
187
+ Names<DescriptorsOf<Selection[keyof Selection]> | With[number] | Without[number] | DescriptorsOf<Filters[number]>>,
188
+ Reader
189
+ >
190
+ : never
191
+
192
+ type SystemReads<Sys> =
193
+ Sys extends AnySystem
194
+ ? IsErased<Sys["spec"]> extends true ? never
195
+ :
196
+ | (Extract<SpecAccessValues<Sys["spec"]>, ThisTick> extends infer Access
197
+ ? Access extends { readonly descriptor: infer D extends { readonly name: string } }
198
+ ? DataRead<DescriptorKind<D>, D["name"], Sys["name"]>
199
+ : never
200
+ : never)
201
+ | (Sys["spec"] extends { readonly queries: infer Queries }
202
+ ? { readonly [K in keyof Queries]: Queries[K] extends ThisTick ? QueryReadOf<K, Queries[K], Sys["name"]> : never }[keyof Queries]
203
+ : never)
204
+ : never
205
+
206
+ /** Names of the systems among `Systems` that write `Key` (other than `Reader`). */
207
+ type WritersOf<Systems, Kind, Key, Reader> =
208
+ Systems extends AnySystem
209
+ ? IsErased<Systems["spec"]> extends true ? never
210
+ : Systems["name"] extends Reader ? never
211
+ : [Extract<SpecAccessValues<Systems["spec"]>, { readonly mode: "write"; readonly descriptor: { readonly kind: Kind; readonly name: Key } }>] extends [never]
212
+ ? never
213
+ : Systems["name"]
214
+ : never
215
+
216
+ /**
217
+ * Whether declared command permissions can change what a query read sees:
218
+ * `spawns` covering every component it requires, or `inserts` / `removes` of
219
+ * any component it names. `despawns` never count.
220
+ */
221
+ type CommandsCount<Commands, Required, Named> =
222
+ CommandNames<Commands> extends readonly [infer Spawns, infer Changes]
223
+ ? [Extract<Changes, Named>] extends [never]
224
+ ? [Spawns] extends [never] ? false
225
+ : [Exclude<Required, Spawns>] extends [never] ? true : false
226
+ : true
227
+ : false
228
+
229
+ /** The component names a system may spawn, and may insert or remove: computed once per system, shared by every read. */
230
+ type CommandNames<Commands> =
231
+ Commands extends { readonly __permissions?: (_: never) => readonly [infer Spawns, infer Inserts, infer Removes, ...Array<unknown>] }
232
+ ? readonly [Names<Spawns>, Names<Inserts | Removes>]
233
+ : readonly [never, never]
234
+
235
+ /** Names of the systems among `Systems` whose commands count for a query read (other than its reader). */
236
+ type CommandWritersOf<Systems, Required, Named, Reader> =
237
+ Systems extends AnySystem
238
+ ? IsErased<Systems["spec"]> extends true ? never
239
+ : Systems["name"] extends Reader ? never
240
+ : Systems["spec"] extends { readonly commands: infer Commands }
241
+ ? CommandsCount<Commands, Required, Named> extends true ? Systems["name"] : never
242
+ : never
243
+ : never
244
+
245
+ type WritersFor<Systems, R> =
246
+ R extends QueryRead<any, infer Required, infer Named, infer Reader> ? CommandWritersOf<Systems, Required, Named, Reader>
247
+ : R extends DataRead<infer Kind, infer Key, infer Reader> ? WritersOf<Systems, Kind, Key, Reader>
248
+ : never
249
+
250
+ /**
251
+ * Some level of the entries is a widened array that may contain a read.
252
+ * `MayRead` is the flag carried by the schedule holding them: a widened array
253
+ * no longer shows its elements, so the flag decides (`boolean`: unknown).
254
+ */
255
+ type UnknownIn<Entries extends ReadonlyArray<unknown>, MayRead extends boolean> =
256
+ Entries extends unknown ? UnknownInOne<Entries, MayRead> : never
257
+
258
+ type UnknownInOne<Entries extends ReadonlyArray<unknown>, MayRead extends boolean> =
259
+ number extends Entries["length"]
260
+ ? [MayRead] extends [false] ? never : UnknownOrderError
261
+ : NestedOrders<Entries[number]> extends infer Order
262
+ ? Order extends AnyScheduleOrder ? UnknownIn<Order["entries"], Order["reads"]> : never
263
+ : never
264
+
265
+ type NestedOrders<Entry> = Entry extends AnySystem ? never : OrderOf<Entry>
266
+
267
+ type NestedEntries<Entry> =
268
+ Entry extends AnySystem ? never
269
+ : OrderOf<Entry> extends infer Order extends AnyScheduleOrder
270
+ ? [Order] extends [never] ? never : Order["entries"]
271
+ : never
272
+
273
+ type TupleOf<Length extends number, Out extends Array<unknown> = []> =
274
+ Out["length"] extends Length ? Out : TupleOf<Length, [...Out, unknown]>
275
+
276
+ /** Literal names of the systems an entry runs; erased (`string`) names identify no one. */
277
+ type NamesIn<Entry> =
278
+ SystemsIn<Entry> extends infer Sys
279
+ ? Sys extends AnySystem ? (string extends Sys["name"] ? never : Sys["name"]) : never
280
+ : never
281
+
282
+ /** Indices (as numbers) of the entries that run the reader. */
283
+ type ReaderIndices<Entries extends ReadonlyArray<unknown>, Reader> = {
284
+ readonly [I in keyof Entries]: Reader extends NamesIn<Entries[I]> ? I : never
285
+ }[number] extends infer Index
286
+ ? Index extends `${infer N extends number}` ? N : never
287
+ : never
288
+
289
+ type LateWriterError<R, Writer> =
290
+ Writer extends string
291
+ ? R extends QueryRead<infer Slot extends string, any, any, infer Reader extends string>
292
+ ? `${Writer} queues commands that ${Reader} needs this tick (queries.${Slot}), but it runs after ${Reader}: move ${Writer} before the applyDeferred() that precedes ${Reader}, or read queries.${Slot} without thisTick`
293
+ : R extends DataRead<any, infer Key extends string, infer Reader extends string>
294
+ ? `${Reader} needs ${Key} from this tick, but its writer ${Writer} runs after it: move ${Writer} before ${Reader}, or read ${Key} without thisTick`
295
+ : never
296
+ : never
297
+
298
+ type UnflushedWriterError<R, Writer> =
299
+ Writer extends string
300
+ ? R extends QueryRead<infer Slot extends string, any, any, infer Reader extends string>
301
+ ? `${Reader} needs this tick's commands for queries.${Slot}, but ${Writer} queues them with no applyDeferred() in between: add Game.Schedule.applyDeferred() after ${Writer}`
302
+ : never
303
+ : never
304
+
305
+ /** Writers of the read's data that run after its reader, at this level and inside the reader's own entry. */
306
+ type LateWriters<Entries, R, Reader extends string> =
307
+ // Distributes over a union of schedules (a tick of one of several), then over each place the reader runs.
308
+ Entries extends ReadonlyArray<unknown> ? LateWritersAt<Entries, ReaderIndices<Entries, Reader>, R, Reader> : never
309
+
310
+ type LateWritersAt<Entries extends ReadonlyArray<unknown>, Index, R, Reader extends string> =
311
+ Index extends number
312
+ ? Entries extends readonly [...TupleOf<Index>, infer At, ...infer Rest]
313
+ ? | LateWriterError<R, WritersFor<SystemsIn<Rest[number]>, R>>
314
+ | LateWriters<NestedEntries<At>, R, Reader>
315
+ : never
316
+ : never
317
+
318
+ /**
319
+ * The systems that run between the last marker and the reader (their commands
320
+ * are still queued when it runs), and whether a marker was found:
321
+ * `[Systems, Flushed]`, one per place the reader runs.
322
+ */
323
+ type Segment<Entries, Reader> =
324
+ Entries extends ReadonlyArray<unknown> ? SegmentAt<Entries, ReaderIndices<Entries, Reader>, Reader> : never
325
+
326
+ type SegmentAt<Entries extends ReadonlyArray<unknown>, Index, Reader> =
327
+ Index extends number
328
+ ? InnerSegment<Entries[Index], Reader> extends readonly [infer Inner, infer Flushed]
329
+ ? Flushed extends true ? readonly [Inner, true]
330
+ : TailBefore<Entries, TupleOf<Index>> extends readonly [infer Outer, infer OuterFlushed]
331
+ ? readonly [Inner | Outer, OuterFlushed]
332
+ : never
333
+ : never
334
+ : never
335
+
336
+ /**
337
+ * `TailSegment` of the entries before a position, walking back by index
338
+ * (`Count` holds one element per entry still to visit).
339
+ */
340
+ type TailBefore<Entries extends ReadonlyArray<unknown>, Count extends ReadonlyArray<unknown>, Acc = never> =
341
+ Count extends readonly [unknown, ...infer Rest]
342
+ ? Entries[Rest["length"]] extends infer Last
343
+ ? Last extends ApplyDeferredStep ? readonly [Acc, true]
344
+ : Last extends AnySystem ? TailBefore<Entries, Rest, Acc | Last>
345
+ : Last extends { readonly kind: "applyStateTransitions" }
346
+ ? TailSegment<NestedEntries<Last>> extends readonly [infer Inner, any] ? readonly [Acc | Inner, true] : readonly [Acc, true]
347
+ : TailSegment<NestedEntries<Last>> extends readonly [infer Inner, infer Flushed]
348
+ ? Flushed extends true ? readonly [Acc | Inner, true] : TailBefore<Entries, Rest, Acc | Inner>
349
+ : TailBefore<Entries, Rest, Acc>
350
+ : never
351
+ : readonly [Acc, false]
352
+
353
+ /** The segment inside the entry that runs the reader: none for the reader itself; a transition step flushes first. */
354
+ type InnerSegment<At, Reader> =
355
+ At extends AnySystem ? readonly [never, false]
356
+ : At extends { readonly kind: "applyStateTransitions" }
357
+ ? Segment<NestedEntries<At>, Reader> extends readonly [infer Inner, any] ? readonly [Inner, true] : readonly [never, true]
358
+ : Segment<NestedEntries<At>, Reader>
359
+
360
+ /** The systems after the last marker of `Entries` (walking back from the end), and whether a marker was found. */
361
+ type TailSegment<Entries, Acc = never> =
362
+ Entries extends readonly [...infer Init, infer Last]
363
+ ? Last extends ApplyDeferredStep ? readonly [Acc, true]
364
+ : Last extends AnySystem ? TailSegment<Init, Acc | Last>
365
+ : Last extends { readonly kind: "applyStateTransitions" }
366
+ // Its bundle runs after its own flush; markers inside the bundle count too.
367
+ ? TailSegment<NestedEntries<Last>> extends readonly [infer Inner, any] ? readonly [Acc | Inner, true] : readonly [Acc, true]
368
+ : TailSegment<NestedEntries<Last>> extends readonly [infer Inner, infer Flushed]
369
+ ? Flushed extends true ? readonly [Acc | Inner, true] : TailSegment<Init, Acc | Inner>
370
+ : TailSegment<Init, Acc>
371
+ : readonly [Acc, false]
372
+
373
+ type UnflushedWriters<Entries, R, Reader extends string> =
374
+ R extends QueryRead<any, any, any, any>
375
+ ? Segment<Entries, Reader> extends infer Found
376
+ ? Found extends readonly [infer Systems, any] ? UnflushedWriterError<R, WritersFor<Systems, R>> : never
377
+ : never
378
+ : never
379
+
380
+ type ReadErrors<Entries extends ReadonlyArray<unknown>, Reads> =
381
+ Reads extends { readonly reader: infer Reader extends string }
382
+ ? LateWriters<Entries, Reads, Reader> | UnflushedWriters<Entries, Reads, Reader>
383
+ : never
384
+
385
+ type CheckTick<Schedules extends ReadonlyArray<unknown>> =
386
+ [UnknownIn<Schedules, HasReads<Schedules>>] extends [never]
387
+ ? ReadErrors<Schedules, SystemReads<SystemsIn<Schedules[number]>>>
388
+ : UnknownOrderError
389
+
390
+ /**
391
+ * Every `thisTick` error of one `runtime.tick(...)` call over its arguments
392
+ * in order: writers that run after their reader, command writers with no
393
+ * `applyDeferred()` before a query reader, and a call whose order is not
394
+ * statically known. Free when nothing has a read.
395
+ */
396
+ export type TickOrderErrors<Schedules extends ReadonlyArray<unknown>> =
397
+ HasReads<Schedules> extends false ? never : CheckTick<Schedules>
398
+
53
399
  export interface TransitionBundleDefinition<
54
400
  S extends Schema.Any = Schema.Any,
55
401
  out Entries extends ReadonlyArray<StateMachine.AnyTransitionSchedule<S, any>> = ReadonlyArray<StateMachine.AnyTransitionSchedule<S, any>>,
56
402
  out Needs extends Requirement.Requirement = Requirement.Requirement,
57
403
  out Root = unknown,
58
404
  out CarriedNeeds extends Requirement.Requirement = Needs,
59
- out Failure extends SystemFailure = never
405
+ out Failure extends SystemFailure = never,
406
+ out Order extends AnyScheduleOrder = AnyScheduleOrder
60
407
  > {
61
408
  readonly kind: "transitionBundle"
62
409
  readonly entries: Entries
63
410
  readonly requirements: ReadonlyArray<CarriedNeeds>
64
411
  readonly __failure?: (_: never) => Failure
412
+ /**
413
+ * The bundle's schedules in order, for `Game.System.thisTick` checks: they
414
+ * count at the position of the `applyStateTransitions(...)` step that
415
+ * applies the bundle, as if every one of them runs.
416
+ */
417
+ readonly __order?: (_: never) => Order
65
418
  readonly __schemaRoot?: Root | undefined
66
419
  }
67
420
 
@@ -70,7 +423,9 @@ export interface ExecutableScheduleDefinition<
70
423
  out Needs extends Requirement.Requirement = Requirement.Requirement,
71
424
  out Root = unknown,
72
425
  out CarriedNeeds extends Requirement.Requirement = Needs,
73
- out Failure extends SystemFailure = never
426
+ out Failure extends SystemFailure = never,
427
+ // Any order by default, so a pattern that names five parameters (`infer S, any, ...`) matches every schedule.
428
+ out Order extends AnyScheduleOrder = AnyScheduleOrder
74
429
  > {
75
430
  readonly kind: "schedule"
76
431
  readonly steps: ReadonlyArray<ScheduleStep>
@@ -78,6 +433,8 @@ export interface ExecutableScheduleDefinition<
78
433
  readonly schema: S
79
434
  readonly requirements: ReadonlyArray<CarriedNeeds>
80
435
  readonly __failure?: (_: never) => Failure
436
+ /** Writes, `thisTick` reads, and order errors, checked by `runtime.tick(...)`. */
437
+ readonly __order?: (_: never) => Order
81
438
  readonly __schemaRoot?: Root | undefined
82
439
  }
83
440
 
@@ -86,8 +443,9 @@ export type ScheduleDefinition<
86
443
  Needs extends Requirement.Requirement = Requirement.Requirement,
87
444
  Root = unknown,
88
445
  CarriedNeeds extends Requirement.Requirement = Needs,
89
- Failure extends SystemFailure = never
90
- > = ExecutableScheduleDefinition<S, Needs, Root, CarriedNeeds, Failure>
446
+ Failure extends SystemFailure = never,
447
+ Order extends AnyScheduleOrder = AnyScheduleOrder
448
+ > = ExecutableScheduleDefinition<S, Needs, Root, CarriedNeeds, Failure, Order>
91
449
 
92
450
  export namespace Schedule {
93
451
  export type Definition<
@@ -114,11 +472,11 @@ export namespace Schedule {
114
472
  */
115
473
  export type ScheduleEntry =
116
474
  | ScheduleStep
117
- | ScheduleDefinition<any, any, any, any, any>
475
+ | ScheduleDefinition<any, any, any, any, any, AnyScheduleOrder>
118
476
 
119
477
  type EntrySchema<Entry> =
120
478
  Entry extends { readonly schema: infer S extends Schema.Any } ? S
121
- : Entry extends SystemDefinition<infer Spec, any, any, any, any, any> ? Spec["schema"]
479
+ : Entry extends SystemDefinition<infer Spec, any, any, any, any> ? Spec["schema"]
122
480
  : never
123
481
 
124
482
  type MarkerNeeds<Step> =
@@ -164,7 +522,8 @@ export type AnonymousScheduleBuildFor<
164
522
  CompositionExactRequirements<Entries>,
165
523
  Root,
166
524
  CompositionExactRequirements<Entries>,
167
- CompositionFailure<Entries>
525
+ CompositionFailure<Entries>,
526
+ CompositionOrder<Entries>
168
527
  >
169
528
 
170
529
  /**
@@ -181,7 +540,8 @@ export type ConditionalScheduleBuildFor<
181
540
  CompositionExactRequirements<Entries> | ConditionNeedsFromConditions<Conditions>,
182
541
  Root,
183
542
  CompositionExactRequirements<Entries> | ConditionNeedsFromConditions<Conditions>,
184
- CompositionFailure<Entries>
543
+ CompositionFailure<Entries>,
544
+ CompositionOrder<Entries>
185
545
  >
186
546
 
187
547
  export type TransitionBundleInput<S extends Schema.Any = Schema.Any, Root = unknown> =
@@ -221,7 +581,8 @@ export const transitions = <
221
581
  TransitionBundleRequirements<FlattenTransitionEntries<Entries>>,
222
582
  unknown,
223
583
  TransitionBundleRequirements<FlattenTransitionEntries<Entries>>,
224
- TransitionBundleFailure<FlattenTransitionEntries<Entries>>
584
+ TransitionBundleFailure<FlattenTransitionEntries<Entries>>,
585
+ CompositionOrder<Entries>
225
586
  > => {
226
587
  const flattened = entries.flatMap((entry) =>
227
588
  "kind" in entry && entry.kind === "transitionBundle" ? [...entry.entries] : [entry]
@@ -237,7 +598,8 @@ export const transitions = <
237
598
  TransitionBundleRequirements<FlattenTransitionEntries<Entries>>,
238
599
  unknown,
239
600
  TransitionBundleRequirements<FlattenTransitionEntries<Entries>>,
240
- TransitionBundleFailure<FlattenTransitionEntries<Entries>>
601
+ TransitionBundleFailure<FlattenTransitionEntries<Entries>>,
602
+ CompositionOrder<Entries>
241
603
  >
242
604
  }
243
605
 
@@ -287,6 +649,11 @@ export const make = <S extends Schema.Any, const Entries extends ReadonlyArray<S
287
649
  * system's `when`, for gating many systems on one mode (playing, paused,
288
650
  * a hit-stop) without repeating it on each.
289
651
  *
652
+ * Use this when the gated systems should drop the events published while
653
+ * they were gated (a mode change makes them stale). Use `pauseWhen` instead
654
+ * when they must read that backlog on resume (hit-stop, pause menu). For one
655
+ * system, put the same conditions in its spec's `when` / `pauseWhen`.
656
+ *
290
657
  * Conditions gate every system in `entries`, nested schedules included.
291
658
  * Marker steps (`applyDeferred`, `applyStateTransitions`) in the group still
292
659
  * run. A gated system is the same system as far as the runtime is concerned:
@@ -367,7 +734,7 @@ const gate = (system: AnySystem, when: ReadonlyArray<Condition>, pauseWhen: Read
367
734
  export const isSystemStep = (step: ScheduleStep | ScheduleEntry): step is AnySystem =>
368
735
  typeof step === "object" && step !== null && "spec" in step
369
736
 
370
- const isScheduleEntry = (entry: ScheduleEntry): entry is ScheduleDefinition<any, any, any, any, any> =>
737
+ const isScheduleEntry = (entry: ScheduleEntry): entry is ScheduleDefinition<any, any, any, any, any, AnyScheduleOrder> =>
371
738
  typeof entry === "object" && entry !== null && "kind" in entry && entry.kind === "schedule"
372
739
 
373
740
  const describeValue = (value: unknown): string =>