@moku-labs/game 0.14.11 → 0.14.13

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 (56) hide show
  1. package/README.md +13 -12
  2. package/dist/app/page.d.mts +8 -3
  3. package/dist/app/page.mjs +57 -12
  4. package/dist/app/system.d.mts +5 -3
  5. package/dist/app/system.mjs +2 -0
  6. package/dist/app.d.mts +15 -7
  7. package/dist/app.mjs +131 -27
  8. package/dist/{assets-BrKSg1O6.mjs → assets-CEMfBogh.mjs} +471 -120
  9. package/dist/assets.d.mts +30 -13
  10. package/dist/assets.mjs +1 -1
  11. package/dist/cli.d.mts +105 -8
  12. package/dist/cli.mjs +1036 -128
  13. package/dist/{commands-d5Zlh9hf.d.mts → commands-DAETmK86.d.mts} +43 -7
  14. package/dist/{config-CdARrrhU.mjs → config-B87aFTW-.mjs} +25 -4
  15. package/dist/control.d.mts +130 -17
  16. package/dist/control.mjs +4 -4
  17. package/dist/{define-BN15lAWb.d.mts → define-BASd9ZUh.d.mts} +32 -4
  18. package/dist/dev-DbHgx-R-.mjs +89 -0
  19. package/dist/{fake-MpzwZhIZ.mjs → fake-6mazmHK3.mjs} +20 -2
  20. package/dist/features-DowEilZm.mjs +266 -0
  21. package/dist/{headless-CrhefcLu.d.mts → headless-cFveciuY.d.mts} +1 -1
  22. package/dist/hot.d.mts +4 -2
  23. package/dist/hot.mjs +7 -4
  24. package/dist/{index-0GIP3tgm.d.mts → index-DedbED3T.d.mts} +317 -69
  25. package/dist/index.d.mts +5 -6
  26. package/dist/index.mjs +3 -3
  27. package/dist/inspect.d.mts +12 -7
  28. package/dist/inspect.mjs +2 -2
  29. package/dist/jsx-dev-runtime.d.mts +1 -1
  30. package/dist/jsx-runtime.d.mts +1 -1
  31. package/dist/lint.d.mts +8 -3
  32. package/dist/lint.mjs +432 -20
  33. package/dist/project.d.mts +15 -1
  34. package/dist/project.mjs +1 -1
  35. package/dist/{run-Bf2hKyjZ.mjs → run-BbipFRc6.mjs} +250 -51
  36. package/dist/{runtime-aLkoeoGj.d.mts → runtime-DoSlRx8X.d.mts} +1 -1
  37. package/dist/{session-DgukhR_7.mjs → session-CeKgO2hy.mjs} +30 -2
  38. package/dist/{sources-Duzj6lhV.mjs → sources-Cdvb0rOS.mjs} +33 -6
  39. package/dist/{src-Do697seR.mjs → src-BEbJ_sSx.mjs} +29154 -11121
  40. package/dist/testing.d.mts +119 -14
  41. package/dist/testing.mjs +97 -7
  42. package/dist/{types-BZa187kz.d.mts → types-7GXcXxxv.d.mts} +196 -242
  43. package/dist/{types-BT9S_Lo2.d.mts → types-BGcda5Le.d.mts} +4464 -735
  44. package/dist/{types-yiHtzg1Y.d.mts → types-C73hbmxi.d.mts} +129 -17
  45. package/dist/{types-Bc9AvZVO.d.mts → types-DngFQJro.d.mts} +43 -5
  46. package/dist/{types-kNiV7iFj.d.mts → types-GHE4J5Uf.d.mts} +1027 -192
  47. package/dist/{visual-dMW8iqAE.mjs → visual-2m4wJL_z.mjs} +111 -7
  48. package/dist/visual-_XhoGeIC.d.mts +345 -0
  49. package/dist/visual.d.mts +1 -332
  50. package/dist/visual.mjs +130 -20
  51. package/dist/{watch-CWI_on4T.mjs → watch-BEdrp80i.mjs} +149 -30
  52. package/llms.txt +103 -51
  53. package/package.json +8 -6
  54. package/dist/dev-Cc50Dk7H.mjs +0 -47
  55. package/dist/memory-DR1ibF_s.d.mts +0 -50
  56. package/dist/types-DMoi447V.d.mts +0 -656
@@ -1,9 +1,91 @@
1
- import { A as Api$2, B as ComponentType, F as AnyComponent, H as Entity, It as TypeTag, L as AnyComponentValue, Lt as Descriptor, R as ComponentHandle, Sn as Require, Xt as Json, Yt as Events$2, at as NumericFields, ct as TrackOptions, dn as Events$3, dt as Api$1, et as Ease, h as Api$5, hn as Api, it as MotionHandle, lt as TrackSegment, nt as LayerSpec, q as Owner, qt as Api$4, s as Events$4, t as Api$3, tt as LayerSort, xn as Events$5 } from "./types-BT9S_Lo2.mjs";
2
- import { A as InputApi, c as UiNode, i as Finding, n as ElementChange, o as JsxState, q as I18nApi, r as ElementMotion, rt as StylesState, x as TextApi } from "./types-BZa187kz.mjs";
1
+ import { A as PixiSprite, Ar as Api$1, Br as Require, Er as Events$5, Jn as Descriptor, Kt as AnyComponentValue, O as PixiFilter, T as Api$4, Wt as AnyComponent, Yt as ComponentType, Zt as Entity, _n as TrackOptions, _t as FilterSlotInputs, bn as Api$2, fn as MotionHandle, gn as ReducedMotion, gt as FilterSlot, j as PixiTexture, k as PixiModule, ln as Ease, lr as Events$3, pn as NumericFields, qn as TypeTag, qt as ComponentHandle, s as Events$1, sr as Api$6, t as Api$5, ur as Json, v as Events$4, vn as TrackSegment, xt as FilterCache, yt as Point, zr as Events$2, zt as Api$3 } from "./types-BGcda5Le.mjs";
2
+ import { I as I18nApi, J as StylesState, S as TextApi, c as UiNode, i as Finding, j as InputApi, n as ElementChange, o as JsxState, r as ElementMotion } from "./types-7GXcXxxv.mjs";
3
3
  import { Log } from "@moku-labs/common/browser";
4
4
  import { PluginCtx } from "@moku-labs/core";
5
5
  import { Node, Yoga } from "yoga-layout/load";
6
6
 
7
+ //#region src/plugins/debug/types.d.ts
8
+ declare namespace types_d_exports$4 {
9
+ export { Api, OverlayApi, OverlayMode };
10
+ }
11
+ /**
12
+ * @file debug plugin — type definitions: the overlay call.
13
+ */
14
+ /**
15
+ * The two visible modes of the editor's overlay: the small pill or the full card.
16
+ */
17
+ type OverlayMode = "pill" | "card";
18
+ /**
19
+ * The editor's overlay, from the game: open it, close it, toggle it, ask whether it shows. Every call is a no-op that
20
+ * answers `false` on a page without the agent (a release build, a test): no import of the editor, no guard needed.
21
+ */
22
+ type OverlayApi = {
23
+ /**
24
+ * Shows the overlay: the card, or the pill with `{ mode: "pill" }`.
25
+ *
26
+ * @param options - How the overlay shows.
27
+ * @param options.mode - `"card"` (the default) or `"pill"`.
28
+ * @returns True when an overlay took the call; false on a page without the agent.
29
+ * @example
30
+ * ```ts
31
+ * // A game's own "?" button of its settings sheet opens the overlay of a debug build.
32
+ * app.debug.overlay.open(); // true on `moku-game build --debug`; false on the store build
33
+ * app.debug.overlay.open({ mode: "pill" }); // true: the small pill
34
+ * ```
35
+ */
36
+ open(options?: {
37
+ mode?: OverlayMode;
38
+ }): boolean;
39
+ /**
40
+ * Hides the overlay.
41
+ *
42
+ * @returns True when an overlay took the call; false without the agent.
43
+ * @example
44
+ * ```ts
45
+ * app.debug.overlay.close(); // true; app.debug.overlay.isOpen() is false after it
46
+ * ```
47
+ */
48
+ close(): boolean;
49
+ /**
50
+ * Switches between hidden and the last visible mode.
51
+ *
52
+ * @returns True when an overlay took the call; false without the agent.
53
+ * @example
54
+ * ```ts
55
+ * // A game binds a three-finger tap of its own to the overlay.
56
+ * app.debug.overlay.toggle(); // true: shown (the card, or the pill if the pill was the last mode)
57
+ * ```
58
+ */
59
+ toggle(): boolean;
60
+ /**
61
+ * Tells whether the overlay shows, as the pill or the card.
62
+ *
63
+ * @returns True while it shows; false when hidden and without the agent.
64
+ * @example
65
+ * ```ts
66
+ * app.debug.overlay.open({ mode: "pill" });
67
+ * app.debug.overlay.isOpen(); // true
68
+ * ```
69
+ */
70
+ isOpen(): boolean;
71
+ };
72
+ /**
73
+ * The `debug` plugin's API: the overlay call.
74
+ */
75
+ type Api = {
76
+ /**
77
+ * The editor's overlay. Usable anywhere the app is; a no-op without the agent. The ui tag `DebugTrigger` is one
78
+ * helper on top of it.
79
+ *
80
+ * @example
81
+ * ```ts
82
+ * // ui's DebugTrigger, after five taps:
83
+ * ctx.require(debugPlugin).overlay.open(); // true on a debug build
84
+ * ```
85
+ */
86
+ overlay: OverlayApi;
87
+ };
88
+ //#endregion
7
89
  //#region src/plugins/anim/tween/types.d.ts
8
90
  /**
9
91
  * One running track: the numeric fields of one component of one entity moving from the values
@@ -16,7 +98,8 @@ import { Node, Yoga } from "yoga-layout/load";
16
98
  * id: 1, entity: 1_048_576, component: Transform, to: { x: 200 },
17
99
  * keys: { x: "1048576:Transform:x" }, ms: 350, ease: "out", delayMs: 0, elapsed: 0,
18
100
  * from: undefined, muted: () => new Set(), additive: false, driven: false, bornFrame: 4,
19
- * ended: false, segments: undefined, repeatsLeft: 0, held: false, masked: new Set()
101
+ * ended: false, segments: undefined, repeatsLeft: 0, held: false, masked: new Set(), keep: false,
102
+ * reveals: "place"
20
103
  * };
21
104
  * ```
22
105
  */
@@ -60,7 +143,14 @@ type Track = {
60
143
  * When such a field is given back, the track adds to the value it finds there and not to the
61
144
  * base it kept. Empty for a track that owns its fields.
62
145
  */
63
- masked: Set<string>;
146
+ masked: Set<string>; /** Started with `reduced: "keep"`: reduced motion neither shortens nor holds it. */
147
+ readonly keep: boolean;
148
+ /**
149
+ * What the track writes that could show its view again, handed to `renderer.sync.loopsHeld` by an endless track
150
+ * (spec 26 R1.7.4): `"alpha"`, `"place"` (where the view is or how large), `"both"`, or `undefined` for a write
151
+ * that cannot (a tint, a fill colour). Computed once when the track is made.
152
+ */
153
+ readonly reveals: "alpha" | "place" | "both" | undefined;
64
154
  };
65
155
  /**
66
156
  * A motion handle that can also be advanced by hand: what the timeline cursor needs, so a track
@@ -178,6 +268,8 @@ declare function mark(name: string): Step;
178
268
  * @param options.additive - `true` adds an offset instead of owning the fields.
179
269
  * @param options.space - `"root"` reads the `Transform` fields as a root pose; `"local"` when
180
270
  * omitted.
271
+ * @param options.reduced - `"keep"` plays the track at its length under reduced motion, a fade
272
+ * for one; `"cut"` (the default) lands it at once.
181
273
  * @returns The tween step.
182
274
  * @example
183
275
  * ```ts
@@ -202,6 +294,7 @@ declare function tween<Value extends object>(target: Target, component: Componen
202
294
  delayMs?: number;
203
295
  additive?: boolean;
204
296
  space?: "local" | "root";
297
+ reduced?: ReducedMotion;
205
298
  }): Step;
206
299
  /**
207
300
  * Writes a component patch at once. Any field may be written, not only the numeric ones. The
@@ -392,7 +485,8 @@ type SfxDescriptor = {
392
485
  readonly key: string;
393
486
  readonly bus: string;
394
487
  };
395
- readonly cosmetic: true;
488
+ readonly cosmetic: true; /** Never set: `await fx(sfx(...))` resolves nothing. */
489
+ readonly $result?: () => void;
396
490
  };
397
491
  /**
398
492
  * One haptic tick a device can play: the impact, selection and notification families of Tauri.
@@ -420,7 +514,8 @@ type HapticDescriptor = {
420
514
  readonly payload: {
421
515
  readonly kind: HapticKind;
422
516
  };
423
- readonly cosmetic: true;
517
+ readonly cosmetic: true; /** Never set: `await fx(haptic(...))` resolves nothing. */
518
+ readonly $result?: () => void;
424
519
  };
425
520
  /**
426
521
  * The two descriptors `anim` owns. As a step, the descriptor is dispatched through
@@ -451,7 +546,8 @@ type PlayDescriptor = {
451
546
  readonly animation: string;
452
547
  readonly slots: Json;
453
548
  };
454
- readonly cosmetic: true;
549
+ readonly cosmetic: true; /** Never set: `await fx(play(...))` resolves nothing. */
550
+ readonly $result?: () => void;
455
551
  };
456
552
  /**
457
553
  * One step of a choreography: plain frozen data with no callback inside, so a step tree can be
@@ -484,7 +580,8 @@ type Step = {
484
580
  readonly ease: Ease;
485
581
  readonly delayMs: number;
486
582
  readonly additive: boolean; /** `"root"`: the `Transform` fields are a root pose, turned local when the track starts. */
487
- readonly space: "local" | "root";
583
+ readonly space: "local" | "root"; /** `"keep"`: reduced motion plays the track as written; `"cut"` lands it at once. */
584
+ readonly reduced: ReducedMotion;
488
585
  } | {
489
586
  readonly kind: "set";
490
587
  readonly target: Target;
@@ -625,20 +722,22 @@ type TimelineRuntime = {
625
722
  };
626
723
  /**
627
724
  * One running timeline: its cursor, the marks it reached, the entities it counts on, the entities
628
- * it spawned and despawns at its end, and the resolver of its `done` promise.
725
+ * its key targets named at its start, the entities it spawned and despawns at its end, and the
726
+ * resolver of its `done` promise.
629
727
  */
630
728
  type RunningTimeline = {
631
729
  readonly id: number;
632
730
  readonly animation: string;
633
731
  readonly cursor: Cursor;
634
732
  readonly marks: string[];
635
- readonly entities: readonly Entity[]; /** The entities its `spawn` steps made, by spawn id. Despawned when the timeline ends. */
733
+ readonly entities: readonly Entity[]; /** The entities its `{ projection, key }` targets named when it started, by target key. */
734
+ readonly targets: ReadonlyMap<string, Entity>; /** The entities its `spawn` steps made, by spawn id. Despawned when the timeline ends. */
636
735
  readonly spawned: Map<string, Entity>;
637
736
  readonly resolve: () => void;
638
737
  ended: boolean;
639
738
  };
640
739
  declare namespace types_d_exports$3 {
641
- export { AnimApi, AnimCtx, AnimPlayerValue, AnimationDefinition, AnimationSpec, AnyAnimationDefinition, BuildTools, Config$3 as Config, Cursor, Deps$3 as Deps, ErasedFields, Events$1 as Events, ExternalPlayer, FrameLoop, FramesValue, FxStep, HapticDescriptor, HapticKind, KernelSlice$3 as KernelSlice, MarkListener, MotionKeyframe, PlayDescriptor, PlayHandle, Pose, RunningTimeline, SfxDescriptor, SlotRecord, SlotTags, SlotValue, SlotValues, State$3 as State, Step, StepMotion, Target, TimelineRuntime, Track };
740
+ export { AnimApi, AnimCtx, AnimPlayerValue, AnimationDefinition, AnimationSpec, AnyAnimationDefinition, BuildTools, Config$3 as Config, Cursor, Deps$3 as Deps, ErasedFields, Events, ExternalPlayer, FrameLoop, FramesValue, FxStep, HapticDescriptor, HapticKind, KernelSlice$3 as KernelSlice, MarkListener, MotionKeyframe, PlayDescriptor, PlayHandle, Pose, RunningTimeline, SfxDescriptor, SlotRecord, SlotTags, SlotValue, SlotValues, State$3 as State, Step, StepMotion, Target, TimelineRuntime, Track };
642
741
  }
643
742
  /**
644
743
  * What a step and a slot are pointed at: the projection key of a live view or of an element a
@@ -806,7 +905,7 @@ type MarkListener = (animation: string, mark: string) => void;
806
905
  * }); // a reached mark("done") step logs { mark: "done" }
807
906
  * ```
808
907
  */
809
- type Events$1 = {
908
+ type Events = {
810
909
  /** A `mark` step was reached, or jumped by `finish()`. */"anim:mark": {
811
910
  animation: string;
812
911
  mark: string;
@@ -931,6 +1030,12 @@ type State$3 = {
931
1030
  */
932
1031
  frameMutes: Map<Entity, () => void>; /** Removers of the `onAdded` and `onRemoved` hooks on `Frames`. */
933
1032
  offFrames: Array<() => void>;
1033
+ /**
1034
+ * The renderer's `sync.loopsHeld`, bound in `onStart`: an endless loop of an entity it answers true for writes
1035
+ * nothing while its clock runs on (spec 22 O9), told what the loop reveals (spec 26 R1.7). `undefined` before the
1036
+ * start and after the stop: nothing is held.
1037
+ */
1038
+ loopsHeld: ((entity: Entity, reveals?: "alpha" | "place" | "both") => boolean) | undefined;
934
1039
  };
935
1040
  /**
936
1041
  * anim plugin API, `app.anim`: play a choreography, end everything, count what runs and listen
@@ -1112,9 +1217,9 @@ type AnimPlayerValue = {
1112
1217
  * `tween` in `space: "root"` local with the pure `localPoseOf` of the same module.
1113
1218
  */
1114
1219
  type Deps$3 = {
1115
- time: Api;
1116
- flow: Api$1;
1117
- world: Api$2;
1220
+ time: Api$1;
1221
+ flow: Api$2;
1222
+ world: Api$3;
1118
1223
  };
1119
1224
  /**
1120
1225
  * What the kernel context offers before the deps are attached.
@@ -1123,7 +1228,7 @@ type Deps$3 = {
1123
1228
  * row 8), so the own events reach the context the kernel hands the factories and `emit` is the
1124
1229
  * kernel's own, anim-typed one. No member of the context is cast.
1125
1230
  */
1126
- type KernelSlice$3 = PluginCtx<Config$3, State$3, Events$1> & {
1231
+ type KernelSlice$3 = PluginCtx<Config$3, State$3, Events> & {
1127
1232
  readonly global: object;
1128
1233
  readonly log: Log.LogApi;
1129
1234
  readonly require: Require;
@@ -1134,297 +1239,1010 @@ type KernelSlice$3 = PluginCtx<Config$3, State$3, Events$1> & {
1134
1239
  type AnimCtx = KernelSlice$3 & {
1135
1240
  readonly deps: Deps$3;
1136
1241
  };
1137
- declare namespace types_d_exports$2 {
1138
- export { AnySceneProjection, CheckedProjections, Config$2 as Config, DefineScene, Deps$2 as Deps, Events, KernelSlice$2 as KernelSlice, LayerMap, RunContext, SceneDefinition, SceneLayer, SceneOwner, SceneProjection, SceneSpec, ScenesApi, ScenesCtx, SortRule, State$2 as State, UiLayer };
1139
- }
1242
+ //#endregion
1243
+ //#region src/plugins/effects/filters/types.d.ts
1140
1244
  /**
1141
- * How a layer orders what is drawn in it. The scene's name for the rule `world` stores.
1245
+ * One uniform of a custom filter, as declared: a number is an `f32` (`float` in GLSL), `{ color }`
1246
+ * a `0xrrggbb` hex sent as a `vec3<f32>` (`vec3`), a tuple of 2 to 4 numbers a `vecN<f32>`
1247
+ * (`vecN`).
1142
1248
  *
1143
1249
  * @example
1144
1250
  * ```ts
1145
- * const sort: SortRule = "y";
1251
+ * const uniforms: Record<string, UniformDeclaration> = { amount: 0.5, color: { color: 0xffd700 }, offset: [2, 2] };
1146
1252
  * ```
1147
1253
  */
1148
- type SortRule = LayerSort;
1254
+ type UniformDeclaration = number | {
1255
+ readonly color: number;
1256
+ } | readonly [number, number] | readonly [number, number, number] | readonly [number, number, number, number];
1149
1257
  /**
1150
- * What a scene declares about its layers: the key is the layer name, the order of the keys is
1151
- * draw order, bottom first. An omitted `sort` means `"none"`.
1258
+ * The component field a uniform declaration becomes: a number for a number and for a colour, a
1259
+ * list of numbers for a tuple.
1152
1260
  *
1153
1261
  * @example
1154
1262
  * ```ts
1155
- * const layers: LayerMap = { background: {}, cells: {}, items: { sort: "y" }, lifted: {} };
1263
+ * type Amount = UniformValue<0.5>; // number
1156
1264
  * ```
1157
1265
  */
1158
- type LayerMap = Record<string, {
1159
- sort?: SortRule;
1160
- }>;
1266
+ type UniformValue<Declaration> = Declaration extends number ? number : Declaration extends {
1267
+ readonly color: number;
1268
+ } ? number : readonly number[];
1161
1269
  /**
1162
- * The name of the layer every scene gets on top of the layers it declared, where popups and the
1163
- * HUD are mounted. A scene that declares it keeps the place it wrote it in.
1270
+ * The value of a filter component made by `defineFilter`: one field per uniform, then `enabled`
1271
+ * and `order`.
1164
1272
  *
1165
1273
  * @example
1166
1274
  * ```ts
1167
- * const layer: UiLayer = "ui";
1275
+ * type TintValue = FilterValueOf<{ amount: number; color: { color: number } }>;
1276
+ * // { amount: number; color: number; enabled: boolean; order: number }
1168
1277
  * ```
1169
1278
  */
1170
- type UiLayer = "ui";
1279
+ type FilterValueOf<Uniforms extends Record<string, UniformDeclaration>> = { -readonly [Name in keyof Uniforms]: UniformValue<Uniforms[Name]> } & {
1280
+ enabled: boolean;
1281
+ order: number;
1282
+ };
1171
1283
  /**
1172
- * The layer names the projections of a scene may use: the keys the scene declared and the
1173
- * appended `ui`, which is there whether the scene wrote it or not.
1284
+ * A rectangle in the filtered entity's own units — the space its box, its hit box and its children's positions are in
1285
+ * (reference units for a view at the root without scale): x, y, width, height.
1174
1286
  *
1175
1287
  * @example
1176
1288
  * ```ts
1177
- * const layer: SceneLayer<{ cells: Record<never, never> }> = "ui"; // or "cells"
1289
+ * // A lens disc of reach 88 around (195, 189): the 176 × 176 box it can draw in.
1290
+ * const area: FilterArea = [107, 101, 176, 176];
1178
1291
  * ```
1179
1292
  */
1180
- type SceneLayer<Layers extends LayerMap> = (keyof Layers & string) | UiLayer;
1293
+ type FilterArea = readonly [x: number, y: number, width: number, height: number];
1181
1294
  /**
1182
- * Any projection as a scene reads it. `projection()` widens `lift` to `string` when the author
1183
- * declared none, so this is the loosest shape `defineScene` accepts.
1295
+ * What `defineFilter` takes besides the id. Both bodies are required, so the filter draws on
1296
+ * WebGPU and on the WebGL fallback: `wgsl` is the WGSL fragment body only, the engine prepends the
1297
+ * vertex stage, the input bindings and the `FilterUniforms` struct; `glsl` is its GLSL ES 3.0 twin
1298
+ * over the same uniform names, the engine prepends the version, the inputs, `finalColor` and one
1299
+ * `uniform` per declared uniform.
1184
1300
  *
1185
1301
  * @example
1186
1302
  * ```ts
1187
- * const spec: AnySceneProjection = { name: "board.items", layer: "items", lift: "lifted" };
1303
+ * // A Memory Missing's frost: the body reads a blurred copy of the scene through sampleBlur(uv).
1304
+ * const Frost = defineFilter("Frost", {
1305
+ * wgsl: FROST_WGSL,
1306
+ * glsl: FROST_GLSL,
1307
+ * uniforms: { view: [0, 0, 1], rectA: [0, 0, 0, 0], strength: 16, vivid: 1.2 },
1308
+ * inputs: { blur: { stages: [FrostBox4, FrostHalf, FrostGauss], chain: frostChain } },
1309
+ * fallback: FrostVeil
1310
+ * });
1311
+ * Frost.filter.fallback?.id; // "FrostVeil"
1188
1312
  * ```
1189
1313
  */
1190
- type AnySceneProjection = {
1314
+ type FilterSpec<Uniforms extends Record<string, UniformDeclaration>> = {
1315
+ /** The WGSL fragment body: `fn mainFragment` and its helpers, without the engine's header. */readonly wgsl: string;
1316
+ /**
1317
+ * The GLSL ES 3.0 fragment body: `void main()`, which sets `finalColor`, and its helpers, without
1318
+ * the engine's header. It reads each uniform under its bare name: `amount`, not `fu.amount`.
1319
+ */
1320
+ readonly glsl: string; /** The uniforms in struct order. Default `{}`. */
1321
+ readonly uniforms?: Uniforms; /** Render passes one apply of the filter costs, an integer of at least 1. Default `1`. */
1322
+ readonly passes?: number; /** Pixels around the view's bounds, or the name of a number uniform whose live value they are. Default `0`. */
1323
+ readonly padding?: number | string;
1324
+ /**
1325
+ * Second textures the body reads, by name: `blur` declares `sampleBlur(uv)` in the WGSL and the GLSL, with the
1326
+ * texture, its sampler and two uniforms the engine fills. Leave `passes` out: the engine counts them. Default none.
1327
+ */
1328
+ readonly inputs?: Readonly<Record<string, FilterInput<FilterValueOf<Uniforms>>>>;
1329
+ /**
1330
+ * What draws instead when the filter is broken (its shader or a stage does not compile on the device, its apply
1331
+ * throws, its chain fails): another filter over a subset of its uniforms, with the same value. Default none: a broken
1332
+ * filter leaves its view unfiltered.
1333
+ */
1334
+ readonly fallback?: FilterStage;
1335
+ /**
1336
+ * What the renderer may do with a view this filter draws on, while nothing it depends on changes: `"local"` (the
1337
+ * default) keeps it as a picture, also while the view moves; `"placed"` keeps it with its canvas origin, for a body
1338
+ * that reads where it is drawn through `canvasPixel(uv)` (a body that calls the helper is placed by itself); set
1339
+ * `false` for a body whose look depends on where the view is drawn in a way the engine cannot see (a time read from
1340
+ * a texture, a hash of the screen position): the view is then drawn live in every frame. A body that reads
1341
+ * `uGlobalFrame`, `@builtin(position)`, `gl_FragCoord`, `uOutputTexture` or the origin of `uOutputFrame` and
1342
+ * declares nothing is drawn live as well, and a dev build warns once: declare `cache: false`.
1343
+ */
1344
+ readonly cache?: "local" | "placed" | false;
1345
+ /**
1346
+ * The rectangle outside of which this filter draws nothing, in the filtered entity's own units, from the component's
1347
+ * value; `undefined` for "the view's bounds" (the default). Outside it Pixi draws nothing of the view at all — neither
1348
+ * the filter nor the view's own content — so declare it only for a filter whose output is transparent there. A view
1349
+ * with several enabled filters gets the union; a view with an enabled filter that declares no area gets none. Called
1350
+ * only when the value changed.
1351
+ */
1352
+ readonly area?: (value: Readonly<FilterValueOf<Uniforms>>) => FilterArea | undefined;
1353
+ };
1354
+ /**
1355
+ * How one uniform travels to the GPU: its name, its WGSL type and the form it was declared in.
1356
+ *
1357
+ * @example
1358
+ * ```ts
1359
+ * const spec: UniformSpec = { name: "color", type: "vec3<f32>", form: "color", size: 3 };
1360
+ * ```
1361
+ */
1362
+ type UniformSpec = {
1191
1363
  readonly name: string;
1192
- readonly layer: string;
1193
- readonly lift?: string;
1364
+ readonly type: "f32" | "vec2<f32>" | "vec3<f32>" | "vec4<f32>";
1365
+ readonly form: "number" | "color" | "vector";
1366
+ readonly size: number;
1194
1367
  };
1195
1368
  /**
1196
- * The three fields of a `ProjectionSpec` a scene reads, with both layers checked against the
1197
- * layers the scene declares. It is what a wrong `layer` or `lift` is reported against.
1369
+ * The filter a component made by `defineFilter` carries: the assembled WGSL in `source`, the
1370
+ * assembled GLSL in `glsl`, the uniforms in struct order, the passes one apply costs, the
1371
+ * padding, the second textures the body reads and the filter drawn in its place when it is broken.
1198
1372
  *
1199
1373
  * @example
1200
1374
  * ```ts
1201
- * const spec: SceneProjection<"items" | "lifted"> = { name: "board.items", layer: "items" };
1375
+ * Tint.filter.source.includes("fn mainFragment"); // true: the WGSL, drawn on WebGPU
1376
+ * Tint.filter.glsl.startsWith("#version 300 es"); // true: the GLSL, drawn on WebGL
1377
+ * Tint.filter.uniforms.map(uniform => uniform.name); // ["amount", "color"]
1378
+ * Tint.filter.inputs.length; // 0: it reads no second texture
1379
+ * Tint.filter.cache; // "local": a still view under it is drawn from a picture
1202
1380
  * ```
1203
1381
  */
1204
- type SceneProjection<Layer extends string> = {
1382
+ type FilterDefinition = {
1383
+ readonly id: string; /** The assembled WGSL: the engine's header, the uniform struct, the input bindings, the body. */
1384
+ readonly source: string; /** The assembled GLSL ES 3.0: the engine's header, one `uniform` per uniform, the inputs, the body. */
1385
+ readonly glsl: string;
1386
+ readonly uniforms: readonly UniformSpec[];
1387
+ readonly passes: number;
1388
+ readonly padding: number | string; /** The second textures the body reads, in declaration order; empty for a filter with none. */
1389
+ readonly inputs: readonly FilterInputDefinition[]; /** What draws in its place when it is broken: another definition over a subset of its uniforms. */
1390
+ readonly fallback: FilterDefinition | undefined; /** What the kept-picture cache may do with it: the worst of its declaration, its body and its stages' bodies. */
1391
+ readonly cache: FilterCache;
1392
+ /**
1393
+ * The engine's `keptOrigin` uniform (`vec2<f32>`) of a body that calls `canvasPixel(uv)`, kept out of `uniforms`;
1394
+ * `undefined` for any other body.
1395
+ */
1396
+ readonly origin: UniformSpec | undefined; /** The rectangle outside of which the filter draws nothing, from the value; present only when the spec gave one. */
1397
+ readonly area?: (value: Readonly<FilterFields>) => FilterArea | undefined;
1398
+ };
1399
+ /**
1400
+ * A filter a chain may draw: any component `defineFilter` made. Listed in an input's `stages` and named by its steps.
1401
+ * A built-in Pixi-core kind (`Blur`, `ColorMatrix`, …) has no `filter` and cannot be a stage.
1402
+ *
1403
+ * @example
1404
+ * ```ts
1405
+ * const FrostGauss = defineFilter("FrostGauss", { wgsl: gaussWgsl, glsl: gaussGlsl, uniforms: { dir: [0, 0], sigma: 0 } });
1406
+ * const stages: readonly FilterStage[] = [FrostBox4, FrostHalf, FrostGauss];
1407
+ * stages.map(stage => stage.filter.id); // ["FrostBox4", "FrostHalf", "FrostGauss"]
1408
+ * ```
1409
+ */
1410
+ type FilterStage = {
1411
+ readonly componentName: string;
1412
+ readonly defaults: object;
1413
+ readonly filter: FilterDefinition;
1414
+ };
1415
+ /**
1416
+ * One step of a chain: the stage that draws, the share of the input's resolution it draws at, and the stage uniforms
1417
+ * of this step. A uniform the step leaves out takes the stage's default. A colour is a `0xrrggbb` number.
1418
+ *
1419
+ * @example
1420
+ * ```ts
1421
+ * // A Gaussian along x at an eighth of the resolution of the view's input.
1422
+ * const step: FilterStep = { stage: FrostGauss, scale: 1 / 8, uniforms: { dir: [1, 0], sigma: 4.66 } };
1423
+ * ```
1424
+ */
1425
+ type FilterStep = {
1426
+ readonly stage: FilterStage; /** Above 0 and at most 1: the target of this step has `scale` × the resolution of the view's input. */
1427
+ readonly scale: number;
1428
+ readonly uniforms?: Readonly<Record<string, number | readonly number[]>>;
1429
+ };
1430
+ /**
1431
+ * What a chain is told besides the value: the real pixels per CSS pixel of the texture the view is drawn into (the
1432
+ * canvas resolution for a view at the top, less under a filter of lower resolution).
1433
+ *
1434
+ * @example
1435
+ * ```ts
1436
+ * // The owner's phone at maxResolution 3: 16 units of blur are 37 real pixels, an eighth is enough.
1437
+ * frostChain({ ...Frost().value, strength: 16, view: [0, 0, 384 / 390] }, { resolution: 2.8125 }).map(step => step.scale);
1438
+ * // [0.25, 0.125, 0.125, 0.125]
1439
+ * ```
1440
+ */
1441
+ type FilterChainContext = {
1442
+ readonly resolution: number;
1443
+ };
1444
+ /**
1445
+ * A second texture the body of a filter reads, named by its key in `inputs`: `blur` gives the body
1446
+ * `sampleBlur(uv)`. `chain` returns the steps that draw it from the view's own input, each from the one before;
1447
+ * the body reads the last one. `stages` lists every filter the chain may name: they are checked on the device and
1448
+ * warmed up before the first draw, and a step naming another one is refused. The chain runs again only when a uniform
1449
+ * of the value or the resolution changed, never per frame; it may return `[]`, and the body then reads the view's own
1450
+ * input.
1451
+ *
1452
+ * @example
1453
+ * ```ts
1454
+ * const input: FilterInput<{ strength: number; enabled: boolean; order: number }> = {
1455
+ * stages: [FrostHalf, FrostGauss],
1456
+ * chain: (value, { resolution }) => [
1457
+ * { stage: FrostHalf, scale: 1 / 2 },
1458
+ * { stage: FrostGauss, scale: 1 / 2, uniforms: { dir: [1, 0], sigma: (value.strength * resolution) / 2 } },
1459
+ * { stage: FrostGauss, scale: 1 / 2, uniforms: { dir: [0, 1], sigma: (value.strength * resolution) / 2 } }
1460
+ * ]
1461
+ * };
1462
+ * ```
1463
+ */
1464
+ type FilterInput<Value> = {
1465
+ readonly stages: readonly FilterStage[];
1466
+ /**
1467
+ * The steps of the second texture for this value and resolution. Pure: no state, no randomness; the context object
1468
+ * is the engine's and valid during the call only.
1469
+ *
1470
+ * @param value - The stored value of the filter component.
1471
+ * @param context - The resolution of the texture the view is drawn into.
1472
+ * @returns The steps in draw order, at most 16.
1473
+ * @example
1474
+ * ```ts
1475
+ * // A quarter is enough at one real pixel per CSS pixel: the box, then the Gaussian along x and along y.
1476
+ * Frost.filter.inputs[0]?.chain({ ...Frost().value, strength: 16, view: [0, 0, 1] }, { resolution: 1 }).length; // 3
1477
+ * ```
1478
+ */
1479
+ chain(value: Readonly<Value>, context: FilterChainContext): readonly FilterStep[];
1480
+ };
1481
+ /**
1482
+ * An input as `defineFilter` stored it: the names the engine declared for it in both shader languages, its two
1483
+ * uniforms, its stages and its chain.
1484
+ *
1485
+ * @example
1486
+ * ```ts
1487
+ * const [blur] = Frost.filter.inputs;
1488
+ * [blur?.texture, blur?.sampler, blur?.scale.name, blur?.clamp.name, blur?.sample];
1489
+ * // ["blurTexture", "blurSampler", "blurScale", "blurClamp", "sampleBlur"]
1490
+ * ```
1491
+ */
1492
+ type FilterInputDefinition = {
1205
1493
  readonly name: string;
1206
- readonly layer: Layer;
1207
- readonly lift?: Layer;
1494
+ readonly texture: string;
1495
+ readonly sampler: string;
1496
+ readonly scale: UniformSpec;
1497
+ readonly clamp: UniformSpec;
1498
+ readonly sample: string;
1499
+ readonly stages: readonly FilterStage[];
1500
+ readonly chain: FilterInput<FilterFields>["chain"];
1208
1501
  };
1209
1502
  /**
1210
- * Checks the `lift` of one projection. A projection built without a `lift` carries the widened
1211
- * `string` of `projection()`, which is the one lift that is always accepted.
1503
+ * A filter component type: the `component()` of its id, with the filter definition attached. Its
1504
+ * numeric fields are tweenable like any component.
1505
+ *
1506
+ * @example
1507
+ * ```ts
1508
+ * const Tint: FilterComponent<{ amount: number; enabled: boolean; order: number }> =
1509
+ * defineFilter("fx.tint", { wgsl: tintWgsl, glsl: tintGlsl, uniforms: { amount: 0 } });
1510
+ * Tint.componentName; // "fx.tint"
1511
+ * ```
1512
+ */
1513
+ type FilterComponent<Value extends object> = ComponentType<Value> & {
1514
+ readonly filter: FilterDefinition;
1515
+ };
1516
+ /**
1517
+ * One field of a stored filter value: a number, a switch, an asset key or a vector.
1518
+ *
1519
+ * @example
1520
+ * ```ts
1521
+ * const field: FilterField = 0.5;
1522
+ * ```
1212
1523
  */
1213
- type CheckedLift<Layer extends string, Spec> = Spec extends {
1214
- readonly lift?: infer Lift;
1215
- } ? string extends Exclude<Lift, undefined> ? Spec : Exclude<Lift, undefined> extends Layer ? Spec : SceneProjection<Layer> : Spec;
1524
+ type FilterField = number | boolean | string | readonly number[];
1216
1525
  /**
1217
- * Checks one projection of a scene: first its `layer`, then its `lift`. A projection that fails
1218
- * becomes `SceneProjection<Layer>`, so the compiler prints the layer names it accepts.
1526
+ * The stored value of any filter component, with the kind erased: the filter sync reads every
1527
+ * kind through it.
1219
1528
  */
1220
- type CheckedProjection<Layer extends string, Spec> = Spec extends {
1221
- readonly layer: infer Declared;
1222
- } ? Declared extends Layer ? CheckedLift<Layer, Spec> : SceneProjection<Layer> : SceneProjection<Layer>;
1529
+ type FilterFields = {
1530
+ readonly enabled: boolean;
1531
+ readonly order: number;
1532
+ readonly [field: string]: FilterField;
1533
+ };
1223
1534
  /**
1224
- * Checking type: validates the projections of a scene one by one against its layer names, so the
1225
- * error sits on the projection that is wrong and not on the whole list.
1535
+ * `Glow`: a soft halo of `color` around the opaque pixels of the view.
1226
1536
  *
1227
1537
  * @example
1228
1538
  * ```ts
1229
- * type Checked = CheckedProjections<"items", readonly [{ name: "board.items"; layer: "items" }]>;
1539
+ * const value: GlowValue = { strength: 2, distance: 10, color: 0xffffff, alpha: 1, enabled: true, order: 0 };
1230
1540
  * ```
1231
1541
  */
1232
- type CheckedProjections<Layer extends string, List> = { readonly [Index in keyof List]: CheckedProjection<Layer, List[Index]> };
1542
+ type GlowValue = {
1543
+ strength: number;
1544
+ distance: number;
1545
+ color: number;
1546
+ alpha: number;
1547
+ enabled: boolean;
1548
+ order: number;
1549
+ };
1550
+ /**
1551
+ * `Outline`: a ring of `thickness` pixels in `color` around the opaque pixels of the view.
1552
+ *
1553
+ * @example
1554
+ * ```ts
1555
+ * const value: OutlineValue = { thickness: 2, color: 0x000000, alpha: 1, enabled: true, order: 0 };
1556
+ * ```
1557
+ */
1558
+ type OutlineValue = {
1559
+ thickness: number;
1560
+ color: number;
1561
+ alpha: number;
1562
+ enabled: boolean;
1563
+ order: number;
1564
+ };
1565
+ /**
1566
+ * `Blur`: Pixi's blur. `quality: 0` means `config.blur.quality`; `resolution: 0` means
1567
+ * `config.blur.phoneResolution` on a phone, otherwise 1.
1568
+ *
1569
+ * @example
1570
+ * ```ts
1571
+ * const value: BlurValue = { strength: 8, quality: 0, resolution: 0, repeatEdgePixels: true, enabled: true, order: 0 };
1572
+ * ```
1573
+ */
1574
+ type BlurValue = {
1575
+ strength: number;
1576
+ quality: number;
1577
+ resolution: number;
1578
+ repeatEdgePixels: boolean;
1579
+ enabled: boolean;
1580
+ order: number;
1581
+ };
1582
+ /**
1583
+ * `ColorMatrix`: brightness, saturation, contrast, hue and grayscale composed in that order.
1584
+ * Every default is the identity.
1585
+ *
1586
+ * @example
1587
+ * ```ts
1588
+ * const value: ColorMatrixValue = { brightness: 1, saturation: 0, contrast: 0, hue: 0, grayscale: 1, enabled: true, order: 0 };
1589
+ * ```
1590
+ */
1591
+ type ColorMatrixValue = {
1592
+ brightness: number;
1593
+ saturation: number;
1594
+ contrast: number;
1595
+ hue: number;
1596
+ grayscale: number;
1597
+ enabled: boolean;
1598
+ order: number;
1599
+ };
1600
+ /**
1601
+ * `Noise`: Pixi's noise with its seed always given, so two runs draw the same grain.
1602
+ *
1603
+ * @example
1604
+ * ```ts
1605
+ * const value: NoiseValue = { amount: 0.5, seed: 7, enabled: true, order: 0 };
1606
+ * ```
1607
+ */
1608
+ type NoiseValue = {
1609
+ amount: number;
1610
+ seed: number;
1611
+ enabled: boolean;
1612
+ order: number;
1613
+ };
1614
+ /**
1615
+ * `Displacement`: Pixi's displacement over the texture of an asset key.
1616
+ *
1617
+ * @example
1618
+ * ```ts
1619
+ * const value: DisplacementValue = { map: "fx.ripple", scaleX: 20, scaleY: 20, enabled: true, order: 0 };
1620
+ * ```
1621
+ */
1622
+ type DisplacementValue = {
1623
+ map: string;
1624
+ scaleX: number;
1625
+ scaleY: number;
1626
+ enabled: boolean;
1627
+ order: number;
1628
+ };
1233
1629
  /**
1234
- * What the author passes to `defineScene`. The layer names come from the keys of `layers` plus
1235
- * the appended `ui`; `NoInfer` keeps `projections` out of that inference, so a wrong name is
1236
- * reported on the projection and never widens the layer union. `music` is an asset key of the
1237
- * game, narrowed to its audio keys once the scanner splits them.
1630
+ * `Alpha`: Pixi's alpha over the whole subtree, as one layer.
1238
1631
  *
1239
1632
  * @example
1240
1633
  * ```ts
1241
- * const spec: SceneSpec<{ items: Record<never, never> }, readonly [], string, "board"> = {
1242
- * bundle: "board",
1243
- * layers: { items: {} },
1244
- * projections: []
1634
+ * const value: AlphaValue = { alpha: 0.5, enabled: true, order: 0 };
1635
+ * ```
1636
+ */
1637
+ type AlphaValue = {
1638
+ alpha: number;
1639
+ enabled: boolean;
1640
+ order: number;
1641
+ };
1642
+ /**
1643
+ * The five built-in kinds drawn by a Pixi core filter.
1644
+ *
1645
+ * @example
1646
+ * ```ts
1647
+ * const kind: CoreKind = "blur";
1648
+ * ```
1649
+ */
1650
+ type CoreKind = "blur" | "colorMatrix" | "noise" | "displacement" | "alpha";
1651
+ /**
1652
+ * Where a filter kind comes from: our or a game's shaders (`"wgsl"`, with the GLSL twin beside
1653
+ * it), or a Pixi core filter.
1654
+ */
1655
+ type KindSource = {
1656
+ readonly source: "wgsl";
1657
+ readonly definition: FilterDefinition;
1658
+ } | {
1659
+ readonly source: "core";
1660
+ readonly core: CoreKind;
1661
+ };
1662
+ /**
1663
+ * A filter kind before it is registered: its id, its component and its source.
1664
+ */
1665
+ type KindEntry = KindSource & {
1666
+ readonly id: string;
1667
+ readonly component: ComponentHandle<FilterFields>;
1668
+ };
1669
+ /**
1670
+ * One registered filter kind. `index` is the registration order: the built-ins first, then the
1671
+ * filters of every feature in feature order.
1672
+ */
1673
+ type FilterKind = KindEntry & {
1674
+ readonly index: number;
1675
+ };
1676
+ /**
1677
+ * The values of the uniform group of one of our filter instances: a number per `f32`, a vector
1678
+ * per colour and tuple. Written where they differ from the stored value.
1679
+ */
1680
+ type UniformValues = Record<string, number | Float32Array>;
1681
+ /**
1682
+ * The uniform group of one of our filter instances, as the instance writes it.
1683
+ */
1684
+ type FilterUniforms = {
1685
+ readonly uniforms: UniformValues;
1686
+ };
1687
+ /**
1688
+ * The Pixi object of one kind on one view, with the passes one apply of it costs now: what the
1689
+ * builders of `instance.ts` make before the instance gets its sort keys. A Pixi-core kind keeps
1690
+ * the fields its setters were last given, `applied`, so an equal value calls no setter.
1691
+ */
1692
+ type FilterBody = {
1693
+ readonly kind: "wgsl";
1694
+ readonly filter: PixiFilter;
1695
+ readonly group: FilterUniforms | undefined;
1696
+ readonly definition: FilterDefinition;
1697
+ passes: number; /** The chains and their places; `undefined` for a definition without inputs. Set once, by `createFilter`. */
1698
+ inputs: InputsState | undefined; /** The dev mark drawn after the fallback by a stand-in (E5.4); `undefined` otherwise. */
1699
+ readonly mark: OursBody | undefined; /** The definition's last area answer, filled in place; `some: false` for `undefined` (B4). */
1700
+ readonly area: AreaBox; /** The value changed since the area was last asked: the next sync asks the definition again. */
1701
+ areaStale: boolean;
1702
+ } | {
1703
+ readonly kind: "blur";
1704
+ readonly filter: InstanceType<PixiModule["BlurFilter"]>; /** The fields the setters were last given; compared before a write. */
1705
+ readonly applied: Record<string, FilterField>;
1706
+ passes: number;
1707
+ } | {
1708
+ readonly kind: "colorMatrix";
1709
+ readonly filter: InstanceType<PixiModule["ColorMatrixFilter"]>; /** The fields the setters were last given; compared before a write. */
1710
+ readonly applied: Record<string, FilterField>;
1711
+ passes: number;
1712
+ } | {
1713
+ readonly kind: "noise";
1714
+ readonly filter: InstanceType<PixiModule["NoiseFilter"]>; /** The fields the setters were last given; compared before a write. */
1715
+ readonly applied: Record<string, FilterField>;
1716
+ passes: number;
1717
+ } | {
1718
+ readonly kind: "displacement";
1719
+ readonly filter: InstanceType<PixiModule["DisplacementFilter"]>;
1720
+ readonly sprite: PixiSprite; /** The fields the setters were last given; compared before a write. */
1721
+ readonly applied: Record<string, FilterField>; /** The asset key the map sprite shows now. */
1722
+ map: string;
1723
+ passes: number;
1724
+ } | {
1725
+ readonly kind: "alpha";
1726
+ readonly filter: InstanceType<PixiModule["AlphaFilter"]>; /** The fields the setters were last given; compared before a write. */
1727
+ readonly applied: Record<string, FilterField>;
1728
+ passes: number;
1729
+ };
1730
+ /**
1731
+ * A probe of the warm-up (spec 27 F4): the Pixi filter handed to `renderer.sync.filters.warm`, and its teardown.
1732
+ */
1733
+ type WarmProbe = {
1734
+ readonly filter: PixiFilter;
1735
+ destroy(): void;
1736
+ };
1737
+ /**
1738
+ * A filter area as the filter sync keeps it, written in place: the four numbers, and whether there is one.
1739
+ */
1740
+ type AreaBox = {
1741
+ x: number;
1742
+ y: number;
1743
+ width: number;
1744
+ height: number;
1745
+ some: boolean;
1746
+ };
1747
+ /**
1748
+ * The instance of one kind on one view: its Pixi object, and what it sorts by on the view, the
1749
+ * `order` of its component (written every frame) and the `index` of its kind. The instance is
1750
+ * the object the builder made, never a copy: a closure made while building (the apply guard)
1751
+ * reads and writes the same fields the filter sync does.
1752
+ */
1753
+ type FilterInstance = FilterBody & {
1754
+ order: number;
1755
+ readonly index: number; /** True once the instance broke (its apply threw, its chain failed): it stays broken until it is retired. */
1756
+ broken: boolean; /** The value the filter sync handed over last: the chain and a stand-in are built from it. */
1757
+ value: Readonly<FilterFields>; /** The kind this instance draws for; a stand-in carries the id of the kind it stands in for. */
1758
+ readonly kindId: string; /** True for an instance that draws in place of a broken kind or instance (E5). */
1759
+ readonly standIn: boolean; /** A stand-in built while this broken instance was drawing; the next sync puts it in its place. */
1760
+ replacement: FilterInstance | undefined;
1761
+ };
1762
+ /**
1763
+ * One of our bodies: a `Filter` over the assembled programs, the kind of every place of a chain and of a mark.
1764
+ */
1765
+ type OursBody = Extract<FilterBody, {
1766
+ kind: "wgsl";
1767
+ }>;
1768
+ /** One place of a chain: the Pixi filter that draws the stage there, and the share of the resolution it draws at. */
1769
+ type Place = {
1770
+ readonly definition: FilterDefinition;
1771
+ readonly body: OursBody;
1772
+ scale: number;
1773
+ };
1774
+ /**
1775
+ * The chains of one instance of a filter with inputs: what draws each second texture, and the seam spec 23 keeps
1776
+ * pictures through.
1777
+ */
1778
+ type InputsState = {
1779
+ /** A uniform of the value changed since the last evaluation (set by `writeFilter`). */stale: boolean; /** The resolution of the last evaluation. */
1780
+ resolution: number; /** Changes whenever what the stages draw changes; 1 after the first evaluation. */
1781
+ version: number; /** The object `chain` receives, reused: its `resolution` is written before each call. */
1782
+ readonly context: {
1783
+ resolution: number;
1784
+ }; /** Per input, its places in draw order (each with the scale of its step). */
1785
+ readonly places: Place[][]; /** Bodies of places that left the chain, destroyed by the next sync. */
1786
+ readonly retired: OursBody[]; /** Pooled textures taken by the running apply; emptied in its `finally`. One array, reused. */
1787
+ readonly taken: PixiTexture[]; /** The options object handed to `TexturePool.getOptimalTexture`, reused. */
1788
+ readonly request: {
1789
+ width: number;
1790
+ height: number;
1791
+ resolution: number;
1792
+ }; /** Results lent by spec 23: while set, the apply draws the main body only, over them. */
1793
+ held: readonly PixiTexture[] | undefined; /** The slot seam, a frozen object replaced when a place is built or dropped. */
1794
+ slot: FilterSlotInputs;
1795
+ };
1796
+ /**
1797
+ * Where the shader check of every definition a kind can draw stands, in every build: one run per start of the
1798
+ * plugin.
1799
+ */
1800
+ type CheckState = {
1801
+ /** Bumped by `stopEffects`: an answer of an earlier run is ignored. */run: number; /** The checks were started on the running backend. */
1802
+ started: boolean; /** Where each definition stands. */
1803
+ status: WeakMap<FilterDefinition, "pending" | "ok" | "broken">; /** Definitions still pending. */
1804
+ waiting: number; /** Definitions started, for the `effects:checks` line. */
1805
+ checked: number; /** Definitions that failed, for the `effects:checks` line. */
1806
+ failed: number; /** GLSL compiles in flight under `KHR_parallel_shader_compile`. */
1807
+ gl: Array<{
1808
+ definition: FilterDefinition;
1809
+ shader: WebGLShader;
1810
+ }>; /** `performance.now()` when the checks started, for the `effects:checks` line. */
1811
+ startedAt: number; /** Resolves when `started` and `waiting === 0`. */
1812
+ settled: Promise<void>; /** Resolves `settled`. */
1813
+ resolve: () => void;
1814
+ };
1815
+ /**
1816
+ * What the filter sync keeps for one entity: the kinds it carries, one instance per kind, the
1817
+ * instances waiting to be destroyed after the next assignment, the slots last handed to
1818
+ * `renderer.sync.filters.set`, the core kinds whose component changed this frame, the
1819
+ * instances of this frame in their sorted order (one array, reused every frame), and the one
1820
+ * `renderer.invalidate` call a visible filter write of the frame makes.
1821
+ */
1822
+ type FilteredView = {
1823
+ readonly kinds: Set<string>;
1824
+ readonly instances: Map<string, FilterInstance>;
1825
+ readonly retired: FilterInstance[];
1826
+ assigned: readonly FilterSlot[];
1827
+ readonly changed: Set<string>;
1828
+ readonly placed: FilterInstance[]; /** True once `renderer.invalidate` was called for this view in the current frame. */
1829
+ signalled: boolean; /** Calls `renderer.invalidate(entity)` once per frame for this view, before the write; made once with the record. */
1830
+ readonly signal: () => void; /** The area last handed to `renderer.sync.filters.area`, written in place (B4). */
1831
+ readonly areaSent: AreaBox; /** The `enabled` switches of the placed instances when the area was last judged, one bit each in placed order. */
1832
+ areaMask: number;
1833
+ };
1834
+ //#endregion
1835
+ //#region src/plugins/effects/particles/types.d.ts
1836
+ /**
1837
+ * An inclusive `[min, max]` range; every particle draws its own value from it.
1838
+ *
1839
+ * @example
1840
+ * ```ts
1841
+ * const lifeMs: Range = [500, 900];
1842
+ * ```
1843
+ */
1844
+ type Range = readonly [min: number, max: number];
1845
+ /**
1846
+ * A value over the life of a particle, linear from its birth (`from`) to its death (`to`).
1847
+ *
1848
+ * @example
1849
+ * ```ts
1850
+ * const shrink: Curve = { from: 1, to: 0.2 };
1851
+ * ```
1852
+ */
1853
+ type Curve = {
1854
+ readonly from: number;
1855
+ readonly to: number;
1856
+ };
1857
+ /**
1858
+ * Where a particle is born, around the origin of its emitter, in reference units. `circle` is
1859
+ * uniform over the disc, `ring` uniform between `radius − width / 2` and `radius + width / 2`,
1860
+ * `rect` uniform over the centred box.
1861
+ *
1862
+ * @example
1863
+ * ```ts
1864
+ * const burstRing: Shape = { kind: "ring", radius: 48, width: 16 };
1865
+ * ```
1866
+ */
1867
+ type Shape = {
1868
+ readonly kind: "point";
1869
+ } | {
1870
+ readonly kind: "circle";
1871
+ readonly radius: number;
1872
+ } | {
1873
+ readonly kind: "rect";
1874
+ readonly w: number;
1875
+ readonly h: number;
1876
+ } | {
1877
+ readonly kind: "ring";
1878
+ readonly radius: number;
1879
+ readonly width: number;
1880
+ };
1881
+ /**
1882
+ * A particle effect as data, what `defineEmitter` takes. Every field is optional except
1883
+ * `textures` and exactly one of `burst` and `rate`. Speeds are reference units per second, angles
1884
+ * degrees with 0 to the right and 90 down, spins radians per second.
1885
+ *
1886
+ * @example
1887
+ * ```ts
1888
+ * const stars: EmitterConfig = {
1889
+ * textures: ["fx.star", "fx.sparkle"], burst: 40, lifeMs: [500, 900], speed: [300, 700],
1890
+ * gravity: 900, drag: 0.2, shape: { kind: "circle", radius: 24 }, alpha: { from: 1, to: 0 }
1245
1891
  * };
1246
1892
  * ```
1247
1893
  */
1248
- type SceneSpec<Layers extends LayerMap, Projections extends readonly AnySceneProjection[], Asset extends string, Bundle extends string> = {
1249
- bundle: Bundle;
1250
- layers: Layers;
1251
- projections: Projections & NoInfer<CheckedProjections<SceneLayer<Layers>, Projections>>;
1252
- music?: Asset;
1894
+ type EmitterConfig = {
1895
+ /** 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. */
1896
+ readonly burst?: number; /** Particles per second while `active` is true and the entity lives. */
1897
+ readonly rate?: number; /** Life of a particle in milliseconds. Default `[600, 1000]`. */
1898
+ readonly lifeMs?: Range; /** Start speed in reference units per second. Default `[0, 0]`. */
1899
+ readonly speed?: Range; /** Start direction in degrees, 0 right, 90 down. Default `[0, 360]`. */
1900
+ readonly angle?: Range; /** Downward acceleration in reference units per second². Default `0`. */
1901
+ readonly gravity?: number; /** Velocity lost per second, 0..1: `v *= (1 − drag) ^ seconds`. Default `0`. */
1902
+ readonly drag?: number; /** Spin in radians per second. Default `[0, 0]`. */
1903
+ readonly spin?: Range; /** Where particles are born around the origin. Default `{ kind: "point" }`. */
1904
+ readonly shape?: Shape; /** Scale over life. Default `{ from: 1, to: 1 }`. */
1905
+ readonly scale?: Curve; /** Alpha over life, 0..1. Default `{ from: 1, to: 1 }`. */
1906
+ readonly alpha?: Curve; /** Tint over life, `0xrrggbb`, linear per channel. Default white to white. */
1907
+ readonly tint?: Curve; /** `"world"`: particles outlive the entity. `"local"`: they move and die with it. Default `"world"`. */
1908
+ readonly space?: "world" | "local"; /** Milliseconds simulated before the first frame, for streams, 0..5000. Default `0`. */
1909
+ readonly prewarmMs?: number; /** Hard ceiling of live particles of one instance, 1..5000. Default `256`. */
1910
+ readonly maxParticles?: number; /** Blend mode of the particle container. Default `"normal"`. */
1911
+ readonly blend?: "normal" | "add";
1253
1912
  };
1254
1913
  /**
1255
- * The signature of `defineScene` bound to the asset and bundle keys of one game.
1256
- * `defineGame<Types>()` returns it; the loose export accepts any string.
1914
+ * An `EmitterConfig` with every default filled in. `burst` and `rate` stay as given: exactly one
1915
+ * of them is present.
1257
1916
  *
1258
1917
  * @example
1259
1918
  * ```ts
1260
- * const defineGameScene: DefineScene<"board.music", "board"> = defineScene;
1919
+ * defineEmitter("fx.steam", { textures: ["fx.puff"], rate: 12 }).config.lifeMs; // [600, 1000]
1261
1920
  * ```
1262
1921
  */
1263
- 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;
1922
+ type ResolvedEmitterConfig = Required<Omit<EmitterConfig, "burst" | "rate">> & Pick<EmitterConfig, "burst" | "rate">;
1264
1923
  /**
1265
- * A scene as `defineScene` returns it: plain frozen data, ready for the `scenes` key of a feature.
1924
+ * A particle effect as `defineEmitter` returns it: frozen, registered through the `emitters` key
1925
+ * of a feature and started by an `Emitter` component that names its id.
1266
1926
  *
1267
1927
  * @example
1268
1928
  * ```ts
1269
- * // What defineScene("board", { bundle: "board", layers: { items: { sort: "y" } }, projections: [] })
1270
- * // returns: { id: "board", bundle: "board", music: undefined,
1271
- * // layers: [{ name: "items", sort: "y" }, { name: "ui", sort: "none" }], projections: [] }
1929
+ * const steam: EmitterDefinition = defineEmitter("fx.steam", { textures: ["fx.puff"], rate: 12 });
1930
+ * steam.config.rate; // 12
1272
1931
  * ```
1273
1932
  */
1274
- type SceneDefinition = {
1933
+ type EmitterDefinition = {
1275
1934
  readonly id: string;
1276
- readonly bundle: string;
1277
- readonly music: string | undefined; /** Draw order, bottom first. */
1278
- readonly layers: readonly LayerSpec[]; /** Names of the projections the scene mounts. */
1279
- readonly projections: readonly string[];
1935
+ readonly config: ResolvedEmitterConfig;
1280
1936
  };
1281
1937
  /**
1282
- * The one owner every mount of this plugin carries. One is enough: one scene is mounted at a
1283
- * time, and `unmount` is what flushes.
1938
+ * The value of the `Emitter` component: which effect runs on the entity and whether a stream
1939
+ * emits. `""` draws nothing.
1284
1940
  *
1285
1941
  * @example
1286
1942
  * ```ts
1287
- * const owner: SceneOwner = { kind: "plugin", name: "scenes" };
1943
+ * const value: EmitterValue = { effect: "fx.steam", active: true };
1288
1944
  * ```
1289
1945
  */
1290
- type SceneOwner = Owner & {
1291
- kind: "plugin";
1292
- name: "scenes";
1946
+ type EmitterValue = {
1947
+ effect: string;
1948
+ active: boolean;
1949
+ };
1950
+ /**
1951
+ * The cosmetic random source of one instance: xorshift32 over its own seed, never `Math.random`
1952
+ * and never the model's rng.
1953
+ */
1954
+ type Rng = {
1955
+ /** The next number in `[0, 1)`. */next(): number; /** An integer in `[0, count)`. */
1956
+ int(count: number): number; /** A number in `[min, max]`. */
1957
+ between(min: number, max: number): number;
1958
+ };
1959
+ /**
1960
+ * A Pixi particle container: what one emitter instance draws in.
1961
+ */
1962
+ type PixiParticleContainer = InstanceType<PixiModule["ParticleContainer"]>;
1963
+ /**
1964
+ * One particle of a container, as the container keeps it.
1965
+ */
1966
+ type PixiParticle = PixiParticleContainer["particleChildren"][number];
1967
+ /**
1968
+ * What one effect id bakes to on first use: the 64-entry curve tables, the reach rectangle's half
1969
+ * side and the resolved textures. Dropped when a bundle with one of its textures leaves.
1970
+ */
1971
+ type BakedEmitter = {
1972
+ readonly id: string;
1973
+ readonly config: ResolvedEmitterConfig; /** Scale per 1/64 of the life. */
1974
+ readonly scaleTable: Float32Array; /** Alpha per 1/64 of the life, 0..1. */
1975
+ readonly alphaTable: Float32Array; /** Tint per 1/64 of the life, packed as Pixi's `0xbbggrr`. */
1976
+ readonly tintTable: Uint32Array; /** Half side of the box no particle leaves, in reference units. */
1977
+ readonly reach: number;
1978
+ readonly textures: readonly PixiTexture[]; /** The texture source of the first texture: every texture of the effect samples it. */
1979
+ readonly source: PixiTexture["source"] | undefined; /** The largest side of the textures, in pixels. */
1980
+ readonly maxSide: number;
1981
+ };
1982
+ /**
1983
+ * The per-particle numbers a Pixi particle has no field for, one entry per live particle, in the
1984
+ * order of `particleChildren`.
1985
+ */
1986
+ type BornFields = {
1987
+ readonly age: Float32Array;
1988
+ readonly life: Float32Array;
1989
+ readonly vx: Float32Array;
1990
+ readonly vy: Float32Array;
1991
+ readonly spin: Float32Array;
1992
+ readonly variant: Float32Array;
1993
+ };
1994
+ /**
1995
+ * One running effect: its container, the plugin-owned entity that carries it, the particle pool
1996
+ * and the numbers the step needs.
1997
+ */
1998
+ type EmitterInstance = {
1999
+ readonly id: string;
2000
+ readonly baked: BakedEmitter; /** The entity whose `Emitter` started it. */
2001
+ readonly host: Entity; /** The plugin-owned entity whose `Display` is the container. */
2002
+ readonly entity: Entity;
2003
+ readonly container: PixiParticleContainer; /** Retired particles, reused before a new one is made. */
2004
+ readonly pool: PixiParticle[];
2005
+ readonly born: BornFields; /** Fraction of a particle owed to the next frame of a stream. */
2006
+ carry: number;
2007
+ readonly rng: Rng; /** The point the container was placed at, in root space (world space only). */
2008
+ readonly origin: Readonly<Point>;
2009
+ readonly space: "world" | "local"; /** Pixi's particle class, from the module the renderer loaded. */
2010
+ readonly particleClass: PixiModule["Particle"];
1293
2011
  };
2012
+ declare namespace types_d_exports$2 {
2013
+ export { AlphaValue, AssetsReplaced$1 as AssetsReplaced, BlurValue, BundleUnloaded$1 as BundleUnloaded, ColorMatrixValue, Config$2 as Config, CoreKind, Curve, Deps$2 as Deps, DisplacementValue, EffectsApi, EffectsCtx, EffectsDiagnostics, EffectsStats, EmitterConfig, EmitterDefinition, EmitterValue, FilterArea, FilterCache, FilterChainContext, FilterComponent, FilterDefinition, FilterInput, FilterInputDefinition, FilterSpec, FilterStage, FilterStep, FilterValueOf, GlowValue, HotSwap, KernelSlice$2 as KernelSlice, NoiseValue, OutlineValue, Range, ResolvedEmitterConfig, Shape, State$2 as State, UniformDeclaration, UniformSpec, UniformValue };
2014
+ }
1294
2015
  /**
1295
- * scenes plugin config: none. A scene is data in a feature, and a transition effect is V3 work
1296
- * of `anim`, so a key here would have no reader.
2016
+ * effects plugin config. `maxParticles` here is the global warning line; the `maxParticles` of an
2017
+ * emitter config caps one instance.
1297
2018
  *
1298
2019
  * @example
1299
2020
  * ```ts
1300
- * const config: Config = {};
2021
+ * createApp({
2022
+ * plugins: [...screen, effectsPlugin],
2023
+ * pluginConfigs: { effects: { maxPasses: 16, phone: false } }
2024
+ * });
1301
2025
  * ```
1302
2026
  */
1303
- type Config$2 = Record<string, never>;
2027
+ type Config$2 = {
2028
+ /** 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. */
2029
+ 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`. */
2030
+ phone: boolean | "auto";
2031
+ /**
2032
+ * What a `Blur` with `quality: 0` and `resolution: 0` resolves to. Shallow merge: a game that
2033
+ * sets `blur` gives both fields.
2034
+ */
2035
+ blur: {
2036
+ quality: number;
2037
+ phoneResolution: number;
2038
+ };
2039
+ };
1304
2040
  /**
1305
- * scenes plugin state.
2041
+ * effects plugin state: the particle half, the filter half, and what both share.
1306
2042
  */
1307
2043
  type State$2 = {
1308
- /** Scene id to its declaration, filled in `onStart` from `flow.features`. */scenes: Map<string, SceneDefinition>; /** Id of the scene that is mounted now. */
1309
- current: string | undefined;
2044
+ /** Resolved in `onStart`. */phone: boolean; /** The `emitters` of every feature, read in `onStart`. */
2045
+ emitters: Map<string, EmitterDefinition>; /** Per effect id, built on first use. */
2046
+ baked: Map<string, BakedEmitter>; /** Host entity to its running instance. */
2047
+ instances: Map<Entity, EmitterInstance>; /** World-space instances whose host left, stepped until empty. */
2048
+ orphans: Set<EmitterInstance>; /** Ordinal of the next instance: the cosmetic seed. */
2049
+ seedCounter: number; /** Live particles after the last step. */
2050
+ particles: number; /** Filter kinds whose shader or one of whose stages failed the check on the running backend, in every build. */
2051
+ broken: Set<string>; /** Filter kinds whose main definition and every stage answered ok: a kind's status is a set lookup once settled. */
2052
+ ready: Set<string>; /** The filter kinds, built-ins first, then the filters of every feature. */
2053
+ kinds: Map<string, FilterKind>; /** The shader check of every definition a kind can draw, in every build. */
2054
+ checks: CheckState; /** Entity to the filters the sync keeps for it. */
2055
+ views: Map<Entity, FilteredView>; /** Keys of the one-shot warnings: `"emitter:fx.x"`, `"texture:board.cell"`, `"atlas:fx.x"`. */
2056
+ warned: Set<string>; /** Whether each budget warning is in force, so a crossing warns once. */
2057
+ over: {
2058
+ particles: boolean;
2059
+ passes: boolean;
2060
+ fullScreen: boolean;
2061
+ }; /** The teardown closure, the two systems and the world hooks. */
2062
+ removers: Array<() => void>;
2063
+ /**
2064
+ * The switches of a debug build: the kind ids in registration order (frozen, filled in `onStart`), the kinds
2065
+ * turned off, and those as a frozen list in the order of `kinds`.
2066
+ */
2067
+ diagnostics: {
2068
+ kinds: readonly string[];
2069
+ off: Set<string>;
2070
+ offList: readonly string[];
2071
+ };
1310
2072
  /**
1311
- * The scene the next node without its own `scene` mounts, at a rest node or at a transit node
1312
- * in live mode: the one a transit node named in fast mode, or the one the restore door expected
1313
- * with `expect`. A rest node clears it; a transit node clears it only when it took it; an over
1314
- * node takes it and clears it, whatever is mounted, except an over transit node of a fast walk,
1315
- * which leaves it alone.
2073
+ * The warm-up hand-over (spec 27 F4): started at the first frame with a ready host, the Pixi-core kinds noted then,
2074
+ * and the probes handed to `renderer.sync.filters.warm`, kept until the app stops.
1316
2075
  */
1317
- pending: string | undefined;
1318
- owner: SceneOwner; /** The remover of the `onEnter` callback: set in `onStart`, run in `onStop`. */
1319
- teardown: (() => void) | undefined;
2076
+ warm: {
2077
+ started: boolean;
2078
+ core: string[];
2079
+ probes: WarmProbe[];
2080
+ };
1320
2081
  };
1321
2082
  /**
1322
- * scenes plugin events.
2083
+ * What `app.effects.stats()` answers: live particles, running emitter instances, filter instances
2084
+ * on views, and the render passes of the frame as the renderer counts them.
1323
2085
  *
1324
2086
  * @example
1325
2087
  * ```ts
1326
- * // A game plugin starts the music of the scene the graph switched to.
1327
- * createPlugin("music", {
1328
- * depends: [scenesPlugin],
1329
- * hooks: ctx => ({ "scenes:changed": ({ music }) => ctx.log.info("scene music", { music }) })
1330
- * }); // entering the board logs { music: "board.theme" }
2088
+ * const stats: EffectsStats = { particles: 120, emitters: 3, filters: 25, renderPasses: 49 };
1331
2089
  * ```
1332
2090
  */
1333
- type Events = {
1334
- /** The mounted scene changed. */"scenes:changed": {
1335
- from: string | undefined;
1336
- to: string;
1337
- music: string | undefined;
1338
- };
2091
+ type EffectsStats = {
2092
+ /** Live particles over every instance and orphan after the last step. */particles: number; /** Running instances plus the orphans still flying. */
2093
+ emitters: number; /** Filter instances over every view; the stages of a filter's inputs are counted in `renderPasses`, not here. */
2094
+ filters: number; /** `renderer.sync.renderPasses()`, read at call time. */
2095
+ renderPasses: number;
1339
2096
  };
1340
2097
  /**
1341
- * scenes plugin API, `app.scenes`. A scene is switched by the graph, never by a call, so the
1342
- * plugin answers one question and takes one order, for the restore door.
2098
+ * effects plugin API, `app.effects`. Everything a game does with effects is data on entities;
2099
+ * the API only counts.
1343
2100
  *
1344
2101
  * @example
1345
2102
  * ```ts
1346
- * // A headless test walks from the menu onto the board and asks what is mounted.
1347
- * await app.flow.walk([{ at: "home", intent: "play" }]);
1348
- * app.scenes.current(); // "board"
2103
+ * // A test waits for a merge burst to land before it compares the board.
2104
+ * while (app.effects.stats().particles > 0) app.time.step(16);
2105
+ * app.effects.stats().particles; // 0
1349
2106
  * ```
1350
2107
  */
1351
- type ScenesApi = {
2108
+ type EffectsApi = {
1352
2109
  /**
1353
- * The id of the mounted scene.
2110
+ * Counts what runs now. `renderPasses` is the renderer's own number, read at call time; the
2111
+ * others are the plugin's. Headless every number is 0.
1354
2112
  *
1355
- * @returns The scene id, `undefined` before the first switch and after the app stopped.
2113
+ * @returns A fresh object: live particles, emitter instances with orphans, filter instances
2114
+ * and render passes.
1356
2115
  * @example
1357
2116
  * ```ts
1358
- * // A dev overlay writes the scene next to the frame counter.
1359
- * app.scenes.current(); // "board", or undefined while the first bundle still loads
2117
+ * // After the board rests: one steam stream, 24 enabled glows on 24 views, one disabled blur.
2118
+ * app.effects.stats();
2119
+ * // { particles: 18, emitters: 1, filters: 25, renderPasses: 49 }: 1 + 24 × (1 + 1), the disabled blur costs 0
1360
2120
  * ```
1361
2121
  */
1362
- current(): string | undefined;
1363
- /**
1364
- * Names the scene the next node without its own `scene` mounts. A rest node reads it, and so
1365
- * does a transit node in live mode; a transit node of a fast walk leaves it for the rest point.
1366
- * The restore door calls it with the scene of a bookmark, so a bookmark at a popup comes back
1367
- * with the scene under it, also when the popup is a transit node that waits for its answer:
1368
- * the scene of such a popup is mounted before `flow.restore` resolves.
1369
- * An over node takes the expected scene too, whatever is mounted, so a popup bookmark restored
1370
- * on a page that shows another scene switches to the scene of the bookmark. With nothing
1371
- * expected an over node never switches. An over transit node of a fast walk mounts nothing and
1372
- * keeps the record.
2122
+ stats(): EffectsStats;
2123
+ /**
2124
+ * Switches of a debug build: filter kinds turned off by hand, to measure what a kind costs on the device. Acts on
2125
+ * a page with the agent flag (a debug build, the dev page); elsewhere `disable` and `enable` change nothing. An off
2126
+ * kind keeps its instances: `stats().filters` still counts them, `renderPasses` drops.
1373
2127
  *
1374
- * @param id - Id of a scene a feature declared.
1375
- * @throws {Error} For an id no feature declared, also before `onStart` filled the registry.
1376
2128
  * @example
1377
2129
  * ```ts
1378
- * // A fresh page restores a bookmark taken at the Settings popup over Home, a transit node
1379
- * // that waits for its answer.
1380
- * app.scenes.expect("home");
1381
- * await app.flow.restore(bookmark); // resolves when the popup's gate is open
1382
- * app.scenes.current(); // "home"
2130
+ * // A game's overlay toggle "Glow": the frame without any glow, then with it again.
2131
+ * app.effects.diagnostics.disable("effects.glow"); // true
2132
+ * app.effects.diagnostics.enable("effects.glow"); // true
1383
2133
  * ```
1384
2134
  */
1385
- expect(id: string): void;
2135
+ diagnostics: EffectsDiagnostics;
2136
+ };
2137
+ /**
2138
+ * Switches of a debug build: filter kinds turned off by hand, to measure what a kind costs on the device. Acts on
2139
+ * a page with the agent flag (a debug build, the dev page); elsewhere `disable` and `enable` change nothing.
2140
+ *
2141
+ * @example
2142
+ * ```ts
2143
+ * // A game's overlay toggle "Glow" (H4.4): the frame without any glow, then with it again.
2144
+ * app.effects.diagnostics.disable("effects.glow"); // true
2145
+ * app.effects.diagnostics.enable("effects.glow"); // true
2146
+ * ```
2147
+ */
2148
+ type EffectsDiagnostics = {
2149
+ /**
2150
+ * The filter kinds this app knows, in registration order: the seven built-ins, then the `filters` of every
2151
+ * feature. The same frozen array on every call; empty before start. Every build.
2152
+ *
2153
+ * @returns The kind ids.
2154
+ * @example
2155
+ * ```ts
2156
+ * // A game with the frost filter of its casefile feature, after start.
2157
+ * app.effects.diagnostics.kinds();
2158
+ * // ["effects.glow", "effects.outline", "effects.blur", "effects.colorMatrix", "effects.noise",
2159
+ * // "effects.displacement", "effects.alpha", "casefile.frost"]
2160
+ * ```
2161
+ */
2162
+ kinds(): readonly string[];
2163
+ /**
2164
+ * Turns a filter kind off: from the next frame no view draws it (no apply, no render pass), while its instances
2165
+ * stay and are still written, so `enable` shows the current look at once. Agent pages only.
2166
+ *
2167
+ * @param kind - A kind id of `kinds()`.
2168
+ * @returns True when the kind is known and now off; false for an unknown kind, and on a page without the agent
2169
+ * flag, where nothing changes.
2170
+ * @example
2171
+ * ```ts
2172
+ * // The owner measures the board without the glow of its four cards (one glow pass each).
2173
+ * app.effects.diagnostics.disable("effects.glow"); // true
2174
+ * app.time.step(16);
2175
+ * app.effects.stats().renderPasses; // 9, down from 13
2176
+ * app.effects.diagnostics.disable("fx.nope"); // false, and one warning effects:diagnostics-unknown
2177
+ * ```
2178
+ */
2179
+ disable(kind: string): boolean;
2180
+ /**
2181
+ * Turns a filter kind on again: from the next frame its views draw it with their current values.
2182
+ *
2183
+ * @param kind - A kind id of `kinds()`.
2184
+ * @returns True when the kind is known and now on; false for an unknown kind and without the agent flag.
2185
+ * @example
2186
+ * ```ts
2187
+ * app.effects.diagnostics.enable("effects.glow"); // true
2188
+ * app.time.step(16);
2189
+ * app.effects.stats().renderPasses; // 13
2190
+ * ```
2191
+ */
2192
+ enable(kind: string): boolean;
2193
+ /**
2194
+ * The kinds turned off now, in registration order. The same frozen array until the next change; empty without
2195
+ * the agent flag.
2196
+ *
2197
+ * @returns The kind ids.
2198
+ * @example
2199
+ * ```ts
2200
+ * app.effects.diagnostics.disable("casefile.frost");
2201
+ * app.effects.diagnostics.disable("effects.glow");
2202
+ * app.effects.diagnostics.disabled(); // ["effects.glow", "casefile.frost"]
2203
+ * ```
2204
+ */
2205
+ disabled(): readonly string[];
1386
2206
  };
1387
2207
  /**
1388
2208
  * Resolved dependency APIs.
1389
2209
  */
1390
2210
  type Deps$2 = {
1391
- flow: Api$1;
1392
- world: Api$2;
1393
- assets: Api$3;
1394
- time: Api;
2211
+ flow: Api$2;
2212
+ world: Api$3;
2213
+ renderer: Api$4;
2214
+ assets: Api$5;
2215
+ anim: AnimApi;
1395
2216
  };
1396
2217
  /**
1397
- * What the kernel context offers before the deps are attached.
1398
- *
1399
- * `index.ts` writes `events` with an annotated `register` (core spec `14-EVENT-REGISTRATION.md`
1400
- * row 8), so the own event reaches the context the kernel hands the factories and `emit` is the
1401
- * kernel's own, scenes-typed one. No member of the context is cast.
2218
+ * What the kernel context offers before the deps are attached. `effects` owns no event, so
2219
+ * `emit` is the kernel's and never called here.
1402
2220
  */
1403
- type KernelSlice$2 = PluginCtx<Config$2, State$2, Events> & {
2221
+ type KernelSlice$2 = PluginCtx<Config$2, State$2> & {
1404
2222
  readonly global: object;
1405
2223
  readonly log: Log.LogApi;
1406
2224
  readonly require: Require;
1407
2225
  };
1408
2226
  /**
1409
- * Domain context shared by the files of the plugin.
2227
+ * Domain context shared by both modules: the kernel slice plus the resolved deps.
1410
2228
  */
1411
- type ScenesCtx = KernelSlice$2 & {
2229
+ type EffectsCtx = KernelSlice$2 & {
1412
2230
  readonly deps: Deps$2;
1413
2231
  };
1414
2232
  /**
1415
- * The mode and the abort signal `flow.onEnter` hands every callback.
1416
- *
1417
- * @example
1418
- * ```ts
1419
- * const run: RunContext = { mode: "live", signal: new AbortController().signal };
1420
- * ```
2233
+ * Payload of the `assets:bundle-unloaded` hook: `keys` names every texture that is gone.
1421
2234
  */
1422
- type RunContext = {
1423
- mode: "live" | "fast";
1424
- signal: AbortSignal;
1425
- };
2235
+ type BundleUnloaded$1 = Events$1["assets:bundle-unloaded"];
2236
+ /**
2237
+ * Payload of the `assets:replaced` hook: `keys` names every asset a dev hot swap gave new bytes.
2238
+ */
2239
+ type AssetsReplaced$1 = Events$1["assets:replaced"];
2240
+ /**
2241
+ * Payload of the global `ui:hot-swap` hook: the saved file and its new exports.
2242
+ */
2243
+ type HotSwap = Events$2["ui:hot-swap"];
1426
2244
  declare namespace types_d_exports$1 {
1427
- export { AssetsReplaced, AudioApi, AudioContextLike, AudioCtx, AudioSessionLike, AudioSessionType, BundleUnloaded, Bus, BusState, Config$1 as Config, Deps$1 as Deps, KernelSlice$1 as KernelSlice, LifecycleChanged, ModelCommitted, MusicDescriptor, MusicMode, MusicOptions, MusicRequest, MusicSwitch, MusicTrack, RetiringTrack, SceneChanged, SfxRequest, SoundEntry, State$1 as State, StreamTrack, Volumes };
2245
+ export { AssetsReplaced, AudioApi, AudioContextLike, AudioCtx, AudioSessionLike, AudioSessionType, BundleUnloaded, Bus, BusState, Config$1 as Config, Deps$1 as Deps, KernelSlice$1 as KernelSlice, LifecycleChanged, ModelCommitted, MusicDescriptor, MusicMode, MusicOptions, MusicRequest, MusicSwitch, MusicTrack, RetiringTrack, SceneChanged, SfxRequest, SoundEntry, State$1 as State, StreamTrack, Volumes, VolumesFor };
1428
2246
  }
1429
2247
  /**
1430
2248
  * The three volume buses. `master` carries the whole game, `music` the scene track, `sfx` every
@@ -1464,14 +2282,14 @@ type MusicOptions = {
1464
2282
  };
1465
2283
  /**
1466
2284
  * `music` bound to the audio asset keys of one game. `defineGame` returns it through `audioFor`;
1467
- * the loose export accepts any string.
2285
+ * the loose export accepts any string. The descriptor resolves nothing.
1468
2286
  *
1469
2287
  * @example
1470
2288
  * ```ts
1471
2289
  * const gameMusic: MusicDescriptor<"board.theme"> = music;
1472
2290
  * ```
1473
2291
  */
1474
- type MusicDescriptor<Asset extends string> = (key: Asset | null, options?: MusicOptions) => Descriptor;
2292
+ type MusicDescriptor<Asset extends string> = (key: Asset | null, options?: MusicOptions) => Descriptor<void>;
1475
2293
  /**
1476
2294
  * Reads the player's volume choice out of the committed player state. Every bus it names is
1477
2295
  * applied; the ones it leaves out keep the value they had.
@@ -1482,6 +2300,17 @@ type MusicDescriptor<Asset extends string> = (key: Asset | null, options?: Music
1482
2300
  * ```
1483
2301
  */
1484
2302
  type Volumes = (player: Json) => Partial<Record<Bus, number>>;
2303
+ /**
2304
+ * `Volumes`, written against the player type of one game: `defineGame` hands it as `volumes`, so
2305
+ * the callback reads the game's player with no cast. The same function at run time.
2306
+ *
2307
+ * @example
2308
+ * ```ts
2309
+ * // index.ts of a game: `volumes` comes from the kit, so `player` is the game's Player.
2310
+ * pluginConfigs: { audio: { volumes: volumes(player => ({ music: player.settings.music })) } }
2311
+ * ```
2312
+ */
2313
+ type VolumesFor<Player> = (read: (player: Player) => Partial<Record<Bus, number>>) => Volumes;
1485
2314
  /**
1486
2315
  * How a music track is played. `"decode"`: one AudioBuffer per track, gapless, about 58 MB per
1487
2316
  * 150 s at 48 kHz. `"stream"`: an `<audio>` element on a `blob:` URL of the bytes `assets` holds,
@@ -1554,10 +2383,11 @@ type RetiringTrack = {
1554
2383
  *
1555
2384
  * @example
1556
2385
  * ```ts
2386
+ * // `volumes` comes from the game's `defineGame` kit, so `player` is the game's Player.
1557
2387
  * createApp({
1558
2388
  * plugins: [...screen, audioPlugin],
1559
2389
  * pluginConfigs: {
1560
- * audio: { musicFadeMs: 400, music: "stream", session: "playback", volumes: player => player.settings.audio }
2390
+ * audio: { musicFadeMs: 400, music: "stream", session: "playback", volumes: volumes(player => player.settings.audio) }
1561
2391
  * }
1562
2392
  * });
1563
2393
  * ```
@@ -1754,6 +2584,9 @@ type AudioApi = {
1754
2584
  * and a music switch to the track that already plays are not in it. Empty while
1755
2585
  * `config.journal` is 0, the default, and after the app stopped.
1756
2586
  *
2587
+ * No copy and no live view: the list and its entries are never written. Every new sound replaces the plugin's list
2588
+ * with a new frozen one, so a list read earlier keeps what it held, and a caller cannot change the journal through it.
2589
+ *
1757
2590
  * @returns The journal, frozen; a later sound replaces the list and leaves this one as it was.
1758
2591
  * @example
1759
2592
  * ```ts
@@ -1770,10 +2603,10 @@ type AudioApi = {
1770
2603
  * Resolved dependency APIs.
1771
2604
  */
1772
2605
  type Deps$1 = {
1773
- flow: Api$1;
1774
- assets: Api$3;
1775
- model: Api$4;
1776
- time: Api;
2606
+ flow: Api$2;
2607
+ assets: Api$5;
2608
+ model: Api$6;
2609
+ time: Api$1;
1777
2610
  };
1778
2611
  /**
1779
2612
  * What the kernel context offers before the deps are attached.
@@ -1794,23 +2627,23 @@ type AudioCtx = KernelSlice$1 & {
1794
2627
  /**
1795
2628
  * Payload of the `model:committed` hook: the source of every volume a game sets.
1796
2629
  */
1797
- type ModelCommitted = Events$2["model:committed"];
2630
+ type ModelCommitted = Events$3["model:committed"];
1798
2631
  /**
1799
2632
  * Payload of the `scenes:changed` hook: `music` is the track the next scene declared.
1800
2633
  */
1801
- type SceneChanged = Events["scenes:changed"];
2634
+ type SceneChanged = Events$4["scenes:changed"];
1802
2635
  /**
1803
2636
  * Payload of the `lifecycle:changed` hook: the pause that holds every bus at zero.
1804
2637
  */
1805
- type LifecycleChanged = Events$3["lifecycle:changed"];
2638
+ type LifecycleChanged = Events$5["lifecycle:changed"];
1806
2639
  /**
1807
2640
  * Payload of the `assets:bundle-unloaded` hook: `keys` names every decoded sound that is gone.
1808
2641
  */
1809
- type BundleUnloaded = Events$4["assets:bundle-unloaded"];
2642
+ type BundleUnloaded = Events$1["assets:bundle-unloaded"];
1810
2643
  /**
1811
2644
  * Payload of the `assets:replaced` hook: `keys` names every asset a dev hot swap gave new bytes.
1812
2645
  */
1813
- type AssetsReplaced = Events$4["assets:replaced"];
2646
+ type AssetsReplaced = Events$1["assets:replaced"];
1814
2647
  /**
1815
2648
  * What one play of a sound asks for: the key and the bus the sound goes to.
1816
2649
  */
@@ -1840,7 +2673,8 @@ type LayoutState = {
1840
2673
  scrollStart: {
1841
2674
  pointerY: number;
1842
2675
  offset: number;
1843
- };
2676
+ }; /** One line of the `body` style as the icons were last measured with it; none before that. */
2677
+ iconLine: number | undefined;
1844
2678
  order: number;
1845
2679
  cleanups: Array<() => void>;
1846
2680
  };
@@ -1949,10 +2783,11 @@ type State = {
1949
2783
  */
1950
2784
  type Deps = {
1951
2785
  anim: AnimApi;
1952
- time: Api;
1953
- flow: Api$1;
1954
- world: Api$2;
1955
- renderer: Api$5;
2786
+ debug: Api;
2787
+ time: Api$1;
2788
+ flow: Api$2;
2789
+ world: Api$3;
2790
+ renderer: Api$4;
1956
2791
  input: InputApi;
1957
2792
  i18n: I18nApi;
1958
2793
  text: TextApi;
@@ -1962,7 +2797,7 @@ type Deps = {
1962
2797
  *
1963
2798
  * `ui` owns no event of its own; `emit` sends the global `ui:hot-swap` of the dev hot swap.
1964
2799
  */
1965
- type KernelSlice = PluginCtx<Config, State, Events$5> & {
2800
+ type KernelSlice = PluginCtx<Config, State, Events$2> & {
1966
2801
  readonly global: object;
1967
2802
  readonly log: Log.LogApi;
1968
2803
  readonly require: Require;
@@ -2061,4 +2896,4 @@ type UiApi = {
2061
2896
  fill(key: string, value: string): boolean;
2062
2897
  };
2063
2898
  //#endregion
2064
- export { SfxDescriptor as A, sequence as B, Config$3 as C, Target as D, State$3 as E, frames as F, stagger as G, sfx as H, haptic as I, wait as J, tween as K, mark as L, HAPTIC_KINDS as M, defineAnimation as N, types_d_exports$3 as O, external as P, parallel as R, AnimPlayerValue as S, MotionKeyframe as T, spawn as U, set as V, spawned as W, SceneSpec as _, AudioApi as a, types_d_exports$2 as b, MusicDescriptor as c, State$1 as d, types_d_exports$1 as f, SceneDefinition as g, LayerMap as h, types_d_exports as i, Step as j, HapticKind as k, MusicOptions as l, DefineScene as m, State as n, AudioContextLike as o, AnySceneProjection as p, use as q, UiApi as r, Config$1 as s, Config as t, SoundEntry as u, ScenesApi as v, FramesValue as w, AnimApi as x, State$2 as y, play as z };
2899
+ export { sequence as $, GlowValue as A, Target as B, BlurValue as C, FilterComponent as D, FilterArea as E, AnimPlayerValue as F, HAPTIC_KINDS as G, HapticKind as H, Config$3 as I, frames as J, defineAnimation as K, FramesValue as L, OutlineValue as M, UniformDeclaration as N, FilterSpec as O, AnimApi as P, play as Q, MotionKeyframe as R, AlphaValue as S, DisplacementValue as T, SfxDescriptor as U, types_d_exports$3 as V, Step as W, mark as X, haptic as Y, parallel as Z, State$2 as _, AudioApi as a, tween as at, EmitterDefinition as b, MusicDescriptor as c, OverlayApi as ct, State$1 as d, set as et, VolumesFor as f, EffectsStats as g, EffectsApi as h, types_d_exports as i, stagger as it, NoiseValue as j, FilterValueOf as k, MusicOptions as l, types_d_exports$4 as lt, Config$2 as m, State as n, spawn as nt, AudioContextLike as o, use as ot, types_d_exports$1 as p, external as q, UiApi as r, spawned as rt, Config$1 as s, wait as st, Config as t, sfx as tt, SoundEntry as u, types_d_exports$2 as v, ColorMatrixValue as w, EmitterValue as x, EmitterConfig as y, State$3 as z };