@flare-engine/postfx 0.2.1 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,49 +1,4 @@
1
- /**
2
- * Ordered post-processing pipeline. Pure data structure — the runtime reads
3
- * the enabled entries in order and feeds each through its compiled shader.
4
- */
5
- declare class EffectChain {
6
- private _effects;
7
- private _globalIntensity;
8
- get length(): number;
9
- /**
10
- * Global intensity multiplier applied to every enabled effect's
11
- * "strength" uniform when the pass is built. Default 1 (no-op).
12
- *
13
- * Scales the following uniform fields at pass-build time:
14
- * - vignette.intensity
15
- * - grayscale-flash.intensity
16
- * - chromatic-aberration.amount
17
- * - crt.scanlineIntensity
18
- * - bloom.intensity
19
- *
20
- * Geometry-like uniforms (radius, curvature, threshold) are not scaled.
21
- * Intended for a11y reduced-motion (set to 0 to null all dampen-able
22
- * effects in one call) without touching per-effect state.
23
- *
24
- * Clamped to `>= 0`. Negative values are clamped to 0.
25
- */
26
- get globalIntensity(): number;
27
- set globalIntensity(value: number);
28
- /** Convenience setter — mirrors the getter/setter. */
29
- setGlobalIntensity(value: number): void;
30
- /** Snapshot view of the chain. Do not mutate. */
31
- all(): readonly Effect[];
32
- /** Append an effect to the end of the chain. */
33
- add(effect: Effect): void;
34
- /** Insert an effect at a specific index (clamped to [0, length]). */
35
- insert(index: number, effect: Effect): void;
36
- remove(id: string): boolean;
37
- get(id: string): Effect | null;
38
- setEnabled(id: string, enabled: boolean): void;
39
- /** Update uniforms in place. Kind must match the existing effect's kind. */
40
- setUniforms(id: string, uniforms: EffectUniforms): void;
41
- move(id: string, newIndex: number): void;
42
- clear(): void;
43
- /** Iterate only over enabled effects, in order. */
44
- enabled(): IterableIterator<Effect>;
45
- }
46
-
1
+ //#region src/runtime.d.ts
47
2
  /**
48
3
  * Runtime — compiles SkSL sources on demand, builds uniform buffers per effect,
49
4
  * and exposes `buildPass` that the bridge calls to get pass descriptors for
@@ -56,171 +11,167 @@ declare class EffectChain {
56
11
  * without coupling this package to a specific Skia version.
57
12
  */
58
13
  interface SkImageLike {
59
- readonly _skImageBrand?: never;
14
+ readonly _skImageBrand?: never;
60
15
  }
61
16
  interface SkiaRuntimeEffectLike {
62
- makeShaderWithChildren(uniforms: ArrayLike<number>, children?: unknown[]): unknown | null;
63
- /** Optional diagnostic — total float count expected by makeShaderWithChildren. */
64
- getUniformFloatCount?(): number;
17
+ makeShaderWithChildren(uniforms: Float32Array | number[], children?: unknown[]): unknown | null;
18
+ /** Optional diagnostic — total float count expected by makeShaderWithChildren. */
19
+ getUniformFloatCount?(): number;
65
20
  }
66
21
  interface SkiaRuntimeEffectFactory {
67
- Make(sksl: string): SkiaRuntimeEffectLike | null;
22
+ Make(sksl: string): SkiaRuntimeEffectLike | null;
68
23
  }
69
24
  interface SkiaPostFxModuleLike {
70
- RuntimeEffect: SkiaRuntimeEffectFactory;
25
+ RuntimeEffect: SkiaRuntimeEffectFactory;
71
26
  }
72
27
  interface CompiledShader {
73
- kind: EffectKind;
74
- effect: SkiaRuntimeEffectLike;
28
+ kind: EffectKind;
29
+ effect: SkiaRuntimeEffectLike;
75
30
  }
76
31
  interface PostFxRuntime {
77
- /** True when the underlying Skia module reported a working RuntimeEffect. */
78
- readonly supported: boolean;
79
- /** Lazily compile (and cache) the shader for a given effect. Returns null on unsupported. */
80
- compile(kind: EffectKind): CompiledShader | null;
81
- /** Pack an effect's uniforms into a Float32Array in the order the shader declares. */
82
- packUniforms(effect: Effect, width: number, height: number): Float32Array;
83
- /** Build a list of compiled pass descriptors in chain order for enabled effects. */
84
- buildPass(chain: EffectChain, width: number, height: number): CompiledPass[];
85
- /** Drop all cached compiled shaders. */
86
- reset(): void;
32
+ /** True when the underlying Skia module reported a working RuntimeEffect. */
33
+ readonly supported: boolean;
34
+ /** Lazily compile (and cache) the shader for a given effect. Returns null on unsupported. */
35
+ compile(kind: EffectKind): CompiledShader | null;
36
+ /** Pack an effect's uniforms into a Float32Array in the order the shader declares. */
37
+ packUniforms(effect: Effect, width: number, height: number): Float32Array;
38
+ /** Build a list of compiled pass descriptors in chain order for enabled effects. */
39
+ buildPass(chain: EffectChain, width: number, height: number): CompiledPass[];
40
+ /** Drop all cached compiled shaders. */
41
+ reset(): void;
87
42
  }
88
43
  interface CompiledPass {
89
- id: string;
90
- kind: EffectKind;
91
- shader: SkiaRuntimeEffectLike;
92
- uniforms: Float32Array;
93
- /**
94
- * Convention A — extra sampler children only (excludes src).
95
- *
96
- * The renderer binds the implicit scene texture as `children[0]` at draw
97
- * time. This array holds any *additional* `uniform shader` slots declared
98
- * beyond `src` (e.g. a noise texture, an LUT). For every built-in effect
99
- * today there are no extra children, so this is always the frozen
100
- * `EMPTY_CHILDREN` sentinel — enabling zero-alloc sharing across passes.
101
- */
102
- children: readonly SkImageLike[];
44
+ id: string;
45
+ kind: EffectKind;
46
+ shader: SkiaRuntimeEffectLike;
47
+ uniforms: Float32Array;
48
+ /**
49
+ * Convention A — extra sampler children only (excludes src).
50
+ *
51
+ * The renderer binds the implicit scene texture as `children[0]` at draw
52
+ * time. This array holds any *additional* `uniform shader` slots declared
53
+ * beyond `src` (e.g. a noise texture, an LUT). Every built-in effect
54
+ * *except* `color-grading-lut` has no extra children, so this is the frozen
55
+ * `EMPTY_CHILDREN` sentinel — enabling zero-alloc sharing across those
56
+ * passes. `color-grading-lut` yields exactly one extra child — the LUT
57
+ * `SkImage` (`effect.uniforms.lut`) — bound as the shader's second
58
+ * `uniform shader` (index 1) after the implicit `src` (index 0).
59
+ */
60
+ children: readonly SkImageLike[];
103
61
  }
104
62
  /**
105
63
  * Create a runtime against the given Skia-like module. If the module is
106
64
  * absent or lacks `RuntimeEffect.Make`, returns a runtime with
107
65
  * `supported: false` — callers should fall through to a passthrough pipeline.
108
66
  */
109
- declare function createPostFxRuntime(skia: SkiaPostFxModuleLike | null): PostFxRuntime;
110
-
111
- /**
112
- * Effect — an ordered entry in the post-processing chain.
113
- *
114
- * `kind` selects a bundled shader (see `shaders.ts`). Each effect owns its
115
- * own uniform object; the runtime feeds these straight into the compiled
116
- * RuntimeEffect at draw time.
117
- */
118
-
67
+ declare function createPostFxRuntime(skia: SkiaPostFxModuleLike | null, opts?: {
68
+ verbose?: boolean;
69
+ }): PostFxRuntime;
70
+ //#endregion
71
+ //#region src/effect.d.ts
119
72
  type EffectKind = 'vignette' | 'grayscale-flash' | 'chromatic-aberration' | 'crt' | 'bloom' | 'god-rays' | 'color-grading-lut' | 'weather-rain' | 'weather-sun-shaft' | 'weather-dark-vignette' | 'weather-clouds' | 'composite';
120
73
  interface VignetteUniforms {
121
- intensity: number;
122
- radius: number;
123
- color?: readonly [number, number, number];
74
+ intensity: number;
75
+ radius: number;
76
+ color?: readonly [number, number, number];
124
77
  }
125
78
  interface GrayscaleFlashUniforms {
126
- intensity: number;
79
+ intensity: number;
127
80
  }
128
81
  interface ChromaticAberrationUniforms {
129
- amount: number;
82
+ amount: number;
130
83
  }
131
84
  interface CrtUniforms {
132
- scanlineIntensity: number;
133
- curvature: number;
85
+ scanlineIntensity: number;
86
+ curvature: number;
134
87
  }
135
88
  interface BloomUniforms {
136
- threshold: number;
137
- intensity: number;
89
+ threshold: number;
90
+ intensity: number;
138
91
  }
139
92
  interface GodRaysUniforms {
140
- lightX: number;
141
- lightY: number;
142
- color: readonly [number, number, number];
143
- intensity: number;
144
- decay: number;
145
- density: number;
146
- weight: number;
147
- samples: number;
93
+ lightX: number;
94
+ lightY: number;
95
+ color: readonly [number, number, number];
96
+ intensity: number;
97
+ decay: number;
98
+ density: number;
99
+ weight: number;
100
+ samples: number;
148
101
  }
149
102
  interface ColorGradingLutUniforms {
150
- lut: SkImageLike;
151
- intensity: number;
103
+ lut: SkImageLike;
104
+ intensity: number;
152
105
  }
153
106
  interface WeatherRainUniforms {
154
- intensity: number;
155
- direction: number;
156
- speed: number;
157
- scrollOffsetY: number;
158
- color?: readonly [number, number, number];
159
- density?: number;
160
- streakLen?: number;
107
+ intensity: number;
108
+ direction: number;
109
+ scrollOffsetY: number;
110
+ color?: readonly [number, number, number];
111
+ density?: number;
112
+ streakLen?: number;
161
113
  }
162
114
  interface WeatherSunShaftUniforms {
163
- intensity: number;
164
- direction: number;
115
+ intensity: number;
116
+ direction: number;
165
117
  }
166
118
  interface WeatherDarkVignetteUniforms {
167
- intensity: number;
119
+ intensity: number;
168
120
  }
169
121
  interface WeatherCloudsUniforms {
170
- intensity: number;
171
- direction: number;
172
- speed: number;
173
- scrollOffsetX: number;
122
+ intensity: number;
123
+ direction: number;
124
+ scrollOffsetX: number;
174
125
  }
175
126
  interface CompositeUniforms {
176
- vignetteIntensity: number;
177
- vignetteRadius: number;
178
- vignetteColor: readonly [number, number, number];
179
- bloomThreshold: number;
180
- bloomIntensity: number;
181
- flashIntensity: number;
182
- chromaAmount: number;
183
- crtScanlineIntensity: number;
184
- crtCurvature: number;
185
- crtEnabled: boolean;
186
- weatherKind: 0 | 1 | 2 | 3 | 4;
187
- weatherIntensity: number;
188
- weatherDirection: number;
189
- weatherScrollX: number;
190
- weatherScrollY: number;
191
- weatherColor: readonly [number, number, number];
192
- weatherDensity: number;
193
- weatherStreakLen: number;
127
+ vignetteIntensity: number;
128
+ vignetteRadius: number;
129
+ vignetteColor: readonly [number, number, number];
130
+ bloomThreshold: number;
131
+ bloomIntensity: number;
132
+ flashIntensity: number;
133
+ chromaAmount: number;
134
+ crtScanlineIntensity: number;
135
+ crtCurvature: number;
136
+ crtEnabled: boolean;
137
+ weatherKind: 0 | 1 | 2 | 3 | 4;
138
+ weatherIntensity: number;
139
+ weatherDirection: number;
140
+ weatherScrollX: number;
141
+ weatherScrollY: number;
142
+ weatherColor: readonly [number, number, number];
143
+ weatherDensity: number;
144
+ weatherStreakLen: number;
194
145
  }
195
146
  type EffectUniforms = ({
196
- kind: 'vignette';
147
+ kind: 'vignette';
197
148
  } & VignetteUniforms) | ({
198
- kind: 'grayscale-flash';
149
+ kind: 'grayscale-flash';
199
150
  } & GrayscaleFlashUniforms) | ({
200
- kind: 'chromatic-aberration';
151
+ kind: 'chromatic-aberration';
201
152
  } & ChromaticAberrationUniforms) | ({
202
- kind: 'crt';
153
+ kind: 'crt';
203
154
  } & CrtUniforms) | ({
204
- kind: 'bloom';
155
+ kind: 'bloom';
205
156
  } & BloomUniforms) | ({
206
- kind: 'god-rays';
157
+ kind: 'god-rays';
207
158
  } & GodRaysUniforms) | ({
208
- kind: 'color-grading-lut';
159
+ kind: 'color-grading-lut';
209
160
  } & ColorGradingLutUniforms) | ({
210
- kind: 'weather-rain';
161
+ kind: 'weather-rain';
211
162
  } & WeatherRainUniforms) | ({
212
- kind: 'weather-sun-shaft';
163
+ kind: 'weather-sun-shaft';
213
164
  } & WeatherSunShaftUniforms) | ({
214
- kind: 'weather-dark-vignette';
165
+ kind: 'weather-dark-vignette';
215
166
  } & WeatherDarkVignetteUniforms) | ({
216
- kind: 'weather-clouds';
167
+ kind: 'weather-clouds';
217
168
  } & WeatherCloudsUniforms) | ({
218
- kind: 'composite';
169
+ kind: 'composite';
219
170
  } & CompositeUniforms);
220
171
  interface Effect {
221
- id: string;
222
- enabled: boolean;
223
- uniforms: EffectUniforms;
172
+ id: string;
173
+ enabled: boolean;
174
+ uniforms: EffectUniforms;
224
175
  }
225
176
  declare function createVignette(id?: string, uniforms?: Partial<VignetteUniforms>): Effect;
226
177
  declare function createGrayscaleFlash(id?: string, uniforms?: Partial<GrayscaleFlashUniforms>): Effect;
@@ -228,16 +179,107 @@ declare function createChromaticAberration(id?: string, uniforms?: Partial<Chrom
228
179
  declare function createCrt(id?: string, uniforms?: Partial<CrtUniforms>): Effect;
229
180
  declare function createBloom(id?: string, uniforms?: Partial<BloomUniforms>): Effect;
230
181
  declare function createGodRays(id?: string, uniforms?: Partial<GodRaysUniforms>): Effect;
231
- declare function createColorGradingLut(uniforms: {
232
- lut: SkImageLike;
233
- intensity?: number;
234
- }, id?: string): Effect;
182
+ declare function createColorGradingLut(id: string, uniforms: {
183
+ lut: SkImageLike;
184
+ intensity?: number;
185
+ }): Effect;
235
186
  declare function createWeatherRain(id?: string, uniforms?: Partial<WeatherRainUniforms>): Effect;
236
187
  declare function createWeatherSunShaft(id?: string, uniforms?: Partial<WeatherSunShaftUniforms>): Effect;
237
188
  declare function createWeatherDarkVignette(id?: string, uniforms?: Partial<WeatherDarkVignetteUniforms>): Effect;
238
189
  declare function createWeatherClouds(id?: string, uniforms?: Partial<WeatherCloudsUniforms>): Effect;
239
190
  declare function createPostFxComposite(id?: string, uniforms?: Partial<CompositeUniforms>): Effect;
240
-
191
+ //#endregion
192
+ //#region src/chain.d.ts
193
+ /**
194
+ * Ordered post-processing pipeline. Pure data structure — the runtime reads
195
+ * the enabled entries in order and feeds each through its compiled shader.
196
+ */
197
+ declare class EffectChain {
198
+ private _effects;
199
+ private _globalIntensity;
200
+ get length(): number;
201
+ /**
202
+ * Global intensity multiplier applied to every enabled effect's
203
+ * "strength" uniform when the pass is built. Default 1 (no-op).
204
+ *
205
+ * Scales the following uniform fields at pass-build time:
206
+ * - vignette.intensity
207
+ * - grayscale-flash.intensity
208
+ * - chromatic-aberration.amount
209
+ * - crt.scanlineIntensity
210
+ * - bloom.intensity
211
+ *
212
+ * Geometry-like uniforms (radius, curvature, threshold) are not scaled.
213
+ * Intended for a11y reduced-motion (set to 0 to null all dampen-able
214
+ * effects in one call) without touching per-effect state.
215
+ *
216
+ * Clamped to `>= 0`. Negative values are clamped to 0.
217
+ */
218
+ get globalIntensity(): number;
219
+ set globalIntensity(value: number);
220
+ /** Convenience setter — mirrors the getter/setter. */
221
+ setGlobalIntensity(value: number): void;
222
+ /** Snapshot view of the chain. Do not mutate. */
223
+ all(): readonly Effect[];
224
+ /** Append an effect to the end of the chain. */
225
+ add(effect: Effect): void;
226
+ /** Insert an effect at a specific index (clamped to [0, length]). */
227
+ insert(index: number, effect: Effect): void;
228
+ remove(id: string): boolean;
229
+ get(id: string): Effect | null;
230
+ setEnabled(id: string, enabled: boolean): void;
231
+ /** Update uniforms in place. Kind must match the existing effect's kind. */
232
+ setUniforms(id: string, uniforms: EffectUniforms): void;
233
+ move(id: string, newIndex: number): void;
234
+ clear(): void;
235
+ /** Iterate only over enabled effects, in order. */
236
+ enabled(): IterableIterator<Effect>;
237
+ }
238
+ //#endregion
239
+ //#region src/controller.d.ts
240
+ interface PostFxFlashConfig {
241
+ /** Id of a `grayscale-flash` effect already added to the chain. */
242
+ readonly id: string;
243
+ /** Decay rate in intensity units per second. Defaults to 4.2 (≈0.24 s to fade from 1). */
244
+ readonly decay?: number;
245
+ }
246
+ interface PostFxControllerOptions {
247
+ /** Effects appended to the internal chain in the given order. */
248
+ readonly effects: readonly Effect[];
249
+ /** Skia module — `null` / `undefined` keeps `buildPass` returning `[]`. */
250
+ readonly skia?: SkiaPostFxModuleLike | null;
251
+ /** Optional hit-flash binding. */
252
+ readonly flash?: PostFxFlashConfig;
253
+ }
254
+ interface PostFxController {
255
+ readonly chain: EffectChain;
256
+ readonly runtime: PostFxRuntime;
257
+ /** Spike the configured flash effect to 1. No-op if no flash configured. */
258
+ triggerHitFlash(): void;
259
+ /**
260
+ * Enable `effectId` immediately and auto-disable it after `decayMs` ms of
261
+ * accumulated `update(dt)` time. Uniforms are restored to the pre-trigger
262
+ * snapshot on expiry. Re-triggering before expiry resets the countdown.
263
+ * No-op if `effectId` is not in the chain.
264
+ */
265
+ /** Optional — enable an effect for `decayMs` ms then restore its pre-trigger uniforms. No-op if id is not in the chain. */
266
+ triggerTimed?(effectId: string, decayMs: number): void;
267
+ /** Advance auto-decaying effects (flash + timed triggers). */
268
+ update(dt: number): void;
269
+ /** Build the per-frame compiled pass list. Empty if runtime is unsupported. */
270
+ buildPass(width: number, height: number): readonly CompiledPass[];
271
+ /**
272
+ * Global intensity multiplier forwarded to the chain — intended for a11y
273
+ * reduced-motion (0 = fully neutralized, 1 = default).
274
+ */
275
+ setMotionScale(k: number): void;
276
+ }
277
+ declare function createPostFxController(options: PostFxControllerOptions): PostFxController;
278
+ //#endregion
279
+ //#region src/lut.d.ts
280
+ declare function createNeutralLutBytes(): Uint8Array;
281
+ //#endregion
282
+ //#region src/shaders.d.ts
241
283
  /**
242
284
  * SkSL runtime-effect shaders used by the postfx stack.
243
285
  *
@@ -254,64 +296,12 @@ declare const CHROMATIC_ABERRATION_SKSL = "\nuniform shader src;\nuniform float2
254
296
  declare const CRT_SKSL = "\nuniform shader src;\nuniform float2 resolution;\nuniform float scanlineIntensity;\nuniform float curvature;\n\nhalf4 main(float2 coord) {\n float2 uv = coord / resolution;\n float2 centered = uv - 0.5;\n float2 warp = centered * (1.0 + dot(centered, centered) * curvature);\n float2 warpedUv = warp + 0.5;\n if (warpedUv.x < 0.0 || warpedUv.x > 1.0 || warpedUv.y < 0.0 || warpedUv.y > 1.0) {\n return half4(0.0, 0.0, 0.0, 1.0);\n }\n half4 c = src.eval(warpedUv * resolution);\n float scan = 0.5 + 0.5 * sin(warpedUv.y * resolution.y * 3.14159);\n float dim = mix(1.0, scan, scanlineIntensity);\n return half4(c.rgb * dim, c.a);\n}\n";
255
297
  declare const BLOOM_SKSL = "\nuniform shader src;\nuniform float2 resolution;\nuniform float threshold;\nuniform float intensity;\n\nhalf4 main(float2 coord) {\n half4 c = src.eval(coord);\n float luma = dot(c.rgb, half3(0.299, 0.587, 0.114));\n float mask = smoothstep(threshold, 1.0, luma);\n half3 boost = c.rgb * mask * intensity;\n return half4(c.rgb + boost, c.a);\n}\n";
256
298
  declare const COLOR_GRADING_LUT_SKSL = "\nuniform shader src;\nuniform shader lut;\nuniform float intensity;\n\nhalf4 main(float2 coord) {\n half4 scene = src.eval(coord);\n\n float r = float(scene.r) * 63.0;\n float g = float(scene.g) * 63.0;\n float b = float(scene.b) * 63.0;\n\n float bFloor = floor(b);\n float bCeil = min(bFloor + 1.0, 63.0);\n float bFrac = b - bFloor;\n\n float2 uvFloor = float2((bFloor * 64.0 + r + 0.5) / 4096.0,\n (g + 0.5) / 64.0);\n float2 uvCeil = float2((bCeil * 64.0 + r + 0.5) / 4096.0,\n (g + 0.5) / 64.0);\n\n half3 lutFloor = lut.eval(uvFloor * float2(4096.0, 64.0)).rgb;\n half3 lutCeil = lut.eval(uvCeil * float2(4096.0, 64.0)).rgb;\n\n half3 graded = mix(lutFloor, lutCeil, half(bFrac));\n half3 result = mix(scene.rgb, graded, half(intensity));\n\n return half4(result, scene.a);\n}\n";
257
- declare const WEATHER_RAIN_SKSL = "\nuniform shader src;\nuniform float2 resolution;\nuniform float intensity;\nuniform float direction;\nuniform float scrollOffsetY;\nuniform float3 color;\nuniform float density;\nuniform float streakLen;\n\n// scrollOffsetY is a normalized fall phase in [0,1). Increase it each tick\n// by (speed * dt / screenHeight) and wrap with fract() on the game side.\n\nfloat hash(float2 p) { return fract(sin(dot(p, float2(127.1, 311.7))) * 43758.5453); }\n\nhalf4 main(float2 coord) {\n half4 scene = src.eval(coord);\n float2 uv = coord / resolution;\n float aspect = resolution.x / resolution.y;\n float rad = direction * 3.14159 / 180.0;\n float sinA = sin(rad);\n float cosA = cos(rad);\n // aspect-corrected rotated axes: fallAxis along drop travel, perpAxis across\n float perpAxis = (cosA * uv.x - sinA * uv.y) * aspect;\n float fallAxis = sinA * uv.x + cosA * uv.y;\n\n float coverage = 0.0;\n\n // near layer \u2014 COLS=45, tiling=6, brightness=1.0\n {\n float COLS = 45.0;\n float tiling = 6.0;\n float brightness = 1.0;\n float cid = floor(perpAxis * COLS);\n float colHash = hash(float2(cid, 0.0));\n if (colHash < density) {\n float phase = hash(float2(cid, 1.0));\n float colBright = brightness * (0.6 + hash(float2(cid, 2.0)) * 0.4);\n float center = 0.5 + (hash(float2(cid, 3.0)) - 0.5) * 0.4;\n float seg = fract(fallAxis * tiling - scrollOffsetY + phase);\n float coverageY = smoothstep(streakLen, 0.0, seg);\n float width = 0.04;\n float xMask = smoothstep(width, 0.0, abs(fract(perpAxis * COLS) - center));\n coverage = max(coverage, coverageY * xMask * colBright);\n }\n }\n\n // far layer \u2014 COLS=85, tiling=11, brightness=0.5\n {\n float COLS = 85.0;\n float tiling = 11.0;\n float brightness = 0.5;\n float cid = floor(perpAxis * COLS);\n float colHash = hash(float2(cid, 10.0));\n if (colHash < density) {\n float phase = hash(float2(cid, 11.0));\n float colBright = brightness * (0.6 + hash(float2(cid, 12.0)) * 0.4);\n float center = 0.5 + (hash(float2(cid, 13.0)) - 0.5) * 0.4;\n float seg = fract(fallAxis * tiling - scrollOffsetY + phase);\n float coverageY = smoothstep(streakLen, 0.0, seg);\n float width = 0.03;\n float xMask = smoothstep(width, 0.0, abs(fract(perpAxis * COLS) - center));\n coverage = max(coverage, coverageY * xMask * colBright);\n }\n }\n\n half3 mixed = mix(scene.rgb, half3(color), half(coverage * intensity));\n return half4(mixed, scene.a);\n}\n";
299
+ declare const WEATHER_RAIN_SKSL = "\nuniform shader src;\nuniform float2 resolution;\nuniform float intensity;\nuniform float direction;\nuniform float scrollOffsetY;\nuniform float3 color;\nuniform float density;\nuniform float streakLen;\n\n// scrollOffsetY is a normalized fall phase in [0,1). Increase it each tick\n// by (speed * dt / screenHeight) and wrap with fract() on the game side.\n\nfloat hash(float2 p) { return fract(sin(dot(p, float2(127.1, 311.7))) * 43758.5453); }\n\nhalf4 main(float2 coord) {\n half4 scene = src.eval(coord);\n float2 uv = coord / resolution;\n float aspect = resolution.x / resolution.y;\n float rad = direction * 3.14159 / 180.0;\n float sinA = sin(rad);\n float cosA = cos(rad);\n // aspect-corrected rotated axes: fallAxis along drop travel, perpAxis across\n float perpAxis = (cosA * uv.x - sinA * uv.y) * aspect;\n float fallAxis = sinA * uv.x + cosA * uv.y;\n\n float coverage = 0.0;\n\n // near layer — COLS=45, tiling=6, brightness=1.0\n {\n float COLS = 45.0;\n float tiling = 6.0;\n float brightness = 1.0;\n float cid = floor(perpAxis * COLS);\n float colHash = hash(float2(cid, 0.0));\n if (colHash < density) {\n float phase = hash(float2(cid, 1.0));\n float colBright = brightness * (0.6 + hash(float2(cid, 2.0)) * 0.4);\n float center = 0.5 + (hash(float2(cid, 3.0)) - 0.5) * 0.4;\n float seg = fract(fallAxis * tiling - scrollOffsetY + phase);\n float coverageY = smoothstep(streakLen, 0.0, seg);\n float width = 0.04;\n float xMask = smoothstep(width, 0.0, abs(fract(perpAxis * COLS) - center));\n coverage = max(coverage, coverageY * xMask * colBright);\n }\n }\n\n // far layer — COLS=85, tiling=11, brightness=0.5\n {\n float COLS = 85.0;\n float tiling = 11.0;\n float brightness = 0.5;\n float cid = floor(perpAxis * COLS);\n float colHash = hash(float2(cid, 10.0));\n if (colHash < density) {\n float phase = hash(float2(cid, 11.0));\n float colBright = brightness * (0.6 + hash(float2(cid, 12.0)) * 0.4);\n float center = 0.5 + (hash(float2(cid, 13.0)) - 0.5) * 0.4;\n float seg = fract(fallAxis * tiling - scrollOffsetY + phase);\n float coverageY = smoothstep(streakLen, 0.0, seg);\n float width = 0.03;\n float xMask = smoothstep(width, 0.0, abs(fract(perpAxis * COLS) - center));\n coverage = max(coverage, coverageY * xMask * colBright);\n }\n }\n\n half3 mixed = mix(scene.rgb, half3(color), half(coverage * intensity));\n return half4(mixed, scene.a);\n}\n";
258
300
  declare const WEATHER_SUN_SHAFT_SKSL = "\nuniform shader src;\nuniform float2 resolution;\nuniform float intensity;\nuniform float direction;\n\nhalf4 main(float2 coord) {\n half4 scene = src.eval(coord);\n float2 uv = coord / resolution;\n float rad = direction * 3.14159 / 180.0;\n float2 src_point = float2(0.5 + sin(rad) * 0.3, -0.05);\n float2 toFrag = uv - src_point;\n float angle = atan(toFrag.x, toFrag.y);\n float shafts = 0.5 + 0.5 * sin(angle * 8.0);\n float dist = length(toFrag);\n float falloff = 1.0 - smoothstep(0.0, 1.2, dist);\n half shaft = half(shafts * falloff * intensity * 0.35);\n return half4(scene.rgb + half3(shaft * 0.95, shaft * 0.9, shaft * 0.6), scene.a);\n}\n";
259
301
  declare const WEATHER_DARK_VIGNETTE_SKSL = "\nuniform shader src;\nuniform float2 resolution;\nuniform float intensity;\n\nhalf4 main(float2 coord) {\n half4 scene = src.eval(coord);\n float2 uv = coord / resolution - 0.5;\n float d = length(uv);\n float v = smoothstep(0.3, 0.85, d);\n half dark = half(v * intensity);\n return half4(scene.rgb * (1.0 - dark), scene.a);\n}\n";
260
- declare const WEATHER_CLOUDS_SKSL = "\nuniform shader src;\nuniform float2 resolution;\nuniform float intensity;\nuniform float scrollOffsetX;\n\nfloat hash(float2 p) { return fract(sin(dot(p, float2(127.1, 311.7))) * 43758.5453); }\nfloat vnoise(float2 p) {\n float2 i = floor(p); float2 f = fract(p);\n float2 u = f * f * (3.0 - 2.0 * f);\n return mix(mix(hash(i), hash(i + float2(1.0, 0.0)), u.x),\n mix(hash(i + float2(0.0, 1.0)), hash(i + float2(1.0, 1.0)), u.x), u.y);\n}\nfloat fbm(float2 p) { return vnoise(p) * 0.6 + vnoise(p * 2.3) * 0.4; }\n\nhalf4 main(float2 coord) {\n half4 scene = src.eval(coord);\n float2 uv = coord / resolution;\n float2 cloudUv = float2(uv.x + scrollOffsetX / resolution.x, uv.y * 0.5);\n float cloud = fbm(cloudUv * 3.0);\n float heightFade = smoothstep(0.6, 0.2, uv.y);\n float alpha = cloud * heightFade * intensity * 0.5;\n half3 cloudColor = half3(0.9, 0.9, 0.95);\n return half4(mix(scene.rgb, cloudColor, half(alpha)), scene.a);\n}\n";
261
- declare const COMPOSITE_SKSL = "\nuniform shader src;\n\nuniform float2 resolution;\nuniform float vignetteIntensity;\nuniform float vignetteRadius;\nuniform float3 vignetteColor;\nuniform float bloomThreshold;\nuniform float bloomIntensity;\nuniform float flashIntensity;\nuniform float chromaAmount;\nuniform float crtScanlineIntensity;\nuniform float crtCurvature;\nuniform float crtEnabled;\nuniform float weatherKind;\nuniform float weatherIntensity;\nuniform float weatherDirection;\nuniform float weatherScrollX;\nuniform float weatherScrollY;\nuniform float3 weatherColor;\nuniform float weatherDensity;\nuniform float weatherStreakLen;\n\nfloat hash(float2 p) { return fract(sin(dot(p, float2(127.1, 311.7))) * 43758.5453); }\nfloat vnoise(float2 p) {\n float2 i = floor(p); float2 f = fract(p);\n float2 u = f * f * (3.0 - 2.0 * f);\n return mix(mix(hash(i), hash(i + float2(1.0, 0.0)), u.x),\n mix(hash(i + float2(0.0, 1.0)), hash(i + float2(1.0, 1.0)), u.x), u.y);\n}\nfloat fbm(float2 p) { return vnoise(p) * 0.6 + vnoise(p * 2.3) * 0.4; }\n\nhalf4 main(float2 coord) {\n // --- chromatic aberration ---\n float2 uv = coord / resolution;\n float2 chromaOffset = (uv - 0.5) * chromaAmount;\n half r = src.eval((uv - chromaOffset) * resolution).r;\n half g = src.eval(coord).g;\n half b = src.eval((uv + chromaOffset) * resolution).b;\n half3 c = half3(r, g, b);\n\n // --- CRT ---\n float2 crtCentered = uv - 0.5;\n float2 crtWarp = crtCentered * (1.0 + dot(crtCentered, crtCentered) * crtCurvature);\n float2 crtUv = crtWarp + 0.5;\n float crtMask = float(crtUv.x >= 0.0 && crtUv.x <= 1.0 && crtUv.y >= 0.0 && crtUv.y <= 1.0);\n {\n float2 chromaOffsetCrt = (crtUv - 0.5) * chromaAmount;\n half rCrt = src.eval((crtUv - chromaOffsetCrt) * resolution).r;\n half gCrt = src.eval(crtUv * resolution).g;\n half bCrt = src.eval((crtUv + chromaOffsetCrt) * resolution).b;\n half3 crtColor = half3(rCrt, gCrt, bCrt);\n float scan = 0.5 + 0.5 * sin(crtUv.y * resolution.y * 3.14159);\n float dim = mix(1.0, scan, crtScanlineIntensity);\n half3 crtResult = crtColor * half(dim);\n c = mix(c, mix(half3(0.0), crtResult, half(crtMask)), half(crtEnabled));\n }\n\n // --- bloom ---\n float luma = dot(c, half3(0.299, 0.587, 0.114));\n float bloomMask = smoothstep(bloomThreshold, 1.0, luma);\n c = c + c * half(bloomMask * bloomIntensity);\n\n // --- weather ---\n {\n float wk = weatherKind;\n if (wk >= 0.5 && wk < 1.5) {\n // rain \u2014 falling sawtooth streaks, 2 parallax layers, alpha-over blend\n // weatherScrollY is normalized [0,1) fall phase (increment speed*dt/screenH each tick)\n float rad = weatherDirection * 3.14159 / 180.0;\n float sinA = sin(rad);\n float cosA = cos(rad);\n float aspect = resolution.x / resolution.y;\n float perpAxis = (cosA * uv.x - sinA * uv.y) * aspect;\n float fallAxis = sinA * uv.x + cosA * uv.y;\n\n float coverage = 0.0;\n\n // near layer \u2014 COLS=45, tiling=6, brightness=1.0\n {\n float COLS = 45.0;\n float tiling = 6.0;\n float brightness = 1.0;\n float cid = floor(perpAxis * COLS);\n float colHash = hash(float2(cid, 0.0));\n if (colHash < weatherDensity) {\n float phase = hash(float2(cid, 1.0));\n float colBright = brightness * (0.6 + hash(float2(cid, 2.0)) * 0.4);\n float center = 0.5 + (hash(float2(cid, 3.0)) - 0.5) * 0.4;\n float seg = fract(fallAxis * tiling - weatherScrollY + phase);\n float coverageY = smoothstep(weatherStreakLen, 0.0, seg);\n float width = 0.04;\n float xMask = smoothstep(width, 0.0, abs(fract(perpAxis * COLS) - center));\n coverage = max(coverage, coverageY * xMask * colBright);\n }\n }\n\n // far layer \u2014 COLS=85, tiling=11, brightness=0.5\n {\n float COLS = 85.0;\n float tiling = 11.0;\n float brightness = 0.5;\n float cid = floor(perpAxis * COLS);\n float colHash = hash(float2(cid, 10.0));\n if (colHash < weatherDensity) {\n float phase = hash(float2(cid, 11.0));\n float colBright = brightness * (0.6 + hash(float2(cid, 12.0)) * 0.4);\n float center = 0.5 + (hash(float2(cid, 13.0)) - 0.5) * 0.4;\n float seg = fract(fallAxis * tiling - weatherScrollY + phase);\n float coverageY = smoothstep(weatherStreakLen, 0.0, seg);\n float width = 0.03;\n float xMask = smoothstep(width, 0.0, abs(fract(perpAxis * COLS) - center));\n coverage = max(coverage, coverageY * xMask * colBright);\n }\n }\n\n c = mix(c, half3(weatherColor), half(coverage * weatherIntensity));\n } else if (wk >= 1.5 && wk < 2.5) {\n // sun-shaft\n float rad = weatherDirection * 3.14159 / 180.0;\n float2 srcPoint = float2(0.5 + sin(rad) * 0.3, -0.05);\n float2 toFrag = uv - srcPoint;\n float angle = atan(toFrag.x, toFrag.y);\n float shafts = 0.5 + 0.5 * sin(angle * 8.0);\n float dist = length(toFrag);\n float falloff = 1.0 - smoothstep(0.0, 1.2, dist);\n half shaft = half(shafts * falloff * weatherIntensity * 0.35);\n c = c + half3(shaft * 0.95, shaft * 0.9, shaft * 0.6);\n } else if (wk >= 2.5 && wk < 3.5) {\n // dark-vignette\n float2 uvShift = uv - 0.5;\n float d2 = length(uvShift);\n float vd = smoothstep(0.3, 0.85, d2);\n half dark = half(vd * weatherIntensity);\n c = c * (1.0 - dark);\n } else if (wk >= 3.5) {\n // clouds\n float2 cloudUv = float2(uv.x + weatherScrollX / resolution.x, uv.y * 0.5);\n float cloud = fbm(cloudUv * 3.0);\n float heightFade = smoothstep(0.6, 0.2, uv.y);\n float alpha = cloud * heightFade * weatherIntensity * 0.5;\n half3 cloudColor = half3(0.9, 0.9, 0.95);\n c = mix(c, cloudColor, half(alpha));\n }\n }\n\n // --- grayscale flash ---\n float lumaFlash = dot(c, half3(0.299, 0.587, 0.114));\n half3 gray = half3(lumaFlash);\n c = mix(c, gray, half(flashIntensity));\n\n // --- vignette ---\n float2 vigCentered = uv - 0.5;\n float vigD = length(vigCentered);\n float vigV = smoothstep(vignetteRadius, vignetteRadius * 0.5, vigD);\n c = mix(half3(vignetteColor), c, half(mix(1.0 - vignetteIntensity, 1.0, vigV)));\n\n return half4(c, 1.0);\n}\n";
302
+ declare const WEATHER_CLOUDS_SKSL = "\nuniform shader src;\nuniform float2 resolution;\nuniform float intensity;\nuniform float scrollOffsetX;\nuniform float direction;\n\nfloat hash(float2 p) { return fract(sin(dot(p, float2(127.1, 311.7))) * 43758.5453); }\nfloat vnoise(float2 p) {\n float2 i = floor(p); float2 f = fract(p);\n float2 u = f * f * (3.0 - 2.0 * f);\n return mix(mix(hash(i), hash(i + float2(1.0, 0.0)), u.x),\n mix(hash(i + float2(0.0, 1.0)), hash(i + float2(1.0, 1.0)), u.x), u.y);\n}\nfloat fbm(float2 p) { return vnoise(p) * 0.6 + vnoise(p * 2.3) * 0.4; }\n\nhalf4 main(float2 coord) {\n half4 scene = src.eval(coord);\n float2 uv = coord / resolution;\n // direction=0 → pure horizontal scroll (cos=1, sin=0), matching the pre-direction default.\n float rad = direction * 3.14159 / 180.0;\n float2 scrollDir = float2(cos(rad), sin(rad));\n float2 cloudUv = float2(\n uv.x + scrollOffsetX * scrollDir.x / resolution.x,\n uv.y * 0.5 + scrollOffsetX * scrollDir.y / resolution.y\n );\n float cloud = fbm(cloudUv * 3.0);\n float heightFade = smoothstep(0.6, 0.2, uv.y);\n float alpha = cloud * heightFade * intensity * 0.5;\n half3 cloudColor = half3(0.9, 0.9, 0.95);\n return half4(mix(scene.rgb, cloudColor, half(alpha)), scene.a);\n}\n";
303
+ declare const COMPOSITE_SKSL = "\nuniform shader src;\n\nuniform float2 resolution;\nuniform float vignetteIntensity;\nuniform float vignetteRadius;\nuniform float3 vignetteColor;\nuniform float bloomThreshold;\nuniform float bloomIntensity;\nuniform float flashIntensity;\nuniform float chromaAmount;\nuniform float crtScanlineIntensity;\nuniform float crtCurvature;\nuniform float crtEnabled;\nuniform float weatherKind;\nuniform float weatherIntensity;\nuniform float weatherDirection;\nuniform float weatherScrollX;\nuniform float weatherScrollY;\nuniform float3 weatherColor;\nuniform float weatherDensity;\nuniform float weatherStreakLen;\n\nfloat hash(float2 p) { return fract(sin(dot(p, float2(127.1, 311.7))) * 43758.5453); }\nfloat vnoise(float2 p) {\n float2 i = floor(p); float2 f = fract(p);\n float2 u = f * f * (3.0 - 2.0 * f);\n return mix(mix(hash(i), hash(i + float2(1.0, 0.0)), u.x),\n mix(hash(i + float2(0.0, 1.0)), hash(i + float2(1.0, 1.0)), u.x), u.y);\n}\nfloat fbm(float2 p) { return vnoise(p) * 0.6 + vnoise(p * 2.3) * 0.4; }\n\nhalf4 main(float2 coord) {\n // --- chromatic aberration ---\n float2 uv = coord / resolution;\n float2 chromaOffset = (uv - 0.5) * chromaAmount;\n half r = src.eval((uv - chromaOffset) * resolution).r;\n half g = src.eval(coord).g;\n half b = src.eval((uv + chromaOffset) * resolution).b;\n half3 c = half3(r, g, b);\n\n // --- CRT ---\n float2 crtCentered = uv - 0.5;\n float2 crtWarp = crtCentered * (1.0 + dot(crtCentered, crtCentered) * crtCurvature);\n float2 crtUv = crtWarp + 0.5;\n float crtMask = float(crtUv.x >= 0.0 && crtUv.x <= 1.0 && crtUv.y >= 0.0 && crtUv.y <= 1.0);\n {\n float2 chromaOffsetCrt = (crtUv - 0.5) * chromaAmount;\n half rCrt = src.eval((crtUv - chromaOffsetCrt) * resolution).r;\n half gCrt = src.eval(crtUv * resolution).g;\n half bCrt = src.eval((crtUv + chromaOffsetCrt) * resolution).b;\n half3 crtColor = half3(rCrt, gCrt, bCrt);\n float scan = 0.5 + 0.5 * sin(crtUv.y * resolution.y * 3.14159);\n float dim = mix(1.0, scan, crtScanlineIntensity);\n half3 crtResult = crtColor * half(dim);\n c = mix(c, mix(half3(0.0), crtResult, half(crtMask)), half(crtEnabled));\n }\n\n // --- bloom ---\n float luma = dot(c, half3(0.299, 0.587, 0.114));\n float bloomMask = smoothstep(bloomThreshold, 1.0, luma);\n c = c + c * half(bloomMask * bloomIntensity);\n\n // --- weather ---\n {\n float wk = weatherKind;\n if (wk >= 0.5 && wk < 1.5) {\n // rain — falling sawtooth streaks, 2 parallax layers, alpha-over blend\n // weatherScrollY is normalized [0,1) fall phase (increment speed*dt/screenH each tick)\n float rad = weatherDirection * 3.14159 / 180.0;\n float sinA = sin(rad);\n float cosA = cos(rad);\n float aspect = resolution.x / resolution.y;\n float perpAxis = (cosA * uv.x - sinA * uv.y) * aspect;\n float fallAxis = sinA * uv.x + cosA * uv.y;\n\n float coverage = 0.0;\n\n // near layer — COLS=45, tiling=6, brightness=1.0\n {\n float COLS = 45.0;\n float tiling = 6.0;\n float brightness = 1.0;\n float cid = floor(perpAxis * COLS);\n float colHash = hash(float2(cid, 0.0));\n if (colHash < weatherDensity) {\n float phase = hash(float2(cid, 1.0));\n float colBright = brightness * (0.6 + hash(float2(cid, 2.0)) * 0.4);\n float center = 0.5 + (hash(float2(cid, 3.0)) - 0.5) * 0.4;\n float seg = fract(fallAxis * tiling - weatherScrollY + phase);\n float coverageY = smoothstep(weatherStreakLen, 0.0, seg);\n float width = 0.04;\n float xMask = smoothstep(width, 0.0, abs(fract(perpAxis * COLS) - center));\n coverage = max(coverage, coverageY * xMask * colBright);\n }\n }\n\n // far layer — COLS=85, tiling=11, brightness=0.5\n {\n float COLS = 85.0;\n float tiling = 11.0;\n float brightness = 0.5;\n float cid = floor(perpAxis * COLS);\n float colHash = hash(float2(cid, 10.0));\n if (colHash < weatherDensity) {\n float phase = hash(float2(cid, 11.0));\n float colBright = brightness * (0.6 + hash(float2(cid, 12.0)) * 0.4);\n float center = 0.5 + (hash(float2(cid, 13.0)) - 0.5) * 0.4;\n float seg = fract(fallAxis * tiling - weatherScrollY + phase);\n float coverageY = smoothstep(weatherStreakLen, 0.0, seg);\n float width = 0.03;\n float xMask = smoothstep(width, 0.0, abs(fract(perpAxis * COLS) - center));\n coverage = max(coverage, coverageY * xMask * colBright);\n }\n }\n\n c = mix(c, half3(weatherColor), half(coverage * weatherIntensity));\n } else if (wk >= 1.5 && wk < 2.5) {\n // sun-shaft\n float rad = weatherDirection * 3.14159 / 180.0;\n float2 srcPoint = float2(0.5 + sin(rad) * 0.3, -0.05);\n float2 toFrag = uv - srcPoint;\n float angle = atan(toFrag.x, toFrag.y);\n float shafts = 0.5 + 0.5 * sin(angle * 8.0);\n float dist = length(toFrag);\n float falloff = 1.0 - smoothstep(0.0, 1.2, dist);\n half shaft = half(shafts * falloff * weatherIntensity * 0.35);\n c = c + half3(shaft * 0.95, shaft * 0.9, shaft * 0.6);\n } else if (wk >= 2.5 && wk < 3.5) {\n // dark-vignette\n float2 uvShift = uv - 0.5;\n float d2 = length(uvShift);\n float vd = smoothstep(0.3, 0.85, d2);\n half dark = half(vd * weatherIntensity);\n c = c * (1.0 - dark);\n } else if (wk >= 3.5) {\n // clouds\n float2 cloudUv = float2(uv.x + weatherScrollX / resolution.x, uv.y * 0.5);\n float cloud = fbm(cloudUv * 3.0);\n float heightFade = smoothstep(0.6, 0.2, uv.y);\n float alpha = cloud * heightFade * weatherIntensity * 0.5;\n half3 cloudColor = half3(0.9, 0.9, 0.95);\n c = mix(c, cloudColor, half(alpha));\n }\n }\n\n // --- grayscale flash ---\n float lumaFlash = dot(c, half3(0.299, 0.587, 0.114));\n half3 gray = half3(lumaFlash);\n c = mix(c, gray, half(flashIntensity));\n\n // --- vignette ---\n float2 vigCentered = uv - 0.5;\n float vigD = length(vigCentered);\n float vigV = smoothstep(vignetteRadius, vignetteRadius * 0.5, vigD);\n c = mix(half3(vignetteColor), c, half(mix(1.0 - vignetteIntensity, 1.0, vigV)));\n\n return half4(c, 1.0);\n}\n";
262
304
  declare const GOD_RAYS_SKSL = "\nuniform shader src;\nuniform float2 resolution;\nuniform float2 u_lightPos;\nuniform float3 u_color;\nuniform float u_intensity;\nuniform float u_decay;\nuniform float u_density;\nuniform float u_weight;\nuniform float u_samples;\n\nconst int MAX_SAMPLES = 64;\n\nhalf4 main(float2 coord) {\n float2 uv = coord / resolution;\n float2 delta = (u_lightPos - uv) * u_density / float(MAX_SAMPLES);\n float2 sampleUv = uv;\n float illuminationDecay = 1.0;\n half3 accumulated = half3(0.0);\n\n for (int i = 0; i < MAX_SAMPLES; i++) {\n if (float(i) >= u_samples) break;\n sampleUv += delta;\n half3 s = src.eval(sampleUv * resolution).rgb;\n accumulated += s * half(illuminationDecay) * half(u_weight);\n illuminationDecay *= u_decay;\n }\n\n half4 orig = src.eval(coord);\n half3 rays = accumulated * half3(u_color) * half(u_weight) * half(u_intensity);\n return half4(orig.rgb + rays, orig.a);\n}\n";
263
-
264
- declare function createNeutralLutBytes(): Uint8Array;
265
-
266
- /**
267
- * PostFxController — a small bridge that owns an EffectChain + PostFxRuntime
268
- * and adds two game-common conveniences:
269
- *
270
- * - hit-flash: spike a grayscale-flash effect to 1 and auto-decay it.
271
- * - motion scale: forward a single scalar to `EffectChain.setGlobalIntensity`
272
- * so a11y reduced-motion can null the whole stack at once.
273
- *
274
- * Extracted from Pan Tvardowski per Phase 11.14. Shape-compatible with the
275
- * PT `createPanTvardowskiPostFx` controller — games can swap implementations.
276
- */
277
-
278
- interface PostFxFlashConfig {
279
- /** Id of a `grayscale-flash` effect already added to the chain. */
280
- readonly id: string;
281
- /** Decay rate in intensity units per second. Defaults to 4.2 (≈0.24 s to fade from 1). */
282
- readonly decay?: number;
283
- }
284
- interface PostFxControllerOptions {
285
- /** Effects appended to the internal chain in the given order. */
286
- readonly effects: readonly Effect[];
287
- /** Skia module — `null` / `undefined` keeps `buildPass` returning `[]`. */
288
- readonly skia?: SkiaPostFxModuleLike | null;
289
- /** Optional hit-flash binding. */
290
- readonly flash?: PostFxFlashConfig;
291
- }
292
- interface PostFxController {
293
- readonly chain: EffectChain;
294
- readonly runtime: PostFxRuntime;
295
- /** Spike the configured flash effect to 1. No-op if no flash configured. */
296
- triggerHitFlash(): void;
297
- /**
298
- * Enable `effectId` immediately and auto-disable it after `decayMs` ms of
299
- * accumulated `update(dt)` time. Uniforms are restored to the pre-trigger
300
- * snapshot on expiry. Re-triggering before expiry resets the countdown.
301
- * No-op if `effectId` is not in the chain.
302
- */
303
- /** Optional pending PT wire-up in session #196. */
304
- triggerTimed?(effectId: string, decayMs: number): void;
305
- /** Advance auto-decaying effects (flash + timed triggers). */
306
- update(dt: number): void;
307
- /** Build the per-frame compiled pass list. Empty if runtime is unsupported. */
308
- buildPass(width: number, height: number): readonly CompiledPass[];
309
- /**
310
- * Global intensity multiplier forwarded to the chain — intended for a11y
311
- * reduced-motion (0 = fully neutralized, 1 = default).
312
- */
313
- setMotionScale(k: number): void;
314
- }
315
- declare function createPostFxController(options: PostFxControllerOptions): PostFxController;
316
-
305
+ //#endregion
317
306
  export { BLOOM_SKSL, type BloomUniforms, CHROMATIC_ABERRATION_SKSL, COLOR_GRADING_LUT_SKSL, COMPOSITE_SKSL, CRT_SKSL, type ChromaticAberrationUniforms, type ColorGradingLutUniforms, type CompiledPass, type CompiledShader, type CompositeUniforms, type CrtUniforms, type Effect, EffectChain, type EffectKind, type EffectUniforms, GOD_RAYS_SKSL, GRAYSCALE_FLASH_SKSL, type GodRaysUniforms, type GrayscaleFlashUniforms, type PostFxController, type PostFxControllerOptions, type PostFxFlashConfig, type PostFxRuntime, type SkImageLike, type SkiaPostFxModuleLike, type SkiaRuntimeEffectFactory, type SkiaRuntimeEffectLike, VIGNETTE_SKSL, type VignetteUniforms, WEATHER_CLOUDS_SKSL, WEATHER_DARK_VIGNETTE_SKSL, WEATHER_RAIN_SKSL, WEATHER_SUN_SHAFT_SKSL, type WeatherCloudsUniforms, type WeatherDarkVignetteUniforms, type WeatherRainUniforms, type WeatherSunShaftUniforms, createBloom, createChromaticAberration, createColorGradingLut, createCrt, createGodRays, createGrayscaleFlash, createNeutralLutBytes, createPostFxComposite, createPostFxController, createPostFxRuntime, createVignette, createWeatherClouds, createWeatherDarkVignette, createWeatherRain, createWeatherSunShaft };
307
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../src/runtime.ts","../src/effect.ts","../src/chain.ts","../src/controller.ts","../src/lut.ts","../src/shaders.ts"],"mappings":";;;;;;;;;;;;UA6BiB;WACN;;UAMM;EACf,uBAAuB,UAAU,yBAAyB;;EAE1D;;UAGe;EACf,KAAK,eAAe;;UAGL;EACf,eAAe;;UAGA;EACf,MAAM;EACN,QAAQ;;UAGO;;WAEN;;EAET,QAAQ,MAAM,aAAa;;EAE3B,aAAa,QAAQ,QAAQ,eAAe,iBAAiB;;EAE7D,UAAU,OAAO,aAAa,eAAe,iBAAiB;;EAE9D;;UAGe;EACf;EACA,MAAM;EACN,QAAQ;EACR,UAAU;;;;;;;;;;;;;EAaV,mBAAmB;;;;;;;iBAuBL,oBACd,MAAM,6BACN;EAAS;IACR;;;KCrGS;UAcK;EACf;EACA;EACA;;UAGe;EACf;;UAGe;EACf;;UAGe;EACf;EACA;;UAGe;EACf;EACA;;UAGe;EACf;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;UAGe;EACf,KAAK;EACL;;UAGe;EACf;EACA;EACA;EACA;EACA;EACA;;UAGe;EACf;EACA;;UAGe;EACf;;UAGe;EACf;EACA;EACA;;UAGe;EACf;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;KAGU;EACL;IAAqB;EACrB;IAA4B;EAC5B;IAAiC;EACjC;IAAgB;EAChB;IAAkB;EAClB;IAAqB;EACrB;IAA8B;EAC9B;IAAyB;EACzB;IAA8B;EAC9B;IAAkC;EAClC;IAA2B;EAC3B;IAAsB;UAEZ;EACf;EACA;EACA,UAAU;;iBAGI,eAAe,aAAiB,WAAU,QAAQ,oBAAyB;iBAa3E,qBACd,aACA,WAAU,QAAQ,0BACjB;iBAQa,0BACd,aACA,WAAU,QAAQ,+BACjB;iBAQa,UAAU,aAAY,WAAU,QAAQ,eAAoB;iBAY5D,YAAY,aAAc,WAAU,QAAQ,iBAAsB;iBAYlE,cAAc,aAAiB,WAAU,QAAQ,mBAAwB;iBAkBzE,sBACd,YACA;EAAY,KAAK;EAAa;IAC7B;iBAYa,kBACd,aACA,WAAU,QAAQ,uBACjB;iBAgBa,sBACd,aACA,WAAU,QAAQ,2BACjB;iBAYa,0BACd,aACA,WAAU,QAAQ,+BACjB;iBAWa,oBACd,aACA,WAAU,QAAQ,yBACjB;iBAaa,sBACd,aACA,WAAU,QAAQ,qBACjB;;;;;;;cC1RU;UACH;UACA;MAEJ;;;;;;;;;;;;;;;;;;MAqBA;MAIA,gBAAgB;;EAKpB,mBAAmB;;EAKnB,gBAAgB;;EAKhB,IAAI,QAAQ;;EAQZ,OAAO,eAAe,QAAQ;EAQ9B,OAAO;EAOP,IAAI,aAAa;EAIjB,WAAW,YAAY;;EAMvB,YAAY,YAAY,UAAU;EAWlC,KAAK,YAAY;EASjB;;EAKC,WAAW,iBAAiB;;;;UC5Fd;;WAEN;;WAEA;;UAGM;;WAEN,kBAAkB;;WAElB,OAAO;;WAEP,QAAQ;;UAGF;WACN,OAAO;WACP,SAAS;;EAElB;;;;;;;;EAQA,cAAc,kBAAkB;;EAEhC,OAAO;;EAEP,UAAU,eAAe,0BAA0B;;;;;EAKnD,eAAe;;iBAKD,uBAAuB,SAAS,0BAA0B;;;iBCxD1D,yBAAyB;;;;;;;;;;;;;cCS5B;cAiBA;cAaA;cAgBA;cAqBA;cAiBA;cA+BA;cAuEA;cAqBA;cAeA;cA8DA;cAqKA"}