@typeonce/ritmo 0.6.0 → 0.7.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 (125) 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 +157 -33
  11. package/dist/Command.d.ts.map +1 -1
  12. package/dist/Command.js +49 -15
  13. package/dist/Command.js.map +1 -1
  14. package/dist/Debug.d.ts +104 -12
  15. package/dist/Debug.d.ts.map +1 -1
  16. package/dist/Debug.js.map +1 -1
  17. package/dist/Decode.d.ts +1 -1
  18. package/dist/Decode.js +1 -1
  19. package/dist/Descriptor.d.ts +31 -21
  20. package/dist/Descriptor.d.ts.map +1 -1
  21. package/dist/Descriptor.js +21 -12
  22. package/dist/Descriptor.js.map +1 -1
  23. package/dist/Entity.d.ts +14 -5
  24. package/dist/Entity.d.ts.map +1 -1
  25. package/dist/Entity.js +2 -2
  26. package/dist/Entity.js.map +1 -1
  27. package/dist/EntityScope.d.ts +6 -0
  28. package/dist/EntityScope.d.ts.map +1 -1
  29. package/dist/EntityScope.js +6 -0
  30. package/dist/EntityScope.js.map +1 -1
  31. package/dist/HostClock.d.ts +49 -0
  32. package/dist/HostClock.d.ts.map +1 -0
  33. package/dist/HostClock.js +46 -0
  34. package/dist/HostClock.js.map +1 -0
  35. package/dist/Inspector.d.ts +7 -1
  36. package/dist/Inspector.d.ts.map +1 -1
  37. package/dist/Inspector.js.map +1 -1
  38. package/dist/Machine.d.ts +12 -3
  39. package/dist/Machine.d.ts.map +1 -1
  40. package/dist/Machine.js +4 -1
  41. package/dist/Machine.js.map +1 -1
  42. package/dist/Query.d.ts +28 -24
  43. package/dist/Query.d.ts.map +1 -1
  44. package/dist/Query.js +0 -24
  45. package/dist/Query.js.map +1 -1
  46. package/dist/Runtime.d.ts +36 -16
  47. package/dist/Runtime.d.ts.map +1 -1
  48. package/dist/Runtime.js +230 -67
  49. package/dist/Runtime.js.map +1 -1
  50. package/dist/Schedule.d.ts +217 -10
  51. package/dist/Schedule.d.ts.map +1 -1
  52. package/dist/Schedule.js +5 -0
  53. package/dist/Schedule.js.map +1 -1
  54. package/dist/Schema.d.ts +61 -25
  55. package/dist/Schema.d.ts.map +1 -1
  56. package/dist/Schema.js +0 -8
  57. package/dist/Schema.js.map +1 -1
  58. package/dist/System.d.ts +149 -52
  59. package/dist/System.d.ts.map +1 -1
  60. package/dist/System.js +52 -6
  61. package/dist/System.js.map +1 -1
  62. package/dist/index.d.ts +8 -7
  63. package/dist/index.d.ts.map +1 -1
  64. package/dist/index.js +8 -7
  65. package/dist/index.js.map +1 -1
  66. package/dist/internal/cells.d.ts +3 -3
  67. package/dist/internal/cells.d.ts.map +1 -1
  68. package/dist/internal/cells.js +9 -11
  69. package/dist/internal/cells.js.map +1 -1
  70. package/dist/internal/debug.d.ts +6 -0
  71. package/dist/internal/debug.d.ts.map +1 -1
  72. package/dist/internal/debug.js +95 -1
  73. package/dist/internal/debug.js.map +1 -1
  74. package/dist/internal/game.d.ts.map +1 -1
  75. package/dist/internal/game.js +9 -2
  76. package/dist/internal/game.js.map +1 -1
  77. package/dist/internal/order.d.ts +10 -0
  78. package/dist/internal/order.d.ts.map +1 -0
  79. package/dist/internal/order.js +120 -0
  80. package/dist/internal/order.js.map +1 -0
  81. package/dist/internal/queries.js +2 -2
  82. package/dist/internal/queries.js.map +1 -1
  83. package/package.json +1 -1
  84. package/src/Brand.ts +9 -4
  85. package/src/Clock.ts +311 -0
  86. package/src/Command.ts +271 -73
  87. package/src/Debug.ts +111 -10
  88. package/src/Decode.ts +1 -1
  89. package/src/Descriptor.ts +37 -27
  90. package/src/Entity.ts +15 -5
  91. package/src/EntityScope.ts +6 -0
  92. package/src/HostClock.ts +64 -0
  93. package/src/Inspector.ts +7 -2
  94. package/src/Machine.ts +13 -3
  95. package/src/Query.ts +41 -33
  96. package/src/Runtime.ts +273 -91
  97. package/src/Schedule.ts +380 -13
  98. package/src/Schema.ts +70 -41
  99. package/src/System.ts +174 -88
  100. package/src/index.ts +8 -7
  101. package/src/internal/cells.ts +10 -12
  102. package/src/internal/debug.ts +104 -4
  103. package/src/internal/game.ts +11 -3
  104. package/src/internal/order.ts +140 -0
  105. package/src/internal/queries.ts +3 -3
  106. package/dist/Definition.d.ts +0 -99
  107. package/dist/Definition.d.ts.map +0 -1
  108. package/dist/Definition.js +0 -68
  109. package/dist/Definition.js.map +0 -1
  110. package/dist/Feature.d.ts +0 -106
  111. package/dist/Feature.d.ts.map +0 -1
  112. package/dist/Feature.js +0 -87
  113. package/dist/Feature.js.map +0 -1
  114. package/dist/Fx.d.ts +0 -128
  115. package/dist/Fx.d.ts.map +0 -1
  116. package/dist/Fx.js +0 -112
  117. package/dist/Fx.js.map +0 -1
  118. package/dist/internal/definition.d.ts +0 -17
  119. package/dist/internal/definition.d.ts.map +0 -1
  120. package/dist/internal/definition.js +0 -25
  121. package/dist/internal/definition.js.map +0 -1
  122. package/src/Definition.ts +0 -106
  123. package/src/Feature.ts +0 -241
  124. package/src/Fx.ts +0 -176
  125. package/src/internal/definition.ts +0 -56
package/src/Decode.ts CHANGED
@@ -188,7 +188,7 @@ export const struct = <const Fields extends Readonly<Record<string, Codec<any>>>
188
188
  /**
189
189
  * A stored entity handle for schema root `Root`, optionally with an intent
190
190
  * component. Decoding never proves the entity exists; resolve it with
191
- * `lookup.getHandle(...)` as usual.
191
+ * `queries.x.getHandle(...)` (through a declared query) as usual.
192
192
  */
193
193
  export const handle = <
194
194
  Root,
package/src/Descriptor.ts CHANGED
@@ -16,8 +16,9 @@
16
16
  * 3. bind one `Game` with `Schema.bind(...)`
17
17
  *
18
18
  * Reach for this module first whenever you introduce a new component,
19
- * resource, event, or service to the game. Discrete modes whose transitions
20
- * matter are state machines (`Game.StateMachine(...)`), not descriptors.
19
+ * resource, event, or service to the game. A game-wide mode whose transitions
20
+ * matter is a state machine (`Game.StateMachine(...)`); a state each entity
21
+ * has on its own is a component (`Descriptor.EntityState(...)`).
21
22
  *
22
23
  * @module Descriptor
23
24
  * @docGroup core
@@ -311,70 +312,77 @@ export const Tag = <const Name extends string>(
311
312
  ): DecodableDescriptor<"component", Name, {}, {}, DecodeModule.DecodeError> =>
312
313
  ConstructedComponent(DecodeModule.struct({}))(name)
313
314
 
314
- /** One state of a {@link State} component: a string or number literal. */
315
- export type StateValue = string | number
315
+ /** One state of a {@link EntityState} component: a string or number literal. */
316
+ export type EntityStateValue = string | number
316
317
 
317
318
  /**
318
- * The allowed moves of a {@link State} component: for every state, the
319
+ * The allowed moves of a {@link EntityState} component: for every state, the
319
320
  * states it may move to (`[]` for a state with no way out).
320
321
  */
321
- export type StateTransitions<States extends readonly [StateValue, ...Array<StateValue>]> = {
322
+ export type EntityStateTransitions<States extends readonly [EntityStateValue, ...Array<EntityStateValue>]> = {
322
323
  readonly [From in States[number]]: ReadonlyArray<States[number]>
323
324
  }
324
325
 
325
326
  /**
326
327
  * A component whose value is one of a closed set of states, with an optional
327
- * graph of allowed moves. Built with {@link State}.
328
+ * graph of allowed moves. Built with {@link EntityState}.
328
329
  */
329
- export interface StateDescriptor<
330
+ export interface EntityStateDescriptor<
330
331
  out Name extends string,
331
- in out States extends readonly [StateValue, ...Array<StateValue>],
332
- in out Transitions extends StateTransitions<States> | undefined
332
+ in out States extends readonly [EntityStateValue, ...Array<EntityStateValue>],
333
+ in out Transitions extends EntityStateTransitions<States> | undefined
333
334
  > extends DecodableDescriptor<"component", Name, States[number], States[number], DecodeModule.DecodeError> {
334
335
  readonly states: States
335
336
  readonly transitions: Transitions
336
337
  }
337
338
 
338
339
  /**
339
- * Defines a state component: its value is one of `states`, validated (so
340
- * snapshots reject unknown states), and it behaves like any other component
341
- * (storage, change detection, rollback, traces).
340
+ * Defines a per-entity state: a component whose value is one of `states`,
341
+ * validated (so snapshots reject unknown states), that behaves like any other
342
+ * component (storage, change detection, rollback, traces).
343
+ *
344
+ * Use this when each entity has its own state: an enemy's attack windup, a
345
+ * door's open/closed, a unit's AI mode. Use `Game.StateMachine(...)` instead
346
+ * for one game-wide mode (menu, playing, paused, game over): machines change
347
+ * only at `applyStateTransitions(...)`, run `onEnter`/`onExit` schedules, and
348
+ * gate systems with `Game.Condition.inState(...)`. An entity state does none
349
+ * of that; it is data on an entity.
342
350
  *
343
351
  * With `transitions`, its write cell gains `transition(from, to)`: the pair
344
352
  * is checked against the graph at compile time, and at runtime the move
345
- * happens only if the current state is still `from`, otherwise it returns a
346
- * `StateMismatch` failure. Transitions are ordinary component writes:
353
+ * happens only if the current state is still `from`, otherwise it returns an
354
+ * `EntityStateMismatch` failure. Transitions are ordinary component writes:
347
355
  * immediate, visible to later systems, rolled back with a failed system.
348
356
  *
349
357
  * @example
350
358
  * ```ts
351
- * const Phase = Descriptor.State("Phase", ["ready", "windup", "active"] as const, {
359
+ * const Attack = Descriptor.EntityState("Attack", ["ready", "windup", "active"] as const, {
352
360
  * transitions: { ready: ["windup"], windup: ["active", "ready"], active: ["ready"] }
353
361
  * })
354
- * // in a system with a write slot `phase`:
355
- * const moved = data.phase.transition("windup", "active") // "ready" -> "active" does not compile
362
+ * // in a system with a write slot `attack`:
363
+ * const moved = data.attack.transition("windup", "active") // "ready" -> "active" does not compile
356
364
  * ```
357
365
  */
358
- export const State = <
366
+ export const EntityState = <
359
367
  const Name extends string,
360
- const States extends readonly [StateValue, ...Array<StateValue>],
361
- const Transitions extends StateTransitions<States> | undefined = undefined
368
+ const States extends readonly [EntityStateValue, ...Array<EntityStateValue>],
369
+ const Transitions extends EntityStateTransitions<States> | undefined = undefined
362
370
  >(
363
371
  name: Name,
364
372
  states: States,
365
373
  options?: {
366
374
  readonly transitions: Transitions & { readonly [Key in Exclude<keyof Transitions, States[number]>]: never }
367
375
  }
368
- ): StateDescriptor<Name, States, Transitions> => ({
376
+ ): EntityStateDescriptor<Name, States, Transitions> => ({
369
377
  ...makeConstructedDescriptor("component", name, DecodeModule.literal(...states)),
370
378
  states,
371
379
  transitions: options?.transitions
372
- }) as unknown as StateDescriptor<Name, States, Transitions>
380
+ }) as unknown as EntityStateDescriptor<Name, States, Transitions>
373
381
 
374
- /** Whether a descriptor was built with {@link State}. */
375
- export const isState = (
382
+ /** Whether a descriptor was built with {@link EntityState}. */
383
+ export const isEntityState = (
376
384
  descriptor: Descriptor.Any
377
- ): descriptor is StateDescriptor<string, readonly [StateValue, ...Array<StateValue>], StateTransitions<readonly [StateValue, ...Array<StateValue>]> | undefined> =>
385
+ ): descriptor is EntityStateDescriptor<string, readonly [EntityStateValue, ...Array<EntityStateValue>], EntityStateTransitions<readonly [EntityStateValue, ...Array<EntityStateValue>]> | undefined> =>
378
386
  "states" in descriptor
379
387
 
380
388
  /**
@@ -568,7 +576,9 @@ export function constructorOf<D extends Descriptor.Any>(
568
576
  * `related` is the reverse collection maintained by the runtime.
569
577
  *
570
578
  * Use hierarchy when the relationship must support ordered children,
571
- * ancestor/descendant traversal, and linked recursive despawn.
579
+ * ancestor/descendant traversal, and linked recursive despawn. Use
580
+ * `Game.EntityScope(...)` instead when entities only share a lifetime (a
581
+ * level, a menu) and need no tree, order, or traversal.
572
582
  */
573
583
  export const Hierarchy = RelationModule.Hierarchy
574
584
 
package/src/Entity.ts CHANGED
@@ -56,7 +56,8 @@ import * as Result from "./Result.ts"
56
56
  * @example
57
57
  * ```ts
58
58
  * const handle = Game.Entity.handle(playerId, Player)
59
- * const resolved = lookup.getHandle(handle, PlayerQuery)
59
+ * // `players` is a query the system declares: queries: { players: PlayerQuery }
60
+ * const resolved = queries.players.getHandle(handle)
60
61
  * if (!resolved.ok) return
61
62
  * ```
62
63
  */
@@ -167,11 +168,17 @@ export type StagedComponent = readonly [Descriptor<"component", string, any>, un
167
168
  *
168
169
  * Query execution is the main source of `EntityRef` values.
169
170
  */
170
- export interface EntityRef<S extends Schema.Any, out P extends ComponentProof, Root = unknown> {
171
+ export interface EntityRef<S extends Schema.Any, out P extends ComponentProof, Root = unknown, out Proves = unknown> {
171
172
  /**
172
173
  * Runtime tag for debugging and pattern matching.
173
174
  */
174
175
  readonly kind: "EntityRef"
176
+ /**
177
+ * The component descriptors the query that produced this ref proves (its
178
+ * required slots and `with`): what `commands.despawn` checks against the
179
+ * system's `despawns` permission. Type-level only.
180
+ */
181
+ readonly __proves?: (_: never) => Proves
175
182
  /**
176
183
  * The schema-bound entity identity.
177
184
  */
@@ -193,12 +200,15 @@ export interface EntityMut<
193
200
  S extends Schema.Any,
194
201
  out P extends ComponentProof,
195
202
  out W extends ComponentProof,
196
- Root = unknown
203
+ Root = unknown,
204
+ out Proves = unknown
197
205
  > {
198
206
  /**
199
207
  * Runtime tag for debugging and pattern matching.
200
208
  */
201
209
  readonly kind: "EntityMut"
210
+ /** The component descriptors the producing query proves; see `EntityRef`. */
211
+ readonly __proves?: (_: never) => Proves
202
212
  /**
203
213
  * The schema-bound entity identity.
204
214
  */
@@ -247,7 +257,7 @@ export type HandleTarget<S extends Schema.Any, Root = unknown> =
247
257
  * Converts a current entity into a durable handle for storage.
248
258
  *
249
259
  * The handle is storage-safe, not a proof of liveness: resolve it later with
250
- * `lookup.getHandle(...)`, which fails explicitly when the entity is gone.
260
+ * `queries.x.getHandle(...)` (through a declared query), which fails explicitly when the entity is gone.
251
261
  *
252
262
  * Pass an `intent` component when later code assumes a role such as "player"
253
263
  * or "damage source". An intent does not prove the component is still present;
@@ -283,7 +293,7 @@ export interface InvalidHandle {
283
293
  * handles, so restoring validates them like any other value.
284
294
  *
285
295
  * A handle only names an entity; it never proves the entity exists. Resolving
286
- * it with `lookup.getHandle(...)` stays the checked step, so any positive
296
+ * it with `queries.x.getHandle(...)` (through a declared query) stays the checked step, so any positive
287
297
  * integer id decodes. Object-shaped handles and other non-numeric values
288
298
  * are rejected.
289
299
  *
@@ -7,6 +7,12 @@
7
7
  * build scenes without teaching the ECS runtime about menus, levels, overlays,
8
8
  * or any other game-specific scene model.
9
9
  *
10
+ * Use a scope when a set of entities shares one lifetime and nothing else: a
11
+ * level's walls, a menu's buttons. Membership is flat, unordered, and not
12
+ * queryable. Use `Descriptor.Hierarchy(...)` instead when entities form a
13
+ * parent-child tree: ordered children, `lookup.parent` / `descendants`
14
+ * traversal, and children despawned with their parent.
15
+ *
10
16
  * @module EntityScope
11
17
  * @docGroup core
12
18
  */
@@ -0,0 +1,64 @@
1
+ /**
2
+ * The host's wall clock, as an injected contract.
3
+ *
4
+ * Core and the generic devtools modules never read a platform clock
5
+ * (`performance`, `Date`) themselves: whatever measures host time (debug
6
+ * trace durations, session timings, replay windows) takes a `HostClock`.
7
+ * Platform packages provide implementations (`BrowserHostClock.make(window)`
8
+ * in the browser package); `standard` works in every JavaScript engine, and
9
+ * `test()` is a manual clock for deterministic tests.
10
+ *
11
+ * Host time is for measuring the host (how long a tick took), never for game
12
+ * logic: simulation time is a resource the schedule advances.
13
+ *
14
+ * @example
15
+ * ```ts
16
+ * const runtime = Game.Runtime.make({ services, debug: { hostClock: HostClock.standard } })
17
+ * ```
18
+ *
19
+ * @module HostClock
20
+ * @docGroup runtime
21
+ */
22
+ import * as Result from "./Result.ts"
23
+
24
+ /** A host clock: `now()` in milliseconds, from an arbitrary origin. */
25
+ export interface HostClock {
26
+ readonly now: () => number
27
+ }
28
+
29
+ /**
30
+ * `Date.now`, which every JavaScript engine provides. Its readings can jump
31
+ * (the system clock is adjusted, or the device sleeps), so a duration
32
+ * computed from two readings can be negative: consumers clamp durations to
33
+ * 0. Prefer a monotonic platform clock when there is one
34
+ * (`BrowserHostClock.make(window)`).
35
+ */
36
+ export const standard: HostClock = { now: () => Date.now() }
37
+
38
+ /** `adjust` must leave a finite clock reading and never move backwards. */
39
+ export interface InvalidAdjustment {
40
+ readonly _tag: "InvalidAdjustment"
41
+ readonly ms: number
42
+ }
43
+
44
+ /** A manual clock for tests: starts at 0 and moves only through `adjust`. */
45
+ export interface TestHostClock extends HostClock {
46
+ /** Moves the clock forward by `ms` (0 or more). */
47
+ readonly adjust: (ms: number) => Result.Result<void, InvalidAdjustment>
48
+ }
49
+
50
+ /** A test clock at 0. Every reading is the same until `adjust` moves it. */
51
+ export const test = (): TestHostClock => {
52
+ let current = 0
53
+ return {
54
+ now: () => current,
55
+ adjust: (ms) => {
56
+ if (!Number.isFinite(ms) || ms < 0 || !Number.isFinite(current + ms)) return Result.failure({ _tag: "InvalidAdjustment", ms })
57
+ current += ms
58
+ return Result.success(undefined)
59
+ }
60
+ }
61
+ }
62
+
63
+ /** A duration between two readings, clamped to 0 when the clock went backwards. */
64
+ export const elapsed = (from: number, to: number): number => Math.max(0, to - from)
package/src/Inspector.ts CHANGED
@@ -6,6 +6,12 @@
6
6
  * They run on demand through `runtime.inspect(...)` and return a value without
7
7
  * advancing schedule visibility.
8
8
  *
9
+ * An inspector is the one typed read-only view of the world: tests, saves, UI,
10
+ * `Game.Condition.check(...)` projections, and devtools invariants
11
+ * (`Invariant.fromInspector(...)`) are all built from it. `runtime.inspect`
12
+ * rejects at compile time an inspector that reads a resource, service, or
13
+ * machine the runtime was not given.
14
+ *
9
15
  * @module Inspector
10
16
  * @docGroup runtime
11
17
  */
@@ -74,7 +80,7 @@ export interface InspectorDefinition<
74
80
  > {
75
81
  readonly kind: "inspector"
76
82
  readonly name: Name
77
- readonly system: System.SystemDefinition<Spec, void, never, Root, Name, Needs>
83
+ readonly system: System.SystemDefinition<Spec, never, Root, Name, Needs>
78
84
  readonly requirements: ReadonlyArray<Needs>
79
85
  readonly read: (context: InspectorContext<Spec>) => Value
80
86
  readonly __schemaRoot?: Root | undefined
@@ -111,7 +117,6 @@ export const make = <
111
117
  () => undefined
112
118
  ) as unknown as System.SystemDefinition<
113
119
  System.SystemSpec<S, Access, Root>,
114
- void,
115
120
  never,
116
121
  Root,
117
122
  Name,
package/src/Machine.ts CHANGED
@@ -9,6 +9,10 @@
9
9
  * run around that boundary. Menus, rounds, encounter phases, pause flows, and
10
10
  * restart/reset pipelines are the canonical cases.
11
11
  *
12
+ * A machine holds one value for the whole game. A state that each entity has
13
+ * on its own (an enemy's attack windup, a door's open/closed) is not a
14
+ * machine: use a `Descriptor.EntityState(...)` component instead.
15
+ *
12
16
  * @example
13
17
  * ```ts
14
18
  * // Model a gameplay phase whose transition timing matters.
@@ -33,7 +37,7 @@
33
37
  * @groupDescription Functions
34
38
  * Public constructors for machines, conditions, and explicit transition bundles.
35
39
  */
36
- import type { Schedule } from "./Schedule.ts"
40
+ import type { AnyScheduleOrder, Schedule } from "./Schedule.ts"
37
41
  import * as Requirement from "./Requirement.ts"
38
42
  import type { Schema } from "./Schema.ts"
39
43
  import type { SystemFailure } from "./System.ts"
@@ -235,13 +239,16 @@ export interface TransitionScheduleDefinition<
235
239
  M extends StateMachineDefinition = StateMachineDefinition,
236
240
  Needs extends Requirement.Requirement = Requirement.Requirement,
237
241
  Root = unknown,
238
- Failure extends SystemFailure = never
242
+ Failure extends SystemFailure = never,
243
+ Order extends AnyScheduleOrder = AnyScheduleOrder
239
244
  > {
240
245
  readonly steps: ReadonlyArray<Schedule.Step>
241
246
  readonly systems: ReadonlyArray<unknown>
242
247
  readonly schema: S
243
248
  readonly requirements: ReadonlyArray<Needs>
244
249
  readonly __failure?: (_: never) => Failure
250
+ /** The plan's order, for `Game.System.thisTick` checks of the tick that applies it. */
251
+ readonly __order?: (_: never) => Order
245
252
  readonly __schemaRoot?: Root | undefined
246
253
  readonly transition: {
247
254
  readonly machine: M
@@ -293,7 +300,7 @@ export type MachineNeedsFromConditions<C extends ReadonlyArray<Condition>> =
293
300
  MachineNeedsFromCondition<C[number]>
294
301
 
295
302
  /**
296
- * Creates a schema-bound finite-state machine definition.
303
+ * Creates a schema-bound finite-state machine definition: one game-wide mode.
297
304
  *
298
305
  * This is the intended default for gameplay phases and other discrete modes
299
306
  * where the transition boundary itself matters.
@@ -304,6 +311,9 @@ export type MachineNeedsFromConditions<C extends ReadonlyArray<Condition>> =
304
311
  * - explicit `applyStateTransitions(...)`
305
312
  * - `inState(...)` gating
306
313
  * - transition events or enter/exit schedules
314
+ *
315
+ * Use `Descriptor.EntityState(...)` instead when every entity carries its own
316
+ * state (per-entity, written immediately, no enter/exit schedules).
307
317
  */
308
318
  export const StateMachine = <
309
319
  const Name extends string,
package/src/Query.ts CHANGED
@@ -69,7 +69,7 @@
69
69
  * Query authoring helpers for selecting components and declaring explicit filters.
70
70
  */
71
71
  import type { DecodeError } from "./Decode.ts"
72
- import type { Descriptor, StateValue } from "./Descriptor.ts"
72
+ import type { Descriptor, EntityStateValue } from "./Descriptor.ts"
73
73
  import type { EntityId, EntityMut, EntityRef } from "./Entity.ts"
74
74
  import type * as Result from "./Result.ts"
75
75
  import type * as Relation from "./Relation.ts"
@@ -384,6 +384,9 @@ export namespace Query {
384
384
  ? false
385
385
  : true
386
386
 
387
+ /** The component descriptors a query proves its matches have: required selection slots and `with`. */
388
+ export type Proven<T extends Any> = RequiredSelectionDescriptors<T> | T["with"][number]
389
+
387
390
  /**
388
391
  * The readable proof produced by a query.
389
392
  */
@@ -488,24 +491,20 @@ export interface ReadCell<T> {
488
491
  * A mutable cell returned from query or resource access.
489
492
  *
490
493
  * This gives a narrow mutation surface instead of exposing raw storage objects.
494
+ * Values written here are already valid. Data that still needs validation
495
+ * goes through its descriptor's own validator: declare the descriptor with
496
+ * `Descriptor.ConstructedComponent` / `ConstructedResource` and write with
497
+ * `setRaw` / `updateRaw` on its {@link ConstructedWriteCell}.
491
498
  */
492
499
  export interface WriteCell<T> extends ReadCell<T> {
493
500
  /**
494
501
  * Replaces the current value.
495
502
  */
496
503
  set(value: T): void
497
- /**
498
- * Replaces the current value from an explicit result.
499
- */
500
- setResult<E>(result: Result.Result<T, E>): Result.Result<void, E>
501
504
  /**
502
505
  * Updates the current value based on the previous one.
503
506
  */
504
507
  update(f: (current: ReadonlyValue<T>) => T): void
505
- /**
506
- * Updates the current value from an explicit result-producing callback.
507
- */
508
- updateResult<E>(f: (current: ReadonlyValue<T>) => Result.Result<T, E>): Result.Result<void, E>
509
508
  }
510
509
 
511
510
  /**
@@ -540,13 +539,13 @@ export interface ConstructedWriteCell<T, Raw, Error> extends WriteCell<T> {
540
539
  }
541
540
 
542
541
  /**
543
- * A `transition(from, to)` found the state component in another state, so
542
+ * A `transition(from, to)` found the entity state in another state, so
544
543
  * nothing was written.
545
544
  */
546
- export interface StateMismatchError<T> {
547
- readonly _tag: "StateMismatch"
548
- /** Name of the state component. */
549
- readonly state: string
545
+ export interface EntityStateMismatchError<T> {
546
+ readonly _tag: "EntityStateMismatch"
547
+ /** Name of the `Descriptor.EntityState` component. */
548
+ readonly component: string
550
549
  /** The `from` state the transition expected. */
551
550
  readonly expected: T
552
551
  /** The state the component was actually in. */
@@ -554,20 +553,20 @@ export interface StateMismatchError<T> {
554
553
  }
555
554
 
556
555
  /**
557
- * Writable cell for a state component (`Descriptor.State`).
556
+ * Writable cell for an entity state component (`Descriptor.EntityState`).
558
557
  *
559
558
  * `transition(from, to)` is a compare-and-set: it writes `to` only while the
560
- * current state is `from`, and otherwise returns `StateMismatch` without
559
+ * current state is `from`, and otherwise returns `EntityStateMismatch` without
561
560
  * writing. With a transition graph, `to` must be one of the moves the graph
562
561
  * allows from `from`; without one, any pair of states is accepted.
563
562
  */
564
- export interface StateWriteCell<T, Transitions> extends ConstructedWriteCell<T, T, DecodeError> {
563
+ export interface EntityStateWriteCell<T, Transitions> extends ConstructedWriteCell<T, T, DecodeError> {
565
564
  /**
566
565
  * Moves from `from` to `to` if the component is still in `from`.
567
566
  *
568
567
  * @example
569
568
  * ```ts
570
- * const moved = data.phase.transition("windup", "active")
569
+ * const moved = data.attack.transition("windup", "active")
571
570
  * if (!moved.ok) {
572
571
  * // moved.error.actual is the state some earlier write left
573
572
  * }
@@ -575,19 +574,19 @@ export interface StateWriteCell<T, Transitions> extends ConstructedWriteCell<T,
575
574
  */
576
575
  transition<const From extends T & keyof Transitions>(
577
576
  from: From,
578
- to: StateTarget<T, Transitions, From>
579
- ): Result.Result<void, StateMismatchError<T>>
577
+ to: EntityStateTarget<T, Transitions, From>
578
+ ): Result.Result<void, EntityStateMismatchError<T>>
580
579
  }
581
580
 
582
- type StateTarget<T, Transitions, From extends keyof Transitions> =
581
+ type EntityStateTarget<T, Transitions, From extends keyof Transitions> =
583
582
  Transitions[From] extends ReadonlyArray<infer To> ? To & T : never
584
583
 
585
584
  /**
586
585
  * Write-cell surface produced for one queried component descriptor.
587
586
  */
588
587
  export type WriteCellForDescriptor<D extends ComponentDescriptor> =
589
- D extends import("./Descriptor.ts").StateDescriptor<string, infer States extends readonly [StateValue, ...Array<StateValue>], any>
590
- ? StateWriteCell<States[number], D["transitions"] extends undefined ? { readonly [K in States[number]]: States } : D["transitions"]>
588
+ D extends import("./Descriptor.ts").EntityStateDescriptor<string, infer States extends readonly [EntityStateValue, ...Array<EntityStateValue>], any>
589
+ ? EntityStateWriteCell<States[number], D["transitions"] extends undefined ? { readonly [K in States[number]]: States } : D["transitions"]>
591
590
  : D extends import("./Descriptor.ts").ConstructedDescriptor<"component", string, infer Value, infer Raw, infer Error>
592
591
  ? ConstructedWriteCell<Value, Raw, Error>
593
592
  : WriteCell<Descriptor.Value<D>>
@@ -622,11 +621,11 @@ export type OptionalReadCell<T> = PresentOptionalReadCell<T> | AbsentOptionalRea
622
621
  export type QueryMatch<S extends Schema.Any, Q extends Query.Any> =
623
622
  keyof Query.WriteProof<Q> extends never
624
623
  ? {
625
- readonly entity: EntityRef<S, Query.ReadProof<Q>, Query.Root<Q>>
624
+ readonly entity: EntityRef<S, Query.ReadProof<Q>, Query.Root<Q>, Query.Proven<Q>>
626
625
  readonly data: Query.Cells<Q>
627
626
  }
628
627
  : {
629
- readonly entity: EntityMut<S, Query.ReadProof<Q>, Query.WriteProof<Q>, Query.Root<Q>>
628
+ readonly entity: EntityMut<S, Query.ReadProof<Q>, Query.WriteProof<Q>, Query.Root<Q>, Query.Proven<Q>>
630
629
  readonly data: Query.Cells<Q>
631
630
  }
632
631
 
@@ -685,6 +684,14 @@ export const multipleEntitiesError = (count: number): Query.MultipleEntitiesErro
685
684
  * })
686
685
  * ```
687
686
  */
687
+ /**
688
+ * `T` itself, but not a place inference reads from: a list a query omits
689
+ * stays `[]` even when the call sits where a wider query type is expected
690
+ * (`queries: { ... }` in a spec). Unlike `NoInfer`, it prints as `T`.
691
+ */
692
+ // `T` appears only as the check type, which inference never reads.
693
+ export type Uninferred<T, Bound> = [T] extends [infer U extends Bound] ? U : never
694
+
688
695
  export const Query = <
689
696
  const Selection extends Record<string, SelectionAccess<any, Root>>,
690
697
  const With extends ReadonlyArray<ComponentDescriptor> = [],
@@ -704,14 +711,15 @@ export const Query = <
704
711
  readonly withoutRelations?: WithoutRelations
705
712
  readonly withRelated?: WithRelated
706
713
  readonly withoutRelated?: WithoutRelated
707
- }): QuerySpec<Selection, With, Without, Filters, WithRelations, WithoutRelations, WithRelated, WithoutRelated, Root> => ({
714
+ // Uninferred: an omitted list is `[]`, never inferred from the spec it is written in (`queries: { ... }`).
715
+ }): QuerySpec<Selection, Uninferred<With, ReadonlyArray<ComponentDescriptor>>, Uninferred<Without, ReadonlyArray<ComponentDescriptor>>, Uninferred<Filters, ReadonlyArray<Filter<ComponentDescriptor>>>, Uninferred<WithRelations, ReadonlyArray<Relation.Relation.Any>>, Uninferred<WithoutRelations, ReadonlyArray<Relation.Relation.Any>>, Uninferred<WithRelated, ReadonlyArray<Relation.Relation.Any>>, Uninferred<WithoutRelated, ReadonlyArray<Relation.Relation.Any>>, Root> => ({
708
716
  selection: spec.selection,
709
- with: (spec.with ?? []) as With,
710
- without: (spec.without ?? []) as Without,
711
- filters: (spec.filters ?? []) as Filters,
712
- withRelations: (spec.withRelations ?? []) as WithRelations,
713
- withoutRelations: (spec.withoutRelations ?? []) as WithoutRelations,
714
- withRelated: (spec.withRelated ?? []) as WithRelated,
715
- withoutRelated: (spec.withoutRelated ?? []) as WithoutRelated,
717
+ with: (spec.with ?? []) as unknown as Uninferred<With, ReadonlyArray<ComponentDescriptor>>,
718
+ without: (spec.without ?? []) as unknown as Uninferred<Without, ReadonlyArray<ComponentDescriptor>>,
719
+ filters: (spec.filters ?? []) as unknown as Uninferred<Filters, ReadonlyArray<Filter<ComponentDescriptor>>>,
720
+ withRelations: (spec.withRelations ?? []) as unknown as Uninferred<WithRelations, ReadonlyArray<Relation.Relation.Any>>,
721
+ withoutRelations: (spec.withoutRelations ?? []) as unknown as Uninferred<WithoutRelations, ReadonlyArray<Relation.Relation.Any>>,
722
+ withRelated: (spec.withRelated ?? []) as unknown as Uninferred<WithRelated, ReadonlyArray<Relation.Relation.Any>>,
723
+ withoutRelated: (spec.withoutRelated ?? []) as unknown as Uninferred<WithoutRelated, ReadonlyArray<Relation.Relation.Any>>,
716
724
  __schemaRoot: undefined as unknown as Root
717
725
  })