@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.
- package/README.md +2 -0
- package/dist/Brand.d.ts +9 -4
- package/dist/Brand.d.ts.map +1 -1
- package/dist/Brand.js +9 -4
- package/dist/Brand.js.map +1 -1
- package/dist/Clock.d.ts +186 -0
- package/dist/Clock.d.ts.map +1 -0
- package/dist/Clock.js +111 -0
- package/dist/Clock.js.map +1 -0
- package/dist/Command.d.ts +250 -41
- package/dist/Command.d.ts.map +1 -1
- package/dist/Command.js +59 -23
- package/dist/Command.js.map +1 -1
- package/dist/Condition.d.ts +12 -1
- package/dist/Condition.d.ts.map +1 -1
- package/dist/Condition.js.map +1 -1
- package/dist/Debug.d.ts +163 -15
- package/dist/Debug.d.ts.map +1 -1
- package/dist/Debug.js.map +1 -1
- package/dist/Decode.d.ts +1 -1
- package/dist/Decode.js +1 -1
- package/dist/Descriptor.d.ts +67 -24
- package/dist/Descriptor.d.ts.map +1 -1
- package/dist/Descriptor.js +32 -15
- package/dist/Descriptor.js.map +1 -1
- package/dist/Entity.d.ts +22 -5
- package/dist/Entity.d.ts.map +1 -1
- package/dist/Entity.js +2 -2
- package/dist/Entity.js.map +1 -1
- package/dist/EntityScope.d.ts +26 -1
- package/dist/EntityScope.d.ts.map +1 -1
- package/dist/EntityScope.js +14 -2
- package/dist/EntityScope.js.map +1 -1
- package/dist/HostClock.d.ts +49 -0
- package/dist/HostClock.d.ts.map +1 -0
- package/dist/HostClock.js +46 -0
- package/dist/HostClock.js.map +1 -0
- package/dist/Inspector.d.ts +7 -1
- package/dist/Inspector.d.ts.map +1 -1
- package/dist/Inspector.js.map +1 -1
- package/dist/Machine.d.ts +67 -36
- package/dist/Machine.d.ts.map +1 -1
- package/dist/Machine.js +10 -3
- package/dist/Machine.js.map +1 -1
- package/dist/Query.d.ts +74 -26
- package/dist/Query.d.ts.map +1 -1
- package/dist/Query.js +32 -28
- package/dist/Query.js.map +1 -1
- package/dist/Runtime.d.ts +39 -16
- package/dist/Runtime.d.ts.map +1 -1
- package/dist/Runtime.js +556 -87
- package/dist/Runtime.js.map +1 -1
- package/dist/Schedule.d.ts +217 -10
- package/dist/Schedule.d.ts.map +1 -1
- package/dist/Schedule.js +5 -0
- package/dist/Schedule.js.map +1 -1
- package/dist/Schema.d.ts +196 -44
- package/dist/Schema.d.ts.map +1 -1
- package/dist/Schema.js +29 -11
- package/dist/Schema.js.map +1 -1
- package/dist/Snapshot.d.ts +14 -0
- package/dist/Snapshot.d.ts.map +1 -1
- package/dist/Snapshot.js.map +1 -1
- package/dist/System.d.ts +212 -58
- package/dist/System.d.ts.map +1 -1
- package/dist/System.js +84 -6
- package/dist/System.js.map +1 -1
- package/dist/index.d.ts +8 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -7
- package/dist/index.js.map +1 -1
- package/dist/internal/cells.d.ts +3 -3
- package/dist/internal/cells.d.ts.map +1 -1
- package/dist/internal/cells.js +9 -11
- package/dist/internal/cells.js.map +1 -1
- package/dist/internal/debug.d.ts +13 -0
- package/dist/internal/debug.d.ts.map +1 -1
- package/dist/internal/debug.js +192 -7
- package/dist/internal/debug.js.map +1 -1
- package/dist/internal/equal.d.ts +8 -0
- package/dist/internal/equal.d.ts.map +1 -0
- package/dist/internal/equal.js +38 -0
- package/dist/internal/equal.js.map +1 -0
- package/dist/internal/fragments.d.ts.map +1 -1
- package/dist/internal/fragments.js +11 -1
- package/dist/internal/fragments.js.map +1 -1
- package/dist/internal/game.d.ts.map +1 -1
- package/dist/internal/game.js +41 -6
- package/dist/internal/game.js.map +1 -1
- package/dist/internal/order.d.ts +10 -0
- package/dist/internal/order.d.ts.map +1 -0
- package/dist/internal/order.js +122 -0
- package/dist/internal/order.js.map +1 -0
- package/dist/internal/queries.d.ts.map +1 -1
- package/dist/internal/queries.js +32 -10
- package/dist/internal/queries.js.map +1 -1
- package/dist/internal/requirements.d.ts +26 -0
- package/dist/internal/requirements.d.ts.map +1 -0
- package/dist/internal/requirements.js +46 -0
- package/dist/internal/requirements.js.map +1 -0
- package/package.json +1 -1
- package/src/Brand.ts +9 -4
- package/src/Clock.ts +311 -0
- package/src/Command.ts +407 -89
- package/src/Condition.ts +17 -1
- package/src/Debug.ts +172 -14
- package/src/Decode.ts +1 -1
- package/src/Descriptor.ts +92 -37
- package/src/Entity.ts +24 -5
- package/src/EntityScope.ts +27 -2
- package/src/HostClock.ts +64 -0
- package/src/Inspector.ts +7 -2
- package/src/Machine.ts +81 -37
- package/src/Query.ts +110 -38
- package/src/Runtime.ts +607 -111
- package/src/Schedule.ts +380 -13
- package/src/Schema.ts +249 -63
- package/src/Snapshot.ts +4 -0
- package/src/System.ts +248 -93
- package/src/index.ts +8 -7
- package/src/internal/cells.ts +10 -12
- package/src/internal/debug.ts +208 -12
- package/src/internal/equal.ts +31 -0
- package/src/internal/fragments.ts +12 -2
- package/src/internal/game.ts +61 -7
- package/src/internal/order.ts +144 -0
- package/src/internal/queries.ts +37 -13
- package/src/internal/requirements.ts +71 -0
- package/dist/Definition.d.ts +0 -99
- package/dist/Definition.d.ts.map +0 -1
- package/dist/Definition.js +0 -68
- package/dist/Definition.js.map +0 -1
- package/dist/Feature.d.ts +0 -106
- package/dist/Feature.d.ts.map +0 -1
- package/dist/Feature.js +0 -87
- package/dist/Feature.js.map +0 -1
- package/dist/Fx.d.ts +0 -128
- package/dist/Fx.d.ts.map +0 -1
- package/dist/Fx.js +0 -112
- package/dist/Fx.js.map +0 -1
- package/dist/internal/definition.d.ts +0 -17
- package/dist/internal/definition.d.ts.map +0 -1
- package/dist/internal/definition.js +0 -25
- package/dist/internal/definition.js.map +0 -1
- package/src/Definition.ts +0 -106
- package/src/Feature.ts +0 -241
- package/src/Fx.ts +0 -176
- package/src/internal/definition.ts +0 -56
package/src/Condition.ts
CHANGED
|
@@ -45,10 +45,25 @@ type AnyQuery = Query.Query.Any<any>
|
|
|
45
45
|
/** Access categories a check may declare. */
|
|
46
46
|
export interface CheckAccessInput {
|
|
47
47
|
readonly queries?: Record<string, AnyQuery>
|
|
48
|
-
readonly resources?: Record<string,
|
|
48
|
+
readonly resources?: Record<string, CheckResourceAccess>
|
|
49
49
|
readonly machines?: Record<string, Machine.MachineRead<Machine.StateMachine.Any>>
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
+
/** Accepted at the constraint so `ExactCheckAccess` can reject the last two with a message naming the fix. */
|
|
53
|
+
type CheckResourceAccess =
|
|
54
|
+
| System.ResourceRead<Descriptor<"resource", string, any>>
|
|
55
|
+
| System.ResourceChanged<Descriptor<"resource", string, any>>
|
|
56
|
+
| System.ResourceWrite<Descriptor<"resource", string, any>>
|
|
57
|
+
|
|
58
|
+
/** A check reads committed values and owns no cursor: `changedResource` and `writeResource` slots become a message naming the fix. */
|
|
59
|
+
type CheckResources<Resources> = {
|
|
60
|
+
readonly [K in keyof Resources]: Resources[K] extends { readonly mode: "changed" }
|
|
61
|
+
? "changedResource consumes a per-reader cursor, which a check does not have: use Game.System.readResource here, and changedResource in the gated system"
|
|
62
|
+
: Resources[K] extends { readonly mode: "write" }
|
|
63
|
+
? "a check cannot write resources: use Game.System.readResource"
|
|
64
|
+
: Resources[K]
|
|
65
|
+
}
|
|
66
|
+
|
|
52
67
|
type QueryWriteAccess<Q extends AnyQuery> =
|
|
53
68
|
Extract<Q["selection"][keyof Q["selection"]], { readonly mode: "write" }>
|
|
54
69
|
|
|
@@ -72,6 +87,7 @@ export type ExactCheckAccess<Access extends CheckAccessInput> = Access & {
|
|
|
72
87
|
readonly queries?: Access["queries"] extends Record<string, AnyQuery>
|
|
73
88
|
? StatelessQueries<Access["queries"]>
|
|
74
89
|
: never
|
|
90
|
+
readonly resources?: CheckResources<Access["resources"]>
|
|
75
91
|
}
|
|
76
92
|
|
|
77
93
|
/** What a check's predicate sees: its declared resources, machines, and queries, read-only. */
|
package/src/Debug.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Opt-in runtime introspection for tools, tests, and coding agents.
|
|
3
3
|
*
|
|
4
|
-
* A runtime made with `debug:
|
|
4
|
+
* A runtime made with `debug: { hostClock }` carries a `debug` handle. Runtimes made
|
|
5
5
|
* without it have no handle at all, so production code cannot reach into
|
|
6
6
|
* internals by accident and pays nothing for them.
|
|
7
7
|
*
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
*
|
|
36
36
|
* @example
|
|
37
37
|
* ```ts
|
|
38
|
-
* const runtime = Game.Runtime.make({ services, resources, debug:
|
|
38
|
+
* const runtime = Game.Runtime.make({ services, resources, debug: { hostClock: HostClock.standard } })
|
|
39
39
|
* runtime.debug.nameSchedules({ setup: setupSchedule, update: updateSchedule })
|
|
40
40
|
*
|
|
41
41
|
* const stop = runtime.debug.observe((event) => {
|
|
@@ -46,15 +46,16 @@
|
|
|
46
46
|
* stop()
|
|
47
47
|
* ```
|
|
48
48
|
*/
|
|
49
|
+
import type { HostClock } from "./HostClock.ts"
|
|
49
50
|
import type * as Machine from "./Machine.ts"
|
|
50
|
-
import type { ExecutableScheduleDefinition } from "./Schedule.ts"
|
|
51
|
+
import type { AnyScheduleOrder, ExecutableScheduleDefinition } from "./Schedule.ts"
|
|
51
52
|
import type { Schema } from "./Schema.ts"
|
|
52
53
|
|
|
53
54
|
/** Version of the description, dump, and trace formats. */
|
|
54
55
|
export const formatVersion = 1 as const
|
|
55
56
|
|
|
56
57
|
/**
|
|
57
|
-
* The debug handle of a runtime made with `debug:
|
|
58
|
+
* The debug handle of a runtime made with `debug: { hostClock }`.
|
|
58
59
|
*/
|
|
59
60
|
export interface Handle<S extends Schema.Any = Schema.Any, Root = unknown> {
|
|
60
61
|
/**
|
|
@@ -64,8 +65,15 @@ export interface Handle<S extends Schema.Any = Schema.Any, Root = unknown> {
|
|
|
64
65
|
* `onEnter(Game/Flow=Playing)`. Naming again replaces earlier names.
|
|
65
66
|
*/
|
|
66
67
|
readonly nameSchedules: (
|
|
67
|
-
schedules: Readonly<Record<string, ExecutableScheduleDefinition<S, any, Root, any, any>>>
|
|
68
|
+
schedules: Readonly<Record<string, ExecutableScheduleDefinition<S, any, Root, any, any, AnyScheduleOrder>>>
|
|
68
69
|
) => void
|
|
70
|
+
/**
|
|
71
|
+
* Declares the events the host publishes with `runtime.send`, so
|
|
72
|
+
* `describe()` counts them as written (`event-never-written`) before the
|
|
73
|
+
* first send happens. Events actually sent count as well. Declaring again
|
|
74
|
+
* replaces the list.
|
|
75
|
+
*/
|
|
76
|
+
readonly declareHostEvents: (events: ReadonlyArray<Schema.EventDescriptor<S>>) => void
|
|
69
77
|
/** The static description of the schema and the named schedules. */
|
|
70
78
|
readonly describe: () => Description
|
|
71
79
|
/** The current world as plain data. */
|
|
@@ -75,6 +83,21 @@ export interface Handle<S extends Schema.Any = Schema.Any, Root = unknown> {
|
|
|
75
83
|
* active only while at least one listener is registered.
|
|
76
84
|
*/
|
|
77
85
|
readonly observe: (listener: (event: TraceEvent) => void) => () => void
|
|
86
|
+
/**
|
|
87
|
+
* Delivers what the host does to the runtime, until the returned function
|
|
88
|
+
* is called: each tick's start and end (with the schedule values it runs)
|
|
89
|
+
* and each `runtime.send`. Unlike `observe`, it turns no tracing on, so a
|
|
90
|
+
* tool can listen for a whole play session (replay recorders do).
|
|
91
|
+
*/
|
|
92
|
+
readonly observeHost: (listener: (event: HostEvent<S, Root>) => void) => () => void
|
|
93
|
+
/**
|
|
94
|
+
* Reports the last failed tick result if nobody read its `ok`, as a
|
|
95
|
+
* `failure.ignored` event (to `observe` and `observeHost` listeners), with
|
|
96
|
+
* `when: "session-end"`. Call it when a session ends; the start of the
|
|
97
|
+
* next tick checks the same with `when: "next-tick"`. Emits nothing to a
|
|
98
|
+
* console.
|
|
99
|
+
*/
|
|
100
|
+
readonly checkIgnoredFailures: () => void
|
|
78
101
|
/** Retention and reader state of every stream that has readers or entries. */
|
|
79
102
|
readonly streams: () => ReadonlyArray<StreamStatus>
|
|
80
103
|
/** Live entity count and, per component, how many entities have it. Cheap enough to sample every frame. */
|
|
@@ -93,6 +116,10 @@ export type Storage = "constructed" | "transient" | "plain"
|
|
|
93
116
|
export interface ComponentDescription {
|
|
94
117
|
readonly name: string
|
|
95
118
|
readonly storage: Storage
|
|
119
|
+
/** Declared `immutable: true`: queries only read it, `commands.insert` replaces it. */
|
|
120
|
+
readonly immutable: boolean
|
|
121
|
+
/** The components it directly requires (`Schema.requires`). */
|
|
122
|
+
readonly requires: ReadonlyArray<string>
|
|
96
123
|
}
|
|
97
124
|
|
|
98
125
|
export interface ResourceDescription {
|
|
@@ -113,8 +140,22 @@ export interface RelationDescription {
|
|
|
113
140
|
export interface MachineDescription {
|
|
114
141
|
readonly name: string
|
|
115
142
|
readonly states: ReadonlyArray<Machine.StateValue>
|
|
116
|
-
/**
|
|
143
|
+
/**
|
|
144
|
+
* The committed value, or `undefined` when the runtime does not provide the
|
|
145
|
+
* machine, or when it is a sub-state whose parent is in another state.
|
|
146
|
+
*/
|
|
117
147
|
readonly current: Machine.StateValue | undefined
|
|
148
|
+
/** Whether the runtime was given an initial value for the machine. */
|
|
149
|
+
readonly provided: boolean
|
|
150
|
+
/** For a sub-state, where it exists, as `Parent=state`. */
|
|
151
|
+
readonly within: string | undefined
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/** An entity scope and the state changes that despawn it (`Game.EntityScope(name, { despawnOnExit, despawnOnEnter })`). */
|
|
155
|
+
export interface ScopeDescription {
|
|
156
|
+
readonly name: string
|
|
157
|
+
/** Triggers as `onExit(Flow=Playing)` / `onEnter(Flow=Menu)`; empty when only `commands.despawnScope` ends it. */
|
|
158
|
+
readonly despawnOn: ReadonlyArray<string>
|
|
118
159
|
}
|
|
119
160
|
|
|
120
161
|
export interface ServiceDescription {
|
|
@@ -132,6 +173,8 @@ export interface QueryDescription {
|
|
|
132
173
|
readonly without: ReadonlyArray<string>
|
|
133
174
|
readonly added: ReadonlyArray<string>
|
|
134
175
|
readonly changed: ReadonlyArray<string>
|
|
176
|
+
/** One entry per `anyOf` filter: the components of its `added` and `changed` members. */
|
|
177
|
+
readonly anyOf: ReadonlyArray<{ readonly added: ReadonlyArray<string>; readonly changed: ReadonlyArray<string> }>
|
|
135
178
|
/** Relation filters and reads, for example `with ChildOf` or `read Children`. */
|
|
136
179
|
readonly relations: ReadonlyArray<string>
|
|
137
180
|
}
|
|
@@ -142,7 +185,8 @@ export interface SystemDescription {
|
|
|
142
185
|
/** Where the system runs, as `schedule#step`. */
|
|
143
186
|
readonly placements: ReadonlyArray<string>
|
|
144
187
|
readonly queries: ReadonlyArray<QueryDescription>
|
|
145
|
-
|
|
188
|
+
/** Resource reads and writes. `changed` lists the reads declared with `changedResource` (also in `reads`). */
|
|
189
|
+
readonly resources: { readonly reads: ReadonlyArray<string>; readonly changed: ReadonlyArray<string>; readonly writes: ReadonlyArray<string> }
|
|
146
190
|
readonly events: { readonly reads: ReadonlyArray<string>; readonly writes: ReadonlyArray<string> }
|
|
147
191
|
readonly machines: {
|
|
148
192
|
readonly reads: ReadonlyArray<string>
|
|
@@ -153,7 +197,15 @@ export interface SystemDescription {
|
|
|
153
197
|
readonly removed: ReadonlyArray<string>
|
|
154
198
|
readonly despawned: boolean
|
|
155
199
|
readonly relationFailures: ReadonlyArray<string>
|
|
200
|
+
/** Events and resources read with `Game.System.thisTick`, and query slots as `queries.<slot>`. */
|
|
201
|
+
readonly thisTick: ReadonlyArray<string>
|
|
156
202
|
readonly services: ReadonlyArray<string>
|
|
203
|
+
/**
|
|
204
|
+
* The declared command permissions (`Game.System.commands(...)`), by
|
|
205
|
+
* kind: component names for spawns, inserts, removes and despawns, scope
|
|
206
|
+
* names for despawnsScope, relation names for relates.
|
|
207
|
+
*/
|
|
208
|
+
readonly commands: CommandPermissions
|
|
157
209
|
/** Run conditions, rendered, for example `stateChanged(Game/Flow)`. */
|
|
158
210
|
readonly when: ReadonlyArray<string>
|
|
159
211
|
/** Pause conditions, rendered: while one fails the system waits and holds its stream entries. */
|
|
@@ -162,6 +214,16 @@ export interface SystemDescription {
|
|
|
162
214
|
readonly local: boolean
|
|
163
215
|
}
|
|
164
216
|
|
|
217
|
+
/** Declared command permissions of one system, by kind. Empty lists when it declares none. */
|
|
218
|
+
export interface CommandPermissions {
|
|
219
|
+
readonly spawns: ReadonlyArray<string>
|
|
220
|
+
readonly inserts: ReadonlyArray<string>
|
|
221
|
+
readonly removes: ReadonlyArray<string>
|
|
222
|
+
readonly despawns: ReadonlyArray<string>
|
|
223
|
+
readonly despawnsScope: ReadonlyArray<string>
|
|
224
|
+
readonly relates: ReadonlyArray<string>
|
|
225
|
+
}
|
|
226
|
+
|
|
165
227
|
export type StepDescription =
|
|
166
228
|
| { readonly kind: "system"; readonly system: string }
|
|
167
229
|
| { readonly kind: "applyDeferred" }
|
|
@@ -175,17 +237,34 @@ export interface ScheduleDescription {
|
|
|
175
237
|
/**
|
|
176
238
|
* A finding about the described schedules. `warning` is almost certainly a
|
|
177
239
|
* bug; `info` is worth a look but can be intentional.
|
|
240
|
+
*
|
|
241
|
+
* Most lints read declarations only. Some also use what the runtime has seen
|
|
242
|
+
* so far: events sent with `runtime.send(...)` count as written,
|
|
243
|
+
* `relation-failure-never-read` fires for a relation that applied commands
|
|
244
|
+
* have tried to relate or reorder (which can fail) while no described system
|
|
245
|
+
* reads its failures, and `requirement-violated` lists commands skipped
|
|
246
|
+
* because they would have broken a `Schema.requires` requirement, so
|
|
247
|
+
* describe after the game has run for a while. `scope-trigger-never-applied`
|
|
248
|
+
* also reads which machines the runtime provides.
|
|
178
249
|
*/
|
|
179
250
|
export interface Lint {
|
|
180
251
|
readonly severity: "warning" | "info"
|
|
181
252
|
readonly code:
|
|
182
253
|
| "event-never-read"
|
|
254
|
+
| "relation-failure-never-read"
|
|
183
255
|
| "event-never-written"
|
|
184
256
|
| "next-state-never-applied"
|
|
257
|
+
| "resource-changed-never-written"
|
|
185
258
|
| "component-never-read"
|
|
186
259
|
| "read-before-write"
|
|
260
|
+
| "this-tick-order"
|
|
261
|
+
| "command-permission-unused"
|
|
262
|
+
| "requirement-violated"
|
|
263
|
+
| "scope-trigger-never-applied"
|
|
187
264
|
readonly message: string
|
|
188
265
|
readonly subject: string
|
|
266
|
+
/** What to change to resolve it (or, for `info`, when it is fine as it is). */
|
|
267
|
+
readonly fix: string
|
|
189
268
|
}
|
|
190
269
|
|
|
191
270
|
/** Readers and writers of one component, resource, or event. */
|
|
@@ -202,12 +281,15 @@ export interface Description {
|
|
|
202
281
|
readonly events: ReadonlyArray<{ readonly name: string }>
|
|
203
282
|
readonly relations: ReadonlyArray<RelationDescription>
|
|
204
283
|
readonly machines: ReadonlyArray<MachineDescription>
|
|
284
|
+
/** Scopes defined through the game, in definition order. */
|
|
285
|
+
readonly scopes: ReadonlyArray<ScopeDescription>
|
|
205
286
|
readonly services: ReadonlyArray<ServiceDescription>
|
|
206
287
|
readonly schedules: ReadonlyArray<ScheduleDescription>
|
|
207
288
|
readonly systems: ReadonlyArray<SystemDescription>
|
|
208
289
|
/**
|
|
209
290
|
* Readers and writers across the named schedules. Component writers are
|
|
210
|
-
* systems with a write query slot
|
|
291
|
+
* systems with a write query slot or a declared `spawns`, `inserts`, or
|
|
292
|
+
* `removes` permission for it.
|
|
211
293
|
*/
|
|
212
294
|
readonly access: {
|
|
213
295
|
readonly components: ReadonlyArray<AccessIndexEntry>
|
|
@@ -340,6 +422,32 @@ export type Effect =
|
|
|
340
422
|
| { readonly kind: "relate"; readonly entity: number; readonly relation: string; readonly target: number }
|
|
341
423
|
| { readonly kind: "unrelate"; readonly entity: number; readonly relation: string }
|
|
342
424
|
| { readonly kind: "relationFailure"; readonly entity: number; readonly relation: string; readonly error: string }
|
|
425
|
+
/** A command skipped because it would leave `component` without `missing` (`Schema.requires`). */
|
|
426
|
+
| {
|
|
427
|
+
readonly kind: "requirementViolated"
|
|
428
|
+
readonly entity: number
|
|
429
|
+
readonly command: RequirementViolation["command"]
|
|
430
|
+
readonly component: string
|
|
431
|
+
readonly missing: string
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/**
|
|
435
|
+
* Commands of one system skipped because applying them would have left a
|
|
436
|
+
* component without one it requires (`Schema.requires`): the
|
|
437
|
+
* `requirement-violated` lint.
|
|
438
|
+
*/
|
|
439
|
+
export interface RequirementViolation {
|
|
440
|
+
readonly system: string
|
|
441
|
+
readonly command: "spawn" | "insert" | "remove"
|
|
442
|
+
/** The requirer. */
|
|
443
|
+
readonly component: string
|
|
444
|
+
/** The required component it would have lacked. */
|
|
445
|
+
readonly missing: string
|
|
446
|
+
/** The last entity it happened to. */
|
|
447
|
+
readonly entity: number
|
|
448
|
+
/** How many commands were skipped this way. */
|
|
449
|
+
readonly count: number
|
|
450
|
+
}
|
|
343
451
|
|
|
344
452
|
export interface AppliedCommand {
|
|
345
453
|
readonly tag: string
|
|
@@ -433,8 +541,10 @@ export interface TransitionEvent {
|
|
|
433
541
|
readonly tick: number
|
|
434
542
|
readonly schedule: string
|
|
435
543
|
readonly machine: string
|
|
436
|
-
|
|
437
|
-
readonly
|
|
544
|
+
/** `undefined` when a sub-state starts existing (its parent entered its state). */
|
|
545
|
+
readonly from: Machine.StateValue | undefined
|
|
546
|
+
/** `undefined` when a sub-state stops existing (its parent left its state). */
|
|
547
|
+
readonly to: Machine.StateValue | undefined
|
|
438
548
|
/**
|
|
439
549
|
* `applied` committed the new value; `unchanged` was a `setIfChanged` to the
|
|
440
550
|
* current value; `failed` stopped in an exit or transition schedule and was
|
|
@@ -450,6 +560,45 @@ export interface RestoreEvent {
|
|
|
450
560
|
readonly ok: boolean
|
|
451
561
|
}
|
|
452
562
|
|
|
563
|
+
/** What `observeHost` delivers. */
|
|
564
|
+
export type HostEvent<S extends Schema.Any = Schema.Any, Root = unknown> =
|
|
565
|
+
| {
|
|
566
|
+
readonly type: "tick.start"
|
|
567
|
+
/** The schedule values passed to this `tick(...)` (or `tryTick`) call, in order. */
|
|
568
|
+
readonly schedules: ReadonlyArray<ExecutableScheduleDefinition<S, any, Root, any, any, AnyScheduleOrder>>
|
|
569
|
+
readonly frame: number
|
|
570
|
+
}
|
|
571
|
+
| {
|
|
572
|
+
readonly type: "tick.end"
|
|
573
|
+
readonly schedules: ReadonlyArray<ExecutableScheduleDefinition<S, any, Root, any, any, AnyScheduleOrder>>
|
|
574
|
+
readonly frame: number
|
|
575
|
+
/** `failed`: a system returned `Result.failure`; `defect`: one threw (rethrown after this event). */
|
|
576
|
+
readonly outcome: "ok" | "failed" | "defect"
|
|
577
|
+
readonly error?: unknown
|
|
578
|
+
}
|
|
579
|
+
| { readonly type: "send"; readonly event: string; readonly value: unknown }
|
|
580
|
+
| FailureIgnoredEvent
|
|
581
|
+
|
|
582
|
+
/**
|
|
583
|
+
* A failed `tick` / `tryTick` result whose `ok` nobody read: the failure
|
|
584
|
+
* went unnoticed, and the systems after the failing one did not run that
|
|
585
|
+
* tick. Found at the start of the next tick (`when: "next-tick"`) or by
|
|
586
|
+
* `checkIgnoredFailures()` (`when: "session-end"`). Debug runtimes only.
|
|
587
|
+
*/
|
|
588
|
+
export interface FailureIgnoredEvent {
|
|
589
|
+
readonly type: "failure.ignored"
|
|
590
|
+
/** The frame of the failed tick. */
|
|
591
|
+
readonly frame: number
|
|
592
|
+
/** The schedule holding the failing system (the first one for a tick that ran nothing). */
|
|
593
|
+
readonly schedule: string
|
|
594
|
+
/** The failing system; `undefined` when `tryTick` ran nothing (missing requirements, order). */
|
|
595
|
+
readonly system: string | undefined
|
|
596
|
+
readonly error: unknown
|
|
597
|
+
/** The systems of the tick that did not run because of the failure, in order. */
|
|
598
|
+
readonly skipped: ReadonlyArray<string>
|
|
599
|
+
readonly when: "next-tick" | "session-end"
|
|
600
|
+
}
|
|
601
|
+
|
|
453
602
|
/** Everything `observe` delivers. */
|
|
454
603
|
export type TraceEvent =
|
|
455
604
|
| FrameEvent
|
|
@@ -461,14 +610,21 @@ export type TraceEvent =
|
|
|
461
610
|
| DeferredEvent
|
|
462
611
|
| TransitionEvent
|
|
463
612
|
| RestoreEvent
|
|
613
|
+
| FailureIgnoredEvent
|
|
464
614
|
|
|
465
615
|
/**
|
|
466
|
-
* The `debug` runtime option: `
|
|
616
|
+
* The `debug` runtime option: `{ hostClock }` attaches a handle, timing
|
|
617
|
+
* traces with that clock. A value that may be `undefined`
|
|
618
|
+
* (`import.meta.env.DEV ? { hostClock } : undefined`) attaches it when
|
|
619
|
+
* present and types `debug` as optional, so code checks it before use.
|
|
467
620
|
*/
|
|
468
|
-
export
|
|
621
|
+
export interface Option {
|
|
622
|
+
/** Times trace events (`ms`); see `HostClock`. */
|
|
623
|
+
readonly hostClock: HostClock
|
|
624
|
+
}
|
|
469
625
|
|
|
470
626
|
/**
|
|
471
|
-
* The member a runtime made with `debug:
|
|
627
|
+
* The member a runtime made with `debug: { hostClock }` carries.
|
|
472
628
|
*/
|
|
473
629
|
export interface Member<S extends Schema.Any, Root> {
|
|
474
630
|
readonly debug: Handle<S, Root>
|
|
@@ -476,4 +632,6 @@ export interface Member<S extends Schema.Any, Root> {
|
|
|
476
632
|
|
|
477
633
|
/** Resolves the extra runtime member for a `debug` option type. */
|
|
478
634
|
export type MemberFor<Enabled, S extends Schema.Any, Root> =
|
|
479
|
-
[Enabled] extends [
|
|
635
|
+
[Enabled] extends [true] ? Member<S, Root>
|
|
636
|
+
: [Enabled] extends [false | undefined] ? {}
|
|
637
|
+
: Partial<Member<S, Root>>
|
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
|
-
* `
|
|
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.
|
|
20
|
-
* matter
|
|
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
|
|
@@ -165,6 +166,44 @@ export interface TransientDescriptor<
|
|
|
165
166
|
readonly transient: true
|
|
166
167
|
}
|
|
167
168
|
|
|
169
|
+
/**
|
|
170
|
+
* Marks a component that is never edited in place: `Game.Query.write(...)`
|
|
171
|
+
* rejects it, and the only way to change its value is to replace it with
|
|
172
|
+
* `commands.insert(...)` (applied at `applyDeferred()`, seen by
|
|
173
|
+
* `Game.Query.changed(...)`).
|
|
174
|
+
*/
|
|
175
|
+
export interface Immutable {
|
|
176
|
+
readonly immutable: true
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Options of the component descriptor constructors (`Component`,
|
|
181
|
+
* `ConstructedComponent`, `TransientComponent`).
|
|
182
|
+
*/
|
|
183
|
+
export interface ComponentOptions {
|
|
184
|
+
/**
|
|
185
|
+
* `true` for data that says what an entity is (a kind, a team, a tile) and
|
|
186
|
+
* is replaced, never edited: queries can only read it, and
|
|
187
|
+
* `commands.insert(...)` replaces it. Leave it out for state that systems
|
|
188
|
+
* update every frame.
|
|
189
|
+
*/
|
|
190
|
+
readonly immutable?: true
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/** The descriptor a component constructor returns for `Options`: `D`, plus `Immutable` when `immutable: true`. */
|
|
194
|
+
export type WithComponentOptions<D, Options extends ComponentOptions> =
|
|
195
|
+
Options extends { readonly immutable: true } ? D & Immutable : D
|
|
196
|
+
|
|
197
|
+
const applyComponentOptions = <D extends object>(descriptor: D, options: ComponentOptions | undefined): D =>
|
|
198
|
+
options?.immutable === true ? { ...descriptor, immutable: true } : descriptor
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Whether a component descriptor was declared `immutable: true` (readable by
|
|
202
|
+
* queries, replaced only with `commands.insert(...)`).
|
|
203
|
+
*/
|
|
204
|
+
export const isImmutable = (descriptor: Descriptor.Any): descriptor is Descriptor.Any & Immutable =>
|
|
205
|
+
"immutable" in descriptor && descriptor.immutable === true
|
|
206
|
+
|
|
168
207
|
/**
|
|
169
208
|
* Type-level helpers for working with descriptors.
|
|
170
209
|
*/
|
|
@@ -253,11 +292,15 @@ const makeConstructedDescriptor = <
|
|
|
253
292
|
* @example
|
|
254
293
|
* ```ts
|
|
255
294
|
* const Position = Descriptor.Component<{ x: number; y: number }>()("Position")
|
|
295
|
+
* // What an entity is, replaced with commands.insert and never written by a query:
|
|
296
|
+
* const EnemyKind = Descriptor.Component<"grunt" | "boss">()("EnemyKind", { immutable: true })
|
|
256
297
|
* ```
|
|
257
298
|
*/
|
|
258
|
-
export const Component = <Value>() => <const Name extends string>(
|
|
259
|
-
name: Name
|
|
260
|
-
|
|
299
|
+
export const Component = <Value>() => <const Name extends string, const Options extends ComponentOptions = {}>(
|
|
300
|
+
name: Name,
|
|
301
|
+
options?: Options
|
|
302
|
+
): WithComponentOptions<Descriptor<"component", Name, Value>, Options> =>
|
|
303
|
+
applyComponentOptions(makeDescriptor("component", name), options) as WithComponentOptions<Descriptor<"component", Name, Value>, Options>
|
|
261
304
|
|
|
262
305
|
/**
|
|
263
306
|
* Defines a component descriptor that also knows how to validate raw values.
|
|
@@ -274,10 +317,11 @@ export const Component = <Value>() => <const Name extends string>(
|
|
|
274
317
|
*/
|
|
275
318
|
export const ConstructedComponent = <Value, Raw, Error, Constructor extends {} = {}>(
|
|
276
319
|
constructor: ResultConstructor<Value, Raw, Error> & Constructor
|
|
277
|
-
) => <const Name extends string>(
|
|
278
|
-
name: Name
|
|
279
|
-
|
|
280
|
-
|
|
320
|
+
) => <const Name extends string, const Options extends ComponentOptions = {}>(
|
|
321
|
+
name: Name,
|
|
322
|
+
options?: Options
|
|
323
|
+
): WithComponentOptions<ConstructedFor<"component", Name, Value, Raw, Error, Constructor>, Options> =>
|
|
324
|
+
applyComponentOptions(makeConstructedDescriptor("component", name, constructor), options) as WithComponentOptions<ConstructedFor<"component", Name, Value, Raw, Error, Constructor>, Options>
|
|
281
325
|
|
|
282
326
|
/**
|
|
283
327
|
* Defines a component descriptor that snapshots skip.
|
|
@@ -290,9 +334,11 @@ export const ConstructedComponent = <Value, Raw, Error, Constructor extends {} =
|
|
|
290
334
|
* const SpriteRef = Descriptor.TransientComponent<{ frame: number }>()("SpriteRef")
|
|
291
335
|
* ```
|
|
292
336
|
*/
|
|
293
|
-
export const TransientComponent = <Value>() => <const Name extends string>(
|
|
294
|
-
name: Name
|
|
295
|
-
|
|
337
|
+
export const TransientComponent = <Value>() => <const Name extends string, const Options extends ComponentOptions = {}>(
|
|
338
|
+
name: Name,
|
|
339
|
+
options?: Options
|
|
340
|
+
): WithComponentOptions<TransientDescriptor<"component", Name, Value>, Options> =>
|
|
341
|
+
applyComponentOptions(makeTransientDescriptor("component", name), options) as WithComponentOptions<TransientDescriptor<"component", Name, Value>, Options>
|
|
296
342
|
|
|
297
343
|
/**
|
|
298
344
|
* Defines a marker component: no data, only presence (`[Player, {}]`).
|
|
@@ -311,70 +357,77 @@ export const Tag = <const Name extends string>(
|
|
|
311
357
|
): DecodableDescriptor<"component", Name, {}, {}, DecodeModule.DecodeError> =>
|
|
312
358
|
ConstructedComponent(DecodeModule.struct({}))(name)
|
|
313
359
|
|
|
314
|
-
/** One state of a {@link
|
|
315
|
-
export type
|
|
360
|
+
/** One state of a {@link EntityState} component: a string or number literal. */
|
|
361
|
+
export type EntityStateValue = string | number
|
|
316
362
|
|
|
317
363
|
/**
|
|
318
|
-
* The allowed moves of a {@link
|
|
364
|
+
* The allowed moves of a {@link EntityState} component: for every state, the
|
|
319
365
|
* states it may move to (`[]` for a state with no way out).
|
|
320
366
|
*/
|
|
321
|
-
export type
|
|
367
|
+
export type EntityStateTransitions<States extends readonly [EntityStateValue, ...Array<EntityStateValue>]> = {
|
|
322
368
|
readonly [From in States[number]]: ReadonlyArray<States[number]>
|
|
323
369
|
}
|
|
324
370
|
|
|
325
371
|
/**
|
|
326
372
|
* A component whose value is one of a closed set of states, with an optional
|
|
327
|
-
* graph of allowed moves. Built with {@link
|
|
373
|
+
* graph of allowed moves. Built with {@link EntityState}.
|
|
328
374
|
*/
|
|
329
|
-
export interface
|
|
375
|
+
export interface EntityStateDescriptor<
|
|
330
376
|
out Name extends string,
|
|
331
|
-
in out States extends readonly [
|
|
332
|
-
in out Transitions extends
|
|
377
|
+
in out States extends readonly [EntityStateValue, ...Array<EntityStateValue>],
|
|
378
|
+
in out Transitions extends EntityStateTransitions<States> | undefined
|
|
333
379
|
> extends DecodableDescriptor<"component", Name, States[number], States[number], DecodeModule.DecodeError> {
|
|
334
380
|
readonly states: States
|
|
335
381
|
readonly transitions: Transitions
|
|
336
382
|
}
|
|
337
383
|
|
|
338
384
|
/**
|
|
339
|
-
* Defines a state component
|
|
340
|
-
* snapshots reject unknown states),
|
|
341
|
-
* (storage, change detection, rollback, traces).
|
|
385
|
+
* Defines a per-entity state: a component whose value is one of `states`,
|
|
386
|
+
* validated (so snapshots reject unknown states), that behaves like any other
|
|
387
|
+
* component (storage, change detection, rollback, traces).
|
|
388
|
+
*
|
|
389
|
+
* Use this when each entity has its own state: an enemy's attack windup, a
|
|
390
|
+
* door's open/closed, a unit's AI mode. Use `Game.StateMachine(...)` instead
|
|
391
|
+
* for one game-wide mode (menu, playing, paused, game over): machines change
|
|
392
|
+
* only at `applyStateTransitions(...)`, run `onEnter`/`onExit` schedules, and
|
|
393
|
+
* gate systems with `Game.Condition.inState(...)`. An entity state does none
|
|
394
|
+
* of that; it is data on an entity.
|
|
342
395
|
*
|
|
343
396
|
* With `transitions`, its write cell gains `transition(from, to)`: the pair
|
|
344
397
|
* 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
|
|
346
|
-
* `
|
|
398
|
+
* happens only if the current state is still `from`, otherwise it returns an
|
|
399
|
+
* `EntityStateMismatch` failure. Transitions are ordinary component writes:
|
|
347
400
|
* immediate, visible to later systems, rolled back with a failed system.
|
|
348
401
|
*
|
|
349
402
|
* @example
|
|
350
403
|
* ```ts
|
|
351
|
-
* const
|
|
404
|
+
* const Attack = Descriptor.EntityState("Attack", ["ready", "windup", "active"] as const, {
|
|
352
405
|
* transitions: { ready: ["windup"], windup: ["active", "ready"], active: ["ready"] }
|
|
353
406
|
* })
|
|
354
|
-
* // in a system with a write slot `
|
|
355
|
-
* const moved = data.
|
|
407
|
+
* // in a system with a write slot `attack`:
|
|
408
|
+
* const moved = data.attack.transition("windup", "active") // "ready" -> "active" does not compile
|
|
356
409
|
* ```
|
|
357
410
|
*/
|
|
358
|
-
export const
|
|
411
|
+
export const EntityState = <
|
|
359
412
|
const Name extends string,
|
|
360
|
-
const States extends readonly [
|
|
361
|
-
const Transitions extends
|
|
413
|
+
const States extends readonly [EntityStateValue, ...Array<EntityStateValue>],
|
|
414
|
+
const Transitions extends EntityStateTransitions<States> | undefined = undefined
|
|
362
415
|
>(
|
|
363
416
|
name: Name,
|
|
364
417
|
states: States,
|
|
365
418
|
options?: {
|
|
366
419
|
readonly transitions: Transitions & { readonly [Key in Exclude<keyof Transitions, States[number]>]: never }
|
|
367
420
|
}
|
|
368
|
-
):
|
|
421
|
+
): EntityStateDescriptor<Name, States, Transitions> => ({
|
|
369
422
|
...makeConstructedDescriptor("component", name, DecodeModule.literal(...states)),
|
|
370
423
|
states,
|
|
371
424
|
transitions: options?.transitions
|
|
372
|
-
}) as unknown as
|
|
425
|
+
}) as unknown as EntityStateDescriptor<Name, States, Transitions>
|
|
373
426
|
|
|
374
|
-
/** Whether a descriptor was built with {@link
|
|
375
|
-
export const
|
|
427
|
+
/** Whether a descriptor was built with {@link EntityState}. */
|
|
428
|
+
export const isEntityState = (
|
|
376
429
|
descriptor: Descriptor.Any
|
|
377
|
-
): descriptor is
|
|
430
|
+
): descriptor is EntityStateDescriptor<string, readonly [EntityStateValue, ...Array<EntityStateValue>], EntityStateTransitions<readonly [EntityStateValue, ...Array<EntityStateValue>]> | undefined> =>
|
|
378
431
|
"states" in descriptor
|
|
379
432
|
|
|
380
433
|
/**
|
|
@@ -568,7 +621,9 @@ export function constructorOf<D extends Descriptor.Any>(
|
|
|
568
621
|
* `related` is the reverse collection maintained by the runtime.
|
|
569
622
|
*
|
|
570
623
|
* Use hierarchy when the relationship must support ordered children,
|
|
571
|
-
* ancestor/descendant traversal, and linked recursive despawn.
|
|
624
|
+
* ancestor/descendant traversal, and linked recursive despawn. Use
|
|
625
|
+
* `Game.EntityScope(...)` instead when entities only share a lifetime (a
|
|
626
|
+
* level, a menu) and need no tree, order, or traversal.
|
|
572
627
|
*/
|
|
573
628
|
export const Hierarchy = RelationModule.Hierarchy
|
|
574
629
|
|
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
|
-
*
|
|
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,23 @@ 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, out Lacks = never> {
|
|
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`, `insert`, and
|
|
179
|
+
* `remove` check. Type-level only.
|
|
180
|
+
*/
|
|
181
|
+
readonly __proves?: (_: never) => Proves
|
|
182
|
+
/**
|
|
183
|
+
* The component descriptors the producing query proves absent (its
|
|
184
|
+
* `without`): what `commands.remove` checks against `Schema.requires`.
|
|
185
|
+
* Type-level only.
|
|
186
|
+
*/
|
|
187
|
+
readonly __lacks?: (_: never) => Lacks
|
|
175
188
|
/**
|
|
176
189
|
* The schema-bound entity identity.
|
|
177
190
|
*/
|
|
@@ -193,12 +206,18 @@ export interface EntityMut<
|
|
|
193
206
|
S extends Schema.Any,
|
|
194
207
|
out P extends ComponentProof,
|
|
195
208
|
out W extends ComponentProof,
|
|
196
|
-
Root = unknown
|
|
209
|
+
Root = unknown,
|
|
210
|
+
out Proves = unknown,
|
|
211
|
+
out Lacks = never
|
|
197
212
|
> {
|
|
198
213
|
/**
|
|
199
214
|
* Runtime tag for debugging and pattern matching.
|
|
200
215
|
*/
|
|
201
216
|
readonly kind: "EntityMut"
|
|
217
|
+
/** The component descriptors the producing query proves; see `EntityRef`. */
|
|
218
|
+
readonly __proves?: (_: never) => Proves
|
|
219
|
+
/** The component descriptors the producing query proves absent; see `EntityRef`. */
|
|
220
|
+
readonly __lacks?: (_: never) => Lacks
|
|
202
221
|
/**
|
|
203
222
|
* The schema-bound entity identity.
|
|
204
223
|
*/
|
|
@@ -247,7 +266,7 @@ export type HandleTarget<S extends Schema.Any, Root = unknown> =
|
|
|
247
266
|
* Converts a current entity into a durable handle for storage.
|
|
248
267
|
*
|
|
249
268
|
* The handle is storage-safe, not a proof of liveness: resolve it later with
|
|
250
|
-
* `
|
|
269
|
+
* `queries.x.getHandle(...)` (through a declared query), which fails explicitly when the entity is gone.
|
|
251
270
|
*
|
|
252
271
|
* Pass an `intent` component when later code assumes a role such as "player"
|
|
253
272
|
* or "damage source". An intent does not prove the component is still present;
|
|
@@ -283,7 +302,7 @@ export interface InvalidHandle {
|
|
|
283
302
|
* handles, so restoring validates them like any other value.
|
|
284
303
|
*
|
|
285
304
|
* A handle only names an entity; it never proves the entity exists. Resolving
|
|
286
|
-
* it with `
|
|
305
|
+
* it with `queries.x.getHandle(...)` (through a declared query) stays the checked step, so any positive
|
|
287
306
|
* integer id decodes. Object-shaped handles and other non-numeric values
|
|
288
307
|
* are rejected.
|
|
289
308
|
*
|