@moku-labs/game 0.4.2 → 0.4.4

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.
@@ -1,1284 +0,0 @@
1
- import { D as Point, E as FilterSlot, Gt as Events$2, K as Owner, Kt as Json, L as ComponentHandle, Pt as Descriptor, Rt as Api$4, Ut as Api$5, V as Entity, _ as PixiModule, et as LayerSort, g as PixiFilter, k as Api$1, m as Api$2, o as Events$1, sn as Events$3, t as Api$3, tt as LayerSpec, ut as Api, v as PixiSprite, vn as Require, y as PixiTexture, z as ComponentType } from "./types-Dy1xB68L.mjs";
2
- import { t as AnimApi } from "./types-CVUHPvMB.mjs";
3
- import { Log } from "@moku-labs/common/browser";
4
- import { PluginCtx } from "@moku-labs/core";
5
-
6
- //#region src/plugins/effects/filters/types.d.ts
7
- /**
8
- * One uniform of a custom filter, as declared: a number is an `f32` (`float` in GLSL), `{ color }`
9
- * a `0xrrggbb` hex sent as a `vec3<f32>` (`vec3`), a tuple of 2 to 4 numbers a `vecN<f32>`
10
- * (`vecN`).
11
- *
12
- * @example
13
- * ```ts
14
- * const uniforms: Record<string, UniformDeclaration> = { amount: 0.5, color: { color: 0xffd700 }, offset: [2, 2] };
15
- * ```
16
- */
17
- type UniformDeclaration = number | {
18
- readonly color: number;
19
- } | readonly [number, number] | readonly [number, number, number] | readonly [number, number, number, number];
20
- /**
21
- * The component field a uniform declaration becomes: a number for a number and for a colour, a
22
- * list of numbers for a tuple.
23
- *
24
- * @example
25
- * ```ts
26
- * type Amount = UniformValue<0.5>; // number
27
- * ```
28
- */
29
- type UniformValue<Declaration> = Declaration extends number ? number : Declaration extends {
30
- readonly color: number;
31
- } ? number : readonly number[];
32
- /**
33
- * The value of a filter component made by `defineFilter`: one field per uniform, then `enabled`
34
- * and `order`.
35
- *
36
- * @example
37
- * ```ts
38
- * type TintValue = FilterValueOf<{ amount: number; color: { color: number } }>;
39
- * // { amount: number; color: number; enabled: boolean; order: number }
40
- * ```
41
- */
42
- type FilterValueOf<Uniforms extends Record<string, UniformDeclaration>> = { -readonly [Name in keyof Uniforms]: UniformValue<Uniforms[Name]> } & {
43
- enabled: boolean;
44
- order: number;
45
- };
46
- /**
47
- * What `defineFilter` takes besides the id. Both bodies are required, so the filter draws on
48
- * WebGPU and on the WebGL fallback: `wgsl` is the WGSL fragment body only, the engine prepends the
49
- * vertex stage, the input bindings and the `FilterUniforms` struct; `glsl` is its GLSL ES 3.0 twin
50
- * over the same uniform names, the engine prepends the version, the inputs, `finalColor` and one
51
- * `uniform` per declared uniform.
52
- *
53
- * @example
54
- * ```ts
55
- * const spec: FilterSpec<{ amount: number }> = {
56
- * wgsl: "@fragment fn mainFragment(@location(0) uv: vec2<f32>) -> @location(0) vec4<f32> { return textureSample(uTexture, uSampler, uv) * fu.amount; }",
57
- * glsl: "void main() { finalColor = texture(uTexture, vTextureCoord) * amount; }",
58
- * uniforms: { amount: 1 }
59
- * };
60
- * ```
61
- */
62
- type FilterSpec<Uniforms extends Record<string, UniformDeclaration>> = {
63
- /** The WGSL fragment body: `fn mainFragment` and its helpers, without the engine's header. */readonly wgsl: string;
64
- /**
65
- * The GLSL ES 3.0 fragment body: `void main()`, which sets `finalColor`, and its helpers, without
66
- * the engine's header. It reads each uniform under its bare name: `amount`, not `fu.amount`.
67
- */
68
- readonly glsl: string; /** The uniforms in struct order. Default `{}`. */
69
- readonly uniforms?: Uniforms; /** Render passes one apply of the filter costs, an integer of at least 1. Default `1`. */
70
- readonly passes?: number; /** Pixels around the view's bounds, or the name of a number uniform whose live value they are. Default `0`. */
71
- readonly padding?: number | string;
72
- };
73
- /**
74
- * How one uniform travels to the GPU: its name, its WGSL type and the form it was declared in.
75
- *
76
- * @example
77
- * ```ts
78
- * const spec: UniformSpec = { name: "color", type: "vec3<f32>", form: "color", size: 3 };
79
- * ```
80
- */
81
- type UniformSpec = {
82
- readonly name: string;
83
- readonly type: "f32" | "vec2<f32>" | "vec3<f32>" | "vec4<f32>";
84
- readonly form: "number" | "color" | "vector";
85
- readonly size: number;
86
- };
87
- /**
88
- * The filter a component made by `defineFilter` carries: the assembled WGSL in `source`, the
89
- * assembled GLSL in `glsl`, the uniforms in struct order, the passes one apply costs and the
90
- * padding.
91
- *
92
- * @example
93
- * ```ts
94
- * Tint.filter.source.includes("fn mainFragment"); // true: the WGSL, drawn on WebGPU
95
- * Tint.filter.glsl.startsWith("#version 300 es"); // true: the GLSL, drawn on WebGL
96
- * Tint.filter.uniforms.map(uniform => uniform.name); // ["amount", "color"]
97
- * ```
98
- */
99
- type FilterDefinition = {
100
- readonly id: string; /** The assembled WGSL: the engine's header, the uniform struct, the body. */
101
- readonly source: string; /** The assembled GLSL ES 3.0: the engine's header, one `uniform` per uniform, the body. */
102
- readonly glsl: string;
103
- readonly uniforms: readonly UniformSpec[];
104
- readonly passes: number;
105
- readonly padding: number | string;
106
- };
107
- /**
108
- * A filter component type: the `component()` of its id, with the filter definition attached. Its
109
- * numeric fields are tweenable like any component.
110
- *
111
- * @example
112
- * ```ts
113
- * const Tint: FilterComponent<{ amount: number; enabled: boolean; order: number }> =
114
- * defineFilter("fx.tint", { wgsl: tintWgsl, glsl: tintGlsl, uniforms: { amount: 0 } });
115
- * Tint.componentName; // "fx.tint"
116
- * ```
117
- */
118
- type FilterComponent<Value extends object> = ComponentType<Value> & {
119
- readonly filter: FilterDefinition;
120
- };
121
- /**
122
- * One field of a stored filter value: a number, a switch, an asset key or a vector.
123
- *
124
- * @example
125
- * ```ts
126
- * const field: FilterField = 0.5;
127
- * ```
128
- */
129
- type FilterField = number | boolean | string | readonly number[];
130
- /**
131
- * The stored value of any filter component, with the kind erased: the filter sync reads every
132
- * kind through it.
133
- */
134
- type FilterFields = {
135
- readonly enabled: boolean;
136
- readonly order: number;
137
- readonly [field: string]: FilterField;
138
- };
139
- /**
140
- * `Glow`: a soft halo of `color` around the opaque pixels of the view.
141
- *
142
- * @example
143
- * ```ts
144
- * const value: GlowValue = { strength: 2, distance: 10, color: 0xffffff, alpha: 1, enabled: true, order: 0 };
145
- * ```
146
- */
147
- type GlowValue = {
148
- strength: number;
149
- distance: number;
150
- color: number;
151
- alpha: number;
152
- enabled: boolean;
153
- order: number;
154
- };
155
- /**
156
- * `Outline`: a ring of `thickness` pixels in `color` around the opaque pixels of the view.
157
- *
158
- * @example
159
- * ```ts
160
- * const value: OutlineValue = { thickness: 2, color: 0x000000, alpha: 1, enabled: true, order: 0 };
161
- * ```
162
- */
163
- type OutlineValue = {
164
- thickness: number;
165
- color: number;
166
- alpha: number;
167
- enabled: boolean;
168
- order: number;
169
- };
170
- /**
171
- * `Blur`: Pixi's blur. `quality: 0` means `config.blur.quality`; `resolution: 0` means
172
- * `config.blur.phoneResolution` on a phone, otherwise 1.
173
- *
174
- * @example
175
- * ```ts
176
- * const value: BlurValue = { strength: 8, quality: 0, resolution: 0, repeatEdgePixels: true, enabled: true, order: 0 };
177
- * ```
178
- */
179
- type BlurValue = {
180
- strength: number;
181
- quality: number;
182
- resolution: number;
183
- repeatEdgePixels: boolean;
184
- enabled: boolean;
185
- order: number;
186
- };
187
- /**
188
- * `ColorMatrix`: brightness, saturation, contrast, hue and grayscale composed in that order.
189
- * Every default is the identity.
190
- *
191
- * @example
192
- * ```ts
193
- * const value: ColorMatrixValue = { brightness: 1, saturation: 0, contrast: 0, hue: 0, grayscale: 1, enabled: true, order: 0 };
194
- * ```
195
- */
196
- type ColorMatrixValue = {
197
- brightness: number;
198
- saturation: number;
199
- contrast: number;
200
- hue: number;
201
- grayscale: number;
202
- enabled: boolean;
203
- order: number;
204
- };
205
- /**
206
- * `Noise`: Pixi's noise with its seed always given, so two runs draw the same grain.
207
- *
208
- * @example
209
- * ```ts
210
- * const value: NoiseValue = { amount: 0.5, seed: 7, enabled: true, order: 0 };
211
- * ```
212
- */
213
- type NoiseValue = {
214
- amount: number;
215
- seed: number;
216
- enabled: boolean;
217
- order: number;
218
- };
219
- /**
220
- * `Displacement`: Pixi's displacement over the texture of an asset key.
221
- *
222
- * @example
223
- * ```ts
224
- * const value: DisplacementValue = { map: "fx.ripple", scaleX: 20, scaleY: 20, enabled: true, order: 0 };
225
- * ```
226
- */
227
- type DisplacementValue = {
228
- map: string;
229
- scaleX: number;
230
- scaleY: number;
231
- enabled: boolean;
232
- order: number;
233
- };
234
- /**
235
- * `Alpha`: Pixi's alpha over the whole subtree, as one layer.
236
- *
237
- * @example
238
- * ```ts
239
- * const value: AlphaValue = { alpha: 0.5, enabled: true, order: 0 };
240
- * ```
241
- */
242
- type AlphaValue = {
243
- alpha: number;
244
- enabled: boolean;
245
- order: number;
246
- };
247
- /**
248
- * The five built-in kinds drawn by a Pixi core filter.
249
- *
250
- * @example
251
- * ```ts
252
- * const kind: CoreKind = "blur";
253
- * ```
254
- */
255
- type CoreKind = "blur" | "colorMatrix" | "noise" | "displacement" | "alpha";
256
- /**
257
- * Where a filter kind comes from: our or a game's shaders (`"wgsl"`, with the GLSL twin beside
258
- * it), or a Pixi core filter.
259
- */
260
- type KindSource = {
261
- readonly source: "wgsl";
262
- readonly definition: FilterDefinition;
263
- } | {
264
- readonly source: "core";
265
- readonly core: CoreKind;
266
- };
267
- /**
268
- * A filter kind before it is registered: its id, its component and its source.
269
- */
270
- type KindEntry = KindSource & {
271
- readonly id: string;
272
- readonly component: ComponentHandle<FilterFields>;
273
- };
274
- /**
275
- * One registered filter kind. `index` is the registration order: the built-ins first, then the
276
- * filters of every feature in feature order.
277
- */
278
- type FilterKind = KindEntry & {
279
- readonly index: number;
280
- };
281
- /**
282
- * The values of the uniform group of one of our filter instances: a number per `f32`, a vector
283
- * per colour and tuple. Written every frame.
284
- */
285
- type UniformValues = Record<string, number | Float32Array>;
286
- /**
287
- * The uniform group of one of our filter instances, as the instance writes it.
288
- */
289
- type FilterUniforms = {
290
- readonly uniforms: UniformValues;
291
- };
292
- /**
293
- * The Pixi object of one kind on one view, with the passes one apply of it costs now: what the
294
- * builders of `instance.ts` make before the instance gets its sort keys.
295
- */
296
- type FilterBody = {
297
- readonly kind: "wgsl";
298
- readonly filter: PixiFilter;
299
- readonly group: FilterUniforms | undefined;
300
- readonly definition: FilterDefinition;
301
- passes: number;
302
- } | {
303
- readonly kind: "blur";
304
- readonly filter: InstanceType<PixiModule["BlurFilter"]>;
305
- passes: number;
306
- } | {
307
- readonly kind: "colorMatrix";
308
- readonly filter: InstanceType<PixiModule["ColorMatrixFilter"]>;
309
- passes: number;
310
- } | {
311
- readonly kind: "noise";
312
- readonly filter: InstanceType<PixiModule["NoiseFilter"]>;
313
- passes: number;
314
- } | {
315
- readonly kind: "displacement";
316
- readonly filter: InstanceType<PixiModule["DisplacementFilter"]>;
317
- readonly sprite: PixiSprite; /** The asset key the map sprite shows now. */
318
- map: string;
319
- passes: number;
320
- } | {
321
- readonly kind: "alpha";
322
- readonly filter: InstanceType<PixiModule["AlphaFilter"]>;
323
- passes: number;
324
- };
325
- /**
326
- * The instance of one kind on one view: its Pixi object, and what it sorts by on the view, the
327
- * `order` of its component (written every frame) and the `index` of its kind.
328
- */
329
- type FilterInstance = FilterBody & {
330
- order: number;
331
- readonly index: number;
332
- };
333
- /**
334
- * What the filter sync keeps for one entity: the kinds it carries, one instance per kind, the
335
- * instances waiting to be destroyed after the next assignment, the slots last handed to
336
- * `renderer.sync.filters.set`, the core kinds whose component changed this frame, and the
337
- * instances of this frame in their sorted order (one array, reused every frame).
338
- */
339
- type FilteredView = {
340
- readonly kinds: Set<string>;
341
- readonly instances: Map<string, FilterInstance>;
342
- readonly retired: FilterInstance[];
343
- assigned: readonly FilterSlot[];
344
- readonly changed: Set<string>;
345
- readonly placed: FilterInstance[];
346
- };
347
- //#endregion
348
- //#region src/plugins/effects/particles/types.d.ts
349
- /**
350
- * An inclusive `[min, max]` range; every particle draws its own value from it.
351
- *
352
- * @example
353
- * ```ts
354
- * const lifeMs: Range = [500, 900];
355
- * ```
356
- */
357
- type Range = readonly [min: number, max: number];
358
- /**
359
- * A value over the life of a particle, linear from its birth (`from`) to its death (`to`).
360
- *
361
- * @example
362
- * ```ts
363
- * const shrink: Curve = { from: 1, to: 0.2 };
364
- * ```
365
- */
366
- type Curve = {
367
- readonly from: number;
368
- readonly to: number;
369
- };
370
- /**
371
- * Where a particle is born, around the origin of its emitter, in reference units. `circle` is
372
- * uniform over the disc, `ring` uniform between `radius − width / 2` and `radius + width / 2`,
373
- * `rect` uniform over the centred box.
374
- *
375
- * @example
376
- * ```ts
377
- * const burstRing: Shape = { kind: "ring", radius: 48, width: 16 };
378
- * ```
379
- */
380
- type Shape = {
381
- readonly kind: "point";
382
- } | {
383
- readonly kind: "circle";
384
- readonly radius: number;
385
- } | {
386
- readonly kind: "rect";
387
- readonly w: number;
388
- readonly h: number;
389
- } | {
390
- readonly kind: "ring";
391
- readonly radius: number;
392
- readonly width: number;
393
- };
394
- /**
395
- * A particle effect as data, what `defineEmitter` takes. Every field is optional except
396
- * `textures` and exactly one of `burst` and `rate`. Speeds are reference units per second, angles
397
- * degrees with 0 to the right and 90 down, spins radians per second.
398
- *
399
- * @example
400
- * ```ts
401
- * const stars: EmitterConfig = {
402
- * textures: ["fx.star", "fx.sparkle"], burst: 40, lifeMs: [500, 900], speed: [300, 700],
403
- * gravity: 900, drag: 0.2, shape: { kind: "circle", radius: 24 }, alpha: { from: 1, to: 0 }
404
- * };
405
- * ```
406
- */
407
- type EmitterConfig = {
408
- /** 1 to 16 asset keys of one atlas page; a particle picks one at random. */readonly textures: readonly string[]; /** Particles emitted once, when the component appears or its `effect` changes. */
409
- readonly burst?: number; /** Particles per second while `active` is true and the entity lives. */
410
- readonly rate?: number; /** Life of a particle in milliseconds. Default `[600, 1000]`. */
411
- readonly lifeMs?: Range; /** Start speed in reference units per second. Default `[0, 0]`. */
412
- readonly speed?: Range; /** Start direction in degrees, 0 right, 90 down. Default `[0, 360]`. */
413
- readonly angle?: Range; /** Downward acceleration in reference units per second². Default `0`. */
414
- readonly gravity?: number; /** Velocity lost per second, 0..1: `v *= (1 − drag) ^ seconds`. Default `0`. */
415
- readonly drag?: number; /** Spin in radians per second. Default `[0, 0]`. */
416
- readonly spin?: Range; /** Where particles are born around the origin. Default `{ kind: "point" }`. */
417
- readonly shape?: Shape; /** Scale over life. Default `{ from: 1, to: 1 }`. */
418
- readonly scale?: Curve; /** Alpha over life, 0..1. Default `{ from: 1, to: 1 }`. */
419
- readonly alpha?: Curve; /** Tint over life, `0xrrggbb`, linear per channel. Default white to white. */
420
- readonly tint?: Curve; /** `"world"`: particles outlive the entity. `"local"`: they move and die with it. Default `"world"`. */
421
- readonly space?: "world" | "local"; /** Milliseconds simulated before the first frame, for streams, 0..5000. Default `0`. */
422
- readonly prewarmMs?: number; /** Hard ceiling of live particles of one instance, 1..5000. Default `256`. */
423
- readonly maxParticles?: number; /** Blend mode of the particle container. Default `"normal"`. */
424
- readonly blend?: "normal" | "add";
425
- };
426
- /**
427
- * An `EmitterConfig` with every default filled in. `burst` and `rate` stay as given: exactly one
428
- * of them is present.
429
- *
430
- * @example
431
- * ```ts
432
- * defineEmitter("fx.steam", { textures: ["fx.puff"], rate: 12 }).config.lifeMs; // [600, 1000]
433
- * ```
434
- */
435
- type ResolvedEmitterConfig = Required<Omit<EmitterConfig, "burst" | "rate">> & Pick<EmitterConfig, "burst" | "rate">;
436
- /**
437
- * A particle effect as `defineEmitter` returns it: frozen, registered through the `emitters` key
438
- * of a feature and started by an `Emitter` component that names its id.
439
- *
440
- * @example
441
- * ```ts
442
- * const steam: EmitterDefinition = defineEmitter("fx.steam", { textures: ["fx.puff"], rate: 12 });
443
- * steam.config.rate; // 12
444
- * ```
445
- */
446
- type EmitterDefinition = {
447
- readonly id: string;
448
- readonly config: ResolvedEmitterConfig;
449
- };
450
- /**
451
- * The value of the `Emitter` component: which effect runs on the entity and whether a stream
452
- * emits. `""` draws nothing.
453
- *
454
- * @example
455
- * ```ts
456
- * const value: EmitterValue = { effect: "fx.steam", active: true };
457
- * ```
458
- */
459
- type EmitterValue = {
460
- effect: string;
461
- active: boolean;
462
- };
463
- /**
464
- * The cosmetic random source of one instance: xorshift32 over its own seed, never `Math.random`
465
- * and never the model's rng.
466
- */
467
- type Rng = {
468
- /** The next number in `[0, 1)`. */next(): number; /** An integer in `[0, count)`. */
469
- int(count: number): number; /** A number in `[min, max]`. */
470
- between(min: number, max: number): number;
471
- };
472
- /**
473
- * A Pixi particle container: what one emitter instance draws in.
474
- */
475
- type PixiParticleContainer = InstanceType<PixiModule["ParticleContainer"]>;
476
- /**
477
- * One particle of a container, as the container keeps it.
478
- */
479
- type PixiParticle = PixiParticleContainer["particleChildren"][number];
480
- /**
481
- * What one effect id bakes to on first use: the 64-entry curve tables, the reach rectangle's half
482
- * side and the resolved textures. Dropped when a bundle with one of its textures leaves.
483
- */
484
- type BakedEmitter = {
485
- readonly id: string;
486
- readonly config: ResolvedEmitterConfig; /** Scale per 1/64 of the life. */
487
- readonly scaleTable: Float32Array; /** Alpha per 1/64 of the life, 0..1. */
488
- readonly alphaTable: Float32Array; /** Tint per 1/64 of the life, packed as Pixi's `0xbbggrr`. */
489
- readonly tintTable: Uint32Array; /** Half side of the box no particle leaves, in reference units. */
490
- readonly reach: number;
491
- readonly textures: readonly PixiTexture[]; /** The texture source of the first texture: every texture of the effect samples it. */
492
- readonly source: PixiTexture["source"] | undefined; /** The largest side of the textures, in pixels. */
493
- readonly maxSide: number;
494
- };
495
- /**
496
- * The per-particle numbers a Pixi particle has no field for, one entry per live particle, in the
497
- * order of `particleChildren`.
498
- */
499
- type BornFields = {
500
- readonly age: Float32Array;
501
- readonly life: Float32Array;
502
- readonly vx: Float32Array;
503
- readonly vy: Float32Array;
504
- readonly spin: Float32Array;
505
- readonly variant: Float32Array;
506
- };
507
- /**
508
- * One running effect: its container, the plugin-owned entity that carries it, the particle pool
509
- * and the numbers the step needs.
510
- */
511
- type EmitterInstance = {
512
- readonly id: string;
513
- readonly baked: BakedEmitter; /** The entity whose `Emitter` started it. */
514
- readonly host: Entity; /** The plugin-owned entity whose `Display` is the container. */
515
- readonly entity: Entity;
516
- readonly container: PixiParticleContainer; /** Retired particles, reused before a new one is made. */
517
- readonly pool: PixiParticle[];
518
- readonly born: BornFields; /** Fraction of a particle owed to the next frame of a stream. */
519
- carry: number;
520
- readonly rng: Rng; /** The point the container was placed at, in root space (world space only). */
521
- readonly origin: Readonly<Point>;
522
- readonly space: "world" | "local"; /** Pixi's particle class, from the module the renderer loaded. */
523
- readonly particleClass: PixiModule["Particle"];
524
- };
525
- declare namespace types_d_exports$2 {
526
- export { AlphaValue, BlurValue, BundleUnloaded$1 as BundleUnloaded, ColorMatrixValue, Config$2 as Config, CoreKind, Curve, Deps$2 as Deps, DisplacementValue, EffectsApi, EffectsCtx, EffectsStats, EmitterConfig, EmitterDefinition, EmitterValue, FilterComponent, FilterDefinition, FilterSpec, FilterValueOf, GlowValue, KernelSlice$2 as KernelSlice, NoiseValue, OutlineValue, Range, ResolvedEmitterConfig, Shape, State$2 as State, UniformDeclaration, UniformSpec, UniformValue };
527
- }
528
- /**
529
- * effects plugin config. `maxParticles` here is the global warning line; the `maxParticles` of an
530
- * emitter config caps one instance.
531
- *
532
- * @example
533
- * ```ts
534
- * createApp({
535
- * plugins: [...screen, effectsPlugin],
536
- * pluginConfigs: { effects: { maxPasses: 16, phone: false } }
537
- * });
538
- * ```
539
- */
540
- type Config$2 = {
541
- /** Live particles over all instances above which one dev warning fires per crossing. */maxParticles: number; /** Render passes per frame above which one dev warning fires per crossing. */
542
- maxPasses: number; /** Whether this device is a phone. `"auto"`: a coarse pointer and a short side of at most 820 CSS px, read once in `onStart`. */
543
- phone: boolean | "auto";
544
- /**
545
- * What a `Blur` with `quality: 0` and `resolution: 0` resolves to. Shallow merge: a game that
546
- * sets `blur` gives both fields.
547
- */
548
- blur: {
549
- quality: number;
550
- phoneResolution: number;
551
- };
552
- };
553
- /**
554
- * effects plugin state: the particle half, the filter half, and what both share.
555
- */
556
- type State$2 = {
557
- /** Resolved in `onStart`. */phone: boolean; /** The `emitters` of every feature, read in `onStart`. */
558
- emitters: Map<string, EmitterDefinition>; /** Per effect id, built on first use. */
559
- baked: Map<string, BakedEmitter>; /** Host entity to its running instance. */
560
- instances: Map<Entity, EmitterInstance>; /** World-space instances whose host left, stepped until empty. */
561
- orphans: Set<EmitterInstance>; /** Ordinal of the next instance: the cosmetic seed. */
562
- seedCounter: number; /** Live particles after the last step. */
563
- particles: number; /** Filter kinds whose shader failed the dev check on the running backend. */
564
- broken: Set<string>; /** The filter kinds, built-ins first, then the filters of every feature. */
565
- kinds: Map<string, FilterKind>; /** The dev shader check per kind; `broken` holds the failed ones. */
566
- checks: Map<string, "pending" | "ok">; /** Entity to the filters the sync keeps for it. */
567
- views: Map<Entity, FilteredView>; /** Keys of the one-shot warnings: `"emitter:fx.x"`, `"texture:board.cell"`, `"atlas:fx.x"`. */
568
- warned: Set<string>; /** Whether each budget warning is in force, so a crossing warns once. */
569
- over: {
570
- particles: boolean;
571
- passes: boolean;
572
- fullScreen: boolean;
573
- }; /** The teardown closure, the two systems and the world hooks. */
574
- removers: Array<() => void>;
575
- };
576
- /**
577
- * What `app.effects.stats()` answers: live particles, running emitter instances, filter instances
578
- * on views, and the render passes of the frame as the renderer counts them.
579
- *
580
- * @example
581
- * ```ts
582
- * const stats: EffectsStats = { particles: 120, emitters: 3, filters: 25, renderPasses: 49 };
583
- * ```
584
- */
585
- type EffectsStats = {
586
- /** Live particles over every instance and orphan after the last step. */particles: number; /** Running instances plus the orphans still flying. */
587
- emitters: number; /** Filter instances over every view. */
588
- filters: number; /** `renderer.sync.renderPasses()`, read at call time. */
589
- renderPasses: number;
590
- };
591
- /**
592
- * effects plugin API, `app.effects`. Everything a game does with effects is data on entities;
593
- * the API only counts.
594
- *
595
- * @example
596
- * ```ts
597
- * // A test waits for a merge burst to land before it compares the board.
598
- * while (app.effects.stats().particles > 0) app.time.step(16);
599
- * app.effects.stats().particles; // 0
600
- * ```
601
- */
602
- type EffectsApi = {
603
- /**
604
- * Counts what runs now. `renderPasses` is the renderer's own number, read at call time; the
605
- * others are the plugin's. Headless every number is 0.
606
- *
607
- * @returns A fresh object: live particles, emitter instances with orphans, filter instances
608
- * and render passes.
609
- * @example
610
- * ```ts
611
- * // After the board rests: one steam stream, 24 enabled glows on 24 views, one disabled blur.
612
- * app.effects.stats();
613
- * // { particles: 18, emitters: 1, filters: 25, renderPasses: 49 }: 1 + 24 × (1 + 1), the disabled blur costs 0
614
- * ```
615
- */
616
- stats(): EffectsStats;
617
- };
618
- /**
619
- * Resolved dependency APIs.
620
- */
621
- type Deps$2 = {
622
- flow: Api;
623
- world: Api$1;
624
- renderer: Api$2;
625
- assets: Api$3;
626
- anim: AnimApi;
627
- };
628
- /**
629
- * What the kernel context offers before the deps are attached. `effects` owns no event, so
630
- * `emit` is the kernel's and never called here.
631
- */
632
- type KernelSlice$2 = PluginCtx<Config$2, State$2> & {
633
- readonly global: object;
634
- readonly log: Log.LogApi;
635
- readonly require: Require;
636
- };
637
- /**
638
- * Domain context shared by both modules: the kernel slice plus the resolved deps.
639
- */
640
- type EffectsCtx = KernelSlice$2 & {
641
- readonly deps: Deps$2;
642
- };
643
- /**
644
- * Payload of the `assets:bundle-unloaded` hook: `keys` names every texture that is gone.
645
- */
646
- type BundleUnloaded$1 = Events$1["assets:bundle-unloaded"];
647
- declare namespace types_d_exports$1 {
648
- export { AnySceneProjection, CheckedProjections, Config$1 as Config, DefineScene, Deps$1 as Deps, Events, KernelSlice$1 as KernelSlice, LayerMap, RunContext, SceneDefinition, SceneLayer, SceneOwner, SceneProjection, SceneSpec, ScenesApi, ScenesCtx, SortRule, State$1 as State, UiLayer };
649
- }
650
- /**
651
- * How a layer orders what is drawn in it. The scene's name for the rule `world` stores.
652
- *
653
- * @example
654
- * ```ts
655
- * const sort: SortRule = "y";
656
- * ```
657
- */
658
- type SortRule = LayerSort;
659
- /**
660
- * What a scene declares about its layers: the key is the layer name, the order of the keys is
661
- * draw order, bottom first. An omitted `sort` means `"none"`.
662
- *
663
- * @example
664
- * ```ts
665
- * const layers: LayerMap = { background: {}, cells: {}, items: { sort: "y" }, lifted: {} };
666
- * ```
667
- */
668
- type LayerMap = Record<string, {
669
- sort?: SortRule;
670
- }>;
671
- /**
672
- * The name of the layer every scene gets on top of the layers it declared, where popups and the
673
- * HUD are mounted. A scene that declares it keeps the place it wrote it in.
674
- *
675
- * @example
676
- * ```ts
677
- * const layer: UiLayer = "ui";
678
- * ```
679
- */
680
- type UiLayer = "ui";
681
- /**
682
- * The layer names the projections of a scene may use: the keys the scene declared and the
683
- * appended `ui`, which is there whether the scene wrote it or not.
684
- *
685
- * @example
686
- * ```ts
687
- * const layer: SceneLayer<{ cells: Record<never, never> }> = "ui"; // or "cells"
688
- * ```
689
- */
690
- type SceneLayer<Layers extends LayerMap> = (keyof Layers & string) | UiLayer;
691
- /**
692
- * Any projection as a scene reads it. `projection()` widens `lift` to `string` when the author
693
- * declared none, so this is the loosest shape `defineScene` accepts.
694
- *
695
- * @example
696
- * ```ts
697
- * const spec: AnySceneProjection = { name: "board.items", layer: "items", lift: "lifted" };
698
- * ```
699
- */
700
- type AnySceneProjection = {
701
- readonly name: string;
702
- readonly layer: string;
703
- readonly lift?: string;
704
- };
705
- /**
706
- * The three fields of a `ProjectionSpec` a scene reads, with both layers checked against the
707
- * layers the scene declares. It is what a wrong `layer` or `lift` is reported against.
708
- *
709
- * @example
710
- * ```ts
711
- * const spec: SceneProjection<"items" | "lifted"> = { name: "board.items", layer: "items" };
712
- * ```
713
- */
714
- type SceneProjection<Layer extends string> = {
715
- readonly name: string;
716
- readonly layer: Layer;
717
- readonly lift?: Layer;
718
- };
719
- /**
720
- * Checks the `lift` of one projection. A projection built without a `lift` carries the widened
721
- * `string` of `projection()`, which is the one lift that is always accepted.
722
- */
723
- type CheckedLift<Layer extends string, Spec> = Spec extends {
724
- readonly lift?: infer Lift;
725
- } ? string extends Exclude<Lift, undefined> ? Spec : Exclude<Lift, undefined> extends Layer ? Spec : SceneProjection<Layer> : Spec;
726
- /**
727
- * Checks one projection of a scene: first its `layer`, then its `lift`. A projection that fails
728
- * becomes `SceneProjection<Layer>`, so the compiler prints the layer names it accepts.
729
- */
730
- type CheckedProjection<Layer extends string, Spec> = Spec extends {
731
- readonly layer: infer Declared;
732
- } ? Declared extends Layer ? CheckedLift<Layer, Spec> : SceneProjection<Layer> : SceneProjection<Layer>;
733
- /**
734
- * Checking type: validates the projections of a scene one by one against its layer names, so the
735
- * error sits on the projection that is wrong and not on the whole list.
736
- *
737
- * @example
738
- * ```ts
739
- * type Checked = CheckedProjections<"items", readonly [{ name: "board.items"; layer: "items" }]>;
740
- * ```
741
- */
742
- type CheckedProjections<Layer extends string, List> = { readonly [Index in keyof List]: CheckedProjection<Layer, List[Index]> };
743
- /**
744
- * What the author passes to `defineScene`. The layer names come from the keys of `layers` plus
745
- * the appended `ui`; `NoInfer` keeps `projections` out of that inference, so a wrong name is
746
- * reported on the projection and never widens the layer union. `music` is an asset key of the
747
- * game, narrowed to its audio keys once the scanner splits them.
748
- *
749
- * @example
750
- * ```ts
751
- * const spec: SceneSpec<{ items: Record<never, never> }, readonly [], string, "board"> = {
752
- * bundle: "board",
753
- * layers: { items: {} },
754
- * projections: []
755
- * };
756
- * ```
757
- */
758
- type SceneSpec<Layers extends LayerMap, Projections extends readonly AnySceneProjection[], Asset extends string, Bundle extends string> = {
759
- bundle: Bundle;
760
- layers: Layers;
761
- projections: Projections & CheckedProjections<NoInfer<SceneLayer<Layers>>, NoInfer<Projections>>;
762
- music?: Asset;
763
- };
764
- /**
765
- * The signature of `defineScene` bound to the asset and bundle keys of one game.
766
- * `defineGame<Types>()` returns it; the loose export accepts any string.
767
- *
768
- * @example
769
- * ```ts
770
- * const defineGameScene: DefineScene<"board.music", "board"> = defineScene;
771
- * ```
772
- */
773
- type DefineScene<Asset extends string, Bundle extends string> = <Layers extends LayerMap, const Projections extends readonly AnySceneProjection[]>(id: string, scene: SceneSpec<Layers, Projections, Asset, Bundle>) => SceneDefinition;
774
- /**
775
- * A scene as `defineScene` returns it: plain frozen data, ready for the `scenes` key of a feature.
776
- *
777
- * @example
778
- * ```ts
779
- * // What defineScene("board", { bundle: "board", layers: { items: { sort: "y" } }, projections: [] })
780
- * // returns: { id: "board", bundle: "board", music: undefined,
781
- * // layers: [{ name: "items", sort: "y" }, { name: "ui", sort: "none" }], projections: [] }
782
- * ```
783
- */
784
- type SceneDefinition = {
785
- readonly id: string;
786
- readonly bundle: string;
787
- readonly music: string | undefined; /** Draw order, bottom first. */
788
- readonly layers: readonly LayerSpec[]; /** Names of the projections the scene mounts. */
789
- readonly projections: readonly string[];
790
- };
791
- /**
792
- * The one owner every mount of this plugin carries. One is enough: one scene is mounted at a
793
- * time, and `unmount` is what flushes.
794
- *
795
- * @example
796
- * ```ts
797
- * const owner: SceneOwner = { kind: "plugin", name: "scenes" };
798
- * ```
799
- */
800
- type SceneOwner = Owner & {
801
- kind: "plugin";
802
- name: "scenes";
803
- };
804
- /**
805
- * scenes plugin config: none. A scene is data in a feature, and a transition effect is V3 work
806
- * of `anim`, so a key here would have no reader.
807
- *
808
- * @example
809
- * ```ts
810
- * const config: Config = {};
811
- * ```
812
- */
813
- type Config$1 = Record<string, never>;
814
- /**
815
- * scenes plugin state.
816
- */
817
- type State$1 = {
818
- /** Scene id to its declaration, filled in `onStart` from `flow.features`. */scenes: Map<string, SceneDefinition>; /** Id of the scene that is mounted now. */
819
- current: string | undefined; /** Fast mode: the scene a transit node named, applied at the next rest node. */
820
- pending: string | undefined;
821
- owner: SceneOwner; /** The remover of the `onEnter` callback: set in `onStart`, run in `onStop`. */
822
- teardown: (() => void) | undefined;
823
- };
824
- /**
825
- * scenes plugin events.
826
- *
827
- * @example
828
- * ```ts
829
- * // A game plugin starts the music of the scene the graph switched to.
830
- * createPlugin("music", {
831
- * depends: [scenesPlugin],
832
- * hooks: ctx => ({ "scenes:changed": ({ music }) => ctx.log.info("scene music", { music }) })
833
- * }); // entering the board logs { music: "board.theme" }
834
- * ```
835
- */
836
- type Events = {
837
- /** The mounted scene changed. */"scenes:changed": {
838
- from: string | undefined;
839
- to: string;
840
- music: string | undefined;
841
- };
842
- };
843
- /**
844
- * scenes plugin API, `app.scenes`. A scene is switched by the graph, never by a call, so the
845
- * plugin answers one question and takes no orders.
846
- *
847
- * @example
848
- * ```ts
849
- * // A headless test walks from the menu onto the board and asks what is mounted.
850
- * await app.flow.walk([{ at: "home", intent: "play" }]);
851
- * app.scenes.current(); // "board"
852
- * ```
853
- */
854
- type ScenesApi = {
855
- /**
856
- * The id of the mounted scene.
857
- *
858
- * @returns The scene id, `undefined` before the first switch and after the app stopped.
859
- * @example
860
- * ```ts
861
- * // A dev overlay writes the scene next to the frame counter.
862
- * app.scenes.current(); // "board", or undefined while the first bundle still loads
863
- * ```
864
- */
865
- current(): string | undefined;
866
- };
867
- /**
868
- * Resolved dependency APIs.
869
- */
870
- type Deps$1 = {
871
- flow: Api;
872
- world: Api$1;
873
- assets: Api$3;
874
- time: Api$4;
875
- };
876
- /**
877
- * What the kernel context offers before the deps are attached.
878
- *
879
- * `index.ts` writes `events` with an annotated `register` (core spec `14-EVENT-REGISTRATION.md`
880
- * row 8), so the own event reaches the context the kernel hands the factories and `emit` is the
881
- * kernel's own, scenes-typed one. No member of the context is cast.
882
- */
883
- type KernelSlice$1 = PluginCtx<Config$1, State$1, Events> & {
884
- readonly global: object;
885
- readonly log: Log.LogApi;
886
- readonly require: Require;
887
- };
888
- /**
889
- * Domain context shared by the files of the plugin.
890
- */
891
- type ScenesCtx = KernelSlice$1 & {
892
- readonly deps: Deps$1;
893
- };
894
- /**
895
- * The mode and the abort signal `flow.onEnter` hands every callback.
896
- *
897
- * @example
898
- * ```ts
899
- * const run: RunContext = { mode: "live", signal: new AbortController().signal };
900
- * ```
901
- */
902
- type RunContext = {
903
- mode: "live" | "fast";
904
- signal: AbortSignal;
905
- };
906
- declare namespace types_d_exports {
907
- export { AudioApi, AudioContextLike, AudioCtx, AudioSessionLike, AudioSessionType, BundleUnloaded, Bus, BusState, Config, Deps, KernelSlice, LifecycleChanged, ModelCommitted, MusicDescriptor, MusicMode, MusicOptions, MusicRequest, MusicSwitch, MusicTrack, RetiringTrack, SceneChanged, SfxRequest, SoundEntry, State, StreamTrack, Volumes };
908
- }
909
- /**
910
- * The three volume buses. `master` carries the whole game, `music` the scene track, `sfx` every
911
- * sound a node or a timeline fires.
912
- *
913
- * @example
914
- * ```ts
915
- * const bus: Bus = "music";
916
- * ```
917
- */
918
- type Bus = "master" | "music" | "sfx";
919
- /**
920
- * The part of `AudioContext` the plugin uses. A test passes its own through `config.context`, so
921
- * nothing in the unit tests touches a sound card. `state` adds Safari's `"interrupted"`, which a
922
- * phone call or the lock screen puts a context into.
923
- *
924
- * @example
925
- * ```ts
926
- * // A dev tool builds the engine's context itself and hands it to the plugin.
927
- * const context: AudioContextLike = new AudioContext();
928
- * createApp({ plugins: [...screen, audioPlugin], pluginConfigs: { audio: { context: () => context } } });
929
- * ```
930
- */
931
- type AudioContextLike = Pick<AudioContext, "destination" | "currentTime" | "createGain" | "createBufferSource" | "decodeAudioData" | "createMediaElementSource" | "resume" | "close"> & {
932
- readonly state: AudioContext["state"] | "interrupted";
933
- };
934
- /**
935
- * What `music` accepts besides the key. An absent `fadeMs` means `config.musicFadeMs` at play time.
936
- *
937
- * @example
938
- * ```ts
939
- * const options: MusicOptions = { fadeMs: 200 };
940
- * ```
941
- */
942
- type MusicOptions = {
943
- fadeMs?: number;
944
- };
945
- /**
946
- * `music` bound to the audio asset keys of one game. `defineGame` returns it through `audioFor`;
947
- * the loose export accepts any string.
948
- *
949
- * @example
950
- * ```ts
951
- * const gameMusic: MusicDescriptor<"board.theme"> = music;
952
- * ```
953
- */
954
- type MusicDescriptor<Asset extends string> = (key: Asset | null, options?: MusicOptions) => Descriptor;
955
- /**
956
- * Reads the player's volume choice out of the committed player state. Every bus it names is
957
- * applied; the ones it leaves out keep the value they had.
958
- *
959
- * @example
960
- * ```ts
961
- * const volumes: Volumes = player => (player as { settings: { audio: { music: number } } }).settings.audio;
962
- * ```
963
- */
964
- type Volumes = (player: Json) => Partial<Record<Bus, number>>;
965
- /**
966
- * How a music track is played. `"decode"`: one AudioBuffer per track, gapless, about 58 MB per
967
- * 150 s at 48 kHz. `"stream"`: an `<audio>` element on a `blob:` URL of the bytes `assets` holds,
968
- * about 12 MB, and a gap at every loop: 5 to 49 ms in Chromium, about 0.4 s in WebKit. A tight
969
- * loop keeps `"decode"`; a long track that tolerates a seam takes `"stream"`.
970
- *
971
- * @example
972
- * ```ts
973
- * const mode: MusicMode = "stream";
974
- * ```
975
- */
976
- type MusicMode = "decode" | "stream";
977
- /**
978
- * What the page asks iOS for. `"ambient"`: mixes with other apps, the silent switch mutes it.
979
- * `"playback"`: stops other apps, plays through the switch. `"auto"`: WebKit picks.
980
- *
981
- * @example
982
- * ```ts
983
- * const session: AudioSessionType = "playback";
984
- * ```
985
- */
986
- type AudioSessionType = "ambient" | "playback" | "auto";
987
- /**
988
- * The part of `navigator.audioSession` the plugin writes. `lib.dom` has no type for it, and only
989
- * WebKit has the object at all.
990
- *
991
- * @example
992
- * ```ts
993
- * // What Safari answers before the plugin started: no override.
994
- * const session: AudioSessionLike = { type: "auto" };
995
- * ```
996
- */
997
- type AudioSessionLike = {
998
- type: string;
999
- };
1000
- /**
1001
- * One music track played by an element at `music: "stream"`: the element, its source node in the
1002
- * graph, the track gain every level goes through, and the `blob:` URL the element reads.
1003
- *
1004
- * @example
1005
- * ```ts
1006
- * // `state.music.stream` once "board.theme" plays at `music: "stream"`.
1007
- * const { element, url }: StreamTrack = track;
1008
- * element.loop; // true
1009
- * url.startsWith("blob:"); // true
1010
- * ```
1011
- */
1012
- type StreamTrack = {
1013
- element: HTMLAudioElement;
1014
- node: MediaElementAudioSourceNode;
1015
- gain: GainNode;
1016
- url: string;
1017
- };
1018
- /**
1019
- * A streamed track that fades out. Its timer frees the element when the fade ends: an element has
1020
- * no `stop(when)` on the context clock.
1021
- *
1022
- * @example
1023
- * ```ts
1024
- * // A switch at `musicFadeMs: 600` keeps the old track here for 600 ms.
1025
- * const entry: RetiringTrack = { stream: oldTrack, timer: setTimeout(free, 600) };
1026
- * ```
1027
- */
1028
- type RetiringTrack = {
1029
- stream: StreamTrack;
1030
- timer: ReturnType<typeof setTimeout>;
1031
- };
1032
- /**
1033
- * audio plugin config.
1034
- *
1035
- * @example
1036
- * ```ts
1037
- * createApp({
1038
- * plugins: [...screen, audioPlugin],
1039
- * pluginConfigs: {
1040
- * audio: { musicFadeMs: 400, music: "stream", session: "playback", volumes: player => player.settings.audio }
1041
- * }
1042
- * });
1043
- * ```
1044
- */
1045
- type Config = {
1046
- /** Start gain of each bus, 0..1. Overridden per commit when `volumes` is set. */buses: {
1047
- master: number;
1048
- music: number;
1049
- sfx: number;
1050
- }; /** Cross-fade of a music switch, in real milliseconds of the context clock. */
1051
- musicFadeMs: number; /** Reads the player's choice from the committed player. `undefined`: the buses stay at `buses`. */
1052
- volumes: Volumes | undefined; /** Context factory, the test seam. `undefined`: `new AudioContext()` when the global exists, otherwise headless. */
1053
- context: (() => AudioContextLike) | undefined; /** How many started sounds `journal()` keeps, newest last. `0` turns the journal off. */
1054
- journal: number; /** How music plays. `"stream"` needs MP3 or AAC: WebM Opus is silent through a media element in WebKit (bug 276813). Sounds always decode. */
1055
- music: MusicMode; /** Set on `navigator.audioSession.type` in `onStart`, before the context exists. No-op where the API is missing (every Chromium, Firefox). */
1056
- session: AudioSessionType;
1057
- };
1058
- /**
1059
- * One sound that started: a play of an `sfx` or a music track that began. `at` is the elapsed
1060
- * game time in milliseconds, `app.time.snapshot().elapsed` at the start.
1061
- *
1062
- * @example
1063
- * ```ts
1064
- * const entry: SoundEntry = { key: "ui.click", bus: "sfx", kind: "sfx", at: 1600 };
1065
- * ```
1066
- */
1067
- type SoundEntry = {
1068
- readonly key: string;
1069
- readonly bus: Bus;
1070
- readonly kind: "sfx" | "music";
1071
- readonly at: number;
1072
- };
1073
- /**
1074
- * What the plugin keeps for one bus: its gain node while a context exists, the volume a game or
1075
- * the player chose, and whether it is muted.
1076
- *
1077
- * @example
1078
- * ```ts
1079
- * const entry: BusState = { gain: undefined, volume: 0.6, muted: false };
1080
- * ```
1081
- */
1082
- type BusState = {
1083
- gain: GainNode | undefined;
1084
- volume: number;
1085
- muted: boolean;
1086
- };
1087
- /**
1088
- * The music that is playing, or the key remembered until the first touch unlocks the context.
1089
- * `source` plays a decoded track, `stream` a streamed one; they are never both set. Both
1090
- * `undefined`: the key is only remembered (a locked context, or a refused `play()`).
1091
- *
1092
- * @example
1093
- * ```ts
1094
- * const track: MusicTrack = { key: "board.theme", gain: undefined, source: undefined, stream: undefined };
1095
- * ```
1096
- */
1097
- type MusicTrack = {
1098
- key: string;
1099
- gain: GainNode | undefined;
1100
- source: AudioBufferSourceNode | undefined;
1101
- stream: StreamTrack | undefined;
1102
- };
1103
- /**
1104
- * A music switch that waits for its buffer to decode. Its identity is the token: a later request
1105
- * replaces it, and the replaced switch neither starts nor fades anything when its buffer arrives.
1106
- *
1107
- * @example
1108
- * ```ts
1109
- * const waiting: MusicSwitch = { key: "ui.theme" };
1110
- * ```
1111
- */
1112
- type MusicSwitch = {
1113
- key: string;
1114
- };
1115
- /**
1116
- * audio plugin state.
1117
- */
1118
- type State = {
1119
- /** `undefined` means headless: no graph, no sound, every member still keeps its value. */context: AudioContextLike | undefined;
1120
- buses: Record<Bus, BusState>; /** A lifecycle push is in force: every bus is held at zero. */
1121
- paused: boolean;
1122
- unlocked: boolean; /** A gesture called `resume()` and it has not settled yet. */
1123
- resuming: boolean; /** Sounds fired while `resuming`, one per key; played when the context runs, dropped when it does not. */
1124
- pendingSfx: Map<string, SfxRequest>; /** Per key, evicted per key on `assets:bundle-unloaded`. */
1125
- decoded: Map<string, Promise<AudioBuffer>>; /** Asset keys that already got their one warning. */
1126
- warned: Set<string>; /** `config.volumes` threw once and was reported; it is not reported again this run. */
1127
- warnedVolumes: boolean;
1128
- music: MusicTrack | undefined; /** The latest switch still decoding; `undefined` once it started or a later request replaced it. */
1129
- musicPending: MusicSwitch | undefined; /** Removes the two window listeners of the unlock. */
1130
- unlock: (() => void) | undefined; /** The removers of the two fx handlers. */
1131
- removers: Array<() => void>; /** The `navigator.audioSession` that took `config.session`; `stop` writes `"auto"` back to it. */
1132
- session: AudioSessionLike | undefined; /** Streamed tracks that fade out, each freed by its timer. */
1133
- retiring: Set<RetiringTrack>;
1134
- /**
1135
- * The sounds that started, oldest first, at most `config.journal` of them. Frozen: a write
1136
- * replaces the whole list, so `journal()` hands it out without a copy.
1137
- */
1138
- journal: readonly SoundEntry[];
1139
- };
1140
- /**
1141
- * audio plugin API, `app.audio`. Three buses and the unlock flag: what a game changes about sound
1142
- * it commits to the player state, which this plugin applies on the next commit.
1143
- *
1144
- * @example
1145
- * ```ts
1146
- * // A dev overlay pulls the music down and reads the value back.
1147
- * app.audio.setVolume("music", 0.2);
1148
- * app.audio.volume("music"); // 0.2
1149
- * ```
1150
- */
1151
- type AudioApi = {
1152
- /**
1153
- * Sets the volume of one bus and schedules the new gain at once. The value is clamped to 0..1
1154
- * and `NaN` becomes 0. Nothing is persisted: a game stores the choice in the player state and
1155
- * lets `config.volumes` apply it, so the next commit overwrites what this call set.
1156
- *
1157
- * @param bus - The bus to change.
1158
- * @param value - The new volume, 0..1.
1159
- * @throws {Error} When the bus does not exist.
1160
- * @example
1161
- * ```ts
1162
- * // A test turns the music off before it walks the graph, so nothing rings.
1163
- * app.audio.setVolume("music", 0);
1164
- * app.audio.volume("music"); // 0
1165
- * ```
1166
- */
1167
- setVolume(bus: Bus, value: number): void;
1168
- /**
1169
- * The stored volume of a bus, never the live gain: a muted or paused bus still answers the
1170
- * value the player chose.
1171
- *
1172
- * @param bus - The bus to read.
1173
- * @returns The stored volume, 0..1.
1174
- * @throws {Error} When the bus does not exist.
1175
- * @example
1176
- * ```ts
1177
- * // The settings screen draws the slider of the sfx bus at its stored place.
1178
- * app.audio.mute("sfx", true);
1179
- * app.audio.volume("sfx"); // 1: muting does not move the slider
1180
- * ```
1181
- */
1182
- volume(bus: Bus): number;
1183
- /**
1184
- * Mutes or unmutes one bus. The flag is independent of the pause: a bus muted here stays muted
1185
- * when the game comes back from the background.
1186
- *
1187
- * @param bus - The bus to change.
1188
- * @param on - `true` mutes it, `false` lets it play again.
1189
- * @throws {Error} When the bus does not exist.
1190
- * @example
1191
- * ```ts
1192
- * // A dev overlay silences the whole game while a profiler trace runs.
1193
- * app.audio.mute("master", true);
1194
- * app.audio.volume("master"); // 1, and nothing is heard
1195
- * ```
1196
- */
1197
- mute(bus: Bus, on: boolean): void;
1198
- /**
1199
- * Tells whether the browser let the audio context run. It is false until the first pointer
1200
- * event on the page resumed it, and false for the whole of a headless run.
1201
- *
1202
- * @returns True once the context is running.
1203
- * @example
1204
- * ```ts
1205
- * // A "tap to start" screen stays up until the browser allowed sound.
1206
- * app.audio.unlocked(); // false before the first touch, true after it
1207
- * ```
1208
- */
1209
- unlocked(): boolean;
1210
- /**
1211
- * The sounds that started, oldest first: every play of an `sfx` and every music track that
1212
- * began, at most `config.journal` of them. A sound dropped before the unlock, a missing file
1213
- * and a music switch to the track that already plays are not in it. Empty while
1214
- * `config.journal` is 0, the default, and after the app stopped.
1215
- *
1216
- * @returns The journal, frozen; a later sound replaces the list and leaves this one as it was.
1217
- * @example
1218
- * ```ts
1219
- * // A test composed with `pluginConfigs.audio = { journal: 200 }` taps "deliver" after the
1220
- * // first touch and checks the chime was heard.
1221
- * app.audio.journal();
1222
- * // [{ key: "board.theme", bus: "music", kind: "music", at: 0 },
1223
- * // { key: "orders.complete", bus: "sfx", kind: "sfx", at: 1600 }]
1224
- * ```
1225
- */
1226
- journal(): readonly SoundEntry[];
1227
- };
1228
- /**
1229
- * Resolved dependency APIs.
1230
- */
1231
- type Deps = {
1232
- flow: Api;
1233
- assets: Api$3;
1234
- model: Api$5;
1235
- time: Api$4;
1236
- };
1237
- /**
1238
- * What the kernel context offers before the deps are attached.
1239
- *
1240
- * `audio` owns no event, so `emit` is the kernel's and never called here.
1241
- */
1242
- type KernelSlice = PluginCtx<Config, State> & {
1243
- readonly global: object;
1244
- readonly log: Log.LogApi;
1245
- readonly require: Require;
1246
- };
1247
- /**
1248
- * Domain context shared by the files of the plugin: the kernel slice plus the resolved deps.
1249
- */
1250
- type AudioCtx = KernelSlice & {
1251
- readonly deps: Deps;
1252
- };
1253
- /**
1254
- * Payload of the `model:committed` hook: the source of every volume a game sets.
1255
- */
1256
- type ModelCommitted = Events$2["model:committed"];
1257
- /**
1258
- * Payload of the `scenes:changed` hook: `music` is the track the next scene declared.
1259
- */
1260
- type SceneChanged = Events["scenes:changed"];
1261
- /**
1262
- * Payload of the `lifecycle:changed` hook: the pause that holds every bus at zero.
1263
- */
1264
- type LifecycleChanged = Events$3["lifecycle:changed"];
1265
- /**
1266
- * Payload of the `assets:bundle-unloaded` hook: `keys` names every decoded sound that is gone.
1267
- */
1268
- type BundleUnloaded = Events$1["assets:bundle-unloaded"];
1269
- /**
1270
- * What one play of a sound asks for: the key and the bus the sound goes to.
1271
- */
1272
- type SfxRequest = {
1273
- key: string;
1274
- bus: string;
1275
- };
1276
- /**
1277
- * What one music switch asks for: the key, or `null` to fade the current track out.
1278
- */
1279
- type MusicRequest = {
1280
- key: string | null;
1281
- fadeMs: number;
1282
- };
1283
- //#endregion
1284
- export { FilterValueOf as A, EmitterValue as C, DisplacementValue as D, ColorMatrixValue as E, NoiseValue as M, OutlineValue as N, FilterComponent as O, UniformDeclaration as P, EmitterDefinition as S, BlurValue as T, EffectsApi as _, SoundEntry as a, types_d_exports$2 as b, AnySceneProjection as c, SceneDefinition as d, SceneSpec as f, Config$2 as g, types_d_exports$1 as h, MusicOptions as i, GlowValue as j, FilterSpec as k, DefineScene as l, State$1 as m, Config as n, State as o, ScenesApi as p, MusicDescriptor as r, types_d_exports as s, AudioApi as t, LayerMap as u, EffectsStats as v, AlphaValue as w, EmitterConfig as x, State$2 as y };