@flare-engine/postfx 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/effect.ts","../src/chain.ts","../src/shaders.ts","../src/runtime.ts","../src/lut.ts","../src/controller.ts"],"sourcesContent":["/**\n * Effect — an ordered entry in the post-processing chain.\n *\n * `kind` selects a bundled shader (see `shaders.ts`). Each effect owns its\n * own uniform object; the runtime feeds these straight into the compiled\n * RuntimeEffect at draw time.\n */\n\nimport type { SkImageLike } from './runtime.js';\n\nexport type EffectKind =\n | 'vignette'\n | 'grayscale-flash'\n | 'chromatic-aberration'\n | 'crt'\n | 'bloom'\n | 'god-rays'\n | 'color-grading-lut'\n | 'weather-rain'\n | 'weather-sun-shaft'\n | 'weather-dark-vignette'\n | 'weather-clouds'\n | 'composite';\n\nexport interface VignetteUniforms {\n intensity: number;\n radius: number;\n color?: readonly [number, number, number];\n}\n\nexport interface GrayscaleFlashUniforms {\n intensity: number;\n}\n\nexport interface ChromaticAberrationUniforms {\n amount: number;\n}\n\nexport interface CrtUniforms {\n scanlineIntensity: number;\n curvature: number;\n}\n\nexport interface BloomUniforms {\n threshold: number;\n intensity: number;\n}\n\nexport interface GodRaysUniforms {\n lightX: number;\n lightY: number;\n color: readonly [number, number, number];\n intensity: number;\n decay: number;\n density: number;\n weight: number;\n samples: number;\n}\n\nexport interface ColorGradingLutUniforms {\n lut: SkImageLike;\n intensity: number;\n}\n\nexport interface WeatherRainUniforms {\n intensity: number;\n direction: number;\n speed: number;\n scrollOffsetY: number;\n color?: readonly [number, number, number];\n density?: number;\n streakLen?: number;\n}\n\nexport interface WeatherSunShaftUniforms {\n intensity: number;\n direction: number;\n}\n\nexport interface WeatherDarkVignetteUniforms {\n intensity: number;\n}\n\nexport interface WeatherCloudsUniforms {\n intensity: number;\n direction: number;\n speed: number;\n scrollOffsetX: number;\n}\n\nexport interface CompositeUniforms {\n vignetteIntensity: number;\n vignetteRadius: number;\n vignetteColor: readonly [number, number, number];\n bloomThreshold: number;\n bloomIntensity: number;\n flashIntensity: number;\n chromaAmount: number;\n crtScanlineIntensity: number;\n crtCurvature: number;\n crtEnabled: boolean;\n weatherKind: 0 | 1 | 2 | 3 | 4;\n weatherIntensity: number;\n weatherDirection: number;\n weatherScrollX: number;\n weatherScrollY: number;\n weatherColor: readonly [number, number, number];\n weatherDensity: number;\n weatherStreakLen: number;\n}\n\nexport type EffectUniforms =\n | ({ kind: 'vignette' } & VignetteUniforms)\n | ({ kind: 'grayscale-flash' } & GrayscaleFlashUniforms)\n | ({ kind: 'chromatic-aberration' } & ChromaticAberrationUniforms)\n | ({ kind: 'crt' } & CrtUniforms)\n | ({ kind: 'bloom' } & BloomUniforms)\n | ({ kind: 'god-rays' } & GodRaysUniforms)\n | ({ kind: 'color-grading-lut' } & ColorGradingLutUniforms)\n | ({ kind: 'weather-rain' } & WeatherRainUniforms)\n | ({ kind: 'weather-sun-shaft' } & WeatherSunShaftUniforms)\n | ({ kind: 'weather-dark-vignette' } & WeatherDarkVignetteUniforms)\n | ({ kind: 'weather-clouds' } & WeatherCloudsUniforms)\n | ({ kind: 'composite' } & CompositeUniforms);\n\nexport interface Effect {\n id: string;\n enabled: boolean;\n uniforms: EffectUniforms;\n}\n\nexport function createVignette(id = 'vignette', uniforms: Partial<VignetteUniforms> = {}): Effect {\n return {\n id,\n enabled: true,\n uniforms: {\n kind: 'vignette',\n intensity: uniforms.intensity ?? 0.4,\n radius: uniforms.radius ?? 0.8,\n color: uniforms.color ?? [0, 0, 0],\n },\n };\n}\n\nexport function createGrayscaleFlash(\n id = 'grayscale-flash',\n uniforms: Partial<GrayscaleFlashUniforms> = {},\n): Effect {\n return {\n id,\n enabled: true,\n uniforms: { kind: 'grayscale-flash', intensity: uniforms.intensity ?? 0 },\n };\n}\n\nexport function createChromaticAberration(\n id = 'chromatic-aberration',\n uniforms: Partial<ChromaticAberrationUniforms> = {},\n): Effect {\n return {\n id,\n enabled: true,\n uniforms: { kind: 'chromatic-aberration', amount: uniforms.amount ?? 0.005 },\n };\n}\n\nexport function createCrt(id = 'crt', uniforms: Partial<CrtUniforms> = {}): Effect {\n return {\n id,\n enabled: true,\n uniforms: {\n kind: 'crt',\n scanlineIntensity: uniforms.scanlineIntensity ?? 0.25,\n curvature: uniforms.curvature ?? 0.12,\n },\n };\n}\n\nexport function createBloom(id = 'bloom', uniforms: Partial<BloomUniforms> = {}): Effect {\n return {\n id,\n enabled: true,\n uniforms: {\n kind: 'bloom',\n threshold: uniforms.threshold ?? 0.7,\n intensity: uniforms.intensity ?? 0.6,\n },\n };\n}\n\nexport function createGodRays(id = 'god-rays', uniforms: Partial<GodRaysUniforms> = {}): Effect {\n return {\n id,\n enabled: true,\n uniforms: {\n kind: 'god-rays',\n lightX: uniforms.lightX ?? 0.5,\n lightY: uniforms.lightY ?? 0.1,\n color: uniforms.color ?? [1.0, 0.95, 0.8],\n intensity: uniforms.intensity ?? 0.6,\n decay: uniforms.decay ?? 0.96,\n density: uniforms.density ?? 0.5,\n weight: uniforms.weight ?? 0.4,\n samples: uniforms.samples ?? 48,\n },\n };\n}\n\nexport function createColorGradingLut(\n uniforms: { lut: SkImageLike; intensity?: number },\n id = 'color-grading-lut',\n): Effect {\n return {\n id,\n enabled: true,\n uniforms: {\n kind: 'color-grading-lut',\n lut: uniforms.lut,\n intensity: uniforms.intensity ?? 1.0,\n },\n };\n}\n\nexport function createWeatherRain(\n id = 'weather-rain',\n uniforms: Partial<WeatherRainUniforms> = {},\n): Effect {\n return {\n id,\n enabled: true,\n uniforms: {\n kind: 'weather-rain',\n intensity: uniforms.intensity ?? 0.5,\n direction: uniforms.direction ?? 0,\n speed: uniforms.speed ?? 0,\n scrollOffsetY: uniforms.scrollOffsetY ?? 0,\n color: uniforms.color ?? [1.0, 1.0, 1.0],\n density: uniforms.density ?? 0.5,\n streakLen: uniforms.streakLen ?? 0.15,\n },\n };\n}\n\nexport function createWeatherSunShaft(\n id = 'weather-sun-shaft',\n uniforms: Partial<WeatherSunShaftUniforms> = {},\n): Effect {\n return {\n id,\n enabled: true,\n uniforms: {\n kind: 'weather-sun-shaft',\n intensity: uniforms.intensity ?? 0.5,\n direction: uniforms.direction ?? 0,\n },\n };\n}\n\nexport function createWeatherDarkVignette(\n id = 'weather-dark-vignette',\n uniforms: Partial<WeatherDarkVignetteUniforms> = {},\n): Effect {\n return {\n id,\n enabled: true,\n uniforms: {\n kind: 'weather-dark-vignette',\n intensity: uniforms.intensity ?? 0.5,\n },\n };\n}\n\nexport function createWeatherClouds(\n id = 'weather-clouds',\n uniforms: Partial<WeatherCloudsUniforms> = {},\n): Effect {\n return {\n id,\n enabled: true,\n uniforms: {\n kind: 'weather-clouds',\n intensity: uniforms.intensity ?? 0.5,\n direction: uniforms.direction ?? 0,\n speed: uniforms.speed ?? 0,\n scrollOffsetX: uniforms.scrollOffsetX ?? 0,\n },\n };\n}\n\nexport function createPostFxComposite(\n id = 'composite',\n uniforms: Partial<CompositeUniforms> = {},\n): Effect {\n return {\n id,\n enabled: true,\n uniforms: {\n kind: 'composite',\n vignetteIntensity: uniforms.vignetteIntensity ?? 0,\n vignetteRadius: uniforms.vignetteRadius ?? 0.8,\n vignetteColor: uniforms.vignetteColor ?? [0, 0, 0],\n bloomThreshold: uniforms.bloomThreshold ?? 0.7,\n bloomIntensity: uniforms.bloomIntensity ?? 0,\n flashIntensity: uniforms.flashIntensity ?? 0,\n chromaAmount: uniforms.chromaAmount ?? 0,\n crtScanlineIntensity: uniforms.crtScanlineIntensity ?? 0.25,\n crtCurvature: uniforms.crtCurvature ?? 0.12,\n crtEnabled: uniforms.crtEnabled ?? false,\n weatherKind: uniforms.weatherKind ?? 0,\n weatherIntensity: uniforms.weatherIntensity ?? 0,\n weatherDirection: uniforms.weatherDirection ?? 0,\n weatherScrollX: uniforms.weatherScrollX ?? 0,\n weatherScrollY: uniforms.weatherScrollY ?? 0,\n weatherColor: uniforms.weatherColor ?? [1.0, 1.0, 1.0],\n weatherDensity: uniforms.weatherDensity ?? 0.5,\n weatherStreakLen: uniforms.weatherStreakLen ?? 0.15,\n },\n };\n}\n","import type { Effect, EffectUniforms } from './effect.js';\n\n/**\n * Ordered post-processing pipeline. Pure data structure — the runtime reads\n * the enabled entries in order and feeds each through its compiled shader.\n */\nexport class EffectChain {\n private _effects: Effect[] = [];\n private _globalIntensity = 1;\n\n get length(): number {\n return this._effects.length;\n }\n\n /**\n * Global intensity multiplier applied to every enabled effect's\n * \"strength\" uniform when the pass is built. Default 1 (no-op).\n *\n * Scales the following uniform fields at pass-build time:\n * - vignette.intensity\n * - grayscale-flash.intensity\n * - chromatic-aberration.amount\n * - crt.scanlineIntensity\n * - bloom.intensity\n *\n * Geometry-like uniforms (radius, curvature, threshold) are not scaled.\n * Intended for a11y reduced-motion (set to 0 to null all dampen-able\n * effects in one call) without touching per-effect state.\n *\n * Clamped to `>= 0`. Negative values are clamped to 0.\n */\n get globalIntensity(): number {\n return this._globalIntensity;\n }\n\n set globalIntensity(value: number) {\n this._globalIntensity = value < 0 ? 0 : value;\n }\n\n /** Convenience setter — mirrors the getter/setter. */\n setGlobalIntensity(value: number): void {\n this.globalIntensity = value;\n }\n\n /** Snapshot view of the chain. Do not mutate. */\n all(): readonly Effect[] {\n return this._effects;\n }\n\n /** Append an effect to the end of the chain. */\n add(effect: Effect): void {\n if (this._effects.some((e) => e.id === effect.id)) {\n throw new Error(`EffectChain: duplicate id \"${effect.id}\"`);\n }\n this._effects.push(effect);\n }\n\n /** Insert an effect at a specific index (clamped to [0, length]). */\n insert(index: number, effect: Effect): void {\n if (this._effects.some((e) => e.id === effect.id)) {\n throw new Error(`EffectChain: duplicate id \"${effect.id}\"`);\n }\n const i = Math.max(0, Math.min(this._effects.length, index));\n this._effects.splice(i, 0, effect);\n }\n\n remove(id: string): boolean {\n const i = this._effects.findIndex((e) => e.id === id);\n if (i === -1) return false;\n this._effects.splice(i, 1);\n return true;\n }\n\n get(id: string): Effect | null {\n return this._effects.find((e) => e.id === id) ?? null;\n }\n\n setEnabled(id: string, enabled: boolean): void {\n const e = this.get(id);\n if (e) e.enabled = enabled;\n }\n\n /** Update uniforms in place. Kind must match the existing effect's kind. */\n setUniforms(id: string, uniforms: EffectUniforms): void {\n const e = this.get(id);\n if (!e) return;\n if (e.uniforms.kind !== uniforms.kind) {\n throw new Error(\n `EffectChain: cannot change kind of \"${id}\" from \"${e.uniforms.kind}\" to \"${uniforms.kind}\"`,\n );\n }\n e.uniforms = uniforms;\n }\n\n move(id: string, newIndex: number): void {\n const i = this._effects.findIndex((e) => e.id === id);\n if (i === -1) return;\n const [removed] = this._effects.splice(i, 1);\n if (!removed) return;\n const clamped = Math.max(0, Math.min(this._effects.length, newIndex));\n this._effects.splice(clamped, 0, removed);\n }\n\n clear(): void {\n this._effects.length = 0;\n }\n\n /** Iterate only over enabled effects, in order. */\n *enabled(): IterableIterator<Effect> {\n for (const e of this._effects) {\n if (e.enabled) yield e;\n }\n }\n}\n","/**\n * SkSL runtime-effect shaders used by the postfx stack.\n *\n * Each source declares a `uniform shader src;` plus per-effect uniforms. They\n * are sampled as `src.eval(fragCoord)` against the scene's offscreen surface.\n *\n * Shader strings are emitted from the package as-is. Fallback: when\n * `Skia.RuntimeEffect.Make` is unavailable, `runtime.ts` short-circuits the\n * chain to a passthrough.\n */\n\nexport const VIGNETTE_SKSL = /* sksl */ `\nuniform shader src;\nuniform float2 resolution;\nuniform float intensity;\nuniform float radius;\nuniform float3 color;\n\nhalf4 main(float2 coord) {\n half4 c = src.eval(coord);\n float2 uv = coord / resolution;\n float2 centered = uv - 0.5;\n float d = length(centered);\n float v = smoothstep(radius, radius * 0.5, d);\n return half4(mix(half3(color), c.rgb, mix(1.0 - intensity, 1.0, v)), c.a);\n}\n`;\n\nexport const GRAYSCALE_FLASH_SKSL = /* sksl */ `\nuniform shader src;\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 half3 gray = half3(luma);\n half3 mixed = mix(c.rgb, gray, half(intensity));\n return half4(mixed, c.a);\n}\n`;\n\nexport const CHROMATIC_ABERRATION_SKSL = /* sksl */ `\nuniform shader src;\nuniform float2 resolution;\nuniform float amount;\n\nhalf4 main(float2 coord) {\n float2 uv = coord / resolution;\n float2 offset = (uv - 0.5) * amount;\n half r = src.eval((uv - offset) * resolution).r;\n half g = src.eval(coord).g;\n half b = src.eval((uv + offset) * resolution).b;\n half a = src.eval(coord).a;\n return half4(r, g, b, a);\n}\n`;\n\nexport const CRT_SKSL = /* 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`;\n\nexport const BLOOM_SKSL = /* 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`;\n\n// Trilinear LUT lookup against a 64³ identity stored as a 4096×64 strip.\n// src is the scene; lut is the 4096×64 RGBA image passed as a child texture.\nexport const COLOR_GRADING_LUT_SKSL = /* 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`;\n\nexport const WEATHER_RAIN_SKSL = /* 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`;\n\nexport const WEATHER_SUN_SHAFT_SKSL = /* 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`;\n\nexport const WEATHER_DARK_VIGNETTE_SKSL = /* 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`;\n\nexport const WEATHER_CLOUDS_SKSL = /* 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`;\n\n// Composite single-pass shader combining all always-runnable effects.\n//\n// Uniform layout (packed in this exact order by packUniforms — 24 floats total):\n// [0] resolution.x\n// [1] resolution.y\n// [2] vignetteIntensity\n// [3] vignetteRadius\n// [4] vignetteColor.r\n// [5] vignetteColor.g\n// [6] vignetteColor.b\n// [7] bloomThreshold\n// [8] bloomIntensity\n// [9] flashIntensity\n// [10] chromaAmount\n// [11] crtScanlineIntensity\n// [12] crtCurvature\n// [13] crtEnabled (0.0 or 1.0)\n// [14] weatherKind (0=none, 1=rain, 2=sun-shaft, 3=dark-vignette, 4=clouds)\n// [15] weatherIntensity\n// [16] weatherDirection (degrees)\n// [17] weatherScrollX (clouds x-offset)\n// [18] weatherScrollY (rain fall phase, normalized [0,1) — increment by speed*dt/screenHeight each tick)\n// [19] weatherColor.r\n// [20] weatherColor.g\n// [21] weatherColor.b\n// [22] weatherDensity (fraction of columns that carry a drop, 0–1; default 0.5)\n// [23] weatherStreakLen (lit fraction of each repeat period, 0–1; default 0.15)\n\nexport const COMPOSITE_SKSL = /* 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`;\n\n// Single-pass volumetric light scattering (GPU Gems 3, Ch. 13).\n// MAX_SAMPLES is a compile-time constant to satisfy SkSL's loop-count rules;\n// the runtime-provided u_samples float drives an early break so the actual\n// sample count is configurable without recompilation.\nexport const GOD_RAYS_SKSL = /* 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`;\n","import type { EffectChain } from './chain.js';\nimport type { Effect, EffectKind, EffectUniforms } from './effect.js';\nimport {\n BLOOM_SKSL,\n CHROMATIC_ABERRATION_SKSL,\n COLOR_GRADING_LUT_SKSL,\n COMPOSITE_SKSL,\n CRT_SKSL,\n GOD_RAYS_SKSL,\n GRAYSCALE_FLASH_SKSL,\n VIGNETTE_SKSL,\n WEATHER_CLOUDS_SKSL,\n WEATHER_DARK_VIGNETTE_SKSL,\n WEATHER_RAIN_SKSL,\n WEATHER_SUN_SHAFT_SKSL,\n} from './shaders.js';\n\n/**\n * Runtime — compiles SkSL sources on demand, builds uniform buffers per effect,\n * and exposes `buildPass` that the bridge calls to get pass descriptors for\n * rendering. Rendering itself is owned by the bridge in `@flare-engine/react`.\n */\n\n/**\n * Structural stub for a Skia image handle. No Skia import is needed here\n * because the renderer owns the actual image objects; this type exists only\n * so callers can assign real `SkImage` values to `CompiledPass.children`\n * without coupling this package to a specific Skia version.\n */\nexport interface SkImageLike {\n readonly _skImageBrand?: never;\n}\n\n/** Shared frozen sentinel — every pass with no extra children points here. */\nconst EMPTY_CHILDREN: readonly SkImageLike[] = Object.freeze([]);\n\nexport interface SkiaRuntimeEffectLike {\n makeShaderWithChildren(uniforms: number[], children?: unknown[]): unknown | null;\n /** Optional diagnostic — total float count expected by makeShaderWithChildren. */\n getUniformFloatCount?(): number;\n}\n\nexport interface SkiaRuntimeEffectFactory {\n Make(sksl: string): SkiaRuntimeEffectLike | null;\n}\n\nexport interface SkiaPostFxModuleLike {\n RuntimeEffect: SkiaRuntimeEffectFactory;\n}\n\nexport interface CompiledShader {\n kind: EffectKind;\n effect: SkiaRuntimeEffectLike;\n}\n\nexport interface PostFxRuntime {\n /** True when the underlying Skia module reported a working RuntimeEffect. */\n readonly supported: boolean;\n /** Lazily compile (and cache) the shader for a given effect. Returns null on unsupported. */\n compile(kind: EffectKind): CompiledShader | null;\n /** Pack an effect's uniforms into a Float32Array in the order the shader declares. */\n packUniforms(effect: Effect, width: number, height: number): Float32Array;\n /** Build a list of compiled pass descriptors in chain order for enabled effects. */\n buildPass(chain: EffectChain, width: number, height: number): CompiledPass[];\n /** Drop all cached compiled shaders. */\n reset(): void;\n}\n\nexport interface CompiledPass {\n id: string;\n kind: EffectKind;\n shader: SkiaRuntimeEffectLike;\n uniforms: Float32Array;\n /**\n * Convention A — extra sampler children only (excludes src).\n *\n * The renderer binds the implicit scene texture as `children[0]` at draw\n * time. This array holds any *additional* `uniform shader` slots declared\n * beyond `src` (e.g. a noise texture, an LUT). For every built-in effect\n * today there are no extra children, so this is always the frozen\n * `EMPTY_CHILDREN` sentinel — enabling zero-alloc sharing across passes.\n */\n children: readonly SkImageLike[];\n}\n\nconst SKSL_SOURCE: Record<EffectKind, string> = {\n vignette: VIGNETTE_SKSL,\n 'grayscale-flash': GRAYSCALE_FLASH_SKSL,\n 'chromatic-aberration': CHROMATIC_ABERRATION_SKSL,\n crt: CRT_SKSL,\n bloom: BLOOM_SKSL,\n 'god-rays': GOD_RAYS_SKSL,\n 'color-grading-lut': COLOR_GRADING_LUT_SKSL,\n 'weather-rain': WEATHER_RAIN_SKSL,\n 'weather-sun-shaft': WEATHER_SUN_SHAFT_SKSL,\n 'weather-dark-vignette': WEATHER_DARK_VIGNETTE_SKSL,\n 'weather-clouds': WEATHER_CLOUDS_SKSL,\n composite: COMPOSITE_SKSL,\n};\n\n/**\n * Create a runtime against the given Skia-like module. If the module is\n * absent or lacks `RuntimeEffect.Make`, returns a runtime with\n * `supported: false` — callers should fall through to a passthrough pipeline.\n */\nexport function createPostFxRuntime(skia: SkiaPostFxModuleLike | null): PostFxRuntime {\n const cache = new Map<EffectKind, CompiledShader>();\n const _loggedCompileFailures = new Set<EffectKind>();\n const _loggedCompileSuccesses = new Set<EffectKind>();\n const supported = skia !== null && typeof skia.RuntimeEffect?.Make === 'function';\n\n return {\n supported,\n compile(kind) {\n if (!supported || !skia) return null;\n const cached = cache.get(kind);\n if (cached) return cached;\n const effect = skia.RuntimeEffect.Make(SKSL_SOURCE[kind]);\n if (!effect) {\n if (!_loggedCompileFailures.has(kind)) {\n _loggedCompileFailures.add(kind);\n console.warn(\n `[postfx] RuntimeEffect.Make returned null for \"${kind}\" — shader failed to compile. Pass will be silently skipped.`,\n );\n }\n return null;\n }\n if (!_loggedCompileSuccesses.has(kind)) {\n _loggedCompileSuccesses.add(kind);\n if (typeof effect.getUniformFloatCount === 'function') {\n console.info(\n `[postfx] compiled \"${kind}\" — getUniformFloatCount=${effect.getUniformFloatCount()}`,\n );\n }\n }\n const compiled: CompiledShader = { kind, effect };\n cache.set(kind, compiled);\n return compiled;\n },\n packUniforms(effect, width, height) {\n return packUniforms(effect.uniforms, width, height);\n },\n buildPass(chain, width, height) {\n const out: CompiledPass[] = [];\n if (!supported) return out;\n const scale = chain.globalIntensity;\n for (const effect of chain.enabled()) {\n const compiled = this.compile(effect.uniforms.kind);\n if (!compiled) continue;\n const uniforms = packUniforms(effect.uniforms, width, height);\n if (scale !== 1) scaleIntensity(effect.uniforms.kind, uniforms, scale);\n const children: readonly SkImageLike[] =\n effect.uniforms.kind === 'color-grading-lut' ? [effect.uniforms.lut] : EMPTY_CHILDREN;\n out.push({\n id: effect.id,\n kind: effect.uniforms.kind,\n shader: compiled.effect,\n uniforms,\n children,\n });\n }\n return out;\n },\n reset() {\n cache.clear();\n },\n };\n}\n\nfunction packUniforms(u: EffectUniforms, width: number, height: number): Float32Array {\n switch (u.kind) {\n case 'vignette': {\n // resolution.xy, intensity, radius, color.rgb\n const col = u.color ?? [0, 0, 0];\n return new Float32Array([width, height, u.intensity, u.radius, col[0], col[1], col[2]]);\n }\n case 'grayscale-flash':\n return new Float32Array([u.intensity]);\n case 'chromatic-aberration':\n return new Float32Array([width, height, u.amount]);\n case 'crt':\n return new Float32Array([width, height, u.scanlineIntensity, u.curvature]);\n case 'bloom':\n return new Float32Array([width, height, u.threshold, u.intensity]);\n case 'god-rays': {\n const samples = Math.max(1, Math.min(64, Math.round(u.samples)));\n return new Float32Array([\n width,\n height,\n u.lightX,\n u.lightY,\n u.color[0],\n u.color[1],\n u.color[2],\n u.intensity,\n u.decay,\n u.density,\n u.weight,\n samples,\n ]);\n }\n case 'color-grading-lut':\n return new Float32Array([u.intensity]);\n // Weather kinds: TS interfaces are wider than the packed SkSL inputs (speed/direction\n // for clouds, speed for rain are B5-passthrough fields; the shader doesn't consume them).\n case 'weather-rain': {\n const col = u.color ?? [1.0, 1.0, 1.0];\n return new Float32Array([\n width,\n height,\n u.intensity,\n u.direction,\n u.scrollOffsetY,\n col[0],\n col[1],\n col[2],\n u.density ?? 0.5,\n u.streakLen ?? 0.15,\n ]);\n }\n case 'weather-sun-shaft':\n return new Float32Array([width, height, u.intensity, u.direction]);\n case 'weather-dark-vignette':\n return new Float32Array([width, height, u.intensity]);\n case 'weather-clouds':\n return new Float32Array([width, height, u.intensity, u.scrollOffsetX]);\n case 'composite':\n // Packed in uniform-declaration order (24 floats); see COMPOSITE_SKSL header comment.\n return new Float32Array([\n width,\n height,\n u.vignetteIntensity,\n u.vignetteRadius,\n u.vignetteColor[0],\n u.vignetteColor[1],\n u.vignetteColor[2],\n u.bloomThreshold,\n u.bloomIntensity,\n u.flashIntensity,\n u.chromaAmount,\n u.crtScanlineIntensity,\n u.crtCurvature,\n u.crtEnabled ? 1 : 0,\n u.weatherKind,\n u.weatherIntensity,\n u.weatherDirection,\n u.weatherScrollX,\n u.weatherScrollY,\n u.weatherColor[0],\n u.weatherColor[1],\n u.weatherColor[2],\n u.weatherDensity,\n u.weatherStreakLen,\n ]);\n }\n}\n\n/**\n * Scale the \"strength\" slot of a packed uniform buffer in place. Geometry\n * fields (radius, curvature, threshold) are intentionally left alone.\n */\nfunction scaleIntensity(kind: EffectKind, packed: Float32Array, scale: number): void {\n switch (kind) {\n case 'vignette':\n // [width, height, intensity, radius]\n packed[2] = (packed[2] ?? 0) * scale;\n return;\n case 'grayscale-flash':\n // [intensity]\n packed[0] = (packed[0] ?? 0) * scale;\n return;\n case 'chromatic-aberration':\n // [width, height, amount]\n packed[2] = (packed[2] ?? 0) * scale;\n return;\n case 'crt':\n // [width, height, scanlineIntensity, curvature]\n packed[2] = (packed[2] ?? 0) * scale;\n return;\n case 'bloom':\n // [width, height, threshold, intensity]\n packed[3] = (packed[3] ?? 0) * scale;\n return;\n case 'god-rays':\n // [width, height, lightX, lightY, colorR, colorG, colorB, intensity, decay, density, weight, samples]\n packed[7] = (packed[7] ?? 0) * scale;\n return;\n case 'color-grading-lut':\n // [intensity]\n packed[0] = (packed[0] ?? 0) * scale;\n return;\n case 'weather-rain':\n case 'weather-sun-shaft':\n case 'weather-dark-vignette':\n case 'weather-clouds':\n // [width, height, intensity, ...]\n packed[2] = (packed[2] ?? 0) * scale;\n return;\n case 'composite':\n // Scale all \"strength\" slots; leave geometry (vignetteRadius, bloomThreshold,\n // crtCurvature, weatherDirection) and the discrete crtEnabled flag alone.\n packed[2] = (packed[2] ?? 0) * scale; // vignetteIntensity\n packed[8] = (packed[8] ?? 0) * scale; // bloomIntensity\n packed[9] = (packed[9] ?? 0) * scale; // flashIntensity\n packed[10] = (packed[10] ?? 0) * scale; // chromaAmount\n packed[15] = (packed[15] ?? 0) * scale; // weatherIntensity\n return;\n }\n}\n","// Returns raw RGBA bytes for a 4096×64 identity LUT strip. The host wraps\n// this via Skia.MakeImage into an SkImage to pass as the lut child texture.\nexport function createNeutralLutBytes(): Uint8Array {\n const buf = new Uint8Array(4096 * 64 * 4);\n for (let b = 0; b < 64; b++) {\n for (let g = 0; g < 64; g++) {\n for (let r = 0; r < 64; r++) {\n const x = b * 64 + r;\n const y = g;\n const off = (y * 4096 + x) * 4;\n buf[off] = Math.round((r / 63) * 255);\n buf[off + 1] = Math.round((g / 63) * 255);\n buf[off + 2] = Math.round((b / 63) * 255);\n buf[off + 3] = 255;\n }\n }\n }\n return buf;\n}\n","/**\n * PostFxController — a small bridge that owns an EffectChain + PostFxRuntime\n * and adds two game-common conveniences:\n *\n * - hit-flash: spike a grayscale-flash effect to 1 and auto-decay it.\n * - motion scale: forward a single scalar to `EffectChain.setGlobalIntensity`\n * so a11y reduced-motion can null the whole stack at once.\n *\n * Extracted from Pan Tvardowski per Phase 11.14. Shape-compatible with the\n * PT `createPanTvardowskiPostFx` controller — games can swap implementations.\n */\n\nimport { EffectChain } from './chain.js';\nimport type { Effect, EffectUniforms } from './effect.js';\nimport { type CompiledPass, type PostFxRuntime, createPostFxRuntime } from './runtime.js';\nimport type { SkiaPostFxModuleLike } from './runtime.js';\n\nexport interface PostFxFlashConfig {\n /** Id of a `grayscale-flash` effect already added to the chain. */\n readonly id: string;\n /** Decay rate in intensity units per second. Defaults to 4.2 (≈0.24 s to fade from 1). */\n readonly decay?: number;\n}\n\nexport interface PostFxControllerOptions {\n /** Effects appended to the internal chain in the given order. */\n readonly effects: readonly Effect[];\n /** Skia module — `null` / `undefined` keeps `buildPass` returning `[]`. */\n readonly skia?: SkiaPostFxModuleLike | null;\n /** Optional hit-flash binding. */\n readonly flash?: PostFxFlashConfig;\n}\n\nexport interface PostFxController {\n readonly chain: EffectChain;\n readonly runtime: PostFxRuntime;\n /** Spike the configured flash effect to 1. No-op if no flash configured. */\n triggerHitFlash(): void;\n /**\n * Enable `effectId` immediately and auto-disable it after `decayMs` ms of\n * accumulated `update(dt)` time. Uniforms are restored to the pre-trigger\n * snapshot on expiry. Re-triggering before expiry resets the countdown.\n * No-op if `effectId` is not in the chain.\n */\n /** Optional pending PT wire-up in session #196. */\n triggerTimed?(effectId: string, decayMs: number): void;\n /** Advance auto-decaying effects (flash + timed triggers). */\n update(dt: number): void;\n /** Build the per-frame compiled pass list. Empty if runtime is unsupported. */\n buildPass(width: number, height: number): readonly CompiledPass[];\n /**\n * Global intensity multiplier forwarded to the chain — intended for a11y\n * reduced-motion (0 = fully neutralized, 1 = default).\n */\n setMotionScale(k: number): void;\n}\n\nconst DEFAULT_FLASH_DECAY = 4.2;\n\nexport function createPostFxController(options: PostFxControllerOptions): PostFxController {\n const chain = new EffectChain();\n for (let i = 0; i < options.effects.length; i++) {\n const e = options.effects[i];\n if (e) chain.add(e);\n }\n\n const runtime = createPostFxRuntime(options.skia ?? null);\n const flashCfg = options.flash;\n const flashDecay = flashCfg?.decay ?? DEFAULT_FLASH_DECAY;\n let flashIntensity = 0;\n\n // Allocated once at construction — never reallocated on the hot path.\n const timedRemaining = new Map<string, number>();\n const timedRestore = new Map<string, EffectUniforms>();\n\n return {\n chain,\n runtime,\n triggerHitFlash(): void {\n if (!flashCfg) return;\n flashIntensity = 1;\n chain.setUniforms(flashCfg.id, { kind: 'grayscale-flash', intensity: 1 });\n },\n triggerTimed(effectId: string, decayMs: number): void {\n const effect = chain.get(effectId);\n if (!effect) return;\n timedRestore.set(effectId, { ...effect.uniforms });\n chain.setEnabled(effectId, true);\n timedRemaining.set(effectId, decayMs);\n },\n update(dt: number): void {\n if (flashCfg && flashIntensity > 0) {\n flashIntensity = Math.max(0, flashIntensity - dt * flashDecay);\n chain.setUniforms(flashCfg.id, {\n kind: 'grayscale-flash',\n intensity: flashIntensity,\n });\n }\n\n if (timedRemaining.size > 0) {\n const dtMs = dt * 1000;\n // Collect expired ids first, then apply — safe under all JS engines.\n const expired: string[] = [];\n for (const [id, rem] of timedRemaining) {\n const next = rem - dtMs;\n if (next <= 0) {\n expired.push(id);\n } else {\n timedRemaining.set(id, next);\n }\n }\n if (expired.length > 0) {\n for (const id of expired) {\n const snapshot = timedRestore.get(id);\n if (snapshot !== undefined) chain.setUniforms(id, snapshot);\n chain.setEnabled(id, false);\n timedRemaining.delete(id);\n timedRestore.delete(id);\n }\n }\n }\n },\n buildPass(width: number, height: number): readonly CompiledPass[] {\n return runtime.buildPass(chain, width, height);\n },\n setMotionScale(k: number): void {\n chain.setGlobalIntensity(k);\n },\n };\n}\n"],"mappings":"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AuErC,IAAM;AAAA;AAAA,EAAoC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAqB1C,IAAM;AAAA;AAAA,EAAwC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAe9C,IAAM;AAAA;AAAA,EAAiC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAuDvC,IAAM;AAAA;AAAA,EAA4B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;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","names":[]}
1
+ {"version":3,"file":"index.mjs","names":["y"],"sources":["../src/chain.ts","../src/shaders.ts","../src/runtime.ts","../src/controller.ts","../src/effect.ts","../src/lut.ts"],"sourcesContent":["import type { Effect, EffectUniforms } from './effect.js';\n\n/**\n * Ordered post-processing pipeline. Pure data structure — the runtime reads\n * the enabled entries in order and feeds each through its compiled shader.\n */\nexport class EffectChain {\n private _effects: Effect[] = [];\n private _globalIntensity = 1;\n\n get length(): number {\n return this._effects.length;\n }\n\n /**\n * Global intensity multiplier applied to every enabled effect's\n * \"strength\" uniform when the pass is built. Default 1 (no-op).\n *\n * Scales the following uniform fields at pass-build time:\n * - vignette.intensity\n * - grayscale-flash.intensity\n * - chromatic-aberration.amount\n * - crt.scanlineIntensity\n * - bloom.intensity\n *\n * Geometry-like uniforms (radius, curvature, threshold) are not scaled.\n * Intended for a11y reduced-motion (set to 0 to null all dampen-able\n * effects in one call) without touching per-effect state.\n *\n * Clamped to `>= 0`. Negative values are clamped to 0.\n */\n get globalIntensity(): number {\n return this._globalIntensity;\n }\n\n set globalIntensity(value: number) {\n this._globalIntensity = value < 0 ? 0 : value;\n }\n\n /** Convenience setter — mirrors the getter/setter. */\n setGlobalIntensity(value: number): void {\n this.globalIntensity = value;\n }\n\n /** Snapshot view of the chain. Do not mutate. */\n all(): readonly Effect[] {\n return this._effects;\n }\n\n /** Append an effect to the end of the chain. */\n add(effect: Effect): void {\n if (this._effects.some((e) => e.id === effect.id)) {\n throw new Error(`EffectChain: duplicate id \"${effect.id}\"`);\n }\n this._effects.push(effect);\n }\n\n /** Insert an effect at a specific index (clamped to [0, length]). */\n insert(index: number, effect: Effect): void {\n if (this._effects.some((e) => e.id === effect.id)) {\n throw new Error(`EffectChain: duplicate id \"${effect.id}\"`);\n }\n const i = Math.max(0, Math.min(this._effects.length, index));\n this._effects.splice(i, 0, effect);\n }\n\n remove(id: string): boolean {\n const i = this._effects.findIndex((e) => e.id === id);\n if (i === -1) return false;\n this._effects.splice(i, 1);\n return true;\n }\n\n get(id: string): Effect | null {\n return this._effects.find((e) => e.id === id) ?? null;\n }\n\n setEnabled(id: string, enabled: boolean): void {\n const e = this.get(id);\n if (e) e.enabled = enabled;\n }\n\n /** Update uniforms in place. Kind must match the existing effect's kind. */\n setUniforms(id: string, uniforms: EffectUniforms): void {\n const e = this.get(id);\n if (!e) return;\n if (e.uniforms.kind !== uniforms.kind) {\n throw new Error(\n `EffectChain: cannot change kind of \"${id}\" from \"${e.uniforms.kind}\" to \"${uniforms.kind}\"`,\n );\n }\n e.uniforms = uniforms;\n }\n\n move(id: string, newIndex: number): void {\n const i = this._effects.findIndex((e) => e.id === id);\n if (i === -1) return;\n const [removed] = this._effects.splice(i, 1);\n if (!removed) return;\n const clamped = Math.max(0, Math.min(this._effects.length, newIndex));\n this._effects.splice(clamped, 0, removed);\n }\n\n clear(): void {\n this._effects.length = 0;\n }\n\n /** Iterate only over enabled effects, in order. */\n *enabled(): IterableIterator<Effect> {\n for (const e of this._effects) {\n if (e.enabled) yield e;\n }\n }\n}\n","/**\n * SkSL runtime-effect shaders used by the postfx stack.\n *\n * Each source declares a `uniform shader src;` plus per-effect uniforms. They\n * are sampled as `src.eval(fragCoord)` against the scene's offscreen surface.\n *\n * Shader strings are emitted from the package as-is. Fallback: when\n * `Skia.RuntimeEffect.Make` is unavailable, `runtime.ts` short-circuits the\n * chain to a passthrough.\n */\n\nexport const VIGNETTE_SKSL = /* sksl */ `\nuniform shader src;\nuniform float2 resolution;\nuniform float intensity;\nuniform float radius;\nuniform float3 color;\n\nhalf4 main(float2 coord) {\n half4 c = src.eval(coord);\n float2 uv = coord / resolution;\n float2 centered = uv - 0.5;\n float d = length(centered);\n float v = smoothstep(radius, radius * 0.5, d);\n return half4(mix(half3(color), c.rgb, mix(1.0 - intensity, 1.0, v)), c.a);\n}\n`;\n\nexport const GRAYSCALE_FLASH_SKSL = /* sksl */ `\nuniform shader src;\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 half3 gray = half3(luma);\n half3 mixed = mix(c.rgb, gray, half(intensity));\n return half4(mixed, c.a);\n}\n`;\n\nexport const CHROMATIC_ABERRATION_SKSL = /* sksl */ `\nuniform shader src;\nuniform float2 resolution;\nuniform float amount;\n\nhalf4 main(float2 coord) {\n float2 uv = coord / resolution;\n float2 offset = (uv - 0.5) * amount;\n half r = src.eval((uv - offset) * resolution).r;\n half g = src.eval(coord).g;\n half b = src.eval((uv + offset) * resolution).b;\n half a = src.eval(coord).a;\n return half4(r, g, b, a);\n}\n`;\n\nexport const CRT_SKSL = /* 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`;\n\nexport const BLOOM_SKSL = /* 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`;\n\n// Trilinear LUT lookup against a 64³ identity stored as a 4096×64 strip.\n// src is the scene; lut is the 4096×64 RGBA image passed as a child texture.\nexport const COLOR_GRADING_LUT_SKSL = /* 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`;\n\nexport const WEATHER_RAIN_SKSL = /* 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`;\n\nexport const WEATHER_SUN_SHAFT_SKSL = /* 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`;\n\nexport const WEATHER_DARK_VIGNETTE_SKSL = /* 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`;\n\nexport const WEATHER_CLOUDS_SKSL = /* 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`;\n\n// Composite single-pass shader combining all always-runnable effects.\n//\n// Uniform layout (packed in this exact order by packUniforms — 24 floats total):\n// [0] resolution.x\n// [1] resolution.y\n// [2] vignetteIntensity\n// [3] vignetteRadius\n// [4] vignetteColor.r\n// [5] vignetteColor.g\n// [6] vignetteColor.b\n// [7] bloomThreshold\n// [8] bloomIntensity\n// [9] flashIntensity\n// [10] chromaAmount\n// [11] crtScanlineIntensity\n// [12] crtCurvature\n// [13] crtEnabled (0.0 or 1.0)\n// [14] weatherKind (0=none, 1=rain, 2=sun-shaft, 3=dark-vignette, 4=clouds)\n// [15] weatherIntensity\n// [16] weatherDirection (degrees)\n// [17] weatherScrollX (clouds x-offset)\n// [18] weatherScrollY (rain fall phase, normalized [0,1) — increment by speed*dt/screenHeight each tick)\n// [19] weatherColor.r\n// [20] weatherColor.g\n// [21] weatherColor.b\n// [22] weatherDensity (fraction of columns that carry a drop, 0–1; default 0.5)\n// [23] weatherStreakLen (lit fraction of each repeat period, 0–1; default 0.15)\n\nexport const COMPOSITE_SKSL = /* 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`;\n\n// Single-pass volumetric light scattering (GPU Gems 3, Ch. 13).\n// MAX_SAMPLES is a compile-time constant to satisfy SkSL's loop-count rules;\n// the runtime-provided u_samples float drives an early break so the actual\n// sample count is configurable without recompilation.\nexport const GOD_RAYS_SKSL = /* 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`;\n","import type { EffectChain } from './chain.js';\nimport type { Effect, EffectKind, EffectUniforms } from './effect.js';\nimport {\n BLOOM_SKSL,\n CHROMATIC_ABERRATION_SKSL,\n COLOR_GRADING_LUT_SKSL,\n COMPOSITE_SKSL,\n CRT_SKSL,\n GOD_RAYS_SKSL,\n GRAYSCALE_FLASH_SKSL,\n VIGNETTE_SKSL,\n WEATHER_CLOUDS_SKSL,\n WEATHER_DARK_VIGNETTE_SKSL,\n WEATHER_RAIN_SKSL,\n WEATHER_SUN_SHAFT_SKSL,\n} from './shaders.js';\n\n/**\n * Runtime — compiles SkSL sources on demand, builds uniform buffers per effect,\n * and exposes `buildPass` that the bridge calls to get pass descriptors for\n * rendering. Rendering itself is owned by the bridge in `@flare-engine/react`.\n */\n\n/**\n * Structural stub for a Skia image handle. No Skia import is needed here\n * because the renderer owns the actual image objects; this type exists only\n * so callers can assign real `SkImage` values to `CompiledPass.children`\n * without coupling this package to a specific Skia version.\n */\nexport interface SkImageLike {\n readonly _skImageBrand?: never;\n}\n\n/** Shared frozen sentinel — every pass with no extra children points here. */\nconst EMPTY_CHILDREN: readonly SkImageLike[] = Object.freeze([]);\n\nexport interface SkiaRuntimeEffectLike {\n makeShaderWithChildren(uniforms: Float32Array, children?: unknown[]): unknown | null;\n /** Optional diagnostic — total float count expected by makeShaderWithChildren. */\n getUniformFloatCount?(): number;\n}\n\nexport interface SkiaRuntimeEffectFactory {\n Make(sksl: string): SkiaRuntimeEffectLike | null;\n}\n\nexport interface SkiaPostFxModuleLike {\n RuntimeEffect: SkiaRuntimeEffectFactory;\n}\n\nexport interface CompiledShader {\n kind: EffectKind;\n effect: SkiaRuntimeEffectLike;\n}\n\nexport interface PostFxRuntime {\n /** True when the underlying Skia module reported a working RuntimeEffect. */\n readonly supported: boolean;\n /** Lazily compile (and cache) the shader for a given effect. Returns null on unsupported. */\n compile(kind: EffectKind): CompiledShader | null;\n /** Pack an effect's uniforms into a Float32Array in the order the shader declares. */\n packUniforms(effect: Effect, width: number, height: number): Float32Array;\n /** Build a list of compiled pass descriptors in chain order for enabled effects. */\n buildPass(chain: EffectChain, width: number, height: number): CompiledPass[];\n /** Drop all cached compiled shaders. */\n reset(): void;\n}\n\nexport interface CompiledPass {\n id: string;\n kind: EffectKind;\n shader: SkiaRuntimeEffectLike;\n uniforms: Float32Array;\n /**\n * Convention A — extra sampler children only (excludes src).\n *\n * The renderer binds the implicit scene texture as `children[0]` at draw\n * time. This array holds any *additional* `uniform shader` slots declared\n * beyond `src` (e.g. a noise texture, an LUT). Every built-in effect\n * *except* `color-grading-lut` has no extra children, so this is the frozen\n * `EMPTY_CHILDREN` sentinel — enabling zero-alloc sharing across those\n * passes. `color-grading-lut` yields exactly one extra child — the LUT\n * `SkImage` (`effect.uniforms.lut`) — bound as the shader's second\n * `uniform shader` (index 1) after the implicit `src` (index 0).\n */\n children: readonly SkImageLike[];\n}\n\nconst SKSL_SOURCE: Record<EffectKind, string> = {\n vignette: VIGNETTE_SKSL,\n 'grayscale-flash': GRAYSCALE_FLASH_SKSL,\n 'chromatic-aberration': CHROMATIC_ABERRATION_SKSL,\n crt: CRT_SKSL,\n bloom: BLOOM_SKSL,\n 'god-rays': GOD_RAYS_SKSL,\n 'color-grading-lut': COLOR_GRADING_LUT_SKSL,\n 'weather-rain': WEATHER_RAIN_SKSL,\n 'weather-sun-shaft': WEATHER_SUN_SHAFT_SKSL,\n 'weather-dark-vignette': WEATHER_DARK_VIGNETTE_SKSL,\n 'weather-clouds': WEATHER_CLOUDS_SKSL,\n composite: COMPOSITE_SKSL,\n};\n\n/**\n * Create a runtime against the given Skia-like module. If the module is\n * absent or lacks `RuntimeEffect.Make`, returns a runtime with\n * `supported: false` — callers should fall through to a passthrough pipeline.\n */\nexport function createPostFxRuntime(\n skia: SkiaPostFxModuleLike | null,\n opts?: { verbose?: boolean },\n): PostFxRuntime {\n const cache = new Map<EffectKind, CompiledShader>();\n const _loggedCompileFailures = new Set<EffectKind>();\n const _loggedCompileSuccesses = new Set<EffectKind>();\n const supported = skia !== null && typeof skia.RuntimeEffect?.Make === 'function';\n\n return {\n supported,\n compile(kind) {\n if (!supported || !skia) return null;\n const cached = cache.get(kind);\n if (cached) return cached;\n const effect = skia.RuntimeEffect.Make(SKSL_SOURCE[kind]);\n if (!effect) {\n if (!_loggedCompileFailures.has(kind)) {\n _loggedCompileFailures.add(kind);\n console.warn(\n `[postfx] RuntimeEffect.Make returned null for \"${kind}\" — shader failed to compile. Pass will be silently skipped.`,\n );\n }\n return null;\n }\n if (!_loggedCompileSuccesses.has(kind)) {\n _loggedCompileSuccesses.add(kind);\n if (opts?.verbose === true && typeof effect.getUniformFloatCount === 'function') {\n console.info(\n `[postfx] compiled \"${kind}\" — getUniformFloatCount=${effect.getUniformFloatCount()}`,\n );\n }\n }\n const compiled: CompiledShader = { kind, effect };\n cache.set(kind, compiled);\n return compiled;\n },\n packUniforms(effect, width, height) {\n return packUniforms(effect.uniforms, width, height);\n },\n buildPass(chain, width, height) {\n const out: CompiledPass[] = [];\n if (!supported) return out;\n const scale = chain.globalIntensity;\n for (const effect of chain.enabled()) {\n const compiled = this.compile(effect.uniforms.kind);\n if (!compiled) continue;\n const uniforms = packUniforms(effect.uniforms, width, height);\n if (scale !== 1) scaleIntensity(effect.uniforms.kind, uniforms, scale);\n const children: readonly SkImageLike[] =\n effect.uniforms.kind === 'color-grading-lut' ? [effect.uniforms.lut] : EMPTY_CHILDREN;\n out.push({\n id: effect.id,\n kind: effect.uniforms.kind,\n shader: compiled.effect,\n uniforms,\n children,\n });\n }\n return out;\n },\n reset() {\n cache.clear();\n },\n };\n}\n\nfunction packUniforms(u: EffectUniforms, width: number, height: number): Float32Array {\n switch (u.kind) {\n case 'vignette': {\n // resolution.xy, intensity, radius, color.rgb\n const col = u.color ?? [0, 0, 0];\n return new Float32Array([width, height, u.intensity, u.radius, col[0], col[1], col[2]]);\n }\n case 'grayscale-flash':\n return new Float32Array([u.intensity]);\n case 'chromatic-aberration':\n return new Float32Array([width, height, u.amount]);\n case 'crt':\n return new Float32Array([width, height, u.scanlineIntensity, u.curvature]);\n case 'bloom':\n return new Float32Array([width, height, u.threshold, u.intensity]);\n case 'god-rays': {\n const samples = Math.max(1, Math.min(64, Math.round(u.samples)));\n return new Float32Array([\n width,\n height,\n u.lightX,\n u.lightY,\n u.color[0],\n u.color[1],\n u.color[2],\n u.intensity,\n u.decay,\n u.density,\n u.weight,\n samples,\n ]);\n }\n case 'color-grading-lut':\n return new Float32Array([u.intensity]);\n // Weather kinds: direction is packed and consumed by both rain and clouds shaders.\n // speed was removed from WeatherRainUniforms and WeatherCloudsUniforms.\n case 'weather-rain': {\n const col = u.color ?? [1.0, 1.0, 1.0];\n return new Float32Array([\n width,\n height,\n u.intensity,\n u.direction,\n u.scrollOffsetY,\n col[0],\n col[1],\n col[2],\n u.density ?? 0.5,\n u.streakLen ?? 0.15,\n ]);\n }\n case 'weather-sun-shaft':\n return new Float32Array([width, height, u.intensity, u.direction]);\n case 'weather-dark-vignette':\n return new Float32Array([width, height, u.intensity]);\n case 'weather-clouds':\n // Packed in uniform-declaration order: resolution.xy, intensity, scrollOffsetX, direction.\n return new Float32Array([width, height, u.intensity, u.scrollOffsetX, u.direction]);\n case 'composite':\n // Packed in uniform-declaration order (24 floats); see COMPOSITE_SKSL header comment.\n return new Float32Array([\n width,\n height,\n u.vignetteIntensity,\n u.vignetteRadius,\n u.vignetteColor[0],\n u.vignetteColor[1],\n u.vignetteColor[2],\n u.bloomThreshold,\n u.bloomIntensity,\n u.flashIntensity,\n u.chromaAmount,\n u.crtScanlineIntensity,\n u.crtCurvature,\n u.crtEnabled ? 1 : 0,\n u.weatherKind,\n u.weatherIntensity,\n u.weatherDirection,\n u.weatherScrollX,\n u.weatherScrollY,\n u.weatherColor[0],\n u.weatherColor[1],\n u.weatherColor[2],\n u.weatherDensity,\n u.weatherStreakLen,\n ]);\n }\n}\n\n/**\n * Scale the \"strength\" slot of a packed uniform buffer in place. Geometry\n * fields (radius, curvature, threshold) are intentionally left alone.\n */\nfunction scaleIntensity(kind: EffectKind, packed: Float32Array, scale: number): void {\n switch (kind) {\n case 'vignette':\n // [width, height, intensity, radius]\n packed[2] = (packed[2] ?? 0) * scale;\n return;\n case 'grayscale-flash':\n // [intensity]\n packed[0] = (packed[0] ?? 0) * scale;\n return;\n case 'chromatic-aberration':\n // [width, height, amount]\n packed[2] = (packed[2] ?? 0) * scale;\n return;\n case 'crt':\n // [width, height, scanlineIntensity, curvature]\n packed[2] = (packed[2] ?? 0) * scale;\n return;\n case 'bloom':\n // [width, height, threshold, intensity]\n packed[3] = (packed[3] ?? 0) * scale;\n return;\n case 'god-rays':\n // [width, height, lightX, lightY, colorR, colorG, colorB, intensity, decay, density, weight, samples]\n packed[7] = (packed[7] ?? 0) * scale;\n return;\n case 'color-grading-lut':\n // [intensity]\n packed[0] = (packed[0] ?? 0) * scale;\n return;\n case 'weather-rain':\n case 'weather-sun-shaft':\n case 'weather-dark-vignette':\n case 'weather-clouds':\n // [width, height, intensity, ...]\n packed[2] = (packed[2] ?? 0) * scale;\n return;\n case 'composite':\n // Scale all \"strength\" slots; leave geometry (vignetteRadius, bloomThreshold,\n // crtCurvature, weatherDirection) and the discrete crtEnabled flag alone.\n packed[2] = (packed[2] ?? 0) * scale; // vignetteIntensity\n packed[8] = (packed[8] ?? 0) * scale; // bloomIntensity\n packed[9] = (packed[9] ?? 0) * scale; // flashIntensity\n packed[10] = (packed[10] ?? 0) * scale; // chromaAmount\n packed[15] = (packed[15] ?? 0) * scale; // weatherIntensity\n return;\n }\n}\n","/**\n * PostFxController — a reusable bridge that owns an EffectChain + PostFxRuntime\n * and adds two game-common conveniences:\n *\n * - hit-flash: spike a grayscale-flash effect to 1 and auto-decay it.\n * - motion scale: forward a single scalar to `EffectChain.setGlobalIntensity`\n * so a11y reduced-motion can null the whole stack at once.\n *\n * Games can instantiate multiple controllers and swap implementations freely.\n */\n\nimport { EffectChain } from './chain.js';\nimport type { Effect, EffectUniforms } from './effect.js';\nimport type { SkiaPostFxModuleLike } from './runtime.js';\nimport { type CompiledPass, createPostFxRuntime, type PostFxRuntime } from './runtime.js';\n\nexport interface PostFxFlashConfig {\n /** Id of a `grayscale-flash` effect already added to the chain. */\n readonly id: string;\n /** Decay rate in intensity units per second. Defaults to 4.2 (≈0.24 s to fade from 1). */\n readonly decay?: number;\n}\n\nexport interface PostFxControllerOptions {\n /** Effects appended to the internal chain in the given order. */\n readonly effects: readonly Effect[];\n /** Skia module — `null` / `undefined` keeps `buildPass` returning `[]`. */\n readonly skia?: SkiaPostFxModuleLike | null;\n /** Optional hit-flash binding. */\n readonly flash?: PostFxFlashConfig;\n}\n\nexport interface PostFxController {\n readonly chain: EffectChain;\n readonly runtime: PostFxRuntime;\n /** Spike the configured flash effect to 1. No-op if no flash configured. */\n triggerHitFlash(): void;\n /**\n * Enable `effectId` immediately and auto-disable it after `decayMs` ms of\n * accumulated `update(dt)` time. Uniforms are restored to the pre-trigger\n * snapshot on expiry. Re-triggering before expiry resets the countdown.\n * No-op if `effectId` is not in the chain.\n */\n /** Optional — enable an effect for `decayMs` ms then restore its pre-trigger uniforms. No-op if id is not in the chain. */\n triggerTimed?(effectId: string, decayMs: number): void;\n /** Advance auto-decaying effects (flash + timed triggers). */\n update(dt: number): void;\n /** Build the per-frame compiled pass list. Empty if runtime is unsupported. */\n buildPass(width: number, height: number): readonly CompiledPass[];\n /**\n * Global intensity multiplier forwarded to the chain — intended for a11y\n * reduced-motion (0 = fully neutralized, 1 = default).\n */\n setMotionScale(k: number): void;\n}\n\nconst DEFAULT_FLASH_DECAY = 4.2;\n\nexport function createPostFxController(options: PostFxControllerOptions): PostFxController {\n const chain = new EffectChain();\n for (let i = 0; i < options.effects.length; i++) {\n const e = options.effects[i];\n if (e) chain.add(e);\n }\n\n const runtime = createPostFxRuntime(options.skia ?? null);\n const flashCfg = options.flash;\n const flashDecay = flashCfg?.decay ?? DEFAULT_FLASH_DECAY;\n let flashIntensity = 0;\n\n // Allocated once at construction — never reallocated on the hot path.\n const timedRemaining = new Map<string, number>();\n const timedRestore = new Map<string, EffectUniforms>();\n\n return {\n chain,\n runtime,\n triggerHitFlash(): void {\n if (!flashCfg) return;\n flashIntensity = 1;\n chain.setUniforms(flashCfg.id, { kind: 'grayscale-flash', intensity: 1 });\n },\n triggerTimed(effectId: string, decayMs: number): void {\n const effect = chain.get(effectId);\n if (!effect) return;\n timedRestore.set(effectId, { ...effect.uniforms });\n chain.setEnabled(effectId, true);\n timedRemaining.set(effectId, decayMs);\n },\n update(dt: number): void {\n if (flashCfg && flashIntensity > 0) {\n flashIntensity = Math.max(0, flashIntensity - dt * flashDecay);\n chain.setUniforms(flashCfg.id, {\n kind: 'grayscale-flash',\n intensity: flashIntensity,\n });\n }\n\n if (timedRemaining.size > 0) {\n const dtMs = dt * 1000;\n // Collect expired ids first, then apply — safe under all JS engines.\n const expired: string[] = [];\n for (const [id, rem] of timedRemaining) {\n const next = rem - dtMs;\n if (next <= 0) {\n expired.push(id);\n } else {\n timedRemaining.set(id, next);\n }\n }\n if (expired.length > 0) {\n for (const id of expired) {\n const snapshot = timedRestore.get(id);\n if (snapshot !== undefined) chain.setUniforms(id, snapshot);\n chain.setEnabled(id, false);\n timedRemaining.delete(id);\n timedRestore.delete(id);\n }\n }\n }\n },\n buildPass(width: number, height: number): readonly CompiledPass[] {\n return runtime.buildPass(chain, width, height);\n },\n setMotionScale(k: number): void {\n chain.setGlobalIntensity(k);\n },\n };\n}\n","/**\n * Effect — an ordered entry in the post-processing chain.\n *\n * `kind` selects a bundled shader (see `shaders.ts`). Each effect owns its\n * own uniform object; the runtime feeds these straight into the compiled\n * RuntimeEffect at draw time.\n */\n\nimport type { SkImageLike } from './runtime.js';\n\nexport type EffectKind =\n | 'vignette'\n | 'grayscale-flash'\n | 'chromatic-aberration'\n | 'crt'\n | 'bloom'\n | 'god-rays'\n | 'color-grading-lut'\n | 'weather-rain'\n | 'weather-sun-shaft'\n | 'weather-dark-vignette'\n | 'weather-clouds'\n | 'composite';\n\nexport interface VignetteUniforms {\n intensity: number;\n radius: number;\n color?: readonly [number, number, number];\n}\n\nexport interface GrayscaleFlashUniforms {\n intensity: number;\n}\n\nexport interface ChromaticAberrationUniforms {\n amount: number;\n}\n\nexport interface CrtUniforms {\n scanlineIntensity: number;\n curvature: number;\n}\n\nexport interface BloomUniforms {\n threshold: number;\n intensity: number;\n}\n\nexport interface GodRaysUniforms {\n lightX: number;\n lightY: number;\n color: readonly [number, number, number];\n intensity: number;\n decay: number;\n density: number;\n weight: number;\n samples: number;\n}\n\nexport interface ColorGradingLutUniforms {\n lut: SkImageLike;\n intensity: number;\n}\n\nexport interface WeatherRainUniforms {\n intensity: number;\n direction: number;\n scrollOffsetY: number;\n color?: readonly [number, number, number];\n density?: number;\n streakLen?: number;\n}\n\nexport interface WeatherSunShaftUniforms {\n intensity: number;\n direction: number;\n}\n\nexport interface WeatherDarkVignetteUniforms {\n intensity: number;\n}\n\nexport interface WeatherCloudsUniforms {\n intensity: number;\n direction: number;\n scrollOffsetX: number;\n}\n\nexport interface CompositeUniforms {\n vignetteIntensity: number;\n vignetteRadius: number;\n vignetteColor: readonly [number, number, number];\n bloomThreshold: number;\n bloomIntensity: number;\n flashIntensity: number;\n chromaAmount: number;\n crtScanlineIntensity: number;\n crtCurvature: number;\n crtEnabled: boolean;\n weatherKind: 0 | 1 | 2 | 3 | 4;\n weatherIntensity: number;\n weatherDirection: number;\n weatherScrollX: number;\n weatherScrollY: number;\n weatherColor: readonly [number, number, number];\n weatherDensity: number;\n weatherStreakLen: number;\n}\n\nexport type EffectUniforms =\n | ({ kind: 'vignette' } & VignetteUniforms)\n | ({ kind: 'grayscale-flash' } & GrayscaleFlashUniforms)\n | ({ kind: 'chromatic-aberration' } & ChromaticAberrationUniforms)\n | ({ kind: 'crt' } & CrtUniforms)\n | ({ kind: 'bloom' } & BloomUniforms)\n | ({ kind: 'god-rays' } & GodRaysUniforms)\n | ({ kind: 'color-grading-lut' } & ColorGradingLutUniforms)\n | ({ kind: 'weather-rain' } & WeatherRainUniforms)\n | ({ kind: 'weather-sun-shaft' } & WeatherSunShaftUniforms)\n | ({ kind: 'weather-dark-vignette' } & WeatherDarkVignetteUniforms)\n | ({ kind: 'weather-clouds' } & WeatherCloudsUniforms)\n | ({ kind: 'composite' } & CompositeUniforms);\n\nexport interface Effect {\n id: string;\n enabled: boolean;\n uniforms: EffectUniforms;\n}\n\nexport function createVignette(id = 'vignette', uniforms: Partial<VignetteUniforms> = {}): Effect {\n return {\n id,\n enabled: true,\n uniforms: {\n kind: 'vignette',\n intensity: uniforms.intensity ?? 0.4,\n radius: uniforms.radius ?? 0.8,\n color: uniforms.color ?? [0, 0, 0],\n },\n };\n}\n\nexport function createGrayscaleFlash(\n id = 'grayscale-flash',\n uniforms: Partial<GrayscaleFlashUniforms> = {},\n): Effect {\n return {\n id,\n enabled: true,\n uniforms: { kind: 'grayscale-flash', intensity: uniforms.intensity ?? 0 },\n };\n}\n\nexport function createChromaticAberration(\n id = 'chromatic-aberration',\n uniforms: Partial<ChromaticAberrationUniforms> = {},\n): Effect {\n return {\n id,\n enabled: true,\n uniforms: { kind: 'chromatic-aberration', amount: uniforms.amount ?? 0.005 },\n };\n}\n\nexport function createCrt(id = 'crt', uniforms: Partial<CrtUniforms> = {}): Effect {\n return {\n id,\n enabled: true,\n uniforms: {\n kind: 'crt',\n scanlineIntensity: uniforms.scanlineIntensity ?? 0.25,\n curvature: uniforms.curvature ?? 0.12,\n },\n };\n}\n\nexport function createBloom(id = 'bloom', uniforms: Partial<BloomUniforms> = {}): Effect {\n return {\n id,\n enabled: true,\n uniforms: {\n kind: 'bloom',\n threshold: uniforms.threshold ?? 0.7,\n intensity: uniforms.intensity ?? 0.6,\n },\n };\n}\n\nexport function createGodRays(id = 'god-rays', uniforms: Partial<GodRaysUniforms> = {}): Effect {\n return {\n id,\n enabled: true,\n uniforms: {\n kind: 'god-rays',\n lightX: uniforms.lightX ?? 0.5,\n lightY: uniforms.lightY ?? 0.1,\n color: uniforms.color ?? [1.0, 0.95, 0.8],\n intensity: uniforms.intensity ?? 0.6,\n decay: uniforms.decay ?? 0.96,\n density: uniforms.density ?? 0.5,\n weight: uniforms.weight ?? 0.4,\n samples: uniforms.samples ?? 48,\n },\n };\n}\n\nexport function createColorGradingLut(\n id: string,\n uniforms: { lut: SkImageLike; intensity?: number },\n): Effect {\n return {\n id,\n enabled: true,\n uniforms: {\n kind: 'color-grading-lut',\n lut: uniforms.lut,\n intensity: uniforms.intensity ?? 1.0,\n },\n };\n}\n\nexport function createWeatherRain(\n id = 'weather-rain',\n uniforms: Partial<WeatherRainUniforms> = {},\n): Effect {\n return {\n id,\n enabled: true,\n uniforms: {\n kind: 'weather-rain',\n intensity: uniforms.intensity ?? 0.5,\n direction: uniforms.direction ?? 0,\n scrollOffsetY: uniforms.scrollOffsetY ?? 0,\n color: uniforms.color ?? [1.0, 1.0, 1.0],\n density: uniforms.density ?? 0.5,\n streakLen: uniforms.streakLen ?? 0.15,\n },\n };\n}\n\nexport function createWeatherSunShaft(\n id = 'weather-sun-shaft',\n uniforms: Partial<WeatherSunShaftUniforms> = {},\n): Effect {\n return {\n id,\n enabled: true,\n uniforms: {\n kind: 'weather-sun-shaft',\n intensity: uniforms.intensity ?? 0.5,\n direction: uniforms.direction ?? 0,\n },\n };\n}\n\nexport function createWeatherDarkVignette(\n id = 'weather-dark-vignette',\n uniforms: Partial<WeatherDarkVignetteUniforms> = {},\n): Effect {\n return {\n id,\n enabled: true,\n uniforms: {\n kind: 'weather-dark-vignette',\n intensity: uniforms.intensity ?? 0.5,\n },\n };\n}\n\nexport function createWeatherClouds(\n id = 'weather-clouds',\n uniforms: Partial<WeatherCloudsUniforms> = {},\n): Effect {\n return {\n id,\n enabled: true,\n uniforms: {\n kind: 'weather-clouds',\n intensity: uniforms.intensity ?? 0.5,\n direction: uniforms.direction ?? 0,\n scrollOffsetX: uniforms.scrollOffsetX ?? 0,\n },\n };\n}\n\nexport function createPostFxComposite(\n id = 'composite',\n uniforms: Partial<CompositeUniforms> = {},\n): Effect {\n return {\n id,\n enabled: true,\n uniforms: {\n kind: 'composite',\n vignetteIntensity: uniforms.vignetteIntensity ?? 0,\n vignetteRadius: uniforms.vignetteRadius ?? 0.8,\n vignetteColor: uniforms.vignetteColor ?? [0, 0, 0],\n bloomThreshold: uniforms.bloomThreshold ?? 0.7,\n bloomIntensity: uniforms.bloomIntensity ?? 0,\n flashIntensity: uniforms.flashIntensity ?? 0,\n chromaAmount: uniforms.chromaAmount ?? 0,\n crtScanlineIntensity: uniforms.crtScanlineIntensity ?? 0.25,\n crtCurvature: uniforms.crtCurvature ?? 0.12,\n crtEnabled: uniforms.crtEnabled ?? false,\n weatherKind: uniforms.weatherKind ?? 0,\n weatherIntensity: uniforms.weatherIntensity ?? 0,\n weatherDirection: uniforms.weatherDirection ?? 0,\n weatherScrollX: uniforms.weatherScrollX ?? 0,\n weatherScrollY: uniforms.weatherScrollY ?? 0,\n weatherColor: uniforms.weatherColor ?? [1.0, 1.0, 1.0],\n weatherDensity: uniforms.weatherDensity ?? 0.5,\n weatherStreakLen: uniforms.weatherStreakLen ?? 0.15,\n },\n };\n}\n","// Returns raw RGBA bytes for a 4096×64 identity LUT strip. The host wraps\n// this via Skia.MakeImage into an SkImage to pass as the lut child texture.\nexport function createNeutralLutBytes(): Uint8Array {\n const buf = new Uint8Array(4096 * 64 * 4);\n for (let b = 0; b < 64; b++) {\n for (let g = 0; g < 64; g++) {\n for (let r = 0; r < 64; r++) {\n const x = b * 64 + r;\n const y = g;\n const off = (y * 4096 + x) * 4;\n buf[off] = Math.round((r / 63) * 255);\n buf[off + 1] = Math.round((g / 63) * 255);\n buf[off + 2] = Math.round((b / 63) * 255);\n buf[off + 3] = 255;\n }\n }\n }\n return buf;\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "@flare-engine/postfx",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Skia RuntimeEffect post-processing: vignette, bloom, CRT, chromatic aberration, grayscale flash",
5
5
  "keywords": ["flare-engine", "postfx", "shader", "skia", "vignette", "bloom", "crt"],
6
- "license": "MIT",
6
+ "license": "Apache-2.0",
7
7
  "author": "Grzegorz Otto",
8
8
  "homepage": "https://github.com/grzott/flare-engine#readme",
9
9
  "bugs": {
@@ -19,20 +19,33 @@
19
19
  "types": "dist/index.d.ts",
20
20
  "exports": {
21
21
  ".": {
22
- "types": "./dist/index.d.ts",
23
- "import": "./dist/index.mjs",
24
- "require": "./dist/index.js"
22
+ "import": {
23
+ "types": "./dist/index.d.mts",
24
+ "default": "./dist/index.mjs"
25
+ },
26
+ "require": {
27
+ "types": "./dist/index.d.ts",
28
+ "default": "./dist/index.js"
29
+ }
25
30
  }
26
31
  },
27
- "files": ["dist", "README.md", "LICENSE"],
32
+ "files": ["dist", "README.md", "LICENSE", "NOTICE"],
28
33
  "scripts": {
29
- "build": "tsup",
34
+ "build": "tsdown",
30
35
  "test": "bun test",
31
36
  "lint": "biome check src/",
32
37
  "typecheck": "tsc --noEmit"
33
38
  },
34
39
  "peerDependencies": {
35
- "@shopify/react-native-skia": ">=1.0.0"
40
+ "@shopify/react-native-skia": ">=2.0.0"
41
+ },
42
+ "peerDependenciesMeta": {
43
+ "@shopify/react-native-skia": {
44
+ "optional": true
45
+ }
46
+ },
47
+ "engines": {
48
+ "node": ">=20"
36
49
  },
37
50
  "publishConfig": {
38
51
  "access": "restricted",