@moku-labs/game 0.0.1 → 0.0.2
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 +424 -37
- package/dist/assets.d.mts +161 -0
- package/dist/assets.mjs +4729 -0
- package/dist/component-DGg5DqKK.mjs +44 -0
- package/dist/control.d.mts +162 -0
- package/dist/control.mjs +758 -0
- package/dist/define-sFoO3y6X.d.mts +263 -0
- package/dist/headless-CvamCUcR.mjs +186 -0
- package/dist/headless-KaSWcd0s.d.mts +181 -0
- package/dist/index.d.mts +1204 -50
- package/dist/index.mjs +20978 -2010
- package/dist/inspect.d.mts +109 -0
- package/dist/inspect.mjs +519 -0
- package/dist/jsx-dev-runtime.d.mts +2 -0
- package/dist/jsx-dev-runtime.mjs +2 -0
- package/dist/jsx-runtime.d.mts +2 -0
- package/dist/jsx-runtime.mjs +2 -0
- package/dist/memory-CvgdnsQO.mjs +259 -0
- package/dist/registry-DlpRCibU.mjs +384 -0
- package/dist/runtime-DRlwxkIv.mjs +182 -0
- package/dist/runtime-DiOTkDZz.d.mts +77 -0
- package/dist/session-DmAxY6Ll.mjs +145 -0
- package/dist/testing.d.mts +23 -111
- package/dist/testing.mjs +12 -289
- package/dist/types-BxkNNYul.d.mts +2840 -0
- package/dist/types-CTPS9GBu.d.mts +742 -0
- package/dist/types-DD-QrG_z.d.mts +588 -0
- package/dist/types-DWILGrPn.d.mts +622 -0
- package/dist/types-DYLnSgMI.d.mts +570 -0
- package/dist/types-JNc_UQBo.d.mts +2896 -0
- package/dist/types-yg_ywtT-.d.mts +1859 -0
- package/dist/visual-BDUHSRvf.mjs +734 -0
- package/package.json +26 -4
- package/dist/registry-DWV5C0Mf.mjs +0 -666
- package/dist/types-BfsmUzLC.d.mts +0 -1908
|
@@ -0,0 +1,742 @@
|
|
|
1
|
+
import { L as Json, k as Api, lt as Require, t as Api$1, w as TypeTag } from "./types-JNc_UQBo.mjs";
|
|
2
|
+
import { D as AnyComponent, N as Entity, T as TrackSegment, b as MotionHandle, g as Ease, k as AnyComponentValue, u as Api$2, w as TrackOptions } from "./types-BxkNNYul.mjs";
|
|
3
|
+
import { Log } from "@moku-labs/common/browser";
|
|
4
|
+
import { PluginCtx } from "@moku-labs/core";
|
|
5
|
+
|
|
6
|
+
//#region src/plugins/anim/tween/types.d.ts
|
|
7
|
+
/**
|
|
8
|
+
* One running track: the numeric fields of one component of one entity moving from the values
|
|
9
|
+
* read at the end of the delay to the exact target, straight or through keyframe segments. A
|
|
10
|
+
* track with repeats walks again from its first segment each time a run ends.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```ts
|
|
14
|
+
* const track: Track = {
|
|
15
|
+
* id: 1, entity: 1_048_576, component: Transform, to: { x: 200 },
|
|
16
|
+
* keys: { x: "1048576:Transform:x" }, ms: 350, ease: "out", delayMs: 0, elapsed: 0,
|
|
17
|
+
* from: undefined, muted: () => new Set(), additive: false, driven: false, bornFrame: 4,
|
|
18
|
+
* ended: false, segments: undefined, repeatsLeft: 0, held: false
|
|
19
|
+
* };
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
type Track = {
|
|
23
|
+
readonly id: number;
|
|
24
|
+
readonly entity: Entity;
|
|
25
|
+
readonly component: AnyComponent; /** The target fields. Retarget deletes the fields a newer absolute track took over. */
|
|
26
|
+
to: Record<string, number>;
|
|
27
|
+
/**
|
|
28
|
+
* The key of the owner, bases and offsets tables per field, built once when the track starts.
|
|
29
|
+
* It keeps a field `to` lost, which nothing reads again.
|
|
30
|
+
*/
|
|
31
|
+
readonly keys: Readonly<Record<string, string>>;
|
|
32
|
+
readonly ms: number;
|
|
33
|
+
readonly ease: Ease;
|
|
34
|
+
readonly delayMs: number;
|
|
35
|
+
elapsed: number; /** Read when the delay ends, never earlier. */
|
|
36
|
+
from: Record<string, number> | undefined;
|
|
37
|
+
readonly muted: () => ReadonlySet<string>;
|
|
38
|
+
readonly additive: boolean; /** True for a track a timeline step advances by hand; the frame step leaves it alone. */
|
|
39
|
+
readonly driven: boolean; /** The frame step the track was born in. That step does not advance it a second time. */
|
|
40
|
+
readonly bornFrame: number;
|
|
41
|
+
ended: boolean;
|
|
42
|
+
/**
|
|
43
|
+
* The keyframe segments the track walks over `ms`, or `undefined` for a straight track. A
|
|
44
|
+
* segment without an ease runs on the track's `ease`.
|
|
45
|
+
*/
|
|
46
|
+
readonly segments: readonly TrackSegment[] | undefined;
|
|
47
|
+
/**
|
|
48
|
+
* The runs still to go after the one that plays: `0` for a track that runs once, `Infinity`
|
|
49
|
+
* for a loop (`repeat: "forever"`), which never ends by itself.
|
|
50
|
+
*/
|
|
51
|
+
repeatsLeft: number;
|
|
52
|
+
/**
|
|
53
|
+
* True while a loop stands on its first key and has written it: the hold writes once, and the
|
|
54
|
+
* walk clears it, so the next hold writes the first key again.
|
|
55
|
+
*/
|
|
56
|
+
held: boolean;
|
|
57
|
+
};
|
|
58
|
+
/**
|
|
59
|
+
* A motion handle that can also be advanced by hand: what the timeline cursor needs, so a track
|
|
60
|
+
* born inside a frame step still consumes the remainder of that step's delta.
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* ```ts
|
|
64
|
+
* const motion: StepMotion = { ...inert, advance: () => 0 };
|
|
65
|
+
* motion.advance(16); // 0: nothing is left over
|
|
66
|
+
* ```
|
|
67
|
+
*/
|
|
68
|
+
type StepMotion = MotionHandle & {
|
|
69
|
+
/**
|
|
70
|
+
* Advances the track by one delta.
|
|
71
|
+
*
|
|
72
|
+
* @param deltaMs - Milliseconds of game time to consume.
|
|
73
|
+
* @returns The milliseconds left over past the end of the track.
|
|
74
|
+
*/
|
|
75
|
+
advance(deltaMs: number): number;
|
|
76
|
+
};
|
|
77
|
+
//#endregion
|
|
78
|
+
//#region src/plugins/anim/timeline/types.d.ts
|
|
79
|
+
/**
|
|
80
|
+
* A sound, owned by `anim` and handled by `audio`. It is a `flow` descriptor and a timeline step
|
|
81
|
+
* at once: `fx(sfx("board.merge"))` in a node, or `sfx("board.merge")` inside a `sequence`.
|
|
82
|
+
*
|
|
83
|
+
* @example
|
|
84
|
+
* ```ts
|
|
85
|
+
* const descriptor: SfxDescriptor = {
|
|
86
|
+
* kind: "sfx", payload: { key: "board.merge", bus: "sfx" }, cosmetic: true
|
|
87
|
+
* };
|
|
88
|
+
* ```
|
|
89
|
+
*/
|
|
90
|
+
type SfxDescriptor = {
|
|
91
|
+
readonly kind: "sfx";
|
|
92
|
+
readonly payload: {
|
|
93
|
+
readonly key: string;
|
|
94
|
+
readonly bus: string;
|
|
95
|
+
};
|
|
96
|
+
readonly cosmetic: true;
|
|
97
|
+
};
|
|
98
|
+
/**
|
|
99
|
+
* A haptic tick, owned by `anim` and handled by `platform`. Like `sfx` it is a descriptor and a
|
|
100
|
+
* timeline step at once.
|
|
101
|
+
*
|
|
102
|
+
* @example
|
|
103
|
+
* ```ts
|
|
104
|
+
* const descriptor: HapticDescriptor = {
|
|
105
|
+
* kind: "haptic", payload: { kind: "light" }, cosmetic: true
|
|
106
|
+
* };
|
|
107
|
+
* ```
|
|
108
|
+
*/
|
|
109
|
+
type HapticDescriptor = {
|
|
110
|
+
readonly kind: "haptic";
|
|
111
|
+
readonly payload: {
|
|
112
|
+
readonly kind: string;
|
|
113
|
+
};
|
|
114
|
+
readonly cosmetic: true;
|
|
115
|
+
};
|
|
116
|
+
/**
|
|
117
|
+
* The two descriptors `anim` owns. As a step, the descriptor is dispatched through
|
|
118
|
+
* `flow.fx.dispatch` when the step is reached; a kind nobody handles is silent.
|
|
119
|
+
*
|
|
120
|
+
* @example
|
|
121
|
+
* ```ts
|
|
122
|
+
* const step: FxStep = { kind: "haptic", payload: { kind: "light" }, cosmetic: true };
|
|
123
|
+
* ```
|
|
124
|
+
*/
|
|
125
|
+
type FxStep = SfxDescriptor | HapticDescriptor;
|
|
126
|
+
/**
|
|
127
|
+
* The descriptor `play(animation, slots)` builds. The payload names the animation, so the
|
|
128
|
+
* handler resolves the definition from the registry instead of carrying a function.
|
|
129
|
+
*
|
|
130
|
+
* @example
|
|
131
|
+
* ```ts
|
|
132
|
+
* const descriptor: PlayDescriptor = {
|
|
133
|
+
* kind: "play",
|
|
134
|
+
* payload: { animation: "hud.coinsFly", slots: { from: { projection: "hud", key: "coins" } } },
|
|
135
|
+
* cosmetic: true
|
|
136
|
+
* };
|
|
137
|
+
* ```
|
|
138
|
+
*/
|
|
139
|
+
type PlayDescriptor = {
|
|
140
|
+
readonly kind: "play";
|
|
141
|
+
readonly payload: {
|
|
142
|
+
readonly animation: string;
|
|
143
|
+
readonly slots: Json;
|
|
144
|
+
};
|
|
145
|
+
readonly cosmetic: true;
|
|
146
|
+
};
|
|
147
|
+
/**
|
|
148
|
+
* One step of a choreography: plain frozen data with no callback inside, so a step tree can be
|
|
149
|
+
* read, logged and compared. A `spawn` step carries the component values of a temporary entity,
|
|
150
|
+
* made when the step is reached and despawned when the timeline ends.
|
|
151
|
+
*
|
|
152
|
+
* @example
|
|
153
|
+
* ```ts
|
|
154
|
+
* const step: Step = { kind: "wait", ms: 120 };
|
|
155
|
+
* ```
|
|
156
|
+
*/
|
|
157
|
+
type Step = {
|
|
158
|
+
readonly kind: "sequence";
|
|
159
|
+
readonly steps: readonly Step[];
|
|
160
|
+
} | {
|
|
161
|
+
readonly kind: "parallel";
|
|
162
|
+
readonly steps: readonly Step[];
|
|
163
|
+
} | {
|
|
164
|
+
readonly kind: "wait";
|
|
165
|
+
readonly ms: number;
|
|
166
|
+
} | {
|
|
167
|
+
readonly kind: "mark";
|
|
168
|
+
readonly name: string;
|
|
169
|
+
} | {
|
|
170
|
+
readonly kind: "tween";
|
|
171
|
+
readonly target: Target;
|
|
172
|
+
readonly component: AnyComponent;
|
|
173
|
+
readonly to: Readonly<Record<string, number>>;
|
|
174
|
+
readonly ms: number;
|
|
175
|
+
readonly ease: Ease;
|
|
176
|
+
readonly delayMs: number;
|
|
177
|
+
readonly additive: boolean; /** `"root"`: the `Transform` fields are a root pose, turned local when the track starts. */
|
|
178
|
+
readonly space: "local" | "root";
|
|
179
|
+
} | {
|
|
180
|
+
readonly kind: "set";
|
|
181
|
+
readonly target: Target;
|
|
182
|
+
readonly component: AnyComponent;
|
|
183
|
+
readonly patch: Readonly<Record<string, unknown>>;
|
|
184
|
+
} | {
|
|
185
|
+
readonly kind: "frames";
|
|
186
|
+
readonly target: Target;
|
|
187
|
+
readonly keys: readonly string[];
|
|
188
|
+
readonly fps: number;
|
|
189
|
+
readonly loop: boolean;
|
|
190
|
+
} | {
|
|
191
|
+
readonly kind: "spawn";
|
|
192
|
+
readonly id: string;
|
|
193
|
+
readonly components: readonly AnyComponentValue[];
|
|
194
|
+
readonly layer: string;
|
|
195
|
+
readonly order: number;
|
|
196
|
+
} | FxStep | {
|
|
197
|
+
readonly kind: "use";
|
|
198
|
+
readonly id: string;
|
|
199
|
+
readonly step: Step;
|
|
200
|
+
};
|
|
201
|
+
/**
|
|
202
|
+
* Reserved for Spine and the other external players: `external` throws, and this is the shape it
|
|
203
|
+
* would take. Exported as a type only.
|
|
204
|
+
*
|
|
205
|
+
* @example
|
|
206
|
+
* ```ts
|
|
207
|
+
* const player: ExternalPlayer = { name: "spine", play: () => undefined };
|
|
208
|
+
* ```
|
|
209
|
+
*/
|
|
210
|
+
type ExternalPlayer = {
|
|
211
|
+
readonly name: string;
|
|
212
|
+
play(clip: string): unknown;
|
|
213
|
+
};
|
|
214
|
+
/**
|
|
215
|
+
* Where one step of a running timeline stands. One mutable record per step of the tree, built
|
|
216
|
+
* when the timeline starts.
|
|
217
|
+
*
|
|
218
|
+
* @example
|
|
219
|
+
* ```ts
|
|
220
|
+
* const cursor: Cursor = {
|
|
221
|
+
* step: { kind: "wait", ms: 120 }, children: [], index: 0, elapsed: 0,
|
|
222
|
+
* motion: undefined, started: false, ended: false
|
|
223
|
+
* };
|
|
224
|
+
* ```
|
|
225
|
+
*/
|
|
226
|
+
type Cursor = {
|
|
227
|
+
readonly step: Step;
|
|
228
|
+
readonly children: readonly Cursor[]; /** Position in a `sequence`, and the last written key of a `frames` step. */
|
|
229
|
+
index: number; /** Consumed milliseconds of a `wait` or a `frames` step. */
|
|
230
|
+
elapsed: number;
|
|
231
|
+
motion: StepMotion | undefined;
|
|
232
|
+
started: boolean;
|
|
233
|
+
ended: boolean;
|
|
234
|
+
};
|
|
235
|
+
/**
|
|
236
|
+
* What the cursor reaches the rest of the engine through. Injected, so `timeline` never imports
|
|
237
|
+
* the run-time code of `tween` or of another plugin.
|
|
238
|
+
*/
|
|
239
|
+
type TimelineRuntime = {
|
|
240
|
+
/**
|
|
241
|
+
* Resolves a target to an entity.
|
|
242
|
+
*
|
|
243
|
+
* @param target - The projection key, the entity or the spawned id a step names.
|
|
244
|
+
* @param spawned - The spawn table of the timeline that asks; a spawned id resolves only there.
|
|
245
|
+
* @returns The entity, or `undefined` when no live view, element or spawned entity holds it.
|
|
246
|
+
*/
|
|
247
|
+
entityOf(target: Target, spawned?: ReadonlyMap<string, Entity>): Entity | undefined;
|
|
248
|
+
/**
|
|
249
|
+
* Spawns a temporary entity owned by `anim`. The timeline that spawned it despawns it when it
|
|
250
|
+
* ends, whichever way it ends.
|
|
251
|
+
*
|
|
252
|
+
* @param components - The component values the entity starts with, `Layer` and `Order` included.
|
|
253
|
+
* @returns The new entity.
|
|
254
|
+
*/
|
|
255
|
+
spawn(components: readonly AnyComponentValue[]): Entity;
|
|
256
|
+
/**
|
|
257
|
+
* Reads a component of an entity, which is also the liveness check of a step.
|
|
258
|
+
*
|
|
259
|
+
* @param entity - The entity to read.
|
|
260
|
+
* @param component - The component to read.
|
|
261
|
+
* @returns The stored value, or `undefined`.
|
|
262
|
+
*/
|
|
263
|
+
read(entity: Entity, component: AnyComponent): Readonly<Record<string, unknown>> | undefined;
|
|
264
|
+
/**
|
|
265
|
+
* Writes a patch through `ecs.set`, so `changed()` sees it.
|
|
266
|
+
*
|
|
267
|
+
* @param entity - The entity to write.
|
|
268
|
+
* @param component - The component to write.
|
|
269
|
+
* @param patch - The fields to overwrite.
|
|
270
|
+
*/
|
|
271
|
+
write(entity: Entity, component: AnyComponent, patch: Record<string, unknown>): void;
|
|
272
|
+
/**
|
|
273
|
+
* Turns the root-space target of a `tween` step into the local target under the entity's
|
|
274
|
+
* parent, through `localPoseOf` of `renderer`.
|
|
275
|
+
*
|
|
276
|
+
* @param entity - The entity the step moves.
|
|
277
|
+
* @param component - The component the step drives; only `Transform` is converted.
|
|
278
|
+
* @param to - The numeric target fields, in root space.
|
|
279
|
+
* @returns The target fields in the space of the entity's parent.
|
|
280
|
+
*/
|
|
281
|
+
toLocal(entity: Entity, component: AnyComponent, to: Readonly<Record<string, number>>): Record<string, number>;
|
|
282
|
+
/**
|
|
283
|
+
* Starts one track on the tween core.
|
|
284
|
+
*
|
|
285
|
+
* @param entity - The entity to animate.
|
|
286
|
+
* @param component - The component to animate.
|
|
287
|
+
* @param to - The numeric target fields.
|
|
288
|
+
* @param options - Duration, easing, delay and the additive flag.
|
|
289
|
+
* @returns The motion of the track, advanceable by hand.
|
|
290
|
+
*/
|
|
291
|
+
start(entity: Entity, component: AnyComponent, to: Record<string, number>, options: TrackOptions): StepMotion;
|
|
292
|
+
/**
|
|
293
|
+
* Hands a descriptor step to `flow.fx.dispatch`.
|
|
294
|
+
*
|
|
295
|
+
* @param descriptor - The `sfx` or `haptic` step that was reached.
|
|
296
|
+
*/
|
|
297
|
+
dispatch(descriptor: FxStep): void;
|
|
298
|
+
/**
|
|
299
|
+
* Reports a mark: the event, the `onMark` listeners and the timeline's own list.
|
|
300
|
+
*
|
|
301
|
+
* @param animation - Id of the animation the mark belongs to.
|
|
302
|
+
* @param name - Name of the mark.
|
|
303
|
+
*/
|
|
304
|
+
mark(animation: string, name: string): void;
|
|
305
|
+
/**
|
|
306
|
+
* Lifts the idle frame rate, because something started to move.
|
|
307
|
+
*/
|
|
308
|
+
wake(): void;
|
|
309
|
+
};
|
|
310
|
+
/**
|
|
311
|
+
* One running timeline: its cursor, the marks it reached, the entities it counts on, the entities
|
|
312
|
+
* it spawned and despawns at its end, and the resolver of its `done` promise.
|
|
313
|
+
*/
|
|
314
|
+
type RunningTimeline = {
|
|
315
|
+
readonly id: number;
|
|
316
|
+
readonly animation: string;
|
|
317
|
+
readonly cursor: Cursor;
|
|
318
|
+
readonly marks: string[];
|
|
319
|
+
readonly entities: readonly Entity[]; /** The entities its `spawn` steps made, by spawn id. Despawned when the timeline ends. */
|
|
320
|
+
readonly spawned: Map<string, Entity>;
|
|
321
|
+
readonly resolve: () => void;
|
|
322
|
+
ended: boolean;
|
|
323
|
+
};
|
|
324
|
+
declare namespace types_d_exports {
|
|
325
|
+
export { AnimApi, AnimCtx, AnimationDefinition, AnimationSpec, AnyAnimationDefinition, BuildTools, Config, Cursor, Deps, Events, ExternalPlayer, FxStep, HapticDescriptor, KernelSlice, MarkListener, MotionKeyframe, PlayDescriptor, PlayHandle, Pose, RunningTimeline, SfxDescriptor, SlotRecord, SlotTags, SlotValue, SlotValues, State, Step, StepMotion, Target, TimelineRuntime, Track };
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* What a step and a slot are pointed at: the projection key of a live view or of an element a
|
|
329
|
+
* plugin above registered with `registerKey`, an entity, or the id of an entity a `spawn` step of
|
|
330
|
+
* the same timeline made (`spawned(id)`). A node passes keys, never entities.
|
|
331
|
+
*
|
|
332
|
+
* @example
|
|
333
|
+
* ```ts
|
|
334
|
+
* const counter: Target = { projection: "hud", key: "coins" };
|
|
335
|
+
* const flyingCoin: Target = spawned("coin1"); // { spawned: "coin1" }
|
|
336
|
+
* ```
|
|
337
|
+
*/
|
|
338
|
+
type Target = {
|
|
339
|
+
projection: string;
|
|
340
|
+
key: string;
|
|
341
|
+
} | Entity | {
|
|
342
|
+
spawned: string;
|
|
343
|
+
};
|
|
344
|
+
/**
|
|
345
|
+
* The pose `at(target)` answers with: where the target rests in root (reference) space, its rest
|
|
346
|
+
* `Transform` composed through the `Parent` chain, or the identity pose when nothing is there.
|
|
347
|
+
*
|
|
348
|
+
* @example
|
|
349
|
+
* ```ts
|
|
350
|
+
* const pose: Pose = { x: 540, y: 960, rotation: 0, scale: 1 };
|
|
351
|
+
* ```
|
|
352
|
+
*/
|
|
353
|
+
type Pose = {
|
|
354
|
+
x: number;
|
|
355
|
+
y: number;
|
|
356
|
+
rotation: number;
|
|
357
|
+
scale: number;
|
|
358
|
+
};
|
|
359
|
+
/**
|
|
360
|
+
* The slot declaration of an animation: one type tag per slot, each carrying one target or a
|
|
361
|
+
* list of targets.
|
|
362
|
+
*
|
|
363
|
+
* @example
|
|
364
|
+
* ```ts
|
|
365
|
+
* const slots: SlotTags = { items: type<Target[]>(), card: type<Target>() };
|
|
366
|
+
* ```
|
|
367
|
+
*/
|
|
368
|
+
type SlotTags = Readonly<Record<string, TypeTag<Target> | TypeTag<Target[]>>>;
|
|
369
|
+
/**
|
|
370
|
+
* The targets `play` and `use` take, read off the slot tags of the animation.
|
|
371
|
+
*
|
|
372
|
+
* @example
|
|
373
|
+
* ```ts
|
|
374
|
+
* type Slots = SlotValues<{ card: TypeTag<Target> }>; // { card: Target }
|
|
375
|
+
* ```
|
|
376
|
+
*/
|
|
377
|
+
type SlotValues<Tags extends SlotTags> = { [Name in keyof Tags]: SlotValue<Tags[Name]> };
|
|
378
|
+
/**
|
|
379
|
+
* What one slot tag stands for: a list of targets when the tag declared one, a single target
|
|
380
|
+
* otherwise. Distributive on purpose, so the erased `SlotTags` widens to both.
|
|
381
|
+
*
|
|
382
|
+
* @example
|
|
383
|
+
* ```ts
|
|
384
|
+
* type One = SlotValue<TypeTag<Target>>; // Target
|
|
385
|
+
* ```
|
|
386
|
+
*/
|
|
387
|
+
type SlotValue<Tag> = Tag extends TypeTag<Target[]> ? Target[] : Target;
|
|
388
|
+
/**
|
|
389
|
+
* The slots of a running animation with their tags erased, as `build` and the `play` handler
|
|
390
|
+
* read them.
|
|
391
|
+
*
|
|
392
|
+
* @example
|
|
393
|
+
* ```ts
|
|
394
|
+
* const slots: SlotRecord = { card: { projection: "hud", key: "order" } };
|
|
395
|
+
* ```
|
|
396
|
+
*/
|
|
397
|
+
type SlotRecord = SlotValues<SlotTags>;
|
|
398
|
+
/**
|
|
399
|
+
* What `build` is handed next to the slots: `at` reads where a target rests at play time, in root
|
|
400
|
+
* space (its rest `Transform` composed through the `Parent` chain, every pivot applied), so a
|
|
401
|
+
* choreography can fly one element to where another one rests, even inside a scaled slot. A view
|
|
402
|
+
* that has a `Parent` itself is aimed at that pose with a `tween` in `space: "root"`.
|
|
403
|
+
*
|
|
404
|
+
* @example
|
|
405
|
+
* ```ts
|
|
406
|
+
* const tools: BuildTools = { at: () => ({ x: 0, y: 0, rotation: 0, scale: 1 }) };
|
|
407
|
+
* ```
|
|
408
|
+
*/
|
|
409
|
+
type BuildTools = {
|
|
410
|
+
at(target: Target): Pose;
|
|
411
|
+
};
|
|
412
|
+
/**
|
|
413
|
+
* One animation: an id, the slots it takes and the pure function that builds its step tree. The
|
|
414
|
+
* same definition may play twice at once, because `build` runs per play.
|
|
415
|
+
*
|
|
416
|
+
* @example
|
|
417
|
+
* ```ts
|
|
418
|
+
* const coinsFly: AnimationDefinition<{ from: TypeTag<Target> }> = defineAnimation("hud.coinsFly", {
|
|
419
|
+
* slots: { from: type<Target>() },
|
|
420
|
+
* build: ({ from }) => tween(from, Transform, { scale: 0.4 }, { ms: 600 })
|
|
421
|
+
* });
|
|
422
|
+
* coinsFly.id; // "hud.coinsFly"
|
|
423
|
+
* ```
|
|
424
|
+
*/
|
|
425
|
+
type AnimationDefinition<Tags extends SlotTags = SlotTags> = {
|
|
426
|
+
readonly id: string;
|
|
427
|
+
readonly slots: Tags;
|
|
428
|
+
build(slots: SlotValues<Tags>, tools: BuildTools): Step;
|
|
429
|
+
};
|
|
430
|
+
/**
|
|
431
|
+
* What `defineAnimation` is given: the slots the animation takes and the pure function that
|
|
432
|
+
* builds its step tree.
|
|
433
|
+
*
|
|
434
|
+
* @example
|
|
435
|
+
* ```ts
|
|
436
|
+
* const spec: AnimationSpec<{ card: TypeTag<Target> }> = {
|
|
437
|
+
* slots: { card: type<Target>() },
|
|
438
|
+
* build: ({ card }) => tween(card, Transform, { scale: 1.2 }, { ms: 120 })
|
|
439
|
+
* };
|
|
440
|
+
* ```
|
|
441
|
+
*/
|
|
442
|
+
type AnimationSpec<Tags extends SlotTags> = {
|
|
443
|
+
readonly slots: Tags;
|
|
444
|
+
build(slots: SlotValues<Tags>, tools: BuildTools): Step;
|
|
445
|
+
};
|
|
446
|
+
/**
|
|
447
|
+
* An animation with its slot tags erased, as the registry stores it.
|
|
448
|
+
*
|
|
449
|
+
* @example
|
|
450
|
+
* ```ts
|
|
451
|
+
* const stored: AnyAnimationDefinition = { id: "hud.coinsFly", slots: {}, build: () => wait(0) };
|
|
452
|
+
* ```
|
|
453
|
+
*/
|
|
454
|
+
type AnyAnimationDefinition = AnimationDefinition<SlotTags>;
|
|
455
|
+
/**
|
|
456
|
+
* What `play` hands back: the motion contract over the whole step tree, the promise of its end
|
|
457
|
+
* and the marks it reached so far.
|
|
458
|
+
*
|
|
459
|
+
* @example
|
|
460
|
+
* ```ts
|
|
461
|
+
* const handle: PlayHandle = app.anim.play(coinsFly, { from: { projection: "hud", key: "coins" } });
|
|
462
|
+
* handle.marks(); // []: nothing reached yet
|
|
463
|
+
* ```
|
|
464
|
+
*/
|
|
465
|
+
type PlayHandle = MotionHandle & {
|
|
466
|
+
readonly done: Promise<void>;
|
|
467
|
+
marks(): readonly string[];
|
|
468
|
+
};
|
|
469
|
+
/**
|
|
470
|
+
* A listener of `onMark`: the animation id and the name of the mark that was reached.
|
|
471
|
+
*
|
|
472
|
+
* @example
|
|
473
|
+
* ```ts
|
|
474
|
+
* const reached: string[] = [];
|
|
475
|
+
* const listener: MarkListener = (_animation, mark) => reached.push(mark);
|
|
476
|
+
* ```
|
|
477
|
+
*/
|
|
478
|
+
type MarkListener = (animation: string, mark: string) => void;
|
|
479
|
+
/**
|
|
480
|
+
* anim plugin events.
|
|
481
|
+
*
|
|
482
|
+
* @example
|
|
483
|
+
* ```ts
|
|
484
|
+
* // A feature plugin waits for the mark its choreography emits.
|
|
485
|
+
* createPlugin("rewardLog", {
|
|
486
|
+
* depends: [animPlugin],
|
|
487
|
+
* hooks: ctx => ({ "anim:mark": ({ mark }) => ctx.log.info("mark", { mark }) })
|
|
488
|
+
* }); // the `done` mark of orders.deliver logs { mark: "done" }
|
|
489
|
+
* ```
|
|
490
|
+
*/
|
|
491
|
+
type Events = {
|
|
492
|
+
/** A `mark` step was reached, or jumped by `finish()`. */"anim:mark": {
|
|
493
|
+
animation: string;
|
|
494
|
+
mark: string;
|
|
495
|
+
}; /** A timeline ended or was finished. Not emitted on `cancel()`. */
|
|
496
|
+
"anim:finished": {
|
|
497
|
+
animation: string;
|
|
498
|
+
};
|
|
499
|
+
};
|
|
500
|
+
/**
|
|
501
|
+
* One key of a keyframe track in `defineMotion`: where on the track it sits (`at`, 0..1 of
|
|
502
|
+
* `transition.ms`), the curve of the segment that ends on it (default `"inOut"`) and the pose.
|
|
503
|
+
* `dx` and `dy` are offsets from the rest pose in reference units; `rotation` (radians), `scale`
|
|
504
|
+
* and `alpha` are absolute. A field the key leaves out holds the previous key's value.
|
|
505
|
+
*
|
|
506
|
+
* An enter walk starts on the first key and ends on the rest pose at `at: 1`; an exit walk starts
|
|
507
|
+
* where the view is and ends on the last key, the pose the element leaves in.
|
|
508
|
+
*
|
|
509
|
+
* In a loop (`loop: { track }`) every Transform field is an offset added to the rest pose: `dx`, `dy`, `rotation`
|
|
510
|
+
* and `scale` (`scale: 0.05` grows a scale-1 view to 1.05). `alpha` stays absolute. The loop
|
|
511
|
+
* stands on its first key until that key's `at`, and its last key repeats its first one.
|
|
512
|
+
*
|
|
513
|
+
* @example
|
|
514
|
+
* ```ts
|
|
515
|
+
* // A popup board at 42 % of its swing: 14 u below its rest, tilted 5°, slightly larger.
|
|
516
|
+
* const key: MotionKeyframe = { at: 0.42, ease: "out", Transform: { dy: 14, rotation: 0.087, scale: 1.04 } };
|
|
517
|
+
* ```
|
|
518
|
+
*/
|
|
519
|
+
type MotionKeyframe = {
|
|
520
|
+
readonly at: number;
|
|
521
|
+
readonly ease?: Ease;
|
|
522
|
+
readonly Transform?: {
|
|
523
|
+
readonly dx?: number;
|
|
524
|
+
readonly dy?: number;
|
|
525
|
+
readonly rotation?: number;
|
|
526
|
+
readonly scale?: number;
|
|
527
|
+
};
|
|
528
|
+
readonly Shape?: {
|
|
529
|
+
readonly alpha?: number;
|
|
530
|
+
};
|
|
531
|
+
readonly Sprite?: {
|
|
532
|
+
readonly alpha?: number;
|
|
533
|
+
};
|
|
534
|
+
readonly NineSlice?: {
|
|
535
|
+
readonly alpha?: number;
|
|
536
|
+
};
|
|
537
|
+
};
|
|
538
|
+
/**
|
|
539
|
+
* anim plugin config. The timings live in the steps and in `defineMotion`, not here: a
|
|
540
|
+
* choreography that reads its duration from a config cannot be read as text.
|
|
541
|
+
*
|
|
542
|
+
* @example
|
|
543
|
+
* ```ts
|
|
544
|
+
* createApp({ plugins: [...screen], pluginConfigs: { anim: { maxTracks: 500, reducedMotion: true } } });
|
|
545
|
+
* ```
|
|
546
|
+
*/
|
|
547
|
+
type Config = {
|
|
548
|
+
/** Dev guard: one warning each time the running track count rises past this number. */maxTracks: number;
|
|
549
|
+
/**
|
|
550
|
+
* Start value of reduced motion: every track but a loop takes 0 ms and every loop stands on
|
|
551
|
+
* its first key. `app.anim.setReducedMotion(on)` switches it while the game runs.
|
|
552
|
+
*/
|
|
553
|
+
reducedMotion: boolean;
|
|
554
|
+
};
|
|
555
|
+
/**
|
|
556
|
+
* anim plugin state: the one track table, the field bookkeeping of the retarget policy and the
|
|
557
|
+
* offsets accumulator, the running timelines and the animation registry.
|
|
558
|
+
*/
|
|
559
|
+
type State = {
|
|
560
|
+
/** The one track table. Insertion order is advance order. */tracks: Map<number, Track>; /** Ids of tracks and timelines. */
|
|
561
|
+
nextId: number; /** `"<entity>:<component>:<field>"` to the id of the absolute track that drives the field. */
|
|
562
|
+
owner: Map<string, number>; /** Per field key, the delta each additive track contributes. */
|
|
563
|
+
offsets: Map<string, Map<number, number>>; /** Per field key, the value the offsets are added to. Dropped when no track drives the field. */
|
|
564
|
+
bases: Map<string, number>; /** The running timelines, by id. */
|
|
565
|
+
timelines: Map<number, RunningTimeline>; /** The `animations` of every feature, read in `onStart`. */
|
|
566
|
+
registry: Map<string, AnyAnimationDefinition>; /** `onMark` subscribers, in subscription order. */
|
|
567
|
+
markListeners: Set<MarkListener>; /** Counter of the frame steps. A track born in the running step is not advanced by it. */
|
|
568
|
+
frame: number; /** True while the track count is over `maxTracks`, so the warning is one per crossing. */
|
|
569
|
+
overMaxTracks: boolean; /** The live reduced-motion switch. The frozen `Config.reducedMotion` only seeds it. */
|
|
570
|
+
reducedMotion: boolean; /** Remover of `world.projection.setDriver`. */
|
|
571
|
+
removeDriver: (() => void) | undefined; /** Remover of `time.onFrame("animate")`. */
|
|
572
|
+
offFrame: (() => void) | undefined; /** Remover of `flow.fx.handle("play")`. */
|
|
573
|
+
offPlay: (() => void) | undefined; /** Bound in `onInit`, so the teardown context can end everything with the state alone. */
|
|
574
|
+
finishAll: (() => void) | undefined;
|
|
575
|
+
};
|
|
576
|
+
/**
|
|
577
|
+
* anim plugin API, `app.anim`: play a choreography, end everything, count what runs and listen
|
|
578
|
+
* to the marks. A choreography may spawn temporary entities; they live exactly as long as its
|
|
579
|
+
* timeline.
|
|
580
|
+
*
|
|
581
|
+
* @example
|
|
582
|
+
* ```ts
|
|
583
|
+
* // The board is full: a toast sign drops in, holds for 1.6 s and leaves. Nothing stays behind.
|
|
584
|
+
* const toastBoardFull = defineAnimation("board.toastBoardFull", {
|
|
585
|
+
* slots: {},
|
|
586
|
+
* build: () =>
|
|
587
|
+
* sequence(
|
|
588
|
+
* spawn("sign", [Text({ content: "Board is full", style: "title" }), Transform({ x: 540, y: -120 })]),
|
|
589
|
+
* tween(spawned("sign"), Transform, { y: 300 }, { ms: 400, ease: "outBack" }),
|
|
590
|
+
* wait(1600),
|
|
591
|
+
* tween(spawned("sign"), Transform, { y: -120 }, { ms: 300, ease: "in" })
|
|
592
|
+
* )
|
|
593
|
+
* });
|
|
594
|
+
* const handle = app.anim.play(toastBoardFull, {});
|
|
595
|
+
*
|
|
596
|
+
* for (let frame = 0; frame < 150; frame += 1) app.time.step(16);
|
|
597
|
+
* handle.active(); // false: the timeline ended and despawned the sign
|
|
598
|
+
* ```
|
|
599
|
+
*/
|
|
600
|
+
type AnimApi = {
|
|
601
|
+
/**
|
|
602
|
+
* Builds the step tree of an animation with the given targets and starts it. The tree runs
|
|
603
|
+
* from the next frame step on, in game time, so `time.setScale` and a pause apply to it. `at`
|
|
604
|
+
* inside `build` answers where a target rests in root space, even inside a scaled slot. A
|
|
605
|
+
* `tween` with `space: "root"` lands a hosted view on such a pose: a board item inside the board
|
|
606
|
+
* slot flies onto an order card of the HUD.
|
|
607
|
+
*
|
|
608
|
+
* @param animation - What `defineAnimation` returned.
|
|
609
|
+
* @param slots - One target, or a list of targets, per declared slot.
|
|
610
|
+
* @returns The handle of the running timeline.
|
|
611
|
+
* @throws {Error} When the tree spawns one id twice.
|
|
612
|
+
* @example
|
|
613
|
+
* ```ts
|
|
614
|
+
* // The reward is claimed: a coin appears on the reward picture and flies to the HUD counter.
|
|
615
|
+
* const coinsFly = defineAnimation("hud.coinsFly", {
|
|
616
|
+
* slots: { from: type<Target>(), to: type<Target>() },
|
|
617
|
+
* build: ({ from, to }, { at }) =>
|
|
618
|
+
* sequence(
|
|
619
|
+
* spawn("coin1", [
|
|
620
|
+
* Sprite({ texture: "ui.icon-coin", width: 64, height: 64, fit: "contain" }),
|
|
621
|
+
* Transform({ x: at(from).x, y: at(from).y })
|
|
622
|
+
* ], { order: 50 }),
|
|
623
|
+
* tween(spawned("coin1"), Transform, { x: at(to).x, y: at(to).y }, { ms: 600, ease: "inCubic" }),
|
|
624
|
+
* mark("landed")
|
|
625
|
+
* )
|
|
626
|
+
* });
|
|
627
|
+
* const handle = app.anim.play(coinsFly, {
|
|
628
|
+
* from: { projection: "reward", key: "picture" },
|
|
629
|
+
* to: { projection: "hud", key: "coins" }
|
|
630
|
+
* });
|
|
631
|
+
*
|
|
632
|
+
* for (let frame = 0; frame < 40; frame += 1) app.time.step(16);
|
|
633
|
+
* handle.marks(); // ["landed"]: the coin reached the counter and was despawned
|
|
634
|
+
* ```
|
|
635
|
+
*/
|
|
636
|
+
play<Tags extends SlotTags>(animation: AnimationDefinition<Tags>, slots: SlotValues<Tags>): PlayHandle;
|
|
637
|
+
/**
|
|
638
|
+
* Ends every track and every timeline now: each writes its exact target, the marks of a
|
|
639
|
+
* running timeline are jumped in tree order, every entity a timeline spawned is despawned and
|
|
640
|
+
* every pending `done` resolves.
|
|
641
|
+
*
|
|
642
|
+
* @example
|
|
643
|
+
* ```ts
|
|
644
|
+
* // A /control tool skips the board-full toast mid-flight: the sign is gone at once.
|
|
645
|
+
* app.anim.play(toastBoardFull, {});
|
|
646
|
+
* app.time.step(16);
|
|
647
|
+
*
|
|
648
|
+
* app.anim.finishAll();
|
|
649
|
+
* app.anim.active(); // 0
|
|
650
|
+
* ```
|
|
651
|
+
*/
|
|
652
|
+
finishAll(): void;
|
|
653
|
+
/**
|
|
654
|
+
* How many tracks are in the table, the delayed ones and the running loops included. A loop
|
|
655
|
+
* never ends by itself, so a screen at rest counts one track per loop lane it shows.
|
|
656
|
+
*
|
|
657
|
+
* @returns The number of running tracks.
|
|
658
|
+
* @example
|
|
659
|
+
* ```ts
|
|
660
|
+
* // The assertion every motion test ends with: the screen came to rest.
|
|
661
|
+
* app.anim.active(); // 0
|
|
662
|
+
*
|
|
663
|
+
* // Two order cards sway on their pins (a `loop` of rotation keys): at rest, two loops run.
|
|
664
|
+
* app.anim.active(); // 2
|
|
665
|
+
* ```
|
|
666
|
+
*/
|
|
667
|
+
active(): number;
|
|
668
|
+
/**
|
|
669
|
+
* Whether reduced motion is on. It starts at `Config.reducedMotion` and `setReducedMotion`
|
|
670
|
+
* switches it.
|
|
671
|
+
*
|
|
672
|
+
* @returns True while every new track takes 0 ms and every loop stands on its first key.
|
|
673
|
+
* @example
|
|
674
|
+
* ```ts
|
|
675
|
+
* // A settings popup shows the switch as the player left it.
|
|
676
|
+
* app.anim.reducedMotion(); // false: the start value of Config.reducedMotion
|
|
677
|
+
* ```
|
|
678
|
+
*/
|
|
679
|
+
reducedMotion(): boolean;
|
|
680
|
+
/**
|
|
681
|
+
* Switches reduced motion. While it is on, every track started from then on takes 0 ms: enter
|
|
682
|
+
* and exit, state changes, change and settle motions, drag returns and timeline tweens land on
|
|
683
|
+
* their target at the next frame, and their marks and sounds still fire. Every loop stands on
|
|
684
|
+
* its first key, a running one at once. Tracks already running keep their length.
|
|
685
|
+
*
|
|
686
|
+
* @param on - True for less motion, false for the full motion.
|
|
687
|
+
* @example
|
|
688
|
+
* ```ts
|
|
689
|
+
* // The player turns on "Less motion" in the settings popup.
|
|
690
|
+
* app.anim.setReducedMotion(true);
|
|
691
|
+
* app.anim.reducedMotion(); // true: a popup opened now stands in its rest pose next frame
|
|
692
|
+
* ```
|
|
693
|
+
*/
|
|
694
|
+
setReducedMotion(on: boolean): void;
|
|
695
|
+
/**
|
|
696
|
+
* Registers a listener called for every mark a timeline reaches, next to the `anim:mark`
|
|
697
|
+
* event. A listener that throws is logged and the listeners after it still run.
|
|
698
|
+
*
|
|
699
|
+
* @param fn - Called with the animation id and the mark name.
|
|
700
|
+
* @returns The remover; calling it twice is a no-op.
|
|
701
|
+
* @example
|
|
702
|
+
* ```ts
|
|
703
|
+
* // A test waits for the "done" mark of the delivery instead of counting frames.
|
|
704
|
+
* const reached: string[] = [];
|
|
705
|
+
* const off = app.anim.onMark((animation, mark) => reached.push(`${animation}:${mark}`));
|
|
706
|
+
*
|
|
707
|
+
* off(); // reached: ["orders.deliver:done"]
|
|
708
|
+
* ```
|
|
709
|
+
*/
|
|
710
|
+
onMark(fn: MarkListener): () => void;
|
|
711
|
+
};
|
|
712
|
+
/**
|
|
713
|
+
* Resolved dependency APIs. `renderer` is not among them: `anim` writes its `Sprite` and reads
|
|
714
|
+
* its `Transform` through the component objects, which are plain data, composes the root pose
|
|
715
|
+
* of `at` with the pure `rootPoseOf` of `renderer/sync/pose.ts`, and turns the target of a
|
|
716
|
+
* `tween` in `space: "root"` local with the pure `localPoseOf` of the same module.
|
|
717
|
+
*/
|
|
718
|
+
type Deps = {
|
|
719
|
+
time: Api;
|
|
720
|
+
flow: Api$1;
|
|
721
|
+
world: Api$2;
|
|
722
|
+
};
|
|
723
|
+
/**
|
|
724
|
+
* What the kernel context offers before the deps are attached.
|
|
725
|
+
*
|
|
726
|
+
* `index.ts` writes `events` with an annotated `register` (core spec `14-EVENT-REGISTRATION.md`
|
|
727
|
+
* row 8), so the own events reach the context the kernel hands the factories and `emit` is the
|
|
728
|
+
* kernel's own, anim-typed one. No member of the context is cast.
|
|
729
|
+
*/
|
|
730
|
+
type KernelSlice = PluginCtx<Config, State, Events> & {
|
|
731
|
+
readonly global: object;
|
|
732
|
+
readonly log: Log.LogApi;
|
|
733
|
+
readonly require: Require;
|
|
734
|
+
};
|
|
735
|
+
/**
|
|
736
|
+
* Domain context of the anim plugin: the kernel slice and the three resolved dependencies.
|
|
737
|
+
*/
|
|
738
|
+
type AnimCtx = KernelSlice & {
|
|
739
|
+
readonly deps: Deps;
|
|
740
|
+
};
|
|
741
|
+
//#endregion
|
|
742
|
+
export { Config as a, SlotValues as c, types_d_exports as d, ExternalPlayer as f, Step as g, SfxDescriptor as h, BuildTools as i, State as l, PlayDescriptor as m, AnimationDefinition as n, MotionKeyframe as o, HapticDescriptor as p, AnimationSpec as r, SlotTags as s, AnimApi as t, Target as u };
|