@moku-labs/game 0.0.1 → 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,570 @@
1
+ import { I as Events$1, L as Json, P as Api$2, Q as Events$2, T as Descriptor, k as Api$1, lt as Require, t as Api } from "./types-JNc_UQBo.mjs";
2
+ import { L as Owner, _ as LayerSort, u as Api$3, v as LayerSpec } from "./types-BxkNNYul.mjs";
3
+ import { o as Events$3, t as Api$4 } from "./types-DWILGrPn.mjs";
4
+ import { Log } from "@moku-labs/common/browser";
5
+ import { PluginCtx } from "@moku-labs/core";
6
+
7
+ //#region src/plugins/scenes/types.d.ts
8
+ declare namespace types_d_exports$1 {
9
+ 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 };
10
+ }
11
+ /**
12
+ * How a layer orders what is drawn in it. The scene's name for the rule `world` stores.
13
+ *
14
+ * @example
15
+ * ```ts
16
+ * const sort: SortRule = "y";
17
+ * ```
18
+ */
19
+ type SortRule = LayerSort;
20
+ /**
21
+ * What a scene declares about its layers: the key is the layer name, the order of the keys is
22
+ * draw order, bottom first. An omitted `sort` means `"none"`.
23
+ *
24
+ * @example
25
+ * ```ts
26
+ * const layers: LayerMap = { background: {}, cells: {}, items: { sort: "y" }, lifted: {} };
27
+ * ```
28
+ */
29
+ type LayerMap = Record<string, {
30
+ sort?: SortRule;
31
+ }>;
32
+ /**
33
+ * The name of the layer every scene gets on top of the layers it declared, where popups and the
34
+ * HUD are mounted. A scene that declares it keeps the place it wrote it in.
35
+ *
36
+ * @example
37
+ * ```ts
38
+ * const layer: UiLayer = "ui";
39
+ * ```
40
+ */
41
+ type UiLayer = "ui";
42
+ /**
43
+ * The layer names the projections of a scene may use: the keys the scene declared and the
44
+ * appended `ui`, which is there whether the scene wrote it or not.
45
+ *
46
+ * @example
47
+ * ```ts
48
+ * const layer: SceneLayer<{ cells: Record<never, never> }> = "ui"; // or "cells"
49
+ * ```
50
+ */
51
+ type SceneLayer<Layers extends LayerMap> = (keyof Layers & string) | UiLayer;
52
+ /**
53
+ * Any projection as a scene reads it. `projection()` widens `lift` to `string` when the author
54
+ * declared none, so this is the loosest shape `defineScene` accepts.
55
+ *
56
+ * @example
57
+ * ```ts
58
+ * const spec: AnySceneProjection = { name: "board.items", layer: "items", lift: "lifted" };
59
+ * ```
60
+ */
61
+ type AnySceneProjection = {
62
+ readonly name: string;
63
+ readonly layer: string;
64
+ readonly lift?: string;
65
+ };
66
+ /**
67
+ * The three fields of a `ProjectionSpec` a scene reads, with both layers checked against the
68
+ * layers the scene declares. It is what a wrong `layer` or `lift` is reported against.
69
+ *
70
+ * @example
71
+ * ```ts
72
+ * const spec: SceneProjection<"items" | "lifted"> = { name: "board.items", layer: "items" };
73
+ * ```
74
+ */
75
+ type SceneProjection<Layer extends string> = {
76
+ readonly name: string;
77
+ readonly layer: Layer;
78
+ readonly lift?: Layer;
79
+ };
80
+ /**
81
+ * Checks the `lift` of one projection. A projection built without a `lift` carries the widened
82
+ * `string` of `projection()`, which is the one lift that is always accepted.
83
+ */
84
+ type CheckedLift<Layer extends string, Spec> = Spec extends {
85
+ readonly lift?: infer Lift;
86
+ } ? string extends Exclude<Lift, undefined> ? Spec : Exclude<Lift, undefined> extends Layer ? Spec : SceneProjection<Layer> : Spec;
87
+ /**
88
+ * Checks one projection of a scene: first its `layer`, then its `lift`. A projection that fails
89
+ * becomes `SceneProjection<Layer>`, so the compiler prints the layer names it accepts.
90
+ */
91
+ type CheckedProjection<Layer extends string, Spec> = Spec extends {
92
+ readonly layer: infer Declared;
93
+ } ? Declared extends Layer ? CheckedLift<Layer, Spec> : SceneProjection<Layer> : SceneProjection<Layer>;
94
+ /**
95
+ * Checking type: validates the projections of a scene one by one against its layer names, so the
96
+ * error sits on the projection that is wrong and not on the whole list.
97
+ *
98
+ * @example
99
+ * ```ts
100
+ * type Checked = CheckedProjections<"items", readonly [{ name: "board.items"; layer: "items" }]>;
101
+ * ```
102
+ */
103
+ type CheckedProjections<Layer extends string, List> = { readonly [Index in keyof List]: CheckedProjection<Layer, List[Index]> };
104
+ /**
105
+ * What the author passes to `defineScene`. The layer names come from the keys of `layers` plus
106
+ * the appended `ui`; `NoInfer` keeps `projections` out of that inference, so a wrong name is
107
+ * reported on the projection and never widens the layer union. `music` is an asset key of the
108
+ * game, narrowed to its audio keys once the scanner splits them.
109
+ *
110
+ * @example
111
+ * ```ts
112
+ * const spec: SceneSpec<{ items: Record<never, never> }, readonly [], string, "board"> = {
113
+ * bundle: "board",
114
+ * layers: { items: {} },
115
+ * projections: []
116
+ * };
117
+ * ```
118
+ */
119
+ type SceneSpec<Layers extends LayerMap, Projections extends readonly AnySceneProjection[], Asset extends string, Bundle extends string> = {
120
+ bundle: Bundle;
121
+ layers: Layers;
122
+ projections: Projections & CheckedProjections<NoInfer<SceneLayer<Layers>>, NoInfer<Projections>>;
123
+ music?: Asset;
124
+ };
125
+ /**
126
+ * The signature of `defineScene` bound to the asset and bundle keys of one game.
127
+ * `defineGame<Types>()` returns it; the loose export accepts any string.
128
+ *
129
+ * @example
130
+ * ```ts
131
+ * const defineGameScene: DefineScene<"board.music", "board"> = defineScene;
132
+ * ```
133
+ */
134
+ 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;
135
+ /**
136
+ * A scene as `defineScene` returns it: plain frozen data, ready for the `scenes` key of a feature.
137
+ *
138
+ * @example
139
+ * ```ts
140
+ * // What defineScene("board", { bundle: "board", layers: { items: { sort: "y" } }, projections: [] })
141
+ * // returns: { id: "board", bundle: "board", music: undefined,
142
+ * // layers: [{ name: "items", sort: "y" }, { name: "ui", sort: "none" }], projections: [] }
143
+ * ```
144
+ */
145
+ type SceneDefinition = {
146
+ readonly id: string;
147
+ readonly bundle: string;
148
+ readonly music: string | undefined; /** Draw order, bottom first. */
149
+ readonly layers: readonly LayerSpec[]; /** Names of the projections the scene mounts. */
150
+ readonly projections: readonly string[];
151
+ };
152
+ /**
153
+ * The one owner every mount of this plugin carries. One is enough: one scene is mounted at a
154
+ * time, and `unmount` is what flushes.
155
+ *
156
+ * @example
157
+ * ```ts
158
+ * const owner: SceneOwner = { kind: "plugin", name: "scenes" };
159
+ * ```
160
+ */
161
+ type SceneOwner = Owner & {
162
+ kind: "plugin";
163
+ name: "scenes";
164
+ };
165
+ /**
166
+ * scenes plugin config: none. A scene is data in a feature, and a transition effect is V3 work
167
+ * of `anim`, so a key here would have no reader.
168
+ *
169
+ * @example
170
+ * ```ts
171
+ * const config: Config = {};
172
+ * ```
173
+ */
174
+ type Config$1 = Record<string, never>;
175
+ /**
176
+ * scenes plugin state.
177
+ */
178
+ type State$1 = {
179
+ /** Scene id to its declaration, filled in `onStart` from `flow.features`. */scenes: Map<string, SceneDefinition>; /** Id of the scene that is mounted now. */
180
+ current: string | undefined; /** Fast mode: the scene a transit node named, applied at the next rest node. */
181
+ pending: string | undefined;
182
+ owner: SceneOwner; /** The remover of the `onEnter` callback: set in `onStart`, run in `onStop`. */
183
+ teardown: (() => void) | undefined;
184
+ };
185
+ /**
186
+ * scenes plugin events.
187
+ *
188
+ * @example
189
+ * ```ts
190
+ * // A game plugin starts the music of the scene the graph switched to.
191
+ * createPlugin("music", {
192
+ * depends: [scenesPlugin],
193
+ * hooks: ctx => ({ "scenes:changed": ({ music }) => ctx.log.info("scene music", { music }) })
194
+ * }); // entering the board logs { music: "board.theme" }
195
+ * ```
196
+ */
197
+ type Events = {
198
+ /** The mounted scene changed. */"scenes:changed": {
199
+ from: string | undefined;
200
+ to: string;
201
+ music: string | undefined;
202
+ };
203
+ };
204
+ /**
205
+ * scenes plugin API, `app.scenes`. A scene is switched by the graph, never by a call, so the
206
+ * plugin answers one question and takes no orders.
207
+ *
208
+ * @example
209
+ * ```ts
210
+ * // A headless test walks from the menu onto the board and asks what is mounted.
211
+ * await app.flow.walk([{ at: "home", intent: "play" }]);
212
+ * app.scenes.current(); // "board"
213
+ * ```
214
+ */
215
+ type ScenesApi = {
216
+ /**
217
+ * The id of the mounted scene.
218
+ *
219
+ * @returns The scene id, `undefined` before the first switch and after the app stopped.
220
+ * @example
221
+ * ```ts
222
+ * // A dev overlay writes the scene next to the frame counter.
223
+ * app.scenes.current(); // "board", or undefined while the first bundle still loads
224
+ * ```
225
+ */
226
+ current(): string | undefined;
227
+ };
228
+ /**
229
+ * Resolved dependency APIs.
230
+ */
231
+ type Deps$1 = {
232
+ flow: Api;
233
+ world: Api$3;
234
+ assets: Api$4;
235
+ time: Api$1;
236
+ };
237
+ /**
238
+ * What the kernel context offers before the deps are attached.
239
+ *
240
+ * `index.ts` writes `events` with an annotated `register` (core spec `14-EVENT-REGISTRATION.md`
241
+ * row 8), so the own event reaches the context the kernel hands the factories and `emit` is the
242
+ * kernel's own, scenes-typed one. No member of the context is cast.
243
+ */
244
+ type KernelSlice$1 = PluginCtx<Config$1, State$1, Events> & {
245
+ readonly global: object;
246
+ readonly log: Log.LogApi;
247
+ readonly require: Require;
248
+ };
249
+ /**
250
+ * Domain context shared by the files of the plugin.
251
+ */
252
+ type ScenesCtx = KernelSlice$1 & {
253
+ readonly deps: Deps$1;
254
+ };
255
+ /**
256
+ * The mode and the abort signal `flow.onEnter` hands every callback.
257
+ *
258
+ * @example
259
+ * ```ts
260
+ * const run: RunContext = { mode: "live", signal: new AbortController().signal };
261
+ * ```
262
+ */
263
+ type RunContext = {
264
+ mode: "live" | "fast";
265
+ signal: AbortSignal;
266
+ };
267
+ declare namespace types_d_exports {
268
+ export { AudioApi, AudioContextLike, AudioCtx, BundleUnloaded, Bus, BusState, Config, Deps, KernelSlice, LifecycleChanged, ModelCommitted, MusicDescriptor, MusicOptions, MusicRequest, MusicSwitch, MusicTrack, SceneChanged, SfxRequest, SoundEntry, State, Volumes };
269
+ }
270
+ /**
271
+ * The three volume buses. `master` carries the whole game, `music` the scene track, `sfx` every
272
+ * sound a node or a timeline fires.
273
+ *
274
+ * @example
275
+ * ```ts
276
+ * const bus: Bus = "music";
277
+ * ```
278
+ */
279
+ type Bus = "master" | "music" | "sfx";
280
+ /**
281
+ * The part of `AudioContext` the plugin uses. A test passes its own through `config.context`, so
282
+ * nothing in the unit tests touches a sound card. `state` adds Safari's `"interrupted"`, which a
283
+ * phone call or the lock screen puts a context into.
284
+ *
285
+ * @example
286
+ * ```ts
287
+ * // A dev tool builds the engine's context itself and hands it to the plugin.
288
+ * const context: AudioContextLike = new AudioContext();
289
+ * createApp({ plugins: [...screen, audioPlugin], pluginConfigs: { audio: { context: () => context } } });
290
+ * ```
291
+ */
292
+ type AudioContextLike = Pick<AudioContext, "destination" | "currentTime" | "createGain" | "createBufferSource" | "decodeAudioData" | "resume" | "close"> & {
293
+ readonly state: AudioContext["state"] | "interrupted";
294
+ };
295
+ /**
296
+ * What `music` accepts besides the key. An absent `fadeMs` means `config.musicFadeMs` at play time.
297
+ *
298
+ * @example
299
+ * ```ts
300
+ * const options: MusicOptions = { fadeMs: 200 };
301
+ * ```
302
+ */
303
+ type MusicOptions = {
304
+ fadeMs?: number;
305
+ };
306
+ /**
307
+ * `music` bound to the audio asset keys of one game. `defineGame` returns it through `audioFor`;
308
+ * the loose export accepts any string.
309
+ *
310
+ * @example
311
+ * ```ts
312
+ * const gameMusic: MusicDescriptor<"board.theme"> = music;
313
+ * ```
314
+ */
315
+ type MusicDescriptor<Asset extends string> = (key: Asset | null, options?: MusicOptions) => Descriptor;
316
+ /**
317
+ * Reads the player's volume choice out of the committed player state. Every bus it names is
318
+ * applied; the ones it leaves out keep the value they had.
319
+ *
320
+ * @example
321
+ * ```ts
322
+ * const volumes: Volumes = player => (player as { settings: { audio: { music: number } } }).settings.audio;
323
+ * ```
324
+ */
325
+ type Volumes = (player: Json) => Partial<Record<Bus, number>>;
326
+ /**
327
+ * audio plugin config.
328
+ *
329
+ * @example
330
+ * ```ts
331
+ * createApp({
332
+ * plugins: [...screen, audioPlugin],
333
+ * pluginConfigs: { audio: { musicFadeMs: 400, volumes: player => player.settings.audio } }
334
+ * });
335
+ * ```
336
+ */
337
+ type Config = {
338
+ /** Start gain of each bus, 0..1. Overridden per commit when `volumes` is set. */buses: {
339
+ master: number;
340
+ music: number;
341
+ sfx: number;
342
+ }; /** Cross-fade of a music switch, in real milliseconds of the context clock. */
343
+ musicFadeMs: number; /** Reads the player's choice from the committed player. `undefined`: the buses stay at `buses`. */
344
+ volumes: Volumes | undefined; /** Context factory, the test seam. `undefined`: `new AudioContext()` when the global exists, otherwise headless. */
345
+ context: (() => AudioContextLike) | undefined; /** How many started sounds `journal()` keeps, newest last. `0` turns the journal off. */
346
+ journal: number;
347
+ };
348
+ /**
349
+ * One sound that started: a play of an `sfx` or a music track that began. `at` is the elapsed
350
+ * game time in milliseconds, `app.time.snapshot().elapsed` at the start.
351
+ *
352
+ * @example
353
+ * ```ts
354
+ * const entry: SoundEntry = { key: "ui.click", bus: "sfx", kind: "sfx", at: 1600 };
355
+ * ```
356
+ */
357
+ type SoundEntry = {
358
+ readonly key: string;
359
+ readonly bus: Bus;
360
+ readonly kind: "sfx" | "music";
361
+ readonly at: number;
362
+ };
363
+ /**
364
+ * What the plugin keeps for one bus: its gain node while a context exists, the volume a game or
365
+ * the player chose, and whether it is muted.
366
+ *
367
+ * @example
368
+ * ```ts
369
+ * const entry: BusState = { gain: undefined, volume: 0.6, muted: false };
370
+ * ```
371
+ */
372
+ type BusState = {
373
+ gain: GainNode | undefined;
374
+ volume: number;
375
+ muted: boolean;
376
+ };
377
+ /**
378
+ * The music that is playing, or the key remembered until the first touch unlocks the context.
379
+ * `source` and `gain` are `undefined` exactly while the track is only remembered.
380
+ *
381
+ * @example
382
+ * ```ts
383
+ * const track: MusicTrack = { key: "board.theme", source: undefined, gain: undefined };
384
+ * ```
385
+ */
386
+ type MusicTrack = {
387
+ key: string;
388
+ source: AudioBufferSourceNode | undefined;
389
+ gain: GainNode | undefined;
390
+ };
391
+ /**
392
+ * A music switch that waits for its buffer to decode. Its identity is the token: a later request
393
+ * replaces it, and the replaced switch neither starts nor fades anything when its buffer arrives.
394
+ *
395
+ * @example
396
+ * ```ts
397
+ * const waiting: MusicSwitch = { key: "ui.theme" };
398
+ * ```
399
+ */
400
+ type MusicSwitch = {
401
+ key: string;
402
+ };
403
+ /**
404
+ * audio plugin state.
405
+ */
406
+ type State = {
407
+ /** `undefined` means headless: no graph, no sound, every member still keeps its value. */context: AudioContextLike | undefined;
408
+ buses: Record<Bus, BusState>; /** A lifecycle push is in force: every bus is held at zero. */
409
+ paused: boolean;
410
+ unlocked: boolean; /** A gesture called `resume()` and it has not settled yet. */
411
+ resuming: boolean; /** Sounds fired while `resuming`, one per key; played when the context runs, dropped when it does not. */
412
+ pendingSfx: Map<string, SfxRequest>; /** Per key, evicted per key on `assets:bundle-unloaded`. */
413
+ decoded: Map<string, Promise<AudioBuffer>>; /** Asset keys that already got their one warning. */
414
+ warned: Set<string>; /** `config.volumes` threw once and was reported; it is not reported again this run. */
415
+ warnedVolumes: boolean;
416
+ music: MusicTrack | undefined; /** The latest switch still decoding; `undefined` once it started or a later request replaced it. */
417
+ musicPending: MusicSwitch | undefined; /** Removes the two window listeners of the unlock. */
418
+ unlock: (() => void) | undefined; /** The removers of the two fx handlers. */
419
+ removers: Array<() => void>;
420
+ /**
421
+ * The sounds that started, oldest first, at most `config.journal` of them. Frozen: a write
422
+ * replaces the whole list, so `journal()` hands it out without a copy.
423
+ */
424
+ journal: readonly SoundEntry[];
425
+ };
426
+ /**
427
+ * audio plugin API, `app.audio`. Three buses and the unlock flag: what a game changes about sound
428
+ * it commits to the player state, which this plugin applies on the next commit.
429
+ *
430
+ * @example
431
+ * ```ts
432
+ * // A dev overlay pulls the music down and reads the value back.
433
+ * app.audio.setVolume("music", 0.2);
434
+ * app.audio.volume("music"); // 0.2
435
+ * ```
436
+ */
437
+ type AudioApi = {
438
+ /**
439
+ * Sets the volume of one bus and schedules the new gain at once. The value is clamped to 0..1
440
+ * and `NaN` becomes 0. Nothing is persisted: a game stores the choice in the player state and
441
+ * lets `config.volumes` apply it, so the next commit overwrites what this call set.
442
+ *
443
+ * @param bus - The bus to change.
444
+ * @param value - The new volume, 0..1.
445
+ * @throws {Error} When the bus does not exist.
446
+ * @example
447
+ * ```ts
448
+ * // A test turns the music off before it walks the graph, so nothing rings.
449
+ * app.audio.setVolume("music", 0);
450
+ * app.audio.volume("music"); // 0
451
+ * ```
452
+ */
453
+ setVolume(bus: Bus, value: number): void;
454
+ /**
455
+ * The stored volume of a bus, never the live gain: a muted or paused bus still answers the
456
+ * value the player chose.
457
+ *
458
+ * @param bus - The bus to read.
459
+ * @returns The stored volume, 0..1.
460
+ * @throws {Error} When the bus does not exist.
461
+ * @example
462
+ * ```ts
463
+ * // The settings screen draws the slider of the sfx bus at its stored place.
464
+ * app.audio.mute("sfx", true);
465
+ * app.audio.volume("sfx"); // 1: muting does not move the slider
466
+ * ```
467
+ */
468
+ volume(bus: Bus): number;
469
+ /**
470
+ * Mutes or unmutes one bus. The flag is independent of the pause: a bus muted here stays muted
471
+ * when the game comes back from the background.
472
+ *
473
+ * @param bus - The bus to change.
474
+ * @param on - `true` mutes it, `false` lets it play again.
475
+ * @throws {Error} When the bus does not exist.
476
+ * @example
477
+ * ```ts
478
+ * // A dev overlay silences the whole game while a profiler trace runs.
479
+ * app.audio.mute("master", true);
480
+ * app.audio.volume("master"); // 1, and nothing is heard
481
+ * ```
482
+ */
483
+ mute(bus: Bus, on: boolean): void;
484
+ /**
485
+ * Tells whether the browser let the audio context run. It is false until the first pointer
486
+ * event on the page resumed it, and false for the whole of a headless run.
487
+ *
488
+ * @returns True once the context is running.
489
+ * @example
490
+ * ```ts
491
+ * // A "tap to start" screen stays up until the browser allowed sound.
492
+ * app.audio.unlocked(); // false before the first touch, true after it
493
+ * ```
494
+ */
495
+ unlocked(): boolean;
496
+ /**
497
+ * The sounds that started, oldest first: every play of an `sfx` and every music track that
498
+ * began, at most `config.journal` of them. A sound dropped before the unlock, a missing file
499
+ * and a music switch to the track that already plays are not in it. Empty while
500
+ * `config.journal` is 0, the default, and after the app stopped.
501
+ *
502
+ * @returns The journal, frozen; a later sound replaces the list and leaves this one as it was.
503
+ * @example
504
+ * ```ts
505
+ * // A test composed with `pluginConfigs.audio = { journal: 200 }` taps "deliver" after the
506
+ * // first touch and checks the chime was heard.
507
+ * app.audio.journal();
508
+ * // [{ key: "board.theme", bus: "music", kind: "music", at: 0 },
509
+ * // { key: "orders.complete", bus: "sfx", kind: "sfx", at: 1600 }]
510
+ * ```
511
+ */
512
+ journal(): readonly SoundEntry[];
513
+ };
514
+ /**
515
+ * Resolved dependency APIs.
516
+ */
517
+ type Deps = {
518
+ flow: Api;
519
+ assets: Api$4;
520
+ model: Api$2;
521
+ time: Api$1;
522
+ };
523
+ /**
524
+ * What the kernel context offers before the deps are attached.
525
+ *
526
+ * `audio` owns no event, so `emit` is the kernel's and never called here.
527
+ */
528
+ type KernelSlice = PluginCtx<Config, State> & {
529
+ readonly global: object;
530
+ readonly log: Log.LogApi;
531
+ readonly require: Require;
532
+ };
533
+ /**
534
+ * Domain context shared by the files of the plugin: the kernel slice plus the resolved deps.
535
+ */
536
+ type AudioCtx = KernelSlice & {
537
+ readonly deps: Deps;
538
+ };
539
+ /**
540
+ * Payload of the `model:committed` hook: the source of every volume a game sets.
541
+ */
542
+ type ModelCommitted = Events$1["model:committed"];
543
+ /**
544
+ * Payload of the `scenes:changed` hook: `music` is the track the next scene declared.
545
+ */
546
+ type SceneChanged = Events["scenes:changed"];
547
+ /**
548
+ * Payload of the `lifecycle:changed` hook: the pause that holds every bus at zero.
549
+ */
550
+ type LifecycleChanged = Events$2["lifecycle:changed"];
551
+ /**
552
+ * Payload of the `assets:bundle-unloaded` hook: `keys` names every decoded sound that is gone.
553
+ */
554
+ type BundleUnloaded = Events$3["assets:bundle-unloaded"];
555
+ /**
556
+ * What one play of a sound asks for: the key and the bus the sound goes to.
557
+ */
558
+ type SfxRequest = {
559
+ key: string;
560
+ bus: string;
561
+ };
562
+ /**
563
+ * What one music switch asks for: the key, or `null` to fade the current track out.
564
+ */
565
+ type MusicRequest = {
566
+ key: string | null;
567
+ fadeMs: number;
568
+ };
569
+ //#endregion
570
+ export { SoundEntry as a, AnySceneProjection as c, SceneDefinition as d, SceneSpec as f, types_d_exports$1 as h, MusicOptions as i, 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 };