@moku-labs/game 0.0.2 → 0.0.3

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 (35) hide show
  1. package/README.md +89 -21
  2. package/dist/assets.d.mts +58 -6
  3. package/dist/assets.mjs +1630 -234
  4. package/dist/commands-BOxqlv7w.d.mts +99 -0
  5. package/dist/control.d.mts +3 -92
  6. package/dist/control.mjs +3 -757
  7. package/dist/define-BMo0pHe_.d.mts +51 -0
  8. package/dist/{registry-DlpRCibU.mjs → dev-CcwFrwok.mjs} +30 -1
  9. package/dist/index.d.mts +217 -29
  10. package/dist/index.mjs +6179 -727
  11. package/dist/inspect.d.mts +10 -8
  12. package/dist/inspect.mjs +2 -518
  13. package/dist/jsx-dev-runtime.d.mts +1 -1
  14. package/dist/jsx-runtime.d.mts +1 -1
  15. package/dist/{rolldown-runtime-D7D4PA-g.mjs → rolldown-runtime-8H4AJuhK.mjs} +1 -0
  16. package/dist/run-tlcn1w7y.mjs +944 -0
  17. package/dist/{runtime-DiOTkDZz.d.mts → runtime-BstIkrhH.d.mts} +1 -1
  18. package/dist/sources-BooL2MsX.mjs +545 -0
  19. package/dist/testing.d.mts +305 -3
  20. package/dist/testing.mjs +2056 -2
  21. package/dist/types-BTycq8_Z.d.mts +1196 -0
  22. package/dist/{types-yg_ywtT-.d.mts → types-C0SLQsx0.d.mts} +230 -31
  23. package/dist/types-Cjf3ywai.d.mts +5982 -0
  24. package/dist/{define-sFoO3y6X.d.mts → types-D0uYTTC3.d.mts} +180 -51
  25. package/dist/{types-DWILGrPn.d.mts → types-DgwbzDLP.d.mts} +87 -19
  26. package/dist/types-KlE_GL1z.d.mts +1444 -0
  27. package/dist/{visual-BDUHSRvf.mjs → visual-NPrDkvUk.mjs} +19 -13
  28. package/package.json +17 -2
  29. package/dist/headless-CvamCUcR.mjs +0 -186
  30. package/dist/headless-KaSWcd0s.d.mts +0 -181
  31. package/dist/types-BxkNNYul.d.mts +0 -2840
  32. package/dist/types-CTPS9GBu.d.mts +0 -742
  33. package/dist/types-DD-QrG_z.d.mts +0 -588
  34. package/dist/types-DYLnSgMI.d.mts +0 -570
  35. package/dist/types-JNc_UQBo.d.mts +0 -2896
@@ -0,0 +1,1444 @@
1
+ import { A as TrackSegment, At as Json, I as ComponentType, M as AnyComponent, P as AnyComponentValue, R as Entity, St as Api, T as MotionHandle, _t as TypeTag, h as Api$2, k as TrackOptions, nn as Require, q as Api$1, t as Api$3, x as Ease } from "./types-Cjf3ywai.mjs";
2
+ import { o as Events$1, t as Api$4 } from "./types-DgwbzDLP.mjs";
3
+ import { F as Events$2, H as InputApi, I as I18nApi, L as Message, c as UiNode, i as Finding, n as ElementChange, o as JsxState, r as ElementMotion, v as StylesState } from "./types-C0SLQsx0.mjs";
4
+ import { Log } from "@moku-labs/common/browser";
5
+ import { PluginCtx } from "@moku-labs/core";
6
+ import { Node, Yoga } from "yoga-layout/load";
7
+
8
+ //#region src/plugins/text/types.d.ts
9
+ declare namespace types_d_exports$2 {
10
+ export { AdvanceTable, BundleLoaded, BundleUnloaded, Config$2 as Config, Deps$2 as Deps, DrawnLabel, IconRun, KernelSlice$2 as KernelSlice, LayoutOptions, Line, LocaleChanged, Point, Run, SeenText, Size, State$2 as State, TextAlign, TextApi, TextBind, TextCtx, TextLayout, TextRun, TextShadow, TextStyle, TextStyleInput, TextStyles, TextValue, TextWrap, Warn };
11
+ }
12
+ /**
13
+ * A point in reference units: where a label sits, and which point of the block sits there.
14
+ *
15
+ * @example
16
+ * ```ts
17
+ * const anchor: Point = { x: 0.5, y: 0.5 };
18
+ * ```
19
+ */
20
+ type Point = {
21
+ x: number;
22
+ y: number;
23
+ };
24
+ /**
25
+ * The size of a measured block, in reference pixels.
26
+ *
27
+ * @example
28
+ * ```ts
29
+ * const size: Size = { width: 57.6, height: 38.4 };
30
+ * ```
31
+ */
32
+ type Size = {
33
+ width: number;
34
+ height: number;
35
+ };
36
+ /**
37
+ * Which numeric component field a label shows. The component is named, not imported: a HUD is
38
+ * written before the game's components are in scope.
39
+ *
40
+ * @example
41
+ * ```ts
42
+ * const bind: TextBind = { component: "Counter", field: "value" };
43
+ * ```
44
+ */
45
+ type TextBind = {
46
+ component: string;
47
+ field: string;
48
+ };
49
+ /**
50
+ * What the `Text` component holds. `resolved` is engine-owned: a game writes `content`, `style`,
51
+ * `bind`, `anchor` and `alpha`, and reads `resolved`. `alpha` fades every run, shadow and icon of
52
+ * the block together, 1 by default; a change is written in place and a tween can drive it.
53
+ *
54
+ * @example
55
+ * ```ts
56
+ * const value: TextValue = {
57
+ * content: "+5", style: "body", bind: undefined, anchor: { x: 0.5, y: 0.5 }, alpha: 1,
58
+ * resolved: "+5"
59
+ * };
60
+ * // The hint of an empty name field, at half alpha.
61
+ * const hint: TextValue = { ...value, content: "Your name", alpha: 0.5, resolved: "Your name" };
62
+ * ```
63
+ */
64
+ type TextValue = {
65
+ content: string | Message;
66
+ style: string;
67
+ bind: TextBind | undefined;
68
+ anchor: Point;
69
+ alpha: number;
70
+ resolved: string;
71
+ };
72
+ /**
73
+ * Where the lines of a block sit inside the widest of them.
74
+ *
75
+ * @example
76
+ * ```ts
77
+ * const align: TextAlign = "center";
78
+ * ```
79
+ */
80
+ type TextAlign = "left" | "center" | "right";
81
+ /**
82
+ * The wrap width of a style in reference pixels, or `"none"` for one line per `\n` segment.
83
+ *
84
+ * @example
85
+ * ```ts
86
+ * const wrap: TextWrap = 480;
87
+ * ```
88
+ */
89
+ type TextWrap = number | "none";
90
+ /**
91
+ * The drop shadow of a style as the plugin stores it: a copy of every glyph run in `color`, at
92
+ * `alpha`, moved by `dx` and `dy` reference pixels.
93
+ *
94
+ * @example
95
+ * ```ts
96
+ * const shadow: TextShadow = { color: 0x5b3a1e, dx: 0, dy: 4, alpha: 1 };
97
+ * ```
98
+ */
99
+ type TextShadow = {
100
+ color: number;
101
+ dx: number;
102
+ dy: number;
103
+ alpha: number;
104
+ };
105
+ /**
106
+ * One style as a game writes it: the font and the size are required, the rest has defaults.
107
+ *
108
+ * @example
109
+ * ```ts
110
+ * // A popup title: cream display glyphs over a brown shadow 4 px down.
111
+ * const title: TextStyleInput = {
112
+ * font: "ui.font-display", size: 56, fill: 0xfff3d6, shadow: { color: 0x5b3a1e, dx: 0, dy: 4 }
113
+ * };
114
+ *
115
+ * defineTextStyles({ "popup.title": title }).map["popup.title"]?.shadow;
116
+ * // { color: 0x5b3a1e, dx: 0, dy: 4, alpha: 1 }
117
+ * ```
118
+ */
119
+ type TextStyleInput = {
120
+ font: string;
121
+ bold?: string;
122
+ italic?: string;
123
+ size: number;
124
+ fill: number; /** The outline colour. Drawn only when `strokeWidth` is above 0. */
125
+ stroke?: number;
126
+ /**
127
+ * The outline width in reference px: 8 copies of each glyph run on a circle this wide (12 from 6
128
+ * on), tinted `stroke`. 0 draws none. It never changes what `measure` answers.
129
+ */
130
+ strokeWidth?: number;
131
+ letterSpacing?: number;
132
+ align?: TextAlign;
133
+ wrap?: TextWrap;
134
+ digits?: boolean;
135
+ /**
136
+ * A drop shadow under every glyph run, in reference px at the style size. `alpha` is 1 when
137
+ * left out. It never changes what `measure` answers.
138
+ */
139
+ shadow?: {
140
+ color: number;
141
+ dx: number;
142
+ dy: number;
143
+ alpha?: number;
144
+ };
145
+ };
146
+ /**
147
+ * One style as the plugin stores it: every field filled, `bold` and `italic` only when the game
148
+ * shipped those MSDF fonts, `shadow` only when the style has one.
149
+ *
150
+ * @example
151
+ * ```ts
152
+ * const style: TextStyle = {
153
+ * font: "ui.font-body", bold: undefined, italic: undefined, size: 32, fill: 0xffffff,
154
+ * stroke: 0x000000, strokeWidth: 0, letterSpacing: 0, align: "left", wrap: "none",
155
+ * digits: false, shadow: undefined
156
+ * };
157
+ * ```
158
+ */
159
+ type TextStyle = {
160
+ font: string;
161
+ bold: string | undefined;
162
+ italic: string | undefined;
163
+ size: number;
164
+ fill: number;
165
+ stroke: number;
166
+ strokeWidth: number;
167
+ letterSpacing: number;
168
+ align: TextAlign;
169
+ wrap: TextWrap;
170
+ digits: boolean;
171
+ shadow: TextShadow | undefined;
172
+ };
173
+ /**
174
+ * What `defineTextStyles` returns and a feature registers under its `textStyles` key. Structural
175
+ * on purpose: `flow` carries the map, `text` reads it.
176
+ *
177
+ * @example
178
+ * ```ts
179
+ * const styles: TextStyles = { kind: "textStyles", map: { "hud.title": bodyStyle } };
180
+ * ```
181
+ */
182
+ type TextStyles = {
183
+ readonly kind: "textStyles";
184
+ readonly map: Record<string, TextStyle>;
185
+ };
186
+ /**
187
+ * The advances of one font at the size the `.fnt` was exported with. Widths scale by
188
+ * `style.size / size`; kerning pairs are ignored.
189
+ *
190
+ * @example
191
+ * ```ts
192
+ * const table: AdvanceTable = { size: 32, lineHeight: 40, advances: new Map([["1", 18]]) };
193
+ * ```
194
+ */
195
+ type AdvanceTable = {
196
+ size: number;
197
+ lineHeight: number;
198
+ advances: Map<string, number>;
199
+ };
200
+ /**
201
+ * One run of glyphs: the text and the flags every glyph in it shares.
202
+ *
203
+ * @example
204
+ * ```ts
205
+ * const run: TextRun = { kind: "text", text: "+5", bold: true, italic: false, color: 0xffe082 };
206
+ * ```
207
+ */
208
+ type TextRun = {
209
+ kind: "text";
210
+ text: string;
211
+ bold: boolean;
212
+ italic: boolean;
213
+ color: number | undefined;
214
+ };
215
+ /**
216
+ * One inline icon: an asset key drawn square at the line height.
217
+ *
218
+ * @example
219
+ * ```ts
220
+ * const run: IconRun = { kind: "icon", key: "hud.coin" };
221
+ * ```
222
+ */
223
+ type IconRun = {
224
+ kind: "icon";
225
+ key: string;
226
+ };
227
+ /**
228
+ * A piece of a line: glyphs or one inline icon.
229
+ *
230
+ * @example
231
+ * ```ts
232
+ * const runs: Run[] = [{ kind: "icon", key: "hud.coin" }];
233
+ * ```
234
+ */
235
+ type Run = TextRun | IconRun;
236
+ /**
237
+ * One laid-out line and how wide it is.
238
+ *
239
+ * @example
240
+ * ```ts
241
+ * const line: Line = { runs: [{ kind: "icon", key: "hud.coin" }], width: 38.4 };
242
+ * ```
243
+ */
244
+ type Line = {
245
+ runs: Run[];
246
+ width: number;
247
+ };
248
+ /**
249
+ * A laid-out block: its lines and the size `measure` answers with.
250
+ *
251
+ * @example
252
+ * ```ts
253
+ * const layout: TextLayout = { lines: [], width: 0, height: 0 };
254
+ * ```
255
+ */
256
+ type TextLayout = {
257
+ lines: Line[];
258
+ width: number;
259
+ height: number;
260
+ };
261
+ /**
262
+ * What one label container was last filled from: the style object and the laid-out block. An
263
+ * update with the same style object, the same anchor and the same lines and runs writes the new
264
+ * text and positions into the objects already there.
265
+ */
266
+ type DrawnLabel = {
267
+ style: TextStyle;
268
+ layout: TextLayout;
269
+ };
270
+ /**
271
+ * A dev warning that is written once per key. Pure modules take it as an argument, so nothing
272
+ * below the plugin context reaches the log itself.
273
+ *
274
+ * @example
275
+ * ```ts
276
+ * const warn: Warn = (key, message) => seen.has(key) || log.warn(message);
277
+ * ```
278
+ */
279
+ type Warn = (key: string, message: string, data?: Record<string, unknown>) => void;
280
+ /**
281
+ * What the pure layout needs next to the runs, the style and the tables.
282
+ *
283
+ * @example
284
+ * ```ts
285
+ * const options: LayoutOptions = { warn: () => undefined };
286
+ * ```
287
+ */
288
+ type LayoutOptions = {
289
+ warn: Warn;
290
+ };
291
+ /**
292
+ * What was resolved for one entity last, so the frame step knows whether anything moved.
293
+ *
294
+ * @example
295
+ * ```ts
296
+ * const seen: SeenText = { content: "+5", style: "body", bind: undefined, locale: "ru" };
297
+ * ```
298
+ */
299
+ type SeenText = {
300
+ content: unknown;
301
+ style: string;
302
+ bind: string | undefined;
303
+ locale: string;
304
+ };
305
+ /**
306
+ * text plugin config.
307
+ *
308
+ * @example
309
+ * ```ts
310
+ * createApp({ pluginConfigs: { text: { fonts: { body: "ui.body", digits: "ui.digits" } } } });
311
+ * ```
312
+ */
313
+ type Config$2 = {
314
+ /** The two fonts every game ships in its boot bundle. They back the built-in styles. */fonts: {
315
+ body: string;
316
+ digits: string;
317
+ };
318
+ };
319
+ /**
320
+ * text plugin state.
321
+ */
322
+ type State$2 = {
323
+ /** Built-in styles first, then the styles of every feature in feature order. */styles: Map<string, TextStyle>; /** Style name to the feature that brought it, for the duplicate error. */
324
+ styleOwner: Map<string, string>; /** The config fonts plus every `font`, `bold` and `italic` of every style. */
325
+ fontKeys: Set<string>; /** The parsed advance table per font key, filled when `assets` has the font. */
326
+ tables: Map<string, AdvanceTable>; /** The font keys handed to `renderer.sync.fonts.install`. */
327
+ installed: Set<string>; /** What was resolved for an entity last. */
328
+ seen: Map<Entity, SeenText>; /** `bind.component` to its world type; `undefined` after the one warning. */
329
+ bindTypes: Map<string, AnyComponent | undefined>; /** The measured size of every live label, for `ui`. */
330
+ measured: Map<Entity, Size>; /** Layout per `style + resolved`, oldest dropped first. */
331
+ cache: Map<string, TextLayout>; /** What every live label container was last filled from, by container. */
332
+ drawn: WeakMap<object, DrawnLabel>; /** Entities to re-resolve in the next layout phase. */
333
+ dirty: Set<Entity>; /** Warning keys already written, so a style, a tag, a glyph or a font warns once. */
334
+ warned: Set<string>; /** The system, the two world hooks and the display adapter. */
335
+ removers: Array<() => void>;
336
+ };
337
+ /**
338
+ * text plugin API, `app.text`. Two questions: how big a piece of text is, and which styles the
339
+ * game registered. Everything else happens in the frame.
340
+ *
341
+ * @example
342
+ * ```ts
343
+ * app.text.measure("120", "digits"); // { width: 57.6, height: 38.4 }
344
+ * app.text.styles(); // ["body", "digits"]
345
+ * ```
346
+ */
347
+ type TextApi = {
348
+ /**
349
+ * The size of a piece of text in reference pixels, from the advance table of the style's font.
350
+ * A `Message` is formatted in the current locale first. Never touches a canvas, so the answer
351
+ * is the same in a browser and in plain Bun. Cached per style and resolved string.
352
+ *
353
+ * @param content - A plain string, or what `tr` returned.
354
+ * @param style - A registered style name. An unknown one warns once and measures as `body`.
355
+ * @returns The width and height of the block.
356
+ * @example
357
+ * ```ts
358
+ * // `ui.layout` asks once per invalidation, from the Yoga measure function of a text element.
359
+ * const text = ctx.require(textPlugin);
360
+ *
361
+ * text.measure("120", "digits"); // { width: 57.6, height: 38.4 } on the 0.6 em fallback
362
+ * ```
363
+ */
364
+ measure(content: string | Message, style: string): Size;
365
+ /**
366
+ * Whether the font of a style has a glyph for one character, the lookup Pixi draws by. A
367
+ * character its font lacks is drawn as nothing and `measure` counts it 0 wide. While the font is
368
+ * not loaded every character counts, as the 0.6 em fallback measures it. An unknown style warns
369
+ * once and looks up `body`.
370
+ *
371
+ * @param char - One character, a whole code point.
372
+ * @param style - A registered style name.
373
+ * @returns False only for a character a loaded font has no glyph for.
374
+ * @example
375
+ * ```ts
376
+ * // `ui` measures the caret of a text field over the characters the font draws.
377
+ * const text = ctx.require(textPlugin);
378
+ *
379
+ * text.hasGlyph("A", "body"); // true
380
+ * text.hasGlyph("😀", "body"); // false once the body font is loaded: it has no emoji glyph
381
+ * ```
382
+ */
383
+ hasGlyph(char: string, style: string): boolean;
384
+ /**
385
+ * The registered style names: the two built-ins first, then the styles of every feature in
386
+ * feature order.
387
+ *
388
+ * @returns A frozen list of style names.
389
+ * @example
390
+ * ```ts
391
+ * // A dev overlay lists what a game may write in a `style` prop.
392
+ * app.text.styles(); // ["body", "digits", "hud.digits"]
393
+ * ```
394
+ */
395
+ styles(): readonly string[];
396
+ };
397
+ /**
398
+ * Resolved dependency APIs.
399
+ */
400
+ type Deps$2 = {
401
+ time: Api;
402
+ flow: Api$1;
403
+ world: Api$2;
404
+ renderer: Api$3;
405
+ assets: Api$4;
406
+ i18n: I18nApi;
407
+ };
408
+ /**
409
+ * What the kernel context offers before the deps are attached.
410
+ *
411
+ * `text` owns no event, so `emit` is the kernel's and never called here.
412
+ */
413
+ type KernelSlice$2 = PluginCtx<Config$2, State$2> & {
414
+ readonly global: object;
415
+ readonly log: Log.LogApi;
416
+ readonly require: Require;
417
+ };
418
+ /**
419
+ * Domain context shared by the files of the plugin: the kernel slice plus the resolved deps.
420
+ */
421
+ type TextCtx = KernelSlice$2 & {
422
+ readonly deps: Deps$2;
423
+ };
424
+ /**
425
+ * Payload of the `assets:bundle-loaded` hook: a bundle landed, so a font may be readable now.
426
+ */
427
+ type BundleLoaded = Events$1["assets:bundle-loaded"];
428
+ /**
429
+ * Payload of the `assets:bundle-unloaded` hook: `keys` names the assets that left.
430
+ */
431
+ type BundleUnloaded = Events$1["assets:bundle-unloaded"];
432
+ /**
433
+ * Payload of the `i18n:locale-changed` hook: every message has to be resolved again.
434
+ */
435
+ type LocaleChanged = Events$2["i18n:locale-changed"];
436
+ //#endregion
437
+ //#region src/plugins/ui/layout/types.d.ts
438
+ /**
439
+ * layout module state. `nodes` is attach minus detach: Yoga 3.2.1 has no instance counter.
440
+ */
441
+ type LayoutState = {
442
+ yoga: Yoga | undefined;
443
+ byEntity: Map<Entity, Node>;
444
+ nodes: number;
445
+ measured: number;
446
+ solves: number;
447
+ scrolling: Entity | undefined;
448
+ scrollStart: {
449
+ pointerY: number;
450
+ offset: number;
451
+ };
452
+ order: number;
453
+ cleanups: Array<() => void>;
454
+ };
455
+ declare namespace types_d_exports$1 {
456
+ export { Config$1 as Config, Deps$1 as Deps, ElementChange, ElementMotion, Finding, FocusRing, KernelSlice$1 as KernelSlice, State$1 as State, TextInputLook, UiApi, UiCtx, UiNode };
457
+ }
458
+ /**
459
+ * The look of the keyboard focus ring: a dashed ring with a solid halo under it, drawn around the
460
+ * focused control. Colours are `0xRRGGBB`, lengths reference units.
461
+ *
462
+ * @example
463
+ * ```ts
464
+ * // A dashed ink ring with a cream halo, 9 u outside the control.
465
+ * const ring: FocusRing = {
466
+ * stroke: 0x3a2212, strokeWidth: 4, dash: 10, offset: 9, halo: 0xfff3d6, haloWidth: 12
467
+ * };
468
+ * ```
469
+ */
470
+ type FocusRing = {
471
+ /** The colour of the dashed ring. */stroke: number; /** The stroke width of the dashed ring. */
472
+ strokeWidth: number; /** The dash length of the ring, with gaps of half a dash; 0 draws it solid. */
473
+ dash: number; /** How far outside the control's rect the ring runs, on every side. */
474
+ offset: number; /** The colour of the solid halo drawn under the ring. */
475
+ halo: number; /** The stroke width of the halo, centred on the same path as the ring. */
476
+ haloWidth: number;
477
+ };
478
+ /**
479
+ * The look of a text field while it is edited: the caret, the selection box and the IME
480
+ * underline. Colours are `0xRRGGBB`, lengths reference units, `keyboardMargin` CSS px. The caret
481
+ * does not blink.
482
+ *
483
+ * @example
484
+ * ```ts
485
+ * // A thick ink caret, a pale blue selection, the field 16 px above the keyboard.
486
+ * const look: TextInputLook = {
487
+ * caretWidth: 3, caret: 0x3a2212, selection: 0x3390ff, selectionAlpha: 0.35,
488
+ * composingUnderline: 3, keyboardMargin: 16
489
+ * };
490
+ * ```
491
+ */
492
+ type TextInputLook = {
493
+ /** The width of the caret. */caretWidth: number; /** The colour of the caret and of the underline under an IME composing range. */
494
+ caret: number; /** The colour of the selection box. */
495
+ selection: number; /** The alpha of the selection box. */
496
+ selectionAlpha: number; /** The height of the underline under an IME composing range. */
497
+ composingUnderline: number; /** How far above the keyboard the bottom of the edited field is lifted, in CSS px. */
498
+ keyboardMargin: number;
499
+ };
500
+ /**
501
+ * ui plugin config.
502
+ *
503
+ * @example
504
+ * ```ts
505
+ * createApp({ pluginConfigs: { ui: { tapTargetPt: 48, breakpoints: { tall: 2, wide: 1.5 } } } });
506
+ * // A game with a white focus ring on a dark board.
507
+ * createApp({
508
+ * pluginConfigs: {
509
+ * ui: {
510
+ * focusRing: {
511
+ * stroke: 0xffffff, strokeWidth: 4, dash: 10, offset: 9, halo: 0x000000, haloWidth: 12
512
+ * }
513
+ * }
514
+ * }
515
+ * });
516
+ * // A game whose text fields have an ink caret.
517
+ * createApp({
518
+ * pluginConfigs: {
519
+ * ui: {
520
+ * textInput: {
521
+ * caretWidth: 3, caret: 0x3a2212, selection: 0x3390ff, selectionAlpha: 0.35,
522
+ * composingUnderline: 3, keyboardMargin: 16
523
+ * }
524
+ * }
525
+ * }
526
+ * });
527
+ * ```
528
+ */
529
+ type Config$1 = {
530
+ /** Smallest tap target `lint()` accepts, in CSS px (points). */tapTargetPt: number; /** `when` flags: `tall` when height / width is at least `tall`, `wide` the other way round. */
531
+ breakpoints: {
532
+ tall: number;
533
+ wide: number;
534
+ };
535
+ /**
536
+ * The ring drawn around the control the keyboard focused. Shallow merge: a game that sets
537
+ * `focusRing` gives all six fields.
538
+ */
539
+ focusRing: FocusRing;
540
+ /**
541
+ * The caret, the selection and the IME underline of a text field. Shallow merge: a game that
542
+ * sets `textInput` gives all six fields.
543
+ */
544
+ textInput: TextInputLook;
545
+ };
546
+ /**
547
+ * ui plugin state: one branch per module.
548
+ */
549
+ type State$1 = {
550
+ jsx: JsxState;
551
+ styles: StylesState;
552
+ layout: LayoutState;
553
+ };
554
+ /**
555
+ * Resolved dependency APIs. `anim` is not here: its edge is validation only, and its work
556
+ * reaches `ui` through the tween driver behind every `ViewHandle`.
557
+ */
558
+ type Deps$1 = {
559
+ time: Api;
560
+ flow: Api$1;
561
+ world: Api$2;
562
+ renderer: Api$3;
563
+ input: InputApi;
564
+ i18n: I18nApi;
565
+ text: TextApi;
566
+ };
567
+ /**
568
+ * What the kernel context offers before the deps are attached.
569
+ *
570
+ * `ui` owns no event, so `emit` is the kernel's and never called here.
571
+ */
572
+ type KernelSlice$1 = PluginCtx<Config$1, State$1> & {
573
+ readonly global: object;
574
+ readonly log: Log.LogApi;
575
+ readonly require: Require;
576
+ };
577
+ /**
578
+ * Domain context shared by the three modules: the kernel slice plus the resolved deps.
579
+ */
580
+ type UiCtx = KernelSlice$1 & {
581
+ readonly deps: Deps$1;
582
+ };
583
+ /**
584
+ * ui plugin API, `app.ui`. The screen is one more projection, so three members read it the way a
585
+ * test reads the board: a snapshot, a key lookup and a list of findings; the fourth types into a
586
+ * text field.
587
+ *
588
+ * @example
589
+ * ```ts
590
+ * // A headless scenario taps the claim button of the reward popup.
591
+ * app.ui.tree().type; // "row", the root element of the HUD
592
+ * app.input.tap(app.ui.find("claim") ?? 0);
593
+ * app.ui.lint(); // []
594
+ * ```
595
+ */
596
+ type UiApi = {
597
+ /**
598
+ * The live screen as plain data: every root in layer order, every element in child order, with
599
+ * its rect in root coordinates, its resolved style and its seven state flags. A rect is natural:
600
+ * under a `fit: "contain"` element it is the rect before that scale, and the fitted element
601
+ * adds `fitScale`. Works headless.
602
+ *
603
+ * @returns The root node; several roots come back under one `screen` node.
604
+ * @example
605
+ * ```ts
606
+ * // On an iPhone SE the 970 u board is drawn smaller to fit the slot between the bars.
607
+ * const screen = app.ui.tree();
608
+ * screen.children.map(child => child.key); // ["fitTop", "slot", "fitBottom"]
609
+ * const board = screen.children[1]?.children[0];
610
+ * board?.rect.w; // 970, the natural width
611
+ * board?.fitScale; // 0.805, the 781 u the slot leaves over 970 u
612
+ * ```
613
+ */
614
+ tree(): UiNode;
615
+ /**
616
+ * The entity of a keyed element, so a test can tap it and `anim` can aim at it. Popup roots
617
+ * come first, then the projection roots in layer order.
618
+ *
619
+ * @param key - The `key` prop of the element.
620
+ * @returns The entity, or `undefined` for an unknown or exiting element.
621
+ * @example
622
+ * ```ts
623
+ * // A headless scenario answers the gate through the button of the popup.
624
+ * app.input.tap(app.ui.find("claim") ?? 0); // true
625
+ * app.ui.find("nothing"); // undefined
626
+ * ```
627
+ */
628
+ find(key: string): Entity | undefined;
629
+ /**
630
+ * Reads the live screen against the five rules: a tap target under `tapTargetPt` at the size
631
+ * it is drawn (a `fit: "contain"` on it or above it shrinks it), a text that does not fit its
632
+ * box in some registered locale, an absolute element with no `reason`, a clipping element
633
+ * (`scroll`, `overflow: "hidden"`) whose style names a nine-slice it never draws, and a root
634
+ * element whose style sets a `zIndex` it ignores. Never throws; empty when nothing is mounted.
635
+ *
636
+ * @returns One finding per rule and element.
637
+ * @example
638
+ * ```ts
639
+ * // A game test keeps the board honest on an iPhone SE: a 140 u cell in the 0.8 slot.
640
+ * app.ui.lint(); // [{ rule: "tap-target", key: "cell", detail: "39 x 39 pt" }]
641
+ * // A list styled with a nine-slice: the clip keeps it from drawing.
642
+ * app.ui.lint(); // [{ rule: "nine-slice-clipped", key: "orders", detail: "scroll" }]
643
+ * // A screen root that asks to be drawn over the others: the layer decides that.
644
+ * app.ui.lint(); // [{ rule: "z-index-on-root", key: "board", detail: "zIndex 2" }]
645
+ * ```
646
+ */
647
+ lint(): readonly Finding[];
648
+ /**
649
+ * Types into a text field the way a player does, for a headless test or an agent: the field
650
+ * becomes the one being edited, `value` cut to its `maxLength` is written into it, and the
651
+ * local field it names is written, so the component re-renders on the next frame. Nothing
652
+ * reaches the gate: Enter submits.
653
+ *
654
+ * @param key - The `key` prop of the `input` element.
655
+ * @param value - The text to type.
656
+ * @returns True when the field took the text; false with one warning when no live `input` has
657
+ * the key.
658
+ * @example
659
+ * ```ts
660
+ * // A headless test renames the player through the Rename popup.
661
+ * app.ui.fill("nameField", "Alex"); // true
662
+ * app.time.step(16);
663
+ * app.input.pressKey("Enter"); // true: the gate took { intent: "save", payload: { name: "Alex" } }
664
+ * app.ui.fill("nothing", "Alex"); // false, with the warning "ui:fill-without-input"
665
+ * ```
666
+ */
667
+ fill(key: string, value: string): boolean;
668
+ };
669
+ //#endregion
670
+ //#region src/plugins/anim/tween/types.d.ts
671
+ /**
672
+ * One running track: the numeric fields of one component of one entity moving from the values
673
+ * read at the end of the delay to the exact target, straight or through keyframe segments. A
674
+ * track with repeats walks again from its first segment each time a run ends.
675
+ *
676
+ * @example
677
+ * ```ts
678
+ * const track: Track = {
679
+ * id: 1, entity: 1_048_576, component: Transform, to: { x: 200 },
680
+ * keys: { x: "1048576:Transform:x" }, ms: 350, ease: "out", delayMs: 0, elapsed: 0,
681
+ * from: undefined, muted: () => new Set(), additive: false, driven: false, bornFrame: 4,
682
+ * ended: false, segments: undefined, repeatsLeft: 0, held: false
683
+ * };
684
+ * ```
685
+ */
686
+ type Track = {
687
+ readonly id: number;
688
+ readonly entity: Entity;
689
+ readonly component: AnyComponent; /** The target fields. Retarget deletes the fields a newer absolute track took over. */
690
+ to: Record<string, number>;
691
+ /**
692
+ * The key of the owner, bases and offsets tables per field, built once when the track starts.
693
+ * It keeps a field `to` lost, which nothing reads again.
694
+ */
695
+ readonly keys: Readonly<Record<string, string>>;
696
+ readonly ms: number;
697
+ readonly ease: Ease;
698
+ readonly delayMs: number;
699
+ elapsed: number; /** Read when the delay ends, never earlier. */
700
+ from: Record<string, number> | undefined;
701
+ readonly muted: () => ReadonlySet<string>;
702
+ readonly additive: boolean; /** True for a track a timeline step advances by hand; the frame step leaves it alone. */
703
+ readonly driven: boolean; /** The frame step the track was born in. That step does not advance it a second time. */
704
+ readonly bornFrame: number;
705
+ ended: boolean;
706
+ /**
707
+ * The keyframe segments the track walks over `ms`, or `undefined` for a straight track. A
708
+ * segment without an ease runs on the track's `ease`.
709
+ */
710
+ readonly segments: readonly TrackSegment[] | undefined;
711
+ /**
712
+ * The runs still to go after the one that plays: `0` for a track that runs once, `Infinity`
713
+ * for a loop (`repeat: "forever"`), which never ends by itself.
714
+ */
715
+ repeatsLeft: number;
716
+ /**
717
+ * True while a loop stands on its first key and has written it: the hold writes once, and the
718
+ * walk clears it, so the next hold writes the first key again.
719
+ */
720
+ held: boolean;
721
+ };
722
+ /**
723
+ * A motion handle that can also be advanced by hand: what the timeline cursor needs, so a track
724
+ * born inside a frame step still consumes the remainder of that step's delta.
725
+ *
726
+ * @example
727
+ * ```ts
728
+ * const motion: StepMotion = { ...inert, advance: () => 0 };
729
+ * motion.advance(16); // 0: nothing is left over
730
+ * ```
731
+ */
732
+ type StepMotion = MotionHandle & {
733
+ /**
734
+ * Advances the track by one delta.
735
+ *
736
+ * @param deltaMs - Milliseconds of game time to consume.
737
+ * @returns The milliseconds left over past the end of the track.
738
+ */
739
+ advance(deltaMs: number): number;
740
+ };
741
+ //#endregion
742
+ //#region src/plugins/anim/timeline/types.d.ts
743
+ /**
744
+ * The value of the component a step names, with its shape erased: the value type of
745
+ * `AnyComponent`. A step is built against the real component, so `set(target, Sprite, patch)`
746
+ * checks the patch; the step tree and the runtime then carry the component erased, which lets one
747
+ * choreography mix components.
748
+ *
749
+ * @example
750
+ * ```ts
751
+ * const patch: ErasedFields = { texture: "hud.coin-gold" };
752
+ * ```
753
+ */
754
+ type ErasedFields = AnyComponent extends ComponentType<infer Value> ? Value : never;
755
+ /**
756
+ * A sound, owned by `anim` and handled by `audio`. It is a `flow` descriptor and a timeline step
757
+ * at once: `fx(sfx("board.merge"))` in a node, or `sfx("board.merge")` inside a `sequence`.
758
+ *
759
+ * @example
760
+ * ```ts
761
+ * const descriptor: SfxDescriptor = {
762
+ * kind: "sfx", payload: { key: "board.merge", bus: "sfx" }, cosmetic: true
763
+ * };
764
+ * ```
765
+ */
766
+ type SfxDescriptor = {
767
+ readonly kind: "sfx";
768
+ readonly payload: {
769
+ readonly key: string;
770
+ readonly bus: string;
771
+ };
772
+ readonly cosmetic: true;
773
+ };
774
+ /**
775
+ * A haptic tick, owned by `anim` and handled by `platform`. Like `sfx` it is a descriptor and a
776
+ * timeline step at once.
777
+ *
778
+ * @example
779
+ * ```ts
780
+ * const descriptor: HapticDescriptor = {
781
+ * kind: "haptic", payload: { kind: "light" }, cosmetic: true
782
+ * };
783
+ * ```
784
+ */
785
+ type HapticDescriptor = {
786
+ readonly kind: "haptic";
787
+ readonly payload: {
788
+ readonly kind: string;
789
+ };
790
+ readonly cosmetic: true;
791
+ };
792
+ /**
793
+ * The two descriptors `anim` owns. As a step, the descriptor is dispatched through
794
+ * `flow.fx.dispatch` when the step is reached; a kind nobody handles is silent.
795
+ *
796
+ * @example
797
+ * ```ts
798
+ * const step: FxStep = { kind: "haptic", payload: { kind: "light" }, cosmetic: true };
799
+ * ```
800
+ */
801
+ type FxStep = SfxDescriptor | HapticDescriptor;
802
+ /**
803
+ * The descriptor `play(animation, slots)` builds. The payload names the animation, so the
804
+ * handler resolves the definition from the registry instead of carrying a function.
805
+ *
806
+ * @example
807
+ * ```ts
808
+ * const descriptor: PlayDescriptor = {
809
+ * kind: "play",
810
+ * payload: { animation: "hud.coinsFly", slots: { from: { projection: "hud", key: "coins" } } },
811
+ * cosmetic: true
812
+ * };
813
+ * ```
814
+ */
815
+ type PlayDescriptor = {
816
+ readonly kind: "play";
817
+ readonly payload: {
818
+ readonly animation: string;
819
+ readonly slots: Json;
820
+ };
821
+ readonly cosmetic: true;
822
+ };
823
+ /**
824
+ * One step of a choreography: plain frozen data with no callback inside, so a step tree can be
825
+ * read, logged and compared. A `spawn` step carries the component values of a temporary entity,
826
+ * made when the step is reached and despawned when the timeline ends.
827
+ *
828
+ * @example
829
+ * ```ts
830
+ * const step: Step = { kind: "wait", ms: 120 };
831
+ * ```
832
+ */
833
+ type Step = {
834
+ readonly kind: "sequence";
835
+ readonly steps: readonly Step[];
836
+ } | {
837
+ readonly kind: "parallel";
838
+ readonly steps: readonly Step[];
839
+ } | {
840
+ readonly kind: "wait";
841
+ readonly ms: number;
842
+ } | {
843
+ readonly kind: "mark";
844
+ readonly name: string;
845
+ } | {
846
+ readonly kind: "tween";
847
+ readonly target: Target;
848
+ readonly component: AnyComponent;
849
+ readonly to: Readonly<Record<string, number>>;
850
+ readonly ms: number;
851
+ readonly ease: Ease;
852
+ readonly delayMs: number;
853
+ readonly additive: boolean; /** `"root"`: the `Transform` fields are a root pose, turned local when the track starts. */
854
+ readonly space: "local" | "root";
855
+ } | {
856
+ readonly kind: "set";
857
+ readonly target: Target;
858
+ readonly component: AnyComponent;
859
+ readonly patch: Readonly<ErasedFields>;
860
+ } | {
861
+ readonly kind: "frames";
862
+ readonly target: Target;
863
+ readonly keys: readonly string[];
864
+ readonly fps: number;
865
+ readonly loop: boolean;
866
+ } | {
867
+ readonly kind: "spawn";
868
+ readonly id: string;
869
+ readonly components: readonly AnyComponentValue[];
870
+ readonly layer: string;
871
+ readonly order: number;
872
+ } | FxStep | {
873
+ readonly kind: "use";
874
+ readonly id: string;
875
+ readonly step: Step;
876
+ };
877
+ /**
878
+ * Reserved for Spine and the other external players: `external` throws, and this is the shape it
879
+ * would take. Exported as a type only.
880
+ *
881
+ * @example
882
+ * ```ts
883
+ * const player: ExternalPlayer = { name: "spine", play: () => undefined };
884
+ * ```
885
+ */
886
+ type ExternalPlayer = {
887
+ readonly name: string;
888
+ play(clip: string): unknown;
889
+ };
890
+ /**
891
+ * Where one step of a running timeline stands. One mutable record per step of the tree, built
892
+ * when the timeline starts.
893
+ */
894
+ type Cursor = {
895
+ readonly step: Step;
896
+ readonly children: readonly Cursor[]; /** Position in a `sequence`, and the last written key of a `frames` step. */
897
+ index: number; /** Consumed milliseconds of a `wait` or a `frames` step. */
898
+ elapsed: number;
899
+ motion: StepMotion | undefined;
900
+ started: boolean;
901
+ ended: boolean; /** The entity a running `frames` step holds against its `Frames` loop, until the step ends. */
902
+ held: Entity | undefined;
903
+ };
904
+ /**
905
+ * What the cursor reaches the rest of the engine through. Injected, so `timeline` never imports
906
+ * the run-time code of `tween` or of another plugin.
907
+ */
908
+ type TimelineRuntime = {
909
+ /**
910
+ * Resolves a target to an entity.
911
+ *
912
+ * @param target - The projection key, the entity or the spawned id a step names.
913
+ * @param spawned - The spawn table of the timeline that asks; a spawned id resolves only there.
914
+ * @returns The entity, or `undefined` when no live view, element or spawned entity holds it.
915
+ */
916
+ entityOf(target: Target, spawned?: ReadonlyMap<string, Entity>): Entity | undefined;
917
+ /**
918
+ * Spawns a temporary entity owned by `anim`. The timeline that spawned it despawns it when it
919
+ * ends, whichever way it ends.
920
+ *
921
+ * @param components - The component values the entity starts with, `Layer` and `Order` included.
922
+ * @returns The new entity.
923
+ */
924
+ spawn(components: readonly AnyComponentValue[]): Entity;
925
+ /**
926
+ * Reads a component of an entity, which is also the liveness check of a step.
927
+ *
928
+ * @param entity - The entity to read.
929
+ * @param component - The component to read.
930
+ * @returns The stored value, or `undefined`.
931
+ */
932
+ read(entity: Entity, component: AnyComponent): Readonly<ErasedFields> | undefined;
933
+ /**
934
+ * Writes a patch through `ecs.set`, so `changed()` sees it.
935
+ *
936
+ * @param entity - The entity to write.
937
+ * @param component - The component to write.
938
+ * @param patch - The fields to overwrite.
939
+ */
940
+ write(entity: Entity, component: AnyComponent, patch: Partial<ErasedFields>): void;
941
+ /**
942
+ * Turns the root-space target of a `tween` step into the local target under the entity's
943
+ * parent, through `localPoseOf` of `renderer`.
944
+ *
945
+ * @param entity - The entity the step moves.
946
+ * @param component - The component the step drives; only `Transform` is converted.
947
+ * @param to - The numeric target fields, in root space.
948
+ * @returns The target fields in the space of the entity's parent.
949
+ */
950
+ toLocal(entity: Entity, component: AnyComponent, to: Readonly<Record<string, number>>): Record<string, number>;
951
+ /**
952
+ * Starts one track on the tween core.
953
+ *
954
+ * @param entity - The entity to animate.
955
+ * @param component - The component to animate.
956
+ * @param to - The numeric target fields.
957
+ * @param options - Duration, easing, delay and the additive flag.
958
+ * @returns The motion of the track, advanceable by hand.
959
+ */
960
+ start(entity: Entity, component: AnyComponent, to: Record<string, number>, options: TrackOptions): StepMotion;
961
+ /**
962
+ * Hands a descriptor step to `flow.fx.dispatch`.
963
+ *
964
+ * @param descriptor - The `sfx` or `haptic` step that was reached.
965
+ */
966
+ dispatch(descriptor: FxStep): void;
967
+ /**
968
+ * Reports a mark: the event, the `onMark` listeners and the timeline's own list.
969
+ *
970
+ * @param animation - Id of the animation the mark belongs to.
971
+ * @param name - Name of the mark.
972
+ */
973
+ mark(animation: string, name: string): void;
974
+ /**
975
+ * Lifts the idle frame rate, because something started to move.
976
+ */
977
+ wake(): void;
978
+ /**
979
+ * Claims the sprite of an entity for a running `frames` step: the `Frames` loop of the entity
980
+ * writes nothing while any claim is open.
981
+ *
982
+ * @param entity - The entity the step writes.
983
+ */
984
+ holdFrames(entity: Entity): void;
985
+ /**
986
+ * Gives one claim back. At the last one the `Frames` loop writes its current key again on its
987
+ * next step, so the sprite does not stay on the step's last key.
988
+ *
989
+ * @param entity - The entity the step wrote.
990
+ */
991
+ releaseFrames(entity: Entity): void;
992
+ };
993
+ /**
994
+ * One running timeline: its cursor, the marks it reached, the entities it counts on, the entities
995
+ * it spawned and despawns at its end, and the resolver of its `done` promise.
996
+ */
997
+ type RunningTimeline = {
998
+ readonly id: number;
999
+ readonly animation: string;
1000
+ readonly cursor: Cursor;
1001
+ readonly marks: string[];
1002
+ readonly entities: readonly Entity[]; /** The entities its `spawn` steps made, by spawn id. Despawned when the timeline ends. */
1003
+ readonly spawned: Map<string, Entity>;
1004
+ readonly resolve: () => void;
1005
+ ended: boolean;
1006
+ };
1007
+ declare namespace types_d_exports {
1008
+ export { AnimApi, AnimCtx, AnimationDefinition, AnimationSpec, AnyAnimationDefinition, BuildTools, Config, Cursor, Deps, ErasedFields, Events, ExternalPlayer, FrameLoop, FramesValue, FxStep, HapticDescriptor, KernelSlice, MarkListener, MotionKeyframe, PlayDescriptor, PlayHandle, Pose, RunningTimeline, SfxDescriptor, SlotRecord, SlotTags, SlotValue, SlotValues, State, Step, StepMotion, Target, TimelineRuntime, Track };
1009
+ }
1010
+ /**
1011
+ * What a step and a slot are pointed at: the projection key of a live view or of an element a
1012
+ * plugin above registered with `registerKey`, an entity, or the id of an entity a `spawn` step of
1013
+ * the same timeline made (`spawned(id)`). A node passes keys, never entities.
1014
+ *
1015
+ * @example
1016
+ * ```ts
1017
+ * const counter: Target = { projection: "hud", key: "coins" };
1018
+ * const flyingCoin: Target = spawned("coin1"); // { spawned: "coin1" }
1019
+ * ```
1020
+ */
1021
+ type Target = {
1022
+ projection: string;
1023
+ key: string;
1024
+ } | Entity | {
1025
+ spawned: string;
1026
+ };
1027
+ /**
1028
+ * The pose `at(target)` answers with: where the target rests in root (reference) space, its rest
1029
+ * `Transform` composed through the `Parent` chain, or the identity pose when nothing is there.
1030
+ *
1031
+ * @example
1032
+ * ```ts
1033
+ * const pose: Pose = { x: 540, y: 960, rotation: 0, scale: 1 };
1034
+ * ```
1035
+ */
1036
+ type Pose = {
1037
+ x: number;
1038
+ y: number;
1039
+ rotation: number;
1040
+ scale: number;
1041
+ };
1042
+ /**
1043
+ * The slot declaration of an animation: one type tag per slot, each carrying one target or a
1044
+ * list of targets.
1045
+ *
1046
+ * @example
1047
+ * ```ts
1048
+ * const slots: SlotTags = { items: type<Target[]>(), card: type<Target>() };
1049
+ * ```
1050
+ */
1051
+ type SlotTags = Readonly<Record<string, TypeTag<Target> | TypeTag<Target[]>>>;
1052
+ /**
1053
+ * The targets `play` and `use` take, read off the slot tags of the animation.
1054
+ *
1055
+ * @example
1056
+ * ```ts
1057
+ * type Slots = SlotValues<{ card: TypeTag<Target> }>; // { card: Target }
1058
+ * ```
1059
+ */
1060
+ type SlotValues<Tags extends SlotTags> = { [Name in keyof Tags]: SlotValue<Tags[Name]> };
1061
+ /**
1062
+ * What one slot tag stands for: a list of targets when the tag declared one, a single target
1063
+ * otherwise. Distributive on purpose, so the erased `SlotTags` widens to both.
1064
+ *
1065
+ * @example
1066
+ * ```ts
1067
+ * type One = SlotValue<TypeTag<Target>>; // Target
1068
+ * ```
1069
+ */
1070
+ type SlotValue<Tag> = Tag extends TypeTag<Target[]> ? Target[] : Target;
1071
+ /**
1072
+ * The slots of a running animation with their tags erased, as `build` and the `play` handler
1073
+ * read them.
1074
+ *
1075
+ * @example
1076
+ * ```ts
1077
+ * const slots: SlotRecord = { card: { projection: "hud", key: "order" } };
1078
+ * ```
1079
+ */
1080
+ type SlotRecord = SlotValues<SlotTags>;
1081
+ /**
1082
+ * What `build` is handed next to the slots: `at` reads where a target rests at play time, in root
1083
+ * space (its rest `Transform` composed through the `Parent` chain, every pivot applied), so a
1084
+ * choreography can fly one element to where another one rests, even inside a scaled slot. A view
1085
+ * that has a `Parent` itself is aimed at that pose with a `tween` in `space: "root"`.
1086
+ *
1087
+ * @example
1088
+ * ```ts
1089
+ * const tools: BuildTools = { at: () => ({ x: 0, y: 0, rotation: 0, scale: 1 }) };
1090
+ * ```
1091
+ */
1092
+ type BuildTools = {
1093
+ at(target: Target): Pose;
1094
+ };
1095
+ /**
1096
+ * One animation: an id, the slots it takes and the pure function that builds its step tree. The
1097
+ * same definition may play twice at once, because `build` runs per play.
1098
+ *
1099
+ * @example
1100
+ * ```ts
1101
+ * const coinsFly: AnimationDefinition<{ from: TypeTag<Target> }> = defineAnimation("hud.coinsFly", {
1102
+ * slots: { from: type<Target>() },
1103
+ * build: ({ from }) => tween(from, Transform, { scale: 0.4 }, { ms: 600 })
1104
+ * });
1105
+ * coinsFly.id; // "hud.coinsFly"
1106
+ * ```
1107
+ */
1108
+ type AnimationDefinition<Tags extends SlotTags = SlotTags> = {
1109
+ readonly id: string;
1110
+ readonly slots: Tags;
1111
+ build(slots: SlotValues<Tags>, tools: BuildTools): Step;
1112
+ };
1113
+ /**
1114
+ * What `defineAnimation` is given: the slots the animation takes and the pure function that
1115
+ * builds its step tree.
1116
+ *
1117
+ * @example
1118
+ * ```ts
1119
+ * const spec: AnimationSpec<{ card: TypeTag<Target> }> = {
1120
+ * slots: { card: type<Target>() },
1121
+ * build: ({ card }) => tween(card, Transform, { scale: 1.2 }, { ms: 120 })
1122
+ * };
1123
+ * ```
1124
+ */
1125
+ type AnimationSpec<Tags extends SlotTags> = {
1126
+ readonly slots: Tags;
1127
+ build(slots: SlotValues<Tags>, tools: BuildTools): Step;
1128
+ };
1129
+ /**
1130
+ * An animation with its slot tags erased, as the registry stores it.
1131
+ *
1132
+ * @example
1133
+ * ```ts
1134
+ * const stored: AnyAnimationDefinition = { id: "hud.coinsFly", slots: {}, build: () => wait(0) };
1135
+ * ```
1136
+ */
1137
+ type AnyAnimationDefinition = AnimationDefinition<SlotTags>;
1138
+ /**
1139
+ * What `play` hands back: the motion contract over the whole step tree, the promise of its end
1140
+ * and the marks it reached so far.
1141
+ *
1142
+ * @example
1143
+ * ```ts
1144
+ * const deliverOrder = defineAnimation("orders.deliver", { slots: { card: type<Target>() },
1145
+ * build: ({ card }) => sequence(tween(card, Transform, { x: 100 }, { ms: 100 }), mark("done")) });
1146
+ * const handle: PlayHandle = app.anim.play(deliverOrder, { card: { projection: "board.items", key: "a" } });
1147
+ * handle.marks(); // []: nothing reached yet
1148
+ * ```
1149
+ */
1150
+ type PlayHandle = MotionHandle & {
1151
+ readonly done: Promise<void>;
1152
+ marks(): readonly string[];
1153
+ };
1154
+ /**
1155
+ * A listener of `onMark`: the animation id and the name of the mark that was reached.
1156
+ *
1157
+ * @example
1158
+ * ```ts
1159
+ * const reached: string[] = [];
1160
+ * const listener: MarkListener = (_animation, mark) => reached.push(mark);
1161
+ * ```
1162
+ */
1163
+ type MarkListener = (animation: string, mark: string) => void;
1164
+ /**
1165
+ * anim plugin events.
1166
+ *
1167
+ * @example
1168
+ * ```ts
1169
+ * // A feature plugin logs every mark a choreography reaches.
1170
+ * createPlugin("rewardLog", {
1171
+ * depends: [animPlugin],
1172
+ * hooks: ctx => ({ "anim:mark": ({ mark }) => ctx.log.info("mark", { mark }) })
1173
+ * }); // a reached mark("done") step logs { mark: "done" }
1174
+ * ```
1175
+ */
1176
+ type Events = {
1177
+ /** A `mark` step was reached, or jumped by `finish()`. */"anim:mark": {
1178
+ animation: string;
1179
+ mark: string;
1180
+ }; /** A timeline ended or was finished. Not emitted on `cancel()`. */
1181
+ "anim:finished": {
1182
+ animation: string;
1183
+ };
1184
+ };
1185
+ /**
1186
+ * One key of a keyframe track in `defineMotion`: where on the track it sits (`at`, 0..1 of
1187
+ * `transition.ms`), the curve of the segment that ends on it (default `"inOut"`) and the pose.
1188
+ * `dx` and `dy` are offsets from the rest pose in reference units; `rotation` (radians), `scale`
1189
+ * and `alpha` are absolute. A field the key leaves out holds the previous key's value.
1190
+ *
1191
+ * An enter walk starts on the first key and ends on the rest pose at `at: 1`; an exit walk starts
1192
+ * where the view is and ends on the last key, the pose the element leaves in.
1193
+ *
1194
+ * In a loop (`loop: { track }`) every Transform field is an offset added to the rest pose: `dx`, `dy`, `rotation`
1195
+ * and `scale` (`scale: 0.05` grows a scale-1 view to 1.05). `alpha` stays absolute. The loop
1196
+ * stands on its first key until that key's `at`, and its last key repeats its first one.
1197
+ *
1198
+ * @example
1199
+ * ```ts
1200
+ * // A popup board at 42 % of its swing: 14 u below its rest, tilted 5°, slightly larger.
1201
+ * const key: MotionKeyframe = { at: 0.42, ease: "out", Transform: { dy: 14, rotation: 0.087, scale: 1.04 } };
1202
+ * ```
1203
+ */
1204
+ type MotionKeyframe = {
1205
+ readonly at: number;
1206
+ readonly ease?: Ease;
1207
+ readonly Transform?: {
1208
+ readonly dx?: number;
1209
+ readonly dy?: number;
1210
+ readonly rotation?: number;
1211
+ readonly scale?: number;
1212
+ };
1213
+ readonly Shape?: {
1214
+ readonly alpha?: number;
1215
+ };
1216
+ readonly Sprite?: {
1217
+ readonly alpha?: number;
1218
+ };
1219
+ readonly NineSlice?: {
1220
+ readonly alpha?: number;
1221
+ };
1222
+ };
1223
+ /**
1224
+ * The value of the `Frames` component: a frame loop that lives as long as its entity. `keys` and
1225
+ * `fps` mean what they mean on the `frames` step; `loop: false` stops on the last key and
1226
+ * `playing: false` holds the key on the screen. A new `keys` array restarts at key 0, the same
1227
+ * array keeps its phase.
1228
+ *
1229
+ * @example
1230
+ * ```ts
1231
+ * const spin: FramesValue = { keys: ["items.coin-0", "items.coin-1"], fps: 12, loop: true, playing: true };
1232
+ * ```
1233
+ */
1234
+ type FramesValue = {
1235
+ keys: readonly string[];
1236
+ fps: number;
1237
+ loop: boolean;
1238
+ playing: boolean;
1239
+ };
1240
+ /**
1241
+ * Where the `Frames` loop of one entity stands. `keys` is the array last seen, for the restart
1242
+ * check; `index` is the key the clock last landed on, `-1` before the first one; `written` is
1243
+ * false while that key still has to reach the sprite.
1244
+ */
1245
+ type FrameLoop = {
1246
+ keys: readonly string[];
1247
+ elapsed: number;
1248
+ index: number;
1249
+ written: boolean;
1250
+ };
1251
+ /**
1252
+ * anim plugin config. The timings live in the steps and in `defineMotion`, not here: a
1253
+ * choreography that reads its duration from a config cannot be read as text.
1254
+ *
1255
+ * @example
1256
+ * ```ts
1257
+ * createApp({ plugins: [...screen], pluginConfigs: { anim: { maxTracks: 500, reducedMotion: true } } });
1258
+ * ```
1259
+ */
1260
+ type Config = {
1261
+ /** Dev guard: one warning each time the running track count rises past this number. */maxTracks: number;
1262
+ /**
1263
+ * Start value of reduced motion: every track but a loop takes 0 ms and every loop stands on
1264
+ * its first key. `app.anim.setReducedMotion(on)` switches it while the game runs.
1265
+ */
1266
+ reducedMotion: boolean;
1267
+ };
1268
+ /**
1269
+ * anim plugin state: the one track table, the field bookkeeping of the retarget policy and the
1270
+ * offsets accumulator, the running timelines and the animation registry.
1271
+ */
1272
+ type State = {
1273
+ /** The one track table. Insertion order is advance order. */tracks: Map<number, Track>; /** Ids of tracks and timelines. */
1274
+ nextId: number; /** `"<entity>:<component>:<field>"` to the id of the absolute track that drives the field. */
1275
+ owner: Map<string, number>; /** Per field key, the delta each additive track contributes. */
1276
+ offsets: Map<string, Map<number, number>>; /** Per field key, the value the offsets are added to. Dropped when no track drives the field. */
1277
+ bases: Map<string, number>; /** The running timelines, by id. */
1278
+ timelines: Map<number, RunningTimeline>; /** The `animations` of every feature, read in `onStart`. */
1279
+ registry: Map<string, AnyAnimationDefinition>; /** `onMark` subscribers, in subscription order. */
1280
+ markListeners: Set<MarkListener>; /** Counter of the frame steps. A track born in the running step is not advanced by it. */
1281
+ frame: number; /** True while the track count is over `maxTracks`, so the warning is one per crossing. */
1282
+ overMaxTracks: boolean; /** The live reduced-motion switch. The frozen `Config.reducedMotion` only seeds it. */
1283
+ reducedMotion: boolean; /** Remover of `world.projection.setDriver`. */
1284
+ removeDriver: (() => void) | undefined; /** Remover of `time.onFrame("animate")`. */
1285
+ offFrame: (() => void) | undefined; /** Remover of `flow.fx.handle("play")`. */
1286
+ offPlay: (() => void) | undefined; /** Bound in `onInit`, so the teardown context can end everything with the state alone. */
1287
+ finishAll: (() => void) | undefined; /** One loop per entity that carries `Frames`, kept by the two world hooks. */
1288
+ frameLoops: Map<Entity, FrameLoop>; /** How many running `frames` steps write this entity now. Its loop writes nothing above 0. */
1289
+ framesHeld: Map<Entity, number>;
1290
+ /**
1291
+ * Per entity that carries `Frames`, the remover of its `world.projection.mute` on
1292
+ * `Sprite.texture`: the loop owns that field, so a projection never corrects it back.
1293
+ */
1294
+ frameMutes: Map<Entity, () => void>; /** Removers of the `onAdded` and `onRemoved` hooks on `Frames`. */
1295
+ offFrames: Array<() => void>;
1296
+ };
1297
+ /**
1298
+ * anim plugin API, `app.anim`: play a choreography, end everything, count what runs and listen
1299
+ * to the marks. A choreography may spawn temporary entities; they live exactly as long as its
1300
+ * timeline.
1301
+ *
1302
+ * @example
1303
+ * ```ts
1304
+ * // A choreography runs in game time: two frames of 50 ms play the 100 ms slide of a card.
1305
+ * const deliverOrder = defineAnimation("orders.deliver", { slots: { card: type<Target>() },
1306
+ * build: ({ card }) => sequence(tween(card, Transform, { x: 100 }, { ms: 100 }), mark("done")) });
1307
+ * app.anim.play(deliverOrder, { card: { projection: "board.items", key: "a" } });
1308
+ * app.time.step(50); // app.anim.active() is 1: the card slides
1309
+ * app.time.step(50); // app.anim.active() is 0: the card rests at x 100
1310
+ * ```
1311
+ */
1312
+ type AnimApi = {
1313
+ /**
1314
+ * Builds the step tree of an animation with the given targets and starts it. The tree runs
1315
+ * from the next frame step on, in game time, so `time.setScale` and a pause apply to it. `at`
1316
+ * inside `build` answers where a target rests in root space, even inside a scaled slot. A
1317
+ * `tween` with `space: "root"` lands a hosted view on such a pose: a board item inside the board
1318
+ * slot flies onto an order card of the HUD.
1319
+ *
1320
+ * @param animation - What `defineAnimation` returned.
1321
+ * @param slots - One target, or a list of targets, per declared slot.
1322
+ * @returns The handle of the running timeline.
1323
+ * @throws {Error} When the tree spawns one id twice.
1324
+ * @example
1325
+ * ```ts
1326
+ * // The player delivers order card "a": it slides to x 100 in 100 ms, then reaches "done".
1327
+ * const deliverOrder = defineAnimation("orders.deliver", { slots: { card: type<Target>() },
1328
+ * build: ({ card }) => sequence(tween(card, Transform, { x: 100 }, { ms: 100 }), mark("done")) });
1329
+ * const handle = app.anim.play(deliverOrder, { card: { projection: "board.items", key: "a" } });
1330
+ * app.time.step(100);
1331
+ * handle.marks(); // ["done"], and handle.done resolves
1332
+ * ```
1333
+ */
1334
+ play<Tags extends SlotTags>(animation: AnimationDefinition<Tags>, slots: SlotValues<Tags>): PlayHandle;
1335
+ /**
1336
+ * Ends every track and every timeline now: each writes its exact target, the marks of a
1337
+ * running timeline are jumped in tree order, every entity a timeline spawned is despawned and
1338
+ * every pending `done` resolves.
1339
+ *
1340
+ * @example
1341
+ * ```ts
1342
+ * // A /control tool skips the delivery mid-flight: the card lands on x 100 at once.
1343
+ * const deliverOrder = defineAnimation("orders.deliver", { slots: { card: type<Target>() },
1344
+ * build: ({ card }) => sequence(tween(card, Transform, { x: 100 }, { ms: 100 }), mark("done")) });
1345
+ * app.anim.play(deliverOrder, { card: { projection: "board.items", key: "a" } });
1346
+ * app.time.step(50);
1347
+ * app.anim.finishAll(); // app.anim.active() is 0
1348
+ * ```
1349
+ */
1350
+ finishAll(): void;
1351
+ /**
1352
+ * How many tracks are in the table, the delayed ones and the running loops included. A loop
1353
+ * never ends by itself, so a screen at rest counts one track per loop lane it shows. A `Frames`
1354
+ * component is not a track: a spinning coin counts nothing.
1355
+ *
1356
+ * @returns The number of running tracks.
1357
+ * @example
1358
+ * ```ts
1359
+ * // The assertion every motion test ends with: the screen came to rest.
1360
+ * const deliverOrder = defineAnimation("orders.deliver", { slots: { card: type<Target>() },
1361
+ * build: ({ card }) => sequence(tween(card, Transform, { x: 100 }, { ms: 100 }), mark("done")) });
1362
+ * app.anim.play(deliverOrder, { card: { projection: "board.items", key: "a" } });
1363
+ * app.time.step(100);
1364
+ * app.anim.active(); // 0
1365
+ * ```
1366
+ */
1367
+ active(): number;
1368
+ /**
1369
+ * Whether reduced motion is on. It starts at `Config.reducedMotion` and `setReducedMotion`
1370
+ * switches it.
1371
+ *
1372
+ * @returns True while every new track takes 0 ms and every loop stands on its first key.
1373
+ * @example
1374
+ * ```ts
1375
+ * // A settings popup shows the switch as the player left it.
1376
+ * app.anim.reducedMotion(); // false: the start value of Config.reducedMotion
1377
+ * ```
1378
+ */
1379
+ reducedMotion(): boolean;
1380
+ /**
1381
+ * Switches reduced motion. While it is on, every track started from then on takes 0 ms: enter
1382
+ * and exit, state changes, change and settle motions, drag returns and timeline tweens land on
1383
+ * their target at the next frame, and their marks and sounds still fire. Every loop stands on
1384
+ * its first key, a running one at once. Tracks already running keep their length. A `Frames`
1385
+ * loop stops on the frame it shows and walks on from that frame when the switch goes off.
1386
+ *
1387
+ * @param on - True for less motion, false for the full motion.
1388
+ * @example
1389
+ * ```ts
1390
+ * // The player turns on "Less motion" in the settings popup.
1391
+ * app.anim.setReducedMotion(true);
1392
+ * app.anim.reducedMotion(); // true: a popup opened now stands in its rest pose next frame
1393
+ * ```
1394
+ */
1395
+ setReducedMotion(on: boolean): void;
1396
+ /**
1397
+ * Registers a listener called for every mark a timeline reaches, next to the `anim:mark`
1398
+ * event. A listener that throws is logged and the listeners after it still run.
1399
+ *
1400
+ * @param fn - Called with the animation id and the mark name.
1401
+ * @returns The remover; calling it twice is a no-op.
1402
+ * @example
1403
+ * ```ts
1404
+ * // A test waits for the "done" mark instead of counting frames.
1405
+ * const ping = defineAnimation("fx.ping", { slots: {}, build: () => sequence(wait(100), mark("done")) });
1406
+ * const reached: string[] = [];
1407
+ * app.anim.onMark((animation, name) => reached.push(`${animation}:${name}`));
1408
+ * app.anim.play(ping, {});
1409
+ * app.time.step(100); // reached: ["fx.ping:done"]
1410
+ * ```
1411
+ */
1412
+ onMark(fn: MarkListener): () => void;
1413
+ };
1414
+ /**
1415
+ * Resolved dependency APIs. `renderer` is not among them: `anim` writes its `Sprite` and reads
1416
+ * its `Transform` through the component objects, which are plain data, composes the root pose
1417
+ * of `at` with the pure `rootPoseOf` of `renderer/sync/pose.ts`, and turns the target of a
1418
+ * `tween` in `space: "root"` local with the pure `localPoseOf` of the same module.
1419
+ */
1420
+ type Deps = {
1421
+ time: Api;
1422
+ flow: Api$1;
1423
+ world: Api$2;
1424
+ };
1425
+ /**
1426
+ * What the kernel context offers before the deps are attached.
1427
+ *
1428
+ * `index.ts` writes `events` with an annotated `register` (core spec `14-EVENT-REGISTRATION.md`
1429
+ * row 8), so the own events reach the context the kernel hands the factories and `emit` is the
1430
+ * kernel's own, anim-typed one. No member of the context is cast.
1431
+ */
1432
+ type KernelSlice = PluginCtx<Config, State, Events> & {
1433
+ readonly global: object;
1434
+ readonly log: Log.LogApi;
1435
+ readonly require: Require;
1436
+ };
1437
+ /**
1438
+ * Domain context of the anim plugin: the kernel slice and the three resolved dependencies.
1439
+ */
1440
+ type AnimCtx = KernelSlice & {
1441
+ readonly deps: Deps;
1442
+ };
1443
+ //#endregion
1444
+ export { Point as C, TextStyles as D, TextStyleInput as E, TextValue as O, Config$2 as S, TextApi as T, Step as _, Config as a, UiApi as b, SlotTags as c, Target as d, types_d_exports as f, SfxDescriptor as g, PlayDescriptor as h, BuildTools as i, types_d_exports$2 as k, SlotValues as l, HapticDescriptor as m, AnimationDefinition as n, FramesValue as o, ExternalPlayer as p, AnimationSpec as r, MotionKeyframe as s, AnimApi as t, State as u, Config$1 as v, State$2 as w, types_d_exports$1 as x, State$1 as y };